An open API service indexing awesome lists of open source software.

https://github.com/catalyst/moodle-local_headlessquiz


https://github.com/catalyst/moodle-local_headlessquiz

Last synced: 12 months ago
JSON representation

Awesome Lists containing this project

README

          



# local_headlessquiz
The primary purpose of this plugin is to facilitate running quizzes on Moodle in 'headless' mode i.e. via webservice calls.

Previously, up to 6 webservice calls were required to just start a single quiz attempt, making this quite difficult. This plugin combines this functionality into a single call, streamlining the process.

## Usage
There is no interface to this plugin, it is interacted with purely via webservices.

1. Create a quiz via the Moodle interface (see Limitations below)
2. Get the course module ID (from the URL query parameters)
3. The headless quiz function is attached to the `moodle_mobile_app` webservice, so ensure that it is enabled.
4. Call the webservice: `/webservice/rest/server.php?wstoken=[TOKEN]&wsfunction=local_headlessquiz_get_attempt&cmid=[CMID]&moodlewsrestformat=json` (replacing variables in brackets where necessary)

Example response from webservice function

```json
{
"data": {
"user": {
"id": 18
},
"quiz": {
"id": 1,
"name": "Headless Quiz Test 1",
"cmid": 66,
"gradetopass": 5,
"bestgrade": 10,
"maxgrade": 15,
"questions": [
{
"id": 11,
"name": "True or false 1",
"questiontext": "

Is this question true? Select True
<\/p>",
"type": "truefalse",
"slot": 1,
"options": "{\"id\":\"6\",\"question\":\"11\",\"trueanswer\":\"21\",\"falseanswer\":\"22\",\"answers\":{\"21\":{\"id\":\"21\",\"question\":\"11\",\"answer\":\"True\",\"answerformat\":\"0\",\"fraction\":\"1.0000000\",\"feedback\":\"

Feed back for true test
<\\\/p>\",\"feedbackformat\":\"1\"},\"22\":{\"id\":\"22\",\"question\":\"11\",\"answer\":\"False\",\"answerformat\":\"0\",\"fraction\":\"0.0000000\",\"feedback\":\"

<\\\/p>

Feed back for false test
<\\\/p>

<\\\/p>\",\"feedbackformat\":\"1\"}}}"
},
{
"id": 12,
"name": "multiple choice test 1",
"questiontext": "

multiple choice test 1
<\/p>",
"type": "multichoice",
"slot": 2,
"options": "{\"id\":\"3\",\"questionid\":\"12\",\"layout\":\"0\",\"single\":\"1\",\"shuffleanswers\":\"1\",\"correctfeedback\":\"Your answer is correct.\",\"correctfeedbackformat\":\"1\",\"partiallycorrectfeedback\":\"Your answer is partially correct.\",\"partiallycorrectfeedbackformat\":\"1\",\"incorrectfeedback\":\"Your answer is incorrect.\",\"incorrectfeedbackformat\":\"1\",\"answernumbering\":\"abc\",\"shownumcorrect\":\"1\",\"showstandardinstruction\":\"0\",\"answers\":{\"23\":{\"id\":\"23\",\"question\":\"12\",\"answer\":\"

C1
<\\\/p>\",\"answerformat\":\"1\",\"fraction\":\"1.0000000\",\"feedback\":\"

C1 feedback here
<\\\/p>\",\"feedbackformat\":\"1\"},\"24\":{\"id\":\"24\",\"question\":\"12\",\"answer\":\"

C2
<\\\/p>\",\"answerformat\":\"1\",\"fraction\":\"0.9000000\",\"feedback\":\"C2 feedback
<\\\/strong>\",\"feedbackformat\":\"1\"},\"25\":{\"id\":\"25\",\"question\":\"12\",\"answer\":\"

C3
<\\\/p>\",\"answerformat\":\"1\",\"fraction\":\"0.1111111\",\"feedback\":\"


C3 feddback
<\\\/em><\\\/p>\",\"feedbackformat\":\"1\"},\"26\":{\"id\":\"26\",\"question\":\"12\",\"answer\":\"

C4
<\\\/p>\",\"answerformat\":\"1\",\"fraction\":\"0.2500000\",\"feedback\":\"


C4 feedback
<\\\/h3>\",\"feedbackformat\":\"1\"}}}"
},
{
"id": 5,
"name": "Test3",
"questiontext": "


test
<\/p>",
"type": "multichoice",
"slot": 3,
"options": "{\"id\":\"2\",\"questionid\":\"5\",\"layout\":\"0\",\"single\":\"0\",\"shuffleanswers\":\"1\",\"correctfeedback\":\"Your answer is correct.\",\"correctfeedbackformat\":\"1\",\"partiallycorrectfeedback\":\"Your answer is partially correct.\",\"partiallycorrectfeedbackformat\":\"1\",\"incorrectfeedback\":\"Your answer is incorrect.\",\"incorrectfeedbackformat\":\"1\",\"answernumbering\":\"abc\",\"shownumcorrect\":\"1\",\"showstandardinstruction\":\"0\",\"answers\":{\"11\":{\"id\":\"11\",\"question\":\"5\",\"answer\":\"


test
<\\\/p>\",\"answerformat\":\"1\",\"fraction\":\"0.9000000\",\"feedback\":\"\",\"feedbackformat\":\"1\"},\"12\":{\"id\":\"12\",\"question\":\"5\",\"answer\":\"


test
<\\\/p>\",\"answerformat\":\"1\",\"fraction\":\"0.1000000\",\"feedback\":\"\",\"feedbackformat\":\"1\"},\"13\":{\"id\":\"13\",\"question\":\"5\",\"answer\":\"


test
<\\\/p>\",\"answerformat\":\"1\",\"fraction\":\"0.0000000\",\"feedback\":\"\",\"feedbackformat\":\"1\"},\"14\":{\"id\":\"14\",\"question\":\"5\",\"answer\":\"


wrong
<\\\/p>\",\"answerformat\":\"1\",\"fraction\":\"0.0000000\",\"feedback\":\"\",\"feedbackformat\":\"1\"}}}"
},
{
"id": 4,
"name": "Short answer",
"questiontext": "


Short answer
<\/p>",
"type": "shortanswer",
"slot": 4,
"options": "{\"usecase\":\"0\",\"answers\":{\"9\":{\"id\":\"9\",\"question\":\"4\",\"answer\":\"correct\",\"answerformat\":\"0\",\"fraction\":\"1.0000000\",\"feedback\":\"\",\"feedbackformat\":\"1\"},\"10\":{\"id\":\"10\",\"question\":\"4\",\"answer\":\"incorrect\",\"answerformat\":\"0\",\"fraction\":\"0.5000000\",\"feedback\":\"\",\"feedbackformat\":\"1\"}}}"
}
]
},
"attempt": {
"id": 77,
"state": "inprogress",
"feedback": "",
"summarks": 0,
"passed": false,
"scaledgrade": 0,
"timestart": 1667178056,
"timemodified": 1667179192,
"number": 64,
"responses": [
{
"questionid": 11,
"state": "gradedright",
"mark": 1,
"status": "Correct",
"data": "{\"answer\":\"1\"}",
"slot": 1,
"html": "


Question 1<\/span><\/h3>
Correct<\/div>
Mark 1.00 out of 1.00<\/div>

\"\"Flag question<\/span><\/label>\n<\/div><\/div>

Question text<\/h4>

Is this question true? Select True
<\/p><\/div>


Select one:<\/div>
True<\/label> <\/i><\/div>
False<\/label> <\/div><\/div><\/div><\/div>

Feedback<\/h4>

Feed back for true test
<\/p><\/div>



General feedback here
<\/p><\/div>


The correct answer is 'True'.<\/div><\/div><\/div><\/div><\/div>",
"sequencecheck": 2,
"feedback": "

Feed back for true test
<\/p><\/div>



General feedback here
<\/p><\/div>


The correct answer is 'True'.<\/div>"
},
{
"questionid": 12,
"state": "gradedpartial",
"mark": 0.56000000000000005,
"status": "Partially correct",
"data": "{\"answer\":\"1\"}",
"slot": 2,
"html": "

Question 2<\/span><\/h3>
Partially correct<\/div>
Mark 0.56 out of 5.00<\/div>

\"\"Flag question<\/span><\/label>\n<\/div><\/div>

Question text<\/h4>

multiple choice test 1
<\/p><\/div>


a. <\/span>

C4
<\/p><\/div><\/div> <\/div>\n


b. <\/span>

C3
<\/p><\/div><\/div> <\/i>

C3 feddback
<\/em><\/div><\/div>\n

c. <\/span>

C2
<\/p><\/div><\/div> <\/div>\n


d. <\/span>

C1
<\/p><\/div><\/div> <\/div>\n<\/div><\/div><\/div>


Feedback<\/h4>
Your answer is partially correct.<\/div>

General feedback
<\/p><\/div>


The correct answer is:

C1<\/div><\/div><\/div><\/div><\/div>",
"sequencecheck": 2,
"feedback": "


Your answer is partially correct.<\/div>

General feedback
<\/p><\/div>


The correct answer is:

C1<\/div>"
},
{
"questionid": 5,
"state": "complete",
"mark": null,
"status": "Not changed since last attempt",
"data": "{\"choice0\":\"1\",\"choice1\":\"1\",\"choice2\":\"0\",\"choice3\":\"1\",\"_order\":\"13,12,14,11\"}",
"slot": 3,
"html": "


Question 3<\/span><\/h3>
Not changed since last attempt<\/div>
Marked out of 5.00<\/div>

\"\"Flag question<\/span><\/label>\n<\/div><\/div>

Question text<\/h4>

test
<\/p><\/div>


a. <\/span>

test
<\/p><\/div><\/div> <\/div>\n


b. <\/span>

test
<\/p><\/div><\/div> <\/div>\n


c. <\/span>

wrong
<\/p><\/div><\/div> <\/div>\n


d. <\/span>

test
<\/p><\/div><\/div> <\/div>\n<\/div><\/div>


<\/div><\/div><\/div><\/div>",
"sequencecheck": 1,
"feedback": "
Your answer is correct.<\/div>
You have selected too many options.<\/div>
The correct answers are:

test,


test<\/div>"
},
{
"questionid": 4,
"state": "complete",
"mark": null,
"status": "Not changed since last attempt",
"data": "{\"answer\":\"correct\"}",
"slot": 4,
"html": "


Question 4<\/span><\/h3>
Not changed since last attempt<\/div>
Marked out of 1.00<\/div>

\"\"Flag question<\/span><\/label>\n<\/div><\/div>

Question text<\/h4>

Short answer
<\/p><\/div>


Answer: <\/span><\/label><\/div>
<\/div><\/div><\/div><\/div>",
"sequencecheck": 1,
"feedback": "
The correct answer is: correct<\/div>"
}
]
}
}
}
```

### Behaviour
When the function is called the following happens:
- If no attempt exists for the user, a new attempt is started
- If an attempt exists for the user, the attempt is returned

A new attempt can be 'forced' by passing the URL query paramter `forcenew=1`, which will create a new attempt and finish any existing previous attempts that are `inprogress`

![attempt flow](pix/attemptflow.png)

### Calling function relative to a user
The function is called relative to the $USER attached to the webservice token. To call this function for abitrary users, a plugin such as [tool_token](https://github.com/catalyst/moodle-tool_token) can be used.

For example, you can use tool_token to get a token for the user with username `testuser` with the following call:

`/webservice/rest/server.php?wstoken=2bc3138a0323283f481313f5caa6bf3c&wsfunction=tool_token_get_token&moodlewsrestformat=json&idtype=username&idvalue=testuser&service=moodle_mobile_app`

This returns:

`{"userid":18,"token":"3a8de124dd5a142be6a5cc2ae838231a","validuntil":0}`

It is then possible to call the headless quiz API using this token:

`/webservice/rest/server.php?wstoken=3a8de124dd5a142be6a5cc2ae838231a&wsfunction=local_headlessquiz_get_attempt&cmid=[CMID]&moodlewsrestformat=json`

This will call the `local_headlessquiz_get_attempt` relative to the user attached to the token, `testuser`.

## Limitations
1. Only single page quizzes are supported
2. Only a subset of question types are supported: `shortanswer`, `truefalse`, `multichoice`, `random`
3. Only plain question text is supported. For e.g. pluginfiles are not supported.

### Note for `qtype_random`
If using the random qtype, you must ensure all questions that it links to (via the selected category) are also of the supported question types and do not contain any non-plain question text.

This also includes questions in a category that have been 'deleted' but are still used elsewhere - these appear greyed out when `Also show old questions` is selected in the category editor.

To fix this, use a new category or remove all usages of the question.
## Branches

| Version | Branch |
|-----------|-------------------|
| 4.3 - 4.4 | MOODLE_403_STABLE |
| 4.2 - 4.2 | MOODLE_402_STABLE |
| 4.0 - 4.1 | MOODLE_400_STABLE |

## Installation

Clone the code to your Moodle installation

`git clone git@github.com:catalyst/moodle-local_headlessquiz.git local/headlessquiz`

Run the Moodle upgrade script to install the plugin

`php admin/cli/upgrade.php`

## Contributing and Support
Issues, and pull requests using github are welcome and encouraged!

If you would like commercial support or would like to sponsor additional improvements to this plugin please contact us:

https://www.catalyst-au.net/contact-us

# Credits

This plugin was developed by Catalyst IT Australia:

https://www.catalyst-au.net/

Catalyst IT

With funding provided by Commerce Tools:

https://commercetools.com/

![Commerce Tools](pix/commercetools.png)

## License
This program 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.

This program 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.