lib.php 6.82 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php

/**
 * @package     local_asystgrade
 * @author      Artem Baranovskyi
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

//
// 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/>.

23
use local_asystgrade\api\client;
24
25
use local_asystgrade\api\http_client;
use local_asystgrade\db\QuizQuery;
26
27
28
29
30
31
32
33
34
35
36
37

defined('MOODLE_INTERNAL') || die();

/**
 * A hook function that will process the data and insert the rating value.
 * The function must be called on the desired page like https://www.moodle.loc/mod/quiz/report.php?id=2&mode=grading&slot=1&qid=1&grade=needsgrading&includeauto=1
 *
 * @return void
 */

function local_asystgrade_before_footer()
{
38
    global $PAGE;
39
    // Obtaining parameters from URL
40
    $qid = optional_param('qid', null, PARAM_INT);
41
42
43
    $slot = optional_param('slot', false, PARAM_INT);

    if ($PAGE->url->compare(new moodle_url('/mod/quiz/report.php'), URL_MATCH_BASE) && $slot) {
44
        $quizQuery = new QuizQuery();
45

46
47
48
49
50
        if ($quizQuery->gradesExist($qid, $slot)) {
            error_log('Grades already exist in the database.');
            return;
        }

51
52
        $question_attempts = $quizQuery->get_question_attempts($qid, $slot);
        $referenceAnswer = $quizQuery->get_reference_answer($qid);
Artem Baranovskyi's avatar
Artem Baranovskyi committed
53
        $maxmark = (float)$question_attempts->current()->maxmark;
54
        $data = prepare_api_data($quizQuery, $question_attempts, $referenceAnswer);
55
56
57

        foreach (array_keys($data['studentData']) as $studentId) {
            if ($quizQuery->gradesExist($qid, $studentId)) {
58
59
60
                return;
            }
        }
61
62
63

        $studentData = $data['studentData'];
        $inputNames = array_column($studentData, 'inputName');
64
65
66
67
68

        error_log('Data prepared: ' . print_r($data, true));

        $apiendpoint = get_config('local_asystgrade', 'apiendpoint');
        if (!$apiendpoint) {
69
            $apiendpoint = 'http://flask:5000/api/autograde'; // Default setting, flask is the name of flask container
70
71
72
73
74
        }

        error_log('APIendpoint: ' . $apiendpoint);

        try {
75
76
            $httpClient = new http_client();
            $apiClient = client::getInstance($apiendpoint, $httpClient);
77
78
79
            error_log('ApiClient initiated.');

            error_log('Sending data to API and getting grade');
80
81
82
83
            $response = $apiClient->send_data([
                'referenceAnswer' => $data['referenceAnswer'],
                'studentAnswers' => array_column($studentData, 'studentAnswer')
            ]);
84
            $grades = json_decode($response, true);
85
86
87
88
89
90
91
92
93

            error_log('Grade obtained: ' . print_r($grades, true));
        } catch (Exception $e) {
            error_log('Error sending data to API: ' . $e->getMessage());
            return;
        }

        error_log('After API call');

Artem Baranovskyi's avatar
Artem Baranovskyi committed
94
        pasteGradedMarks($grades, $inputNames, $maxmark);
95
96
97
98
99
100
101

        error_log('URL matches /mod/quiz/report.php in page_init');
    }
}

/**
 * Adds JavasScript scrypt to update marks
Artem Baranovskyi's avatar
Artem Baranovskyi committed
102
103
104
 * @param  array $grades
 * @param  array $inputNames
 * @param  float $maxmark
105
106
 * @return void
 */
Artem Baranovskyi's avatar
Artem Baranovskyi committed
107
108

function pasteGradedMarks(array $grades, array $inputNames, float $maxmark): void
109
{
Artem Baranovskyi's avatar
Artem Baranovskyi committed
110
    echo generate_script($grades, $inputNames, $maxmark);
111
112
113
}

/**
Artem Baranovskyi's avatar
Artem Baranovskyi committed
114
115
 * Processes question attempts and answers to prepare for API a data to estimate answers
 *
116
117
118
119
120
121
122
 * @param QuizQuery $database
 * @param $question_attempts
 * @param $referenceAnswer
 * @return array
 */
function prepare_api_data(QuizQuery $database, $question_attempts, $referenceAnswer): array
{
123
    $studentData = [];
124
125
126
127
128
129
130

    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);
131
132
133
134
135
136
137
138
139
140
                $studentId = $quizattempt_step->userid;
                $inputName = "q" . $question_attempt->questionusageid . ":" . $question_attempt->slot . "_-mark";

                // Adding data to an associative array
                $studentData[$studentId] = [
                    'studentAnswer' => $studentAnswer,
                    'inputName' => $inputName // identifying name for mark input field updating
                ];

                error_log("Student ID: $studentId, Student Answer: $studentAnswer, Input Name: $inputName");
141
142
            }
        }
143

144
        $quizattempt_steps->close();
145
    }
146
147
148
149
150

    $question_attempts->close();

    return [
        'referenceAnswer' => $referenceAnswer,
151
        'studentData' => $studentData
152
    ];
153
154
}

155
156
157
/**
 * Builds JavasScript scrypt to update marks using DOM manipulations
 *
Artem Baranovskyi's avatar
Artem Baranovskyi committed
158
159
160
 * @param  array $grades
 * @param  array $inputNames
 * @param  float $maxmark
161
162
 * @return string
 */
Artem Baranovskyi's avatar
Artem Baranovskyi committed
163
function generate_script(array $grades, array $inputNames, float $maxmark) {
164
165
166
167
168
    $script = "<script type='text/javascript'>
        document.addEventListener('DOMContentLoaded', function() {";

    foreach ($grades as $index => $grade) {
        if (isset($grade['predicted_grade'])) {
Artem Baranovskyi's avatar
Artem Baranovskyi committed
169
            $predicted_grade = $grade['predicted_grade'] == 'correct' ? $maxmark : 0;
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
            $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
 */
192
193
194
spl_autoload_register(function ($classname) {
    // Check if the class name starts with our plugin's namespace
    if (strpos($classname, 'local_asystgrade\\') === 0) {
195
        // Transforming the Namespace into the Path
196
197
198
199
200
201
202
203
204
        $classname = str_replace('local_asystgrade\\', '', $classname);
        $classname = str_replace('\\', DIRECTORY_SEPARATOR, $classname);
        $filepath  = __DIR__ . DIRECTORY_SEPARATOR . 'classes' . DIRECTORY_SEPARATOR . $classname . '.php';

        if (file_exists($filepath)) {
            require_once($filepath);
        }
    }
});