diff --git a/amd/build/spellcheck.min.js b/amd/build/spellcheck.min.js
index 8c7860e..3fa7313 100644
--- a/amd/build/spellcheck.min.js
+++ b/amd/build/spellcheck.min.js
@@ -6,6 +6,6 @@ define("qtype_aitext/spellcheck",["exports","qtype_aitext/diff","core_form/modal
* @copyright 2024, ISB Bayern
* @author Dr. Peter Mayer
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */(Diff),_modalform=(obj=_modalform)&&obj.__esModule?obj:{default:obj};_exports.init=(cmid,readonlyareaselector,spellcheckeditbuttonselector)=>{renderDiff(readonlyareaselector),document.querySelector(spellcheckeditbuttonselector)&&document.querySelector(spellcheckeditbuttonselector).addEventListener("click",(async event=>{event.preventDefault(),await showModalForm(cmid,readonlyareaselector)}))};const renderDiff=readonlyareaselector=>{const studentanswer=document.querySelector(readonlyareaselector).innerHTML,spellcheck=document.querySelector(readonlyareaselector).dataset.spellcheck;let span=null;const diff=Diff.diffChars(studentanswer,spellcheck),fragment=document.createElement("div");let fullspellcheck="";diff.forEach((part=>{part.value=part.value.replace(/ /g," ");const parser=new DOMParser;part.value=parser.parseFromString(part.value,"text/html");const cls=part.added?"qtype_aitext_spellcheck_new":part.removed?"qtype_aitext_spellcheck_wrong":"";part.added||part.removed?(span=document.createElement("span"),span.classList=cls,span.appendChild(part.value.documentElement),fullspellcheck+=span.outerHTML):fullspellcheck+=part.value.documentElement.textContent})),fragment.innerHTML=fullspellcheck,document.querySelector(readonlyareaselector).replaceChildren(fragment)};_exports.renderDiff=renderDiff;const showModalForm=async(cmid,readonlyareaselector)=>{const attemptstepid=document.querySelector(readonlyareaselector).dataset.spellcheckattemptstepid,answerstepid=document.querySelector(readonlyareaselector).dataset.spellcheckattemptstepanswerid,title=await(0,_str.getString)("spellcheckedit","qtype_aitext"),modalForm=new _modalform.default({formClass:"qtype_aitext\\form\\edit_spellchek",args:{attemptstepid:attemptstepid,answerstepid:answerstepid,cmid:cmid},modalConfig:{title:title}});modalForm.addEventListener(modalForm.events.FORM_SUBMITTED,reloadpage),await modalForm.show()};_exports.showModalForm=showModalForm;const reloadpage=()=>{location.reload()}}));
+ */(Diff),_modalform=(obj=_modalform)&&obj.__esModule?obj:{default:obj};_exports.init=(readonlyareaselector,spellcheckeditbuttonselector)=>{renderDiff(readonlyareaselector),document.querySelector(spellcheckeditbuttonselector)&&document.querySelector(spellcheckeditbuttonselector).addEventListener("click",(async event=>{event.preventDefault(),await showModalForm(readonlyareaselector)}))};const renderDiff=readonlyareaselector=>{const studentanswer=document.querySelector(readonlyareaselector).innerHTML,spellcheck=document.querySelector(readonlyareaselector).dataset.spellcheck;let span=null;const diff=Diff.diffChars(studentanswer,spellcheck),fragment=document.createElement("div");let fullspellcheck="";diff.forEach((part=>{part.value=part.value.replace(/ /g," ");const parser=new DOMParser;part.value=parser.parseFromString(part.value,"text/html");const cls=part.added?"qtype_aitext_spellcheck_new":part.removed?"qtype_aitext_spellcheck_wrong":"";part.added||part.removed?(span=document.createElement("span"),span.classList=cls,span.appendChild(part.value.documentElement),fullspellcheck+=span.outerHTML):fullspellcheck+=part.value.documentElement.textContent})),fragment.innerHTML=fullspellcheck,document.querySelector(readonlyareaselector).replaceChildren(fragment)};_exports.renderDiff=renderDiff;const showModalForm=async readonlyareaselector=>{const attemptstepid=document.querySelector(readonlyareaselector).dataset.spellcheckattemptstepid,answerstepid=document.querySelector(readonlyareaselector).dataset.spellcheckattemptstepanswerid,title=await(0,_str.getString)("spellcheckedit","qtype_aitext"),modalForm=new _modalform.default({formClass:"qtype_aitext\\form\\edit_spellcheck",args:{attemptstepid:attemptstepid,answerstepid:answerstepid},modalConfig:{title:title}});modalForm.addEventListener(modalForm.events.FORM_SUBMITTED,reloadpage),await modalForm.show()};_exports.showModalForm=showModalForm;const reloadpage=()=>{location.reload()}}));
//# sourceMappingURL=spellcheck.min.js.map
\ No newline at end of file
diff --git a/amd/build/spellcheck.min.js.map b/amd/build/spellcheck.min.js.map
index f2478c9..cbc2565 100644
--- a/amd/build/spellcheck.min.js.map
+++ b/amd/build/spellcheck.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"spellcheck.min.js","sources":["../src/spellcheck.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Generates the spellcheck diff view.\n *\n * @module qtype_aitext/spellcheck\n * @copyright 2024, ISB Bayern\n * @author Dr. Peter Mayer\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport * as Diff from 'qtype_aitext/diff';\nimport ModalForm from 'core_form/modalform';\nimport {getString as getString} from 'core/str';\n\n/**\n * Init the module.\n *\n * @param {int} cmid the course module id of the quiz.\n * @param {string} readonlyareaselector the selector for the readonly area to apply the spellchecking\n * @param {string} spellcheckeditbuttonselector the selector for the spell check edit button\n */\nexport const init = (cmid, readonlyareaselector, spellcheckeditbuttonselector) => {\n renderDiff(readonlyareaselector);\n\n if (!document.querySelector(spellcheckeditbuttonselector)) {\n return;\n }\n document.querySelector(spellcheckeditbuttonselector).addEventListener('click',\n async(event) => {\n event.preventDefault();\n await showModalForm(cmid, readonlyareaselector);\n });\n};\n\n/**\n * Render the spell check highlighting.\n *\n * @param {string} readonlyareaselector the selector for the readonly area to apply the spell check diff to\n */\nexport const renderDiff = (readonlyareaselector) => {\n const studentanswer = document.querySelector(readonlyareaselector).innerHTML;\n const spellcheck = document.querySelector(readonlyareaselector).dataset.spellcheck;\n let span = null;\n\n const diff = Diff.diffChars(studentanswer, spellcheck);\n const fragment = document.createElement('div');\n\n let fullspellcheck = '';\n\n diff.forEach(part => {\n // We need to replace the whitespaces, because otherwise they will be removed by\n // calling parseFromString of the DOMParser.\n part.value = part.value.replace(/ /g, ' ');\n const parser = new DOMParser();\n part.value = parser.parseFromString(part.value, 'text/html');\n const cls = part.added ? 'qtype_aitext_spellcheck_new' :\n part.removed ? 'qtype_aitext_spellcheck_wrong' : '';\n if (part.added || part.removed) {\n span = document.createElement('span');\n span.classList = cls;\n span.appendChild(part.value.documentElement);\n fullspellcheck += span.outerHTML;\n } else {\n fullspellcheck += part.value.documentElement.textContent;\n }\n });\n\n fragment.innerHTML = fullspellcheck;\n document.querySelector(readonlyareaselector).replaceChildren(fragment);\n};\n\n/**\n * Show the dynamic spellcheck form.\n *\n * @param {int} cmid the course module id of the quiz\n * @param {string} readonlyareaselector the selector for the readonly area\n */\nexport const showModalForm = async(cmid, readonlyareaselector) => {\n const attemptstepid = document.querySelector(readonlyareaselector).dataset.spellcheckattemptstepid;\n const answerstepid = document.querySelector(readonlyareaselector).dataset.spellcheckattemptstepanswerid;\n const title = await getString('spellcheckedit', 'qtype_aitext');\n const modalForm = new ModalForm({\n formClass: \"qtype_aitext\\\\form\\\\edit_spellchek\",\n args: {\n attemptstepid,\n answerstepid,\n cmid\n },\n modalConfig: {title},\n });\n modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, reloadpage);\n await modalForm.show();\n};\n\n/**\n * Reload the page.\n *\n * This is not nice, but easy :-) .\n */\nconst reloadpage = () => {\n location.reload();\n};\n"],"names":["cmid","readonlyareaselector","spellcheckeditbuttonselector","renderDiff","document","querySelector","addEventListener","async","event","preventDefault","showModalForm","studentanswer","innerHTML","spellcheck","dataset","span","diff","Diff","diffChars","fragment","createElement","fullspellcheck","forEach","part","value","replace","parser","DOMParser","parseFromString","cls","added","removed","classList","appendChild","documentElement","outerHTML","textContent","replaceChildren","attemptstepid","spellcheckattemptstepid","answerstepid","spellcheckattemptstepanswerid","title","modalForm","ModalForm","formClass","args","modalConfig","events","FORM_SUBMITTED","reloadpage","show","location","reload"],"mappings":";;;;;;;;wFAmCoB,CAACA,KAAMC,qBAAsBC,gCAC7CC,WAAWF,sBAENG,SAASC,cAAcH,+BAG5BE,SAASC,cAAcH,8BAA8BI,iBAAiB,SAClEC,MAAAA,QACIC,MAAMC,uBACAC,cAAcV,KAAMC,gCASzBE,WAAcF,6BACjBU,cAAgBP,SAASC,cAAcJ,sBAAsBW,UAC7DC,WAAaT,SAASC,cAAcJ,sBAAsBa,QAAQD,eACpEE,KAAO,WAELC,KAAOC,KAAKC,UAAUP,cAAeE,YACrCM,SAAWf,SAASgB,cAAc,WAEpCC,eAAiB,GAErBL,KAAKM,SAAQC,OAGTA,KAAKC,MAAQD,KAAKC,MAAMC,QAAQ,KAAM,gBAChCC,OAAS,IAAIC,UACnBJ,KAAKC,MAAQE,OAAOE,gBAAgBL,KAAKC,MAAO,mBAC1CK,IAAMN,KAAKO,MAAQ,8BACrBP,KAAKQ,QAAU,gCAAkC,GACjDR,KAAKO,OAASP,KAAKQ,SACnBhB,KAAOX,SAASgB,cAAc,QAC9BL,KAAKiB,UAAYH,IACjBd,KAAKkB,YAAYV,KAAKC,MAAMU,iBAC5Bb,gBAAkBN,KAAKoB,WAEvBd,gBAAkBE,KAAKC,MAAMU,gBAAgBE,eAIrDjB,SAASP,UAAYS,eACrBjB,SAASC,cAAcJ,sBAAsBoC,gBAAgBlB,gDASpDT,cAAgBH,MAAMP,KAAMC,8BAC/BqC,cAAgBlC,SAASC,cAAcJ,sBAAsBa,QAAQyB,wBACrEC,aAAepC,SAASC,cAAcJ,sBAAsBa,QAAQ2B,8BACpEC,YAAc,kBAAU,iBAAkB,gBAC1CC,UAAY,IAAIC,mBAAU,CAC5BC,UAAW,qCACXC,KAAM,CACFR,cAAAA,cACAE,aAAAA,aACAxC,KAAAA,MAEJ+C,YAAa,CAACL,MAAAA,SAElBC,UAAUrC,iBAAiBqC,UAAUK,OAAOC,eAAgBC,kBACtDP,UAAUQ,mDAQdD,WAAa,KACfE,SAASC"}
\ No newline at end of file
+{"version":3,"file":"spellcheck.min.js","sources":["../src/spellcheck.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Generates the spellcheck diff view.\n *\n * @module qtype_aitext/spellcheck\n * @copyright 2024, ISB Bayern\n * @author Dr. Peter Mayer\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport * as Diff from 'qtype_aitext/diff';\nimport ModalForm from 'core_form/modalform';\nimport {getString as getString} from 'core/str';\n\n/**\n * Init the module.\n *\n * @param {string} readonlyareaselector the selector for the readonly area to apply the spellchecking\n * @param {string} spellcheckeditbuttonselector the selector for the spell check edit button\n */\nexport const init = (readonlyareaselector, spellcheckeditbuttonselector) => {\n renderDiff(readonlyareaselector);\n\n if (!document.querySelector(spellcheckeditbuttonselector)) {\n return;\n }\n document.querySelector(spellcheckeditbuttonselector).addEventListener('click',\n async(event) => {\n event.preventDefault();\n await showModalForm(readonlyareaselector);\n });\n};\n\n/**\n * Render the spell check highlighting.\n *\n * @param {string} readonlyareaselector the selector for the readonly area to apply the spell check diff to\n */\nexport const renderDiff = (readonlyareaselector) => {\n const studentanswer = document.querySelector(readonlyareaselector).innerHTML;\n const spellcheck = document.querySelector(readonlyareaselector).dataset.spellcheck;\n let span = null;\n\n const diff = Diff.diffChars(studentanswer, spellcheck);\n const fragment = document.createElement('div');\n\n let fullspellcheck = '';\n\n diff.forEach(part => {\n // We need to replace the whitespaces, because otherwise they will be removed by\n // calling parseFromString of the DOMParser.\n part.value = part.value.replace(/ /g, ' ');\n const parser = new DOMParser();\n part.value = parser.parseFromString(part.value, 'text/html');\n const cls = part.added ? 'qtype_aitext_spellcheck_new' :\n part.removed ? 'qtype_aitext_spellcheck_wrong' : '';\n if (part.added || part.removed) {\n span = document.createElement('span');\n span.classList = cls;\n span.appendChild(part.value.documentElement);\n fullspellcheck += span.outerHTML;\n } else {\n fullspellcheck += part.value.documentElement.textContent;\n }\n });\n\n fragment.innerHTML = fullspellcheck;\n document.querySelector(readonlyareaselector).replaceChildren(fragment);\n};\n\n/**\n * Show the dynamic spellcheck form.\n *\n * @param {string} readonlyareaselector the selector for the readonly area\n */\nexport const showModalForm = async(readonlyareaselector) => {\n const attemptstepid = document.querySelector(readonlyareaselector).dataset.spellcheckattemptstepid;\n const answerstepid = document.querySelector(readonlyareaselector).dataset.spellcheckattemptstepanswerid;\n const title = await getString('spellcheckedit', 'qtype_aitext');\n const modalForm = new ModalForm({\n formClass: \"qtype_aitext\\\\form\\\\edit_spellcheck\",\n args: {\n attemptstepid,\n answerstepid\n },\n modalConfig: {title},\n });\n modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, reloadpage);\n await modalForm.show();\n};\n\n/**\n * Reload the page.\n *\n * This is not nice, but easy :-) .\n */\nconst reloadpage = () => {\n location.reload();\n};\n"],"names":["readonlyareaselector","spellcheckeditbuttonselector","renderDiff","document","querySelector","addEventListener","async","event","preventDefault","showModalForm","studentanswer","innerHTML","spellcheck","dataset","span","diff","Diff","diffChars","fragment","createElement","fullspellcheck","forEach","part","value","replace","parser","DOMParser","parseFromString","cls","added","removed","classList","appendChild","documentElement","outerHTML","textContent","replaceChildren","attemptstepid","spellcheckattemptstepid","answerstepid","spellcheckattemptstepanswerid","title","modalForm","ModalForm","formClass","args","modalConfig","events","FORM_SUBMITTED","reloadpage","show","location","reload"],"mappings":";;;;;;;;wFAkCoB,CAACA,qBAAsBC,gCACvCC,WAAWF,sBAENG,SAASC,cAAcH,+BAG5BE,SAASC,cAAcH,8BAA8BI,iBAAiB,SAClEC,MAAAA,QACIC,MAAMC,uBACAC,cAAcT,gCASnBE,WAAcF,6BACjBU,cAAgBP,SAASC,cAAcJ,sBAAsBW,UAC7DC,WAAaT,SAASC,cAAcJ,sBAAsBa,QAAQD,eACpEE,KAAO,WAELC,KAAOC,KAAKC,UAAUP,cAAeE,YACrCM,SAAWf,SAASgB,cAAc,WAEpCC,eAAiB,GAErBL,KAAKM,SAAQC,OAGTA,KAAKC,MAAQD,KAAKC,MAAMC,QAAQ,KAAM,gBAChCC,OAAS,IAAIC,UACnBJ,KAAKC,MAAQE,OAAOE,gBAAgBL,KAAKC,MAAO,mBAC1CK,IAAMN,KAAKO,MAAQ,8BACrBP,KAAKQ,QAAU,gCAAkC,GACjDR,KAAKO,OAASP,KAAKQ,SACnBhB,KAAOX,SAASgB,cAAc,QAC9BL,KAAKiB,UAAYH,IACjBd,KAAKkB,YAAYV,KAAKC,MAAMU,iBAC5Bb,gBAAkBN,KAAKoB,WAEvBd,gBAAkBE,KAAKC,MAAMU,gBAAgBE,eAIrDjB,SAASP,UAAYS,eACrBjB,SAASC,cAAcJ,sBAAsBoC,gBAAgBlB,gDAQpDT,cAAgBH,MAAAA,6BACnB+B,cAAgBlC,SAASC,cAAcJ,sBAAsBa,QAAQyB,wBACrEC,aAAepC,SAASC,cAAcJ,sBAAsBa,QAAQ2B,8BACpEC,YAAc,kBAAU,iBAAkB,gBAC1CC,UAAY,IAAIC,mBAAU,CAC5BC,UAAW,sCACXC,KAAM,CACFR,cAAAA,cACAE,aAAAA,cAEJO,YAAa,CAACL,MAAAA,SAElBC,UAAUrC,iBAAiBqC,UAAUK,OAAOC,eAAgBC,kBACtDP,UAAUQ,mDAQdD,WAAa,KACfE,SAASC"}
\ No newline at end of file
diff --git a/amd/src/spellcheck.js b/amd/src/spellcheck.js
index 6639a5a..a46c1f2 100644
--- a/amd/src/spellcheck.js
+++ b/amd/src/spellcheck.js
@@ -29,11 +29,10 @@ import {getString as getString} from 'core/str';
/**
* Init the module.
*
- * @param {int} cmid the course module id of the quiz.
* @param {string} readonlyareaselector the selector for the readonly area to apply the spellchecking
* @param {string} spellcheckeditbuttonselector the selector for the spell check edit button
*/
-export const init = (cmid, readonlyareaselector, spellcheckeditbuttonselector) => {
+export const init = (readonlyareaselector, spellcheckeditbuttonselector) => {
renderDiff(readonlyareaselector);
if (!document.querySelector(spellcheckeditbuttonselector)) {
@@ -42,7 +41,7 @@ export const init = (cmid, readonlyareaselector, spellcheckeditbuttonselector) =
document.querySelector(spellcheckeditbuttonselector).addEventListener('click',
async(event) => {
event.preventDefault();
- await showModalForm(cmid, readonlyareaselector);
+ await showModalForm(readonlyareaselector);
});
};
@@ -86,19 +85,17 @@ export const renderDiff = (readonlyareaselector) => {
/**
* Show the dynamic spellcheck form.
*
- * @param {int} cmid the course module id of the quiz
* @param {string} readonlyareaselector the selector for the readonly area
*/
-export const showModalForm = async(cmid, readonlyareaselector) => {
+export const showModalForm = async(readonlyareaselector) => {
const attemptstepid = document.querySelector(readonlyareaselector).dataset.spellcheckattemptstepid;
const answerstepid = document.querySelector(readonlyareaselector).dataset.spellcheckattemptstepanswerid;
const title = await getString('spellcheckedit', 'qtype_aitext');
const modalForm = new ModalForm({
- formClass: "qtype_aitext\\form\\edit_spellchek",
+ formClass: "qtype_aitext\\form\\edit_spellcheck",
args: {
attemptstepid,
- answerstepid,
- cmid
+ answerstepid
},
modalConfig: {title},
});
diff --git a/classes/external.php b/classes/external.php
index 3c9e807..a405e14 100644
--- a/classes/external.php
+++ b/classes/external.php
@@ -72,7 +72,7 @@ public static function fetch_ai_grade($response, $defaultmark, $prompt, $markssc
if (!empty($response) && !empty($prompt) && $defaultmark > 0) {
$fullaiprompt = $aiquestion->build_full_ai_prompt($response, $prompt, $defaultmark, $marksscheme);
$feedback = $aiquestion->perform_request($fullaiprompt);
- $contentobject = json_decode($feedback);
+ $contentobject = $aiquestion->process_feedback($feedback);
} else {
$contentobject = (object)["feedback" => get_string('err_parammissing', 'qtype_aitext'), "marks" => 0];
}
diff --git a/classes/form/edit_spellchek.php b/classes/form/edit_spellcheck.php
similarity index 76%
rename from classes/form/edit_spellchek.php
rename to classes/form/edit_spellcheck.php
index 380db99..83b40e2 100644
--- a/classes/form/edit_spellchek.php
+++ b/classes/form/edit_spellcheck.php
@@ -29,7 +29,10 @@
* @author Dr. Peter Mayer
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class edit_spellchek extends dynamic_form {
+class edit_spellcheck extends dynamic_form {
+
+ /** @var context|null Variable to store the context because it is expensive to retrieve. */
+ private ?context $context = null;
/**
* Define the form
@@ -38,9 +41,6 @@ public function definition() {
$mform = &$this->_form;
- $mform->addElement('hidden', 'cmid');
- $mform->setType('cmid', PARAM_INT);
-
$mform->addElement('hidden', 'attemptstepid');
$mform->setType('attemptstepid', PARAM_INT);
@@ -66,13 +66,8 @@ public function definition() {
* @return context
*/
protected function get_context_for_dynamic_submission(): context {
- $cmid = $this->optional_param('cmid', null, PARAM_INT);
- if (empty($cmid)) {
- $cmid = $this->_ajaxformdata['cmid'];
- }
- // Verify cm exists.
- [, $cm] = get_course_and_cm_from_cmid($cmid);
- return context_module::instance($cm->id);
+ $attemptstepid = $this->_ajaxformdata['attemptstepid'];
+ return $this->get_context_from_attemptstepid($attemptstepid);
}
/**
@@ -81,13 +76,26 @@ protected function get_context_for_dynamic_submission(): context {
* @throws \moodle_exception User does not have capability to access the form
*/
protected function check_access_for_dynamic_submission(): void {
+ global $USER;
$context = $this->get_context_for_dynamic_submission();
+
+ if ($context->contextlevel === CONTEXT_USER) {
+ // This will happen in preview mode.
+ // In preview mode we just check if the user context belongs to the current user.
+ if (intval($context->instanceid) !== intval($USER->id)) {
+ throw new \moodle_exception('nocapabilitytousethisservice');
+ }
+ return;
+ }
+ // We usually end up with a course module context otherwise. Even if not we just check for
+ // decent capabilities to edit the result of the AI.
if (
!has_capability('mod/quiz:grade', $context) &&
!has_capability('mod/quiz:regrade', $context)
) {
throw new \moodle_exception('nocapabilitytousethisservice');
}
+
}
/**
@@ -130,7 +138,6 @@ public function set_data_for_dynamic_submission(): void {
'test' => $spellcheckrecord->value,
'spellcheck_editor' => ['text' => $spellcheckrecord->value, 'format' => FORMAT_HTML, 'itemid' => $draftitemid],
'attemptstepid' => $this->optional_param('attemptstepid', 0, PARAM_INT),
- 'cmid' => $this->optional_param('cmid', 0, PARAM_INT),
'student_answer' => $answerrecord->value,
]);
}
@@ -147,4 +154,16 @@ protected function get_page_url_for_dynamic_submission(): moodle_url {
];
return new moodle_url('/mod/quiz/review.php', $params);
}
+
+ private function get_context_from_attemptstepid(int $attemptstepid) {
+ global $DB;
+ if (!is_null($this->context)) {
+ return $this->context;
+ }
+ $attemptstep = $DB->get_record('question_attempt_steps', ['id' => $attemptstepid]);
+ $attempt = $DB->get_record('question_attempts', ['id' => $attemptstep->questionattemptid]);
+ $questionusage = $DB->get_record('question_usages', ['id' => $attempt->questionusageid]);
+ $this->context = context::instance_by_id($questionusage->contextid);
+ return $this->context;
+ }
}
diff --git a/question.php b/question.php
index f25e57d..01cc092 100755
--- a/question.php
+++ b/question.php
@@ -147,8 +147,6 @@ public function apply_attempt_state(question_attempt_step $step) {
* Call the llm using either the 4.5 core api or the backend provided by
* local_ai_manager (mebis) or tool_aimanager
*
- * See "uselocalaimanager" admin setting.
- *
* @param string $prompt
* @param string $purpose
*/
@@ -341,7 +339,8 @@ protected function llm_translate(string $text): string {
$cache = cache::make('qtype_aitext', 'stringdata');
if (($translation = $cache->get(current_language().'_'.$text)) === false) {
- $prompt = 'translate "'.$text .'" into '.current_language();
+ $prompt = 'translate "'.$text .'" into '.current_language() .
+ 'Only return the exact text, do not wrap it in other text.';
$translation = $this->perform_request($prompt, 'translate');
$translation = trim($translation, '"');
$cache->set(current_language().'_'.$text, $translation);
diff --git a/questiontype.php b/questiontype.php
index 2fd86f5..0d712d2 100755
--- a/questiontype.php
+++ b/questiontype.php
@@ -388,7 +388,7 @@ protected function export_errorcmid($cmid) {
*/
public function menu_name() {
if (class_exists('\local_ai_manager\local\tenant')) {
- if (get_config('qtype_aitext', 'uselocalaimanager')) {
+ if (get_config('qtype_aitext', 'backend') === 'local_ai_manager') {
$tenant = \core\di::get(\local_ai_manager\local\tenant::class);
return $tenant->is_tenant_allowed() ? parent::menu_name() : '';
}
diff --git a/renderer.php b/renderer.php
index 10ee294..2902cc6 100755
--- a/renderer.php
+++ b/renderer.php
@@ -23,7 +23,7 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-use qtype_aitext\form\edit_spellchek;
+use qtype_aitext\form\edit_spellcheck;
defined('MOODLE_INTERNAL') || die();
/**
@@ -93,7 +93,7 @@ public function formulation_and_controls(question_attempt $qa,
}
$result = '';
- if (get_config('qtype_aitext', 'uselocalaimanager')) {
+ if (get_config('qtype_aitext', 'backend') === 'local_ai_manager') {
$uniqid = uniqid();
$result .= html_writer::tag('div', '',
['data-content' => 'local_ai_manager_infobox', 'data-boxid' => $uniqid]);
@@ -114,7 +114,7 @@ public function formulation_and_controls(question_attempt $qa,
}
$result .= html_writer::tag('div', $files, ['class' => 'attachments']);
$result .= html_writer::end_tag('div');
- if (get_config('qtype_aitext', 'uselocalaimanager')) {
+ if (get_config('qtype_aitext', 'backend') === 'local_ai_manager') {
$result .= html_writer::tag('div', '',
['data-content' => 'local_ai_manager_warningbox', 'data-boxid' => $uniqid]);
$this->page->requires->js_call_amd('local_ai_manager/warningbox', 'renderWarningBox',
@@ -247,7 +247,6 @@ public function files_input(question_attempt $qa, $numallowed,
return $output;
}
-
}
@@ -273,7 +272,7 @@ public function set_displayoptions(question_display_options $displayoptions): vo
}
/**
- * Render the students respone when the question is in read-only mode.
+ * Render the students response when the question is in read-only mode.
*
* @param string $name the variable name this input edits.
* @param question_attempt $qa the question attempt being display.
@@ -282,56 +281,9 @@ public function set_displayoptions(question_display_options $displayoptions): vo
* @param object $context the context teh output belongs to.
* @return string html to display the response.
*/
- abstract public function response_area_read_only($name, question_attempt $qa,
- question_attempt_step $step, $lines, $context);
-
- /**
- * Render the students respone when the question is in read-only mode.
- * @param string $name the variable name this input edits.
- * @param question_attempt $qa the question attempt being display.
- * @param question_attempt_step $step the current step.
- * @param int $lines approximate size of input box to display.
- * @param object $context the context teh output belongs to.
- * @return string html to display the response for editing.
- */
- abstract public function response_area_input($name, question_attempt $qa,
- question_attempt_step $step, $lines, $context);
-
- /**
- * Specific class name to add to the input element.
- *
- * @return string
- */
- abstract protected function class_name();
-}
-
-/**
- * Where the student use the HTML editor
- *
- * @author Marcus Green 2024 building on work by the UK OU
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class qtype_aitext_format_editor_renderer extends qtype_aitext_format_renderer_base {
- /**
- * Specific class name to add to the input element.
- *
- * @return string
- */
- protected function class_name() {
- return 'qtype_aitext_editor';
- }
- /**
- * Return a read only version of the response areay. Typically for after
- * a quesiton has been answered and the response cannot be modified.
- * @param string $name
- * @param question_attempt $qa
- * @param question_attempt_step $step
- * @param int $lines number of lines in the editor
- * @param object $context
- * @return string
- * @throws coding_exception
- */
public function response_area_read_only($name, $qa, $step, $lines, $context) {
+ global $USER;
+
$question = $qa->get_question();
$uniqid = uniqid();
$readonlyareaid = 'aitext_readonly_area' . $uniqid;
@@ -340,7 +292,7 @@ public function response_area_read_only($name, $qa, $step, $lines, $context) {
if ($question->spellcheck) {
$this->page->requires->js_call_amd('qtype_aitext/diff');
$this->page->requires->js_call_amd('qtype_aitext/spellcheck', 'init',
- [$this->get_page()->cm->id, '#' . $readonlyareaid, '#' . $spellcheckeditbuttonid]);
+ ['#' . $readonlyareaid, '#' . $spellcheckeditbuttonid]);
$stepspellcheck = $qa->get_last_step_with_qt_var('-spellcheckresponse');
$stepanswer = $qa->get_last_step_with_qt_var('answer');
}
@@ -350,12 +302,12 @@ public function response_area_read_only($name, $qa, $step, $lines, $context) {
$output = html_writer::tag('h4', $responselabel, ['id' => $labelbyid, 'class' => 'sr-only']);
$divoptions = [
- 'id' => $readonlyareaid,
- 'role' => 'textbox',
- 'aria-readonly' => 'true',
- 'aria-labelledby' => $labelbyid,
- 'class' => $this->class_name() . ' qtype_aitext_response readonly',
- 'style' => 'min-height: ' . ($lines * 1.25) . 'em;',
+ 'id' => $readonlyareaid,
+ 'role' => 'textbox',
+ 'aria-readonly' => 'true',
+ 'aria-labelledby' => $labelbyid,
+ 'class' => $this->class_name() . ' qtype_aitext_response readonly',
+ 'style' => 'min-height: ' . ($lines * 1.25) . 'em;',
];
if ($qa->get_question()->spellcheck) {
@@ -368,27 +320,66 @@ public function response_area_read_only($name, $qa, $step, $lines, $context) {
$output .= html_writer::tag('div', $this->prepare_response($name, $qa, $step, $context), $divoptions);
if (
- $qa->get_question()->spellcheck &&
- (
- has_capability('mod/quiz:grade', $context) ||
- has_capability('mod/quiz:regrade', $context)
- )
+ $qa->get_question()->spellcheck &&
+ (
+ has_capability('mod/quiz:grade', $context) ||
+ has_capability('mod/quiz:regrade', $context) ||
+ ($context->contextlevel === CONTEXT_USER && intval($USER->id) === intval($context->instanceid))
+ )
) {
$btnoptions = ['id' => $spellcheckeditbuttonid, 'class' => 'btn btn-link'];
$output .= html_writer::tag(
- 'button',
- $this->output->pix_icon(
- 'i/edit',
- get_string('spellcheckedit', 'qtype_aitext'),
- 'moodle'
- ) . " " . get_string('spellcheckedit', 'qtype_aitext'),
- $btnoptions
+ 'button',
+ $this->output->pix_icon(
+ 'i/edit',
+ get_string('spellcheckedit', 'qtype_aitext'),
+ 'moodle'
+ ) . " " . get_string('spellcheckedit', 'qtype_aitext'),
+ $btnoptions
);
}
+ // Height $lines * 1.25 because that is a typical line-height on web pages.
+ // That seems to give results that look OK.
return $output;
}
+ /**
+ * Render the students respone when the question is in read-only mode.
+ * @param string $name the variable name this input edits.
+ * @param question_attempt $qa the question attempt being display.
+ * @param question_attempt_step $step the current step.
+ * @param int $lines approximate size of input box to display.
+ * @param object $context the context teh output belongs to.
+ * @return string html to display the response for editing.
+ */
+ abstract public function response_area_input($name, question_attempt $qa,
+ question_attempt_step $step, $lines, $context);
+
+ /**
+ * Specific class name to add to the input element.
+ *
+ * @return string
+ */
+ abstract protected function class_name();
+}
+
+/**
+ * Where the student use the HTML editor
+ *
+ * @author Marcus Green 2024 building on work by the UK OU
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qtype_aitext_format_editor_renderer extends qtype_aitext_format_renderer_base {
+ /**
+ * Specific class name to add to the input element.
+ *
+ * @return string
+ */
+ protected function class_name() {
+ return 'qtype_aitext_editor';
+ }
+
/**
* Where the student types in their response
*
@@ -684,76 +675,6 @@ protected function textarea($response, $lines, $attributes) {
protected function class_name() {
return 'qtype_aitext_plain';
}
- /**
- * Read only version of response (typically after submission)
- * @param string $name
- * @param question_attempt $qa
- * @param question_attempt_step $step
- * @param int $lines
- * @param object $context
- * @return string
- * @throws coding_exception
- */
- public function response_area_read_only($name, $qa, $step, $lines, $context) {
- // CARE: This is basically duplicating response_area_read_only from qtype_aitext_format_editor_renderer.
- $question = $qa->get_question();
- $uniqid = uniqid();
- $readonlyareaid = 'aitext_readonly_area' . $uniqid;
- $spellcheckeditbuttonid = 'aitext_spellcheckedit' . $uniqid;
-
- if ($question->spellcheck) {
- $this->page->requires->js_call_amd('qtype_aitext/diff');
- $this->page->requires->js_call_amd('qtype_aitext/spellcheck', 'init',
- [$this->get_page()->cm->id, '#' . $readonlyareaid, '#' . $spellcheckeditbuttonid]);
- $stepspellcheck = $qa->get_last_step_with_qt_var('-spellcheckresponse');
- $stepanswer = $qa->get_last_step_with_qt_var('answer');
- }
- // Lib to display the spellcheck diff.
- $labelbyid = $qa->get_qt_field_name($name) . '_label';
- $responselabel = $this->displayoptions->add_question_identifier_to_label(get_string('answertext', 'qtype_aitext'));
- $output = html_writer::tag('h4', $responselabel, ['id' => $labelbyid, 'class' => 'sr-only']);
-
- $divoptions = [
- 'id' => $readonlyareaid,
- 'role' => 'textbox',
- 'aria-readonly' => 'true',
- 'aria-labelledby' => $labelbyid,
- 'class' => $this->class_name() . ' qtype_aitext_response readonly',
- 'style' => 'min-height: ' . ($lines * 1.25) . 'em;',
- ];
-
- if ($qa->get_question()->spellcheck) {
- $divoptions['data-spellcheck'] = $this->prepare_response('-spellcheckresponse', $qa, $stepspellcheck, $context);
- $divoptions['data-spellcheckattemptstepid'] = $stepspellcheck->get_id();
- $divoptions['data-spellcheckattemptstepanswerid'] = $stepanswer->get_id();
- $divoptions['data-answer'] = $this->prepare_response($name, $qa, $step, $context);
- }
-
- $output .= html_writer::tag('div', $this->prepare_response($name, $qa, $step, $context), $divoptions);
-
- if (
- $qa->get_question()->spellcheck &&
- (
- has_capability('mod/quiz:grade', $context) ||
- has_capability('mod/quiz:regrade', $context)
- )
- ) {
- $btnoptions = ['id' => $spellcheckeditbuttonid, 'class' => 'btn btn-link'];
- $output .= html_writer::tag(
- 'button',
- $this->output->pix_icon(
- 'i/edit',
- get_string('spellcheckedit', 'qtype_aitext'),
- 'moodle'
- ) . " " . get_string('spellcheckedit', 'qtype_aitext'),
- $btnoptions
- );
- }
- // Height $lines * 1.25 because that is a typical line-height on web pages.
- // That seems to give results that look OK.
-
- return $output;
- }
/**
* Text area for response to be keyed in
diff --git a/settings.php b/settings.php
index 9db359d..51bb114 100644
--- a/settings.php
+++ b/settings.php
@@ -66,12 +66,6 @@
new lang_string('responseformat_setting', 'qtype_aitext'),
0, ['plain' => 'plain', 'editor' => 'editor', 'monospaced' => 'monospaced']
));
- $settings->add(new admin_setting_configcheckbox(
- 'qtype_aitext/uselocalaimanager',
- new lang_string('use_local_ai_manager', 'qtype_aitext'),
- new lang_string('use_local_ai_manager_setting', 'qtype_aitext'),
- 0
- ));
// Define the choices for the radio buttons.
$backends = [
'local_ai_manager' => get_string('localaimanager', 'qtype_aitext'),