diff --git a/dta.zip b/dta.zip index b63cd5fd0c2660cc8626519f46958bd7ae94f5df..7dcd55a2535cd9a81852ca626777d90e7c5afd0e 100644 Binary files a/dta.zip and b/dta.zip differ diff --git a/dta/classes/backend.php b/dta/classes/backend.php deleted file mode 100644 index d0bc5bcbf44ecdbfc07fd3f06687b41efb2125e4..0000000000000000000000000000000000000000 --- a/dta/classes/backend.php +++ /dev/null @@ -1,144 +0,0 @@ -<?php -// This file is part of Moodle - http://moodle.org/ -// -// Moodle is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Moodle is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Moodle. If not, see <http://www.gnu.org/licenses/>. - -/** - * This file contains the backend webservice contact functionality for the DTA plugin - * - * @package assignsubmission_dta - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @copyright Gero Lueckemeyer and student project teams - */ - -/** - * backend webservice contact utility class - * - * @package assignsubmission_dta - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @copyright Gero Lueckemeyer and student project teams - */ -class DtaBackendUtils { - - /** - * Returns the base url of the backend webservice as configured in the administration settings. - * @return string backend host base url - */ - private static function getbackendbaseurl(): string { - $backendaddress = get_config(assign_submission_dta::COMPONENT_NAME, "backendHost"); - - if (empty($backendaddress)) { - \core\notification::error(get_string("backendHost_not_set", assign_submission_dta::COMPONENT_NAME)); - } - - return $backendaddress; - } - - /** - * Sends the configuration textfile uploaded by prof to the backend. - * - * @param stdClass $assignment assignment this test-config belongs to - * @param stdClass $file uploaded test-config - * @return bool true if no error occurred - */ - public static function sendtestconfigtobackend($assignment, $file): bool { - $backendaddress = self::getbackendbaseurl(); - if (empty($backendaddress)) { - return true; - } - - // Set endpoint for test upload. - $url = $backendaddress . "/v1/unittest"; - - // Prepare params. - $params = [ - "unitTestFile" => $file, - "assignmentId" => $assignment->get_instance()->id, - ]; - - // If request returned null, return false to indicate failure. - if (is_null(self::post($url, $params))) { - return false; - } else { - return true; - } - } - - /** - * Sends submission config or archive to backend to be tested. - * - * @param stdClass $assignment assignment for the submission - * @param int $submissionid submissionid of the current file - * @param stdClass $file submission config file or archive with submission - * @return string json string with testresults or null on error - */ - public static function send_submission_to_backend($assignment, $submissionid, $file): ?string { - $backendaddress = self::getbackendbaseurl(); - if (empty($backendaddress)) { - return true; - } - - // Set endpoint for test upload. - $url = $backendaddress . "/v1/task/" . $submissionid; - - // Prepare params. - $params = [ - "taskFile" => $file, - "assignmentId" => $assignment->get_instance()->id, - ]; - - return self::post($url, $params); - } - - /** - * Posts the given params to the given url and returns the response as a string. - * @param string $url full url to request to - * @param array $params parameters for http-request - * - * @return string received body on success or null on error - */ - private static function post($url, $params): ?string { - if (!isset($url) || !isset($params)) { - return false; - } - - $options = ["CURLOPT_RETURNTRANSFER" => true]; - - $curl = new curl(); - $response = $curl->post($url, $params, $options); - - // Check state of request, if response code is a 2xx return the answer. - $info = $curl->get_info(); - if ($info["http_code"] >= 200 && $info["http_code"] < 300) { - return $response; - } - - // Something went wrong, return null and give an error message. - debugging(assign_submission_dta::COMPONENT_NAME . ": Post file to server was not successful: http_code=" . - $info["http_code"]); - - if ($info['http_code'] >= 400 && $info['http_code'] < 500) { - \core\notification::error(get_string("http_client_error_msg", assign_submission_dta::COMPONENT_NAME)); - return null; - } else if ($info['http_code'] >= 500 && $info['http_code'] < 600) { - \core\notification::error(get_string("http_server_error_msg", assign_submission_dta::COMPONENT_NAME)); - return null; - } else { - \core\notification::error(get_string("http_unknown_error_msg", assign_submission_dta::COMPONENT_NAME) . - $info["http_code"] . $response); - return null; - } - } - -} diff --git a/dta/classes/database.php b/dta/classes/db_utils.php similarity index 82% rename from dta/classes/database.php rename to dta/classes/db_utils.php index f93fd066d39c411c6d3da3aafe50f30fd4674bd9..0dfb452f9ae6c6b37216faeabd730d93bbe204a2 100644 --- a/dta/classes/database.php +++ b/dta/classes/db_utils.php @@ -1,4 +1,5 @@ <?php +namespace assignsubmission_dta; // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify @@ -21,7 +22,13 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @copyright Gero Lueckemeyer and student project teams */ -class DbUtils { + +use assignsubmission_dta\dta_backend_utils; +use assignsubmission_dta\view_submission_utils; +use assignsubmission_dta\models\dta_result; +use assignsubmission_dta\models\dta_result_summary; +use assignsubmission_dta\models\dta_recommendation; +class db_utils { /** * Summary database table name. @@ -77,6 +84,7 @@ class DbUtils { // Wenn der Abschlussstatus 1 ist, entferne den Datensatz aus $records if ($completion && $completion->completionstate == 1) { unset($records[$key]); + } } } @@ -94,10 +102,10 @@ class DbUtils { * @param int $submissionid submission id to search for * @return DttResultSummary representing given submission */ - public static function getresultsummaryfromdatabase( + public static function get_result_summary_from_database( int $assignmentid, int $submissionid - ): DtaResultSummary { + ): dta_result_summary { global $DB; // Fetch data from database. @@ -112,7 +120,7 @@ class DbUtils { ]); // Create a summary instance. - $summary = new DtaResultSummary(); + $summary = new dta_result_summary(); $summary->timestamp = $summaryrecord->timestamp; $summary->globalstacktrace = $summaryrecord->global_stacktrace; $summary->successfultestcompetencies = $summaryrecord->successful_competencies; @@ -121,7 +129,7 @@ class DbUtils { // Create result instances and add to array of summary instance. foreach ($resultsarray as $rr) { - $result = new DtaResult(); + $result = new dta_result(); $result->packagename = $rr->package_name; $result->classname = $rr->class_name; $result->name = $rr->name; @@ -138,33 +146,16 @@ class DbUtils { return $summary; } - public static function storeRecommendationstoDatabase( + public static function store_recommendations_to_database( int $assignmentid, int $submissionid, array $recommendations ): void { global $DB; - error_log(print_r($recommendations,true)); - - // Prepare recommendations to persist to array. - $recommendationrecords = []; - foreach ($recommendations as $recommendation) { - $record = new stdClass(); - $record->assignment_id = $assignmentid; - $record->submission_id = $submissionid; - $record->topic = $recommendation['topic']; - $record->url = $recommendation['url']; - $record->exercise_name = $recommendation['exercise_name']; - $record->difficulty = $recommendation['difficulty']; - $record->score = $recommendation['score']; - $recommendationrecords[] = $record; - } - - error_log("Das sind die Recommendationrecords."); - error_log(print_r($recommendationrecords,true)); + error_log(print_r($recommendations, true)); // If recommendations already exist, delete old values beforehand. - $existingrecords = $DB->get_record('assignsubmission_dta_recommendations', [ + $existingrecords = $DB->get_records('assignsubmission_dta_recommendations', [ 'assignment_id' => $assignmentid, 'submission_id' => $submissionid, ]); @@ -177,10 +168,22 @@ class DbUtils { } // Create new recommendation entries. - foreach ($recommendationrecords as $rec) { - error_log("Insert record"); - error_log(print_r($rec,true)); - $DB->insert_record('assignsubmission_dta_recommendations', $rec); + foreach ($recommendations as $recommendation) { + // Ensure $recommendation is an instance of DtaRecommendation + if ($recommendation instanceof dta_recommendation) { + // Add assignment and submission IDs to the recommendation object + $recommendation->assignment_id = $assignmentid; + $recommendation->submission_id = $submissionid; + + error_log("Insert record"); + error_log(print_r($recommendation, true)); + + // Insert the recommendation into the database + $DB->insert_record('assignsubmission_dta_recommendations', $recommendation); + } else { + // Handle the case where $recommendation is not a DtaRecommendation instance + error_log("Invalid recommendation object"); + } } } @@ -191,17 +194,17 @@ class DbUtils { * * @param int $assignmentid assigment this is submission is linked to * @param int $submissionid submission of this result - * @param DtaResultSummary $summary instance to persist + * @param dta_result_summary $summary instance to persist */ - public static function storeresultsummarytodatabase( + public static function store_result_summary_to_database( int $assignmentid, int $submissionid, - DtaResultSummary $summary + dta_result_summary $summary ): void { global $DB; // Prepare new database entries. - $summaryrecord = new stdClass(); + $summaryrecord = new dta_result_summary(); $summaryrecord->assignment_id = $assignmentid; $summaryrecord->submission_id = $submissionid; $summaryrecord->timestamp = $summary->timestamp; @@ -212,7 +215,7 @@ class DbUtils { // Prepare results to persist to array. $resultrecords = []; foreach ($summary->results as $r) { - $record = new stdClass(); + $record = new dta_result(); $record->assignment_id = $assignmentid; $record->submission_id = $submissionid; $record->package_name = $r->packagename; @@ -256,7 +259,7 @@ class DbUtils { /** * cleans up database if plugin is uninstalled */ - public static function uninstallplugincleaup(): void { + public static function uninstall_plugin_cleaup(): void { global $DB; $DB->delete_records(self::TABLE_RESULT, null); diff --git a/dta/classes/dta_backend_utils.php b/dta/classes/dta_backend_utils.php new file mode 100644 index 0000000000000000000000000000000000000000..cfe0ac925781b6a6dcd60a23cc461141776301fa --- /dev/null +++ b/dta/classes/dta_backend_utils.php @@ -0,0 +1,151 @@ +<?php + +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see <http://www.gnu.org/licenses/>. + +/** + * This file contains the backend webservice contact functionality for the DTA plugin + * + * @package assignsubmission_dta + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + + +/** + * Backend webservice contact utility class + * + * @package assignsubmission_dta + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace assignsubmission_dta; + +defined('MOODLE_INTERNAL') || die(); + +class dta_backend_utils { + + /** + * Component name for the plugin. + */ + const COMPONENT_NAME = 'assignsubmission_dta'; + + /** + * Returns the base URL of the backend webservice as configured in the administration settings. + * @return string Backend host base URL + */ + private static function get_backend_baseurl(): string { + $backendaddress = get_config(self::COMPONENT_NAME, 'backendHost'); + + if (empty($backendaddress)) { + \core\notification::error(get_string('backendHost_not_set', self::COMPONENT_NAME)); + } + + return $backendaddress; + } + + /** + * Sends the configuration text file uploaded by the teacher to the backend. + * + * @param \assign $assignment Assignment this test-config belongs to + * @param \stored_file $file Uploaded test-config + * @return bool True if no error occurred + */ + public static function send_testconfig_to_backend($assignment, $file): bool { + $backendaddress = self::get_backend_baseurl(); + if (empty($backendaddress)) { + return true; + } + + // Set endpoint for test upload. + $url = $backendaddress . '/v1/unittest'; + + // Prepare params. + $params = [ + 'unitTestFile' => $file, + 'assignmentId' => $assignment->get_instance()->id, + ]; + + // If request returned null, return false to indicate failure. + if (is_null(self::post($url, $params))) { + return false; + } else { + return true; + } + } + + /** + * Sends submission config or archive to backend to be tested. + * + * @param \assign $assignment Assignment for the submission + * @param int $submissionid Submission ID of the current file + * @param \stored_file $file Submission config file or archive with submission + * @return string|null JSON string with test results or null on error + */ + public static function send_submission_to_backend($assignment, $submissionid, $file): ?string { + $backendaddress = self::get_backend_baseurl(); + if (empty($backendaddress)) { + return null; + } + + // Set endpoint for submission upload. + $url = $backendaddress . '/v1/task/' . $submissionid; + + // Prepare params. + $params = [ + 'taskFile' => $file, + 'assignmentId' => $assignment->get_instance()->id, + ]; + + return self::post($url, $params); + } + + /** + * Posts the given params to the given URL and returns the response as a string. + * @param string $url Full URL to request to + * @param array $params Parameters for HTTP request + * + * @return string|null Received body on success or null on error + */ + private static function post($url, $params): ?string { + if (!isset($url) || !isset($params)) { + return null; + } + + $options = ['CURLOPT_RETURNTRANSFER' => true]; + + $curl = new \curl(); + $response = $curl->post($url, $params, $options); + + // Check state of request, if response code is a 2xx return the answer. + $info = $curl->get_info(); + if ($info['http_code'] >= 200 && $info['http_code'] < 300) { + return $response; + } + + // Something went wrong, return null and give an error message. + debugging(self::COMPONENT_NAME . ': Post file to server was not successful: http_code=' . $info['http_code']); + + if ($info['http_code'] >= 400 && $info['http_code'] < 500) { + \core\notification::error(get_string('http_client_error_msg', self::COMPONENT_NAME)); + return null; + } else if ($info['http_code'] >= 500 && $info['http_code'] < 600) { + \core\notification::error(get_string('http_server_error_msg', self::COMPONENT_NAME)); + return null; + } else { + \core\notification::error(get_string('http_unknown_error_msg', self::COMPONENT_NAME) . $info['http_code'] . $response); + return null; + } + } +} diff --git a/dta/classes/models/dta_recommendation.php b/dta/classes/models/dta_recommendation.php new file mode 100644 index 0000000000000000000000000000000000000000..19a1caeedb72798b6c05643e1d9f9c007d34205e --- /dev/null +++ b/dta/classes/models/dta_recommendation.php @@ -0,0 +1,90 @@ +<?php +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see <http://www.gnu.org/licenses/>. + +/** + * Entity class for DTA submission plugin recommendation + * + * @package assignsubmission_dta + * @copyright 2023 Gero Lueckemeyer + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace assignsubmission_dta\models; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Entity class for DTA submission plugin recommendation + * + * @package assignsubmission_dta + * @copyright 2023 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class dta_recommendation { + + /** + * @var string $topic Topic of the recommendation. + */ + public $topic; + + /** + * @var string $exercise_name Name of the exercise. + */ + public $exercise_name; + + /** + * @var string $url URL of the exercise. + */ + public $url; + + /** + * @var int $difficulty Difficulty level of the exercise. + */ + public $difficulty; + + /** + * @var int $score Score associated with the recommendation. + */ + public $score; + + /** + * Decodes the JSON recommendations returned by the backend service call into an array of DtaRecommendation objects. + * + * @param string $jsonstring JSON string containing recommendations + * @return array Array of DtaRecommendation objects + */ + public static function decode_json_recommendations(string $jsonstring): array { + $response = json_decode($jsonstring); + + $recommendations = []; + + // Prüfe, ob Empfehlungen vorhanden sind + if (!empty($response->recommendations)) { + foreach ($response->recommendations as $recommendation) { + $rec = new dta_recommendation(); + $rec->topic = $recommendation->topic ?? null; + $rec->exercise_name = $recommendation->url ?? null; + $rec->url = $recommendation->exerciseName ?? null; + $rec->difficulty = $recommendation->difficulty ?? null; + $rec->score = $recommendation->score ?? null; + + $recommendations[] = $rec; + } + } + + return $recommendations; + } +} diff --git a/dta/classes/models/dta_result.php b/dta/classes/models/dta_result.php new file mode 100644 index 0000000000000000000000000000000000000000..d1650eec94ea7cd7d4db9e107754dac606d44686 --- /dev/null +++ b/dta/classes/models/dta_result.php @@ -0,0 +1,114 @@ +<?php +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see <http://www.gnu.org/licenses/>. + +/** + * Entity class for DTA submission plugin result + * + * @package assignsubmission_dta + * @copyright 2023 Gero Lueckemeyer and student project teams + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace assignsubmission_dta\models; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Entity class for DTA submission plugin result + * + * @package assignsubmission_dta + * @copyright 2023 Gero Lueckemeyer and student project teams + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class dta_result { + + /** + * Broadly used in logic, parametrized for easier change. + */ + const COMPONENT_NAME = 'assignsubmission_dta'; + + /** + * @var string $packagename Package name of the test. + */ + public $packagename; + + /** + * @var string $classname Unit name of the test. + */ + public $classname; + + /** + * @var string $name Name of the test. + */ + public $name; + + /** + * @var int $state State is defined as: + * 0 UNKNOWN + * 1 SUCCESS + * 2 FAILURE + * 3 COMPILATIONERROR + */ + public $state; + + /** + * @var string $failuretype Type of test failure if applicable, empty string otherwise. + */ + public $failuretype; + + /** + * @var string $failurereason Reason of test failure if applicable, empty string otherwise. + */ + public $failurereason; + + /** + * @var string $stacktrace Stack trace of test failure if applicable, empty string otherwise. + */ + public $stacktrace; + + /** + * @var int|string $columnnumber Column number of compile failure if applicable, empty string otherwise. + */ + public $columnnumber; + + /** + * @var int|string $linenumber Line number of compile failure if applicable, empty string otherwise. + */ + public $linenumber; + + /** + * @var int|string $position Position of compile failure if applicable, empty string otherwise. + */ + public $position; + + /** + * Returns the name of a state with the given number for display. + * + * @param int $state Number of the state + * @return string Name of state as defined + */ + public static function getstatename(int $state): string { + if ($state == 1) { + return get_string('tests_successful', self::COMPONENT_NAME); + } else if ($state == 2) { + return get_string('failures', self::COMPONENT_NAME); + } else if ($state == 3) { + return get_string('compilation_errors', self::COMPONENT_NAME); + } else { + return get_string('unknown_state', self::COMPONENT_NAME); + } + } +} diff --git a/dta/classes/models/dta_result_summary.php b/dta/classes/models/dta_result_summary.php new file mode 100644 index 0000000000000000000000000000000000000000..56ed33eeb3238699e9a1ef9fcc1d8aba532c0cb7 --- /dev/null +++ b/dta/classes/models/dta_result_summary.php @@ -0,0 +1,173 @@ +<?php +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see <http://www.gnu.org/licenses/>. + +/** + * Entity class for DTA submission plugin result summary + * + * @package assignsubmission_dta + * @copyright 2023 Gero Lueckemeyer and student project teams + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace assignsubmission_dta\models; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Entity class for DTA submission plugin result summary + * + * @package assignsubmission_dta + * @copyright 2023 Gero Lueckemeyer and student project teams + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class dta_result_summary { + + /** + * @var int $timestamp Result timestamp for chronological ordering and deletion of previous results. + */ + public $timestamp; + + /** + * @var string $globalstacktrace Global stack trace if applicable, empty string otherwise. + */ + public $globalstacktrace; + + /** + * @var string $successfultestcompetencies Successfully tested competencies according to tests and weights, empty string otherwise. + */ + public $successfultestcompetencies; + + /** + * @var string $overalltestcompetencies Overall tested competencies according to tests and weights, empty string otherwise. + */ + public $overalltestcompetencies; + + /** + * @var array $results List of detail results. + */ + public $results; + + /** + * Decodes the JSON result summary returned by the backend service call into the plugin PHP data structure. + * + * @param string $jsonstring JSON string containing DtaResultSummary + * @return DtaResultSummary The result summary + */ + public static function decode_json(string $jsonstring): dta_result_summary { + $response = json_decode($jsonstring); + + $summary = new dta_result_summary(); + $summary->timestamp = $response->timestamp; + $summary->globalstacktrace = $response->globalstacktrace; + + $summary->successfultestcompetencies = $response->successfulTestCompetencyProfile ?? ''; + $summary->overalltestcompetencies = $response->overallTestCompetencyProfile ?? ''; + + $summary->results = self::decode_json_result_array($response->results); + + return $summary; + } + + /** + * Decodes the array of JSON detail results returned by the backend service call into the plugin PHP data structure. + * + * @param array $jsonarray Decoded JSON array of results + * @return array Array of DtaResult + */ + private static function decode_json_result_array(array $jsonarray): array { + $ret = []; + foreach ($jsonarray as $entry) { + $value = new dta_result(); + $value->packagename = $entry->packageName ?? ''; + $value->classname = $entry->className ?? ''; + $value->name = $entry->name ?? ''; + + $value->state = $entry->state ?? 0; + + $value->failuretype = $entry->failureType ?? ''; + $value->failurereason = $entry->failureReason ?? ''; + $value->stacktrace = $entry->stacktrace ?? ''; + + $value->columnnumber = $entry->columnNumber ?? ''; + $value->linenumber = $entry->lineNumber ?? ''; + $value->position = $entry->position ?? ''; + + $ret[] = $value; + } + return $ret; + } + + /** + * Returns the number of detail results attached to the summary. + * + * @return int Count of occurrences + */ + public function result_count(): int { + return count($this->results); + } + + /** + * Returns the number of detail results with the given state attached to the summary. + * + * @param int $state State ordinal number + * @return int Count of occurrences for the provided state + */ + public function state_occurence_count(int $state): int { + $num = 0; + foreach ($this->results as $r) { + if ($r->state == $state) { + $num++; + } + } + return $num; + } + + /** + * Returns the number of detail results with compilation errors attached to the summary. + * + * @return int Count of occurrences + */ + public function compilationerrorcount(): int { + return $this->state_occurence_count(3); + } + + /** + * Returns the number of detail results with test failures attached to the summary. + * + * @return int Count of occurrences + */ + public function failedcount(): int { + return $this->state_occurence_count(2); + } + + /** + * Returns the number of detail results with successful tests attached to the summary. + * + * @return int Count of occurrences + */ + public function successfulcount(): int { + return $this->state_occurence_count(1); + } + + /** + * Returns the number of detail results with an unknown result attached to the summary. + * + * @return int Count of occurrences + */ + public function unknowncount(): int { + return $this->state_occurence_count(0); + } +} diff --git a/dta/classes/view.php b/dta/classes/view_submission_utils.php similarity index 75% rename from dta/classes/view.php rename to dta/classes/view_submission_utils.php index d6258cd7118d8d20e6948f261b34db2abb7fad14..fed291adbd024ae77bf5eb75b742e11d024e524d 100644 --- a/dta/classes/view.php +++ b/dta/classes/view_submission_utils.php @@ -1,4 +1,5 @@ <?php +namespace assignsubmission_dta; // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify @@ -20,6 +21,12 @@ * @package assignsubmission_dta * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +use assignsubmission_dta\db_utils; +use assignsubmission_dta\dta_backend_utils; +use assignsubmission_dta\models\dta_result; +use assignsubmission_dta\models\dta_result_summary; +use assignsubmission_dta\models\dta_recommendation; + class view_submission_utils { /** @@ -34,13 +41,13 @@ class view_submission_utils { * @param int $submissionid submission to create a report for * @return string html */ - public static function generatesummaryhtml( + public static function generate_summary_html( int $assignmentid, int $submissionid ): string { // Fetch data. - $summary = DbUtils::getResultSummaryFromDatabase($assignmentid, $submissionid); + $summary = db_utils::get_Result_Summary_From_Database($assignmentid, $submissionid); $html = ""; // Calculate success rate, if no unknown result states or compilation errors. @@ -52,7 +59,7 @@ class view_submission_utils { // Generate html. $html .= $summary->successfulCount() . "/"; $html .= ($summary->compilationErrorCount() == 0 && $summary->unknownCount() == 0) - ? $summary->resultCount() . " (" . $successrate . "%)" + ? $summary->result_Count() . " (" . $successrate . "%)" : "?"; $html .= get_string("tests_successful", self::COMPONENT_NAME) . "<br />"; @@ -80,7 +87,7 @@ class view_submission_utils { $html .= get_string("success_competencies", self::COMPONENT_NAME) . "<br />" . $tmp . "<br />"; - return html_writer::div($html, "dtaSubmissionSummary"); + return \html_writer::div($html, "dtaSubmissionSummary"); } /** @@ -90,7 +97,7 @@ class view_submission_utils { * @param int $submissionid Submission-ID, für die der Bericht erstellt wird * @return string HTML-Code */ -public static function generatedetailhtml( +public static function generate_detail_html( int $assignmentid, int $submissionid ): string { @@ -99,7 +106,7 @@ public static function generatedetailhtml( $html = ""; // Daten abrufen - $summary = DbUtils::getResultSummaryFromDatabase($assignmentid, $submissionid); + $summary = db_utils::get_Result_Summary_From_Database($assignmentid, $submissionid); // CSS-Klassen und HTML-Attributarrays definieren $tableheaderrowattributes = ["class" => "dtaTableHeaderRow"]; @@ -114,11 +121,11 @@ public static function generatedetailhtml( // (Ihr bisheriger Code bleibt unverändert) // **Abstand zwischen Tabellen** - $html .= html_writer::empty_tag("div", ["class" => "dtaSpacer"]); + $html .= \html_writer::empty_tag("div", ["class" => "dtaSpacer"]); // **Empfehlungstabelle hinzufügen** // Empfehlungen für die Submission abrufen - $recommendations = DbUtils::get_recommendations_from_database($assignmentid, $submissionid); + $recommendations = db_utils::get_recommendations_from_database($assignmentid, $submissionid); if (!empty($recommendations)) { // **Sortierparameter abrufen** @@ -160,7 +167,7 @@ public static function generatedetailhtml( }); // Überschrift für Empfehlungen - $html .= html_writer::tag('h3', get_string('recommendations', self::COMPONENT_NAME)); + $html .= \html_writer::tag('h3', get_string('recommendations', self::COMPONENT_NAME)); // Helper-Funktion zum Generieren von sortierbaren Headern $generate_sortable_header = function($column_name, $display_name) use ($sortby, $sortdir) { @@ -171,7 +178,7 @@ public static function generatedetailhtml( } // Button erstellen - $button = html_writer::empty_tag('input', [ + $button = \html_writer::empty_tag('input', [ 'type' => 'submit', 'name' => 'sortbutton', 'value' => ($new_sortdir == 'asc' ? '↑' : '↓'), @@ -179,57 +186,57 @@ public static function generatedetailhtml( ]); // Hidden Inputs für Sortierparameter - $hidden_inputs = html_writer::empty_tag('input', [ + $hidden_inputs = \html_writer::empty_tag('input', [ 'type' => 'hidden', 'name' => 'sortby', 'value' => $column_name ]); - $hidden_inputs .= html_writer::empty_tag('input', [ + $hidden_inputs .= \html_writer::empty_tag('input', [ 'type' => 'hidden', 'name' => 'sortdir', 'value' => $new_sortdir ]); // Formular für den Button erstellen - $form = html_writer::start_tag('form', ['method' => 'post', 'style' => 'display:inline']); + $form = \html_writer::start_tag('form', ['method' => 'post', 'style' => 'display:inline']); $form .= $hidden_inputs; $form .= $display_name . ' ' . $button; - $form .= html_writer::end_tag('form'); + $form .= \html_writer::end_tag('form'); - return html_writer::tag("th", $form, ["class" => $class]); + return \html_writer::tag("th", $form, ["class" => $class]); }; // Tabellenkopf für Empfehlungen $tableheader = ""; $tableheader .= $generate_sortable_header('topic', get_string("topic", self::COMPONENT_NAME)); $tableheader .= $generate_sortable_header('exercise_name', get_string("exercise_name", self::COMPONENT_NAME)); - $tableheader .= html_writer::tag("th", get_string("url", self::COMPONENT_NAME), ["class" => "dtaTableHeader"]); + $tableheader .= \html_writer::tag("th", get_string("url", self::COMPONENT_NAME), ["class" => "dtaTableHeader"]); $tableheader .= $generate_sortable_header('difficulty', get_string("difficulty", self::COMPONENT_NAME)); $tableheader .= $generate_sortable_header('score', get_string("score", self::COMPONENT_NAME)); - $tableheader = html_writer::tag("tr", $tableheader, ["class" => "dtaTableHeaderRow"]); - $tableheader = html_writer::tag("thead", $tableheader); + $tableheader = \html_writer::tag("tr", $tableheader, ["class" => "dtaTableHeaderRow"]); + $tableheader = \html_writer::tag("thead", $tableheader); // Tabellenkörper für Empfehlungen $tablebody = ""; foreach ($recommendations as $recommendation) { $tablerow = ""; - $tablerow .= html_writer::tag("td", $recommendation->topic, $attributes); - $tablerow .= html_writer::tag("td", $recommendation->exercise_name, $attributes); - $tablerow .= html_writer::tag("td", html_writer::link($recommendation->url, $recommendation->url), $attributes); - $tablerow .= html_writer::tag("td", $recommendation->difficulty, $attributes); - $tablerow .= html_writer::tag("td", $recommendation->score, $attributes); + $tablerow .= \html_writer::tag("td", $recommendation->topic, $attributes); + $tablerow .= \html_writer::tag("td", $recommendation->exercise_name, $attributes); + $tablerow .= \html_writer::tag("td", \html_writer::link($recommendation->url, $recommendation->url), $attributes); + $tablerow .= \html_writer::tag("td", $recommendation->difficulty, $attributes); + $tablerow .= \html_writer::tag("td", $recommendation->score, $attributes); - $tablebody .= html_writer::tag("tr", $tablerow, $tablerowattributes); + $tablebody .= \html_writer::tag("tr", $tablerow, $tablerowattributes); } - $tablebody = html_writer::tag("tbody", $tablebody); + $tablebody = \html_writer::tag("tbody", $tablebody); // Empfehlungstabelle zusammenstellen - $html .= html_writer::tag("table", $tableheader . $tablebody, ["class" => "dtaTable"]); + $html .= \html_writer::tag("table", $tableheader . $tablebody, ["class" => "dtaTable"]); } // Abschließendes Div für die gesamte HTML-Ausgabe - $html = html_writer::div($html, "dtaSubmissionDetails"); + $html = \html_writer::div($html, "dtaSubmissionDetails"); return $html; } diff --git a/dta/locallib.php b/dta/locallib.php index 28ea9be89c82b9bc1b2f3cbd75629a316caa7fef..cbea7697437db71a8717c39482d4e263024829b7 100644 --- a/dta/locallib.php +++ b/dta/locallib.php @@ -16,14 +16,15 @@ defined('MOODLE_INTERNAL') || die(); -// Import various entity and application logic files. -require_once($CFG->dirroot . '/mod/assign/submission/dta/models/DtaResult.php'); -require_once($CFG->dirroot . '/mod/assign/submission/dta/classes/database.php'); -require_once($CFG->dirroot . '/mod/assign/submission/dta/classes/backend.php'); -require_once($CFG->dirroot . '/mod/assign/submission/dta/classes/view.php'); +use assignsubmission_dta\db_utils; +use assignsubmission_dta\dta_backend_utils; +use assignsubmission_dta\view_submission_utils; +use assignsubmission_dta\models\dta_result; +use assignsubmission_dta\models\dta_result_summary; +use assignsubmission_dta\models\dta_recommendation; /** - * library class for DTA submission plugin extending assign submission plugin base class + * Library class for DTA submission plugin extending assign submission plugin base class * * @package assignsubmission_dta * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later @@ -35,20 +36,20 @@ class assign_submission_dta extends assign_submission_plugin { */ const COMPONENT_NAME = "assignsubmission_dta"; /** - * Draft file area for dta tests to be uploaded by the teacher. + * Draft file area for DTA tests to be uploaded by the teacher. */ const ASSIGNSUBMISSION_DTA_DRAFT_FILEAREA_TEST = "tests_draft_dta"; /** - * File area for dta tests to be uploaded by the teacher. + * File area for DTA tests to be uploaded by the teacher. */ const ASSIGNSUBMISSION_DTA_FILEAREA_TEST = "tests_dta"; /** - * File area for dta submission assignment. + * File area for DTA submission assignment. */ const ASSIGNSUBMISSION_DTA_FILEAREA_SUBMISSION = "submissions_dta"; /** - * get plugin name + * Get plugin name * @return string */ public function get_name(): string { @@ -93,7 +94,7 @@ class assign_submission_dta extends assign_submission_plugin { } /** - * Allows the plugin to update the defaultvalues passed in to + * Allows the plugin to update the default values passed into * the settings form (needed to set up draft areas for editor * and filemanager elements) * @param array $defaultvalues @@ -162,7 +163,7 @@ class assign_submission_dta extends assign_submission_plugin { $file = reset($files); // Send file to backend. - return DtaBackendUtils::sendtestconfigtobackend($this->assignment, $file); + return dta_backend_utils::send_testconfig_to_backend($this->assignment, $file); } /** @@ -240,86 +241,78 @@ class assign_submission_dta extends assign_submission_plugin { return count($files); } - /** - * Save data to the database - * - * @param stdClass $submission - * @param stdClass $data - * @return bool - */ -public function save(stdClass $submission, stdClass $data) { - $data = file_postupdate_standard_filemanager( - $data, - 'tasks', - $this->get_file_options(false), - $this->assignment->get_context(), - self::COMPONENT_NAME, - self::ASSIGNSUBMISSION_DTA_FILEAREA_SUBMISSION, - $submission->id - ); - - // If submission is empty leave directly. - if ($this->is_empty($submission)) { - return true; - } - - // Get submitted files. - $fs = get_file_storage(); - $files = $fs->get_area_files( - // Id of current assignment. - $this->assignment->get_context()->id, - self::COMPONENT_NAME, - self::ASSIGNSUBMISSION_DTA_FILEAREA_SUBMISSION, - $submission->id, - 'id', - false - ); - - // Check if a file is uploaded. - if (empty($files)) { - \core\notification::error(get_string("no_submissionfile_warning", self::COMPONENT_NAME)); - return true; - } - - // Get the file. - $file = reset($files); + /** + * Save data to the database + * + * @param stdClass $submission + * @param stdClass $data + * @return bool + */ + public function save(stdClass $submission, stdClass $data) { + $data = file_postupdate_standard_filemanager( + $data, + 'tasks', + $this->get_file_options(false), + $this->assignment->get_context(), + self::COMPONENT_NAME, + self::ASSIGNSUBMISSION_DTA_FILEAREA_SUBMISSION, + $submission->id + ); - // Send file to backend. - $response = DtaBackendUtils::send_submission_to_backend($this->assignment, $submission->id, $file); + // If submission is empty, leave directly. + if ($this->is_empty($submission)) { + return true; + } - // With a null response, return an error. - if (is_null($response)) { - return false; - } + // Get submitted files. + $fs = get_file_storage(); + $files = $fs->get_area_files( + // Id of current assignment. + $this->assignment->get_context()->id, + self::COMPONENT_NAME, + self::ASSIGNSUBMISSION_DTA_FILEAREA_SUBMISSION, + $submission->id, + 'id', + false + ); - // Convert received json to valid class instances. - $resultsummary = DtaResultSummary::decodejson($response); - // Log an error message. - - $recommendations = DtaResultSummary::decodeJsonRecommendation($response); + // Check if a file is uploaded. + if (empty($files)) { + \core\notification::error(get_string("no_submissionfile_warning", self::COMPONENT_NAME)); + return true; + } - error_log(print_r($recommendations,true)); + // Get the file. + $file = reset($files); + // Send file to backend. + $response = \assignsubmission_dta\dta_backend_utils::send_submission_to_backend($this->assignment, $submission->id, $file); + // With a null response, return an error. + if (is_null($response)) { + return false; + } - // Persist new results to database. - DbUtils::storeresultsummarytodatabase($this->assignment->get_instance()->id, $submission->id, $resultsummary); + // Convert received JSON to valid class instances. + $resultsummary = dta_result_summary::decode_json($response); + // Decode recommendations from response. + $recommendations = dta_recommendation::decode_json_recommendations($response); + error_log(print_r($recommendations, true)); - - // Store the array of records in the database. - DbUtils::storeRecommendationstoDatabase($this->assignment->get_instance()->id, $submission->id, $recommendations); - - + // Persist new results to database. + db_utils::store_result_summary_to_database($this->assignment->get_instance()->id, $submission->id, $resultsummary); - return true; -} + // Store the array of recommendations in the database. + db_utils::store_recommendations_to_database($this->assignment->get_instance()->id, $submission->id, $recommendations); + return true; + } /** * Display a short summary of the test results of the submission - * This is diplayed as default view, with the option to expand + * This is displayed as default view, with the option to expand * to the full detailed results. * * @param stdClass $submission to show @@ -329,7 +322,7 @@ public function save(stdClass $submission, stdClass $data) { public function view_summary(stdClass $submission, & $showviewlink) { $showviewlink = true; - return view_submission_utils::generatesummaryhtml( + return view_submission_utils::generate_summary_html( $this->assignment->get_instance()->id, $submission->id ); @@ -342,16 +335,14 @@ public function save(stdClass $submission, stdClass $data) { * @return string detailed results html */ public function view(stdClass $submission) { - // Sicherstellen, dass $cmid verfügbar ist - - return view_submission_utils::generatedetailhtml( + return view_submission_utils::generate_detail_html( $this->assignment->get_instance()->id, $submission->id ); } /** - * generate array of allowed filetypes to upload. + * Generate array of allowed file types to upload. * * @param bool $settings switch to define if list for assignment settings * or active submission should be returned @@ -413,12 +404,12 @@ public function save(stdClass $submission, stdClass $data) { } /** - * The plugin is beeing uninstalled - cleanup + * The plugin is being uninstalled - cleanup * * @return bool */ public function delete_instance() { - DbUtils::uninstallplugincleanup(); + db_utils::uninstall_plugin_cleanup(); return true; } diff --git a/dta/models/DtaResult.php b/dta/models/DtaResult.php deleted file mode 100644 index 867ee8182ee5968b10ea77cd36d84a77da5ed4e8..0000000000000000000000000000000000000000 --- a/dta/models/DtaResult.php +++ /dev/null @@ -1,277 +0,0 @@ -<?php -// This file is part of Moodle - http://moodle.org/ -// -// Moodle is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Moodle is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Moodle. If not, see <http://www.gnu.org/licenses/>. - -/** - * entity classes for DTA submission plugin result summary and test results - * - * @package assignsubmission_dta - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @copyright Gero Lueckemeyer and student project teams - */ -defined('MOODLE_INTERNAL') || die(); - -/** - * entity class for DTA submission plugin result - * - * @package assignsubmission_dta - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @copyright Gero Lueckemeyer and student project teams - */ -class DtaResult { - - /** - * Broadly used in logic, parametrized for easier change. - */ - const COMPONENT_NAME = "assignsubmission_dta"; - - /** - * @var $packagename Package name of the test. - */ - public $packagename; - - /** - * @var $classname Unit name of the test. - */ - public $classname; - - /** - * @var $name Name of the test. - */ - public $name; - - /** - * @var $state State is defined like below - * - * 0 UNKNOWN - * 1 SUCCESS - * 2 FAILURE - * 3 COMPILATIONERROR - */ - public $state; - - /** - * @var $failuretype Type of test failure if applicable, "" otherwise. - */ - public $failuretype; - - /** - * @var $failurereason Reason of test failure if applicable, "" otherwise. - */ - public $failurereason; - - /** - * @var $stacktrace Stack trace of test failure if applicable, "" otherwise. - */ - public $stacktrace; - - /** - * @var $columnnumber Column number of compile failure if applicable, "" otherwise. - */ - public $columnnumber; - /** - * @var $linenumber Line number of compile failure if applicable, "" otherwise. - */ - public $linenumber; - /** - * @var $position Position of compile failure if applicable, "" otherwise. - */ - public $position; - - /** - * Returns the name of a state with the given number of display. - * @param int $state number of the state - * @return string name of state as defined - */ - public static function getstatename(int $state): string { - if ($state == 1) { - return get_string("tests_successful", self::COMPONENT_NAME); - } else if ($state == 2) { - return get_string("failures", self::COMPONENT_NAME); - } else if ($state == 3) { - return get_string("compilation_errors", self::COMPONENT_NAME); - } else { - return get_string("unknown_state", self::COMPONENT_NAME); - } - } -} - -/** - * entity class for DTA submission plugin result - * - * @package assignsubmission_dta - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @copyright Gero Lueckemeyer and student project teams - */ -class DtaResultSummary { - - /** - * @var $timestamp Result timestamp for chronological ordering and deletion of previous results. - */ - public $timestamp; - - /** - * @var $globalstacktrace Global stack trace if applicable, "" otherwise. - */ - public $globalstacktrace; - - /** - * @var $successfultestcompetencies Successfully tested competencies according to tests and weights, "" otherwise. - */ - public $successfultestcompetencies; - /** - * @var overalltestcompetencies Overall tested competencies according to tests and weights, "" otherwise. - */ - public $overalltestcompetencies; - /** - * @var results List of detail results. - */ - public $results; - - /** - * Decodes the JSON result summary returned by the backend service call into the plugin PHP data structure. - * @param string $jsonstring jsonString containing DtaResultSummary - * @return DtaResultSummary the result summary - */ - public static function decodejson(string $jsonstring): DtaResultSummary { - $response = json_decode($jsonstring); - - - $summary = new DtaResultSummary(); - $summary->timestamp = $response->timestamp; - $summary->globalstacktrace = $response->globalstacktrace; - - $summary->successfultestcompetencies = $response->successfulTestCompetencyProfile; - $summary->overalltestcompetencies = $response->overallTestCompetencyProfile; - - $summary->results = self::decodejsonresultarray($response->results); - - return $summary; - } - - public static function decodeJsonRecommendation(string $jsonstring): array { - // Decode the JSON string into a PHP object - $response = json_decode($jsonstring); - error_log("decodeJsonRecommendation"); - error_log(print_r($response, true)); - - - // Initialize an empty array to store recommendations - $recommendations = []; - - // Loop through the recommendations in the response - if (!empty($response->recommendations)) { - foreach ($response->recommendations as $recommendation) { - // For each recommendation, create an associative array with the properties - $recommendations[] = [ - 'topic' => $recommendation->topic ?? null, - 'url' => $recommendation->exerciseName ?? null, - 'exercise_name' => $recommendation->url ?? null, - 'difficulty' => $recommendation->difficulty ?? null, - 'score' => $recommendation->score ?? null - ]; - } - } - error_log(print_r($recommendations,true)); - // Return the array of recommendations - return $recommendations; - } - - - /** - * Decodes the array of JSON detail results returned by the backend service call into the plugin PHP data structure. - * @param array $jsonarray decoded json array of results array - * @return array of DtaResult - */ - private static function decodejsonresultarray($jsonarray): array { - $ret = []; - foreach ($jsonarray as $entry) { - $value = new DtaResult(); - $value->packagename = $entry->packageName; - $value->classname = $entry->className; - $value->name = $entry->name; - - $value->state = $entry->state; - - $value->failuretype = $entry->failureType; - $value->failurereason = $entry->failureReason; - $value->stacktrace = $entry->stacktrace; - - $value->columnnumber = $entry->columnNumber; - $value->linenumber = $entry->lineNumber; - $value->position = $entry->position; - - $ret[] = $value; - } - return $ret; - } - - - /** - * Returns the number of detail results attached to the summary. - * @return int count of occurences - */ - public function resultcount(): int { - return count($this->results); - } - - /** - * Returns the number of detail results with the given state attached to the summary. - * @param int $state state ordinal number - * @return int count of occurences provided state has - */ - public function stateoccurencecount(int $state): int { - $num = 0; - foreach ($this->results as $r) { - if ($r->state == $state) { - $num++; - } - } - return $num; - } - - /** - * Returns the number of detail results with compilation errors attached to the summary. - * @return int count of occurences - */ - public function compilationerrorcount(): int { - return $this->stateoccurencecount(3); - } - - /** - * Returns the number of detail results with test failures attached to the summary. - * @return int count of occurences - */ - public function failedcount(): int { - return $this->stateoccurencecount(2); - } - - /** - * Returns the number of detail results with successful tests attached to the summary. - * @return int count of occurences - */ - public function successfulcount(): int { - return $this->stateoccurencecount(1); - } - - /** - * Returns the number of detail results with an unknown result - mostly due to compile errors - attached to the summary. - * @return int count of occurences - */ - public function unknowncount(): int { - return $this->stateoccurencecount(0); - } - -}