Project 'ulrike.pado/asyst-moodle-plugin' was moved to 'knight/asyst-moodle-plugin'. Please update any links and bookmarks that may still have the old path.
Commit 6d93bfa3 authored by Artem Baranovskyi's avatar Artem Baranovskyi
Browse files

Architectural refactoring of plugin. Extracted Class QuizQuery for all DB-related queries.

parent 9acbb49a
Showing with 211 additions and 109 deletions
+211 -109
...@@ -3,20 +3,35 @@ ...@@ -3,20 +3,35 @@
namespace local_asystgrade\api; namespace local_asystgrade\api;
use Exception; use Exception;
use \local_asystgrade\api\http_client_interface;
defined('MOODLE_INTERNAL') || die(); defined('MOODLE_INTERNAL') || die();
class client { class client {
private $endpoint; private string $endpoint;
private $httpClient; private http_client_interface $httpClient;
private static ?client $instance = null;
/** /**
* @param string $endpoint * @param string $endpoint
* @param http_client|null $httpClient * @param http_client_interface $httpClient
*/ */
public function __construct(string $endpoint, http_client $httpClient = null) { private function __construct(string $endpoint, http_client_interface $httpClient) {
$this->endpoint = $endpoint; $this->endpoint = $endpoint;
$this->httpClient = $httpClient ?: new http_client(); $this->httpClient = $httpClient;
}
/**
* @param string $endpoint
* @param http_client_interface $httpClient
* @return client
*/
public static function getInstance(string $endpoint, http_client_interface $httpClient): client
{
if (self::$instance === null) {
self::$instance = new client($endpoint, $httpClient);
}
return self::$instance;
} }
/** /**
...@@ -24,9 +39,12 @@ class client { ...@@ -24,9 +39,12 @@ class client {
* @return bool|string * @return bool|string
* @throws Exception * @throws Exception
*/ */
public function send_data(array $data) { public function send_data(array $data): bool|string
$response = $this->httpClient->post($this->endpoint, $data); {
try {
return $response; return $this->httpClient->post($this->endpoint, $data);
} catch (Exception $e) {
throw new Exception('HTTP request error: ' . $e->getMessage());
}
} }
} }
\ No newline at end of file
<?php
namespace local_asystgrade\db;
class QuizQuery implements QuizQueryInterface
{
private $db;
public function __construct() {
global $DB;
$this->db = $DB;
}
/**
* @param $qid
* @param $slot
* @return mixed
*/
public function get_question_attempts($qid, $slot) {
return $this->db->get_recordset(
'question_attempts',
[
'questionid' => $qid,
'slot' => $slot
],
'',
'*'
);
}
/**
* @param $qid
* @return mixed
*/
public function get_reference_answer($qid) {
return $this->db->get_record(
'qtype_essay_options',
[
'questionid' => $qid
],
'*',
MUST_EXIST
)->graderinfo;
}
/**
* @param $question_attempt_id
* @return mixed
*/
public function get_attempt_steps($question_attempt_id) {
return $this->db->get_recordset(
'question_attempt_steps',
[
'questionattemptid' => $question_attempt_id
],
'',
'*'
);
}
/**
* @param $attemptstepid
* @return mixed
*/
public function get_student_answer($attemptstepid) {
return $this->db->get_record(
'question_attempt_step_data',
[
'attemptstepid' => $attemptstepid,
'name' => 'answer'
],
'*',
MUST_EXIST
)->value;
}
}
<?php
namespace local_asystgrade\db;
interface QuizQueryInterface
{
public function get_question_attempts($qid, $slot);
public function get_reference_answer($qid);
public function get_attempt_steps($question_attempt_id);
public function get_student_answer($attemptstepid);
}
\ No newline at end of file
...@@ -21,6 +21,8 @@ ...@@ -21,6 +21,8 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
use local_asystgrade\api\client; use local_asystgrade\api\client;
use local_asystgrade\api\http_client;
use local_asystgrade\db\QuizQuery;
defined('MOODLE_INTERNAL') || die(); defined('MOODLE_INTERNAL') || die();
...@@ -33,90 +35,24 @@ defined('MOODLE_INTERNAL') || die(); ...@@ -33,90 +35,24 @@ defined('MOODLE_INTERNAL') || die();
function local_asystgrade_before_footer() function local_asystgrade_before_footer()
{ {
global $PAGE, $DB; global $PAGE;
// Получение параметров из URL // Получение параметров из URL
$qid = optional_param('qid', null, PARAM_INT); $qid = optional_param('qid', null, PARAM_INT);
$slot = optional_param('slot', false, PARAM_INT); $slot = optional_param('slot', false, PARAM_INT);
if ($PAGE->url->compare(new moodle_url('/mod/quiz/report.php'), URL_MATCH_BASE) && $slot) { if ($PAGE->url->compare(new moodle_url('/mod/quiz/report.php'), URL_MATCH_BASE) && $slot) {
$question_attempts = $DB->get_recordset( $quizQuery = new QuizQuery();
'question_attempts',
[
'questionid' => $qid,
'slot' => $slot
],
'',
'*'
);
// Obtaining exemplary answer
$referenceAnswer = $DB->get_record(
'qtype_essay_options',
[
'questionid' => $qid
],
'*',
MUST_EXIST
)->graderinfo;
$studentAnswers = [];
$inputNames = [];
foreach ($question_attempts as $question_attempt) {
// Obtaining all steps for this questionusageid
$quizattempt_steps = $DB->get_recordset(
'question_attempt_steps',
[
'questionattemptid' => $question_attempt->id
],
'',
'*'
);
// Processing every quiz attempt step
foreach ($quizattempt_steps as $quizattempt_step) {
if ($quizattempt_step->state === 'complete') {
$userid = $quizattempt_step->userid;
$attemptstepid = $quizattempt_step->id;
// Obtaining student's answer
$studentAnswer = $DB->get_record(
'question_attempt_step_data',
[
'attemptstepid' => $attemptstepid,
'name' => 'answer'
],
'*',
MUST_EXIST
)->value;
// Forming student's answers array
$studentAnswers[] = $studentAnswer;
// Forming correct mark text input field name: q + questionusageid : question's slot + _mark
$inputNames[] = "q" . $question_attempt->questionusageid . ":" . $question_attempt->slot . "_-mark";
error_log("User ID: $userid, Student Answer: $studentAnswer, Reference Answer: $referenceAnswer, Input Name: $inputNames[-1]");
}
}
// Closing of record's sets $question_attempts = $quizQuery->get_question_attempts($qid, $slot);
$quizattempt_steps->close(); $referenceAnswer = $quizQuery->get_reference_answer($qid);
}
// Closing of record's sets $data = prepare_api_data($quizQuery, $question_attempts, $referenceAnswer);
$question_attempts->close();
// API request preparation $inputNames = $data['inputNames'];
$data = [
'referenceAnswer' => $referenceAnswer,
'studentAnswers' => $studentAnswers
];
error_log('Data prepared: ' . print_r($data, true)); error_log('Data prepared: ' . print_r($data, true));
// Obtaining API settings
$apiendpoint = get_config('local_asystgrade', 'apiendpoint'); $apiendpoint = get_config('local_asystgrade', 'apiendpoint');
if (!$apiendpoint) { if (!$apiendpoint) {
$apiendpoint = 'http://127.0.0.1:5000/api/autograde'; // Default setting $apiendpoint = 'http://127.0.0.1:5000/api/autograde'; // Default setting
...@@ -124,15 +60,14 @@ function local_asystgrade_before_footer() ...@@ -124,15 +60,14 @@ function local_asystgrade_before_footer()
error_log('APIendpoint: ' . $apiendpoint); error_log('APIendpoint: ' . $apiendpoint);
// Initializing API client
try { try {
$apiClient = new client($apiendpoint); $httpClient = new http_client();
$apiClient = client::getInstance($apiendpoint, $httpClient);
error_log('ApiClient initiated.'); error_log('ApiClient initiated.');
// Sending data on API and obtaining auto grades
error_log('Sending data to API and getting grade'); error_log('Sending data to API and getting grade');
$response = $apiClient->send_data($data); $response = $apiClient->send_data($data);
$grades = json_decode($response, true); $grades = json_decode($response, true);
error_log('Grade obtained: ' . print_r($grades, true)); error_log('Grade obtained: ' . print_r($grades, true));
} catch (Exception $e) { } catch (Exception $e) {
...@@ -142,36 +77,96 @@ function local_asystgrade_before_footer() ...@@ -142,36 +77,96 @@ function local_asystgrade_before_footer()
error_log('After API call'); error_log('After API call');
// Check grades existence and pasting them at grade input fields through JavaScript DOM manipulations pasteGradedMarks($grades, $inputNames);
$script = "
<script type='text/javascript'> error_log('URL matches /mod/quiz/report.php in page_init');
document.addEventListener('DOMContentLoaded', function() {"; }
foreach ($grades as $index => $grade) { }
if (isset($grade['predicted_grade'])) {
$predicted_grade = $grade['predicted_grade'] == 'correct' ? 1 : 0; /**
// How forms param name="q2:1_-mark" see at https://github.com/moodle/moodle/blob/main/question/behaviour/rendererbase.php#L132 * Adds JavasScript scrypt to update marks
// and https://github.com/moodle/moodle/blob/main/question/engine/questionattempt.php#L381 , L407 *
$input_name = $inputNames[$index]; // Q is an question attempt -> ID of mdl_quiz_attempts, :1_ is question attempt -> step * @param mixed $grades
$script .= " * @param mixed $inputNames
console.log('Trying to update input: {$input_name} with grade: {$predicted_grade}'); * @return void
var gradeInput = document.querySelector('input[name=\"{$input_name}\"]'); */
if (gradeInput) { function pasteGradedMarks(mixed $grades, mixed $inputNames): void
console.log('Found input: {$input_name}'); {
gradeInput.value = '{$predicted_grade}'; echo generate_script($grades, $inputNames);;
} else { }
console.log('Input not found: {$input_name}');
}"; /**
* @param QuizQuery $database
* @param $question_attempts
* @param $referenceAnswer
* @return array
*/
function prepare_api_data(QuizQuery $database, $question_attempts, $referenceAnswer): array
{
$studentAnswers = [];
$inputNames = [];
foreach ($question_attempts as $question_attempt) {
$quizattempt_steps = $database->get_attempt_steps($question_attempt->id);
foreach ($quizattempt_steps as $quizattempt_step) {
if ($quizattempt_step->state === 'complete') {
$studentAnswer = $database->get_student_answer($quizattempt_step->id);
$studentAnswers[] = $studentAnswer;
$inputNames[] = "q" . $question_attempt->questionusageid . ":" . $question_attempt->slot . "_-mark";
error_log("Student Answer: $studentAnswer, Input Name: " . end($inputNames));
} }
} }
$script .= "
});
</script>";
echo $script; $quizattempt_steps->close();
error_log('URL matches /mod/quiz/report.php in page_init');
} }
$question_attempts->close();
return [
'referenceAnswer' => $referenceAnswer,
'studentAnswers' => $studentAnswers,
'inputNames' => $inputNames
];
} }
/**
* Builds JavasScript scrypt to update marks using DOM manipulations
*
* @param $grades
* @param $inputNames
* @return string
*/
function generate_script($grades, $inputNames) {
$script = "<script type='text/javascript'>
document.addEventListener('DOMContentLoaded', function() {";
foreach ($grades as $index => $grade) {
if (isset($grade['predicted_grade'])) {
$predicted_grade = $grade['predicted_grade'] == 'correct' ? 1 : 0;
$input_name = $inputNames[$index];
$script .= "
console.log('Trying to update input: {$input_name} with grade: {$predicted_grade}');
var gradeInput = document.querySelector('input[name=\"{$input_name}\"]');
if (gradeInput) {
console.log('Found input: {$input_name}');
gradeInput.value = '{$predicted_grade}';
} else {
console.log('Input not found: {$input_name}');
}";
}
}
$script .= "});
</script>";
return $script;
}
/**
* Autoloader registration
*/
spl_autoload_register(function ($classname) { spl_autoload_register(function ($classname) {
// Check if the class name starts with our plugin's namespace // Check if the class name starts with our plugin's namespace
if (strpos($classname, 'local_asystgrade\\') === 0) { if (strpos($classname, 'local_asystgrade\\') === 0) {
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment