Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
985292b
start of template-based quiz authoring
jonhartm Apr 19, 2018
cf518b8
just a file rename
jonhartm Apr 19, 2018
327c1c1
renamed tempalate names to make renumbering easier
jonhartm Apr 19, 2018
2d988c7
fixed mis-numbering of answers for new questions
jonhartm Apr 19, 2018
edb0648
re-ordered js code and added comments for each function
jonhartm Apr 19, 2018
6d17c16
removed unnessecary function.
jonhartm Apr 19, 2018
a605ef5
fixed some dumb little syntax/numbering mistakes
jonhartm Apr 19, 2018
9ce8319
added buttons to remove answers from mc and sa questions
jonhartm Apr 19, 2018
58acc40
moved adding a potential answer to a function
jonhartm Apr 19, 2018
09f5200
First shot at adding a potential answer from a button press
jonhartm Apr 19, 2018
172894a
added buttons to the top of config
jonhartm Apr 19, 2018
8ef7a69
added function to re-purpose buttons to delete, rather than add
jonhartm Apr 19, 2018
4228837
fixed re-numbering of answers when one is removed
jonhartm Apr 19, 2018
a022dc3
made addAnswer with a default paramater
jonhartm Apr 19, 2018
8b9e3a5
questions are now totally renumbered when one is deleted
jonhartm Apr 19, 2018
2672e15
we don't need to bother keeping count of the number of questions
jonhartm Apr 19, 2018
cb625a1
added call to lti_resize() when adding an answer option to a question
jonhartm Apr 24, 2018
fddf6ce
removed a leftover debug print
jonhartm Apr 24, 2018
0c43e39
added missing values to the T/F template
jonhartm Apr 24, 2018
7d713b8
Fixed an html error.
jonhartm Apr 24, 2018
d7fd050
Add a hidden field for question type
jonhartm Apr 24, 2018
6cf1903
question prefixes aren't really needed, now that we have a type field
jonhartm Apr 24, 2018
cc9141c
no reason to make t/f question any different than the others
jonhartm Apr 25, 2018
759de59
Basic authoring done
jonhartm Apr 26, 2018
b22bd31
some super basic styling
jonhartm Apr 26, 2018
cb6cdad
fixed javascript bug from changing input names earlier
jonhartm Apr 26, 2018
1f319ea
set focus to the input field when adding or removing an answer
jonhartm Apr 26, 2018
125e4eb
added a "save and return" option
jonhartm Apr 26, 2018
f885ddb
added confirmation buttons to question delete
jonhartm Apr 26, 2018
0e5c6f0
a whole bunch of comments
jonhartm Apr 26, 2018
0a56ec9
fixed parsing answers for quizes with more than 9 questions
jonhartm Apr 26, 2018
d4e6dd6
combine the js files
jonhartm Apr 26, 2018
6e84c62
marked question titles as disabled for now
jonhartm Apr 26, 2018
775a46d
old configure should return to configure.php instead of index
jonhartm Apr 26, 2018
8b2756e
hide the question title for now if it's not being used
jonhartm Apr 27, 2018
7b8af04
create a generic css rule for highlighting validation errors
jonhartm Apr 28, 2018
fa446b1
First shot at adding javascript validation
jonhartm Apr 28, 2018
213db01
create a "right" css class for right aligned objects
jonhartm Apr 28, 2018
3b640b6
added ability to put simple html in question
jonhartm Apr 28, 2018
edf3dad
converted question text to a textarea field
jonhartm Apr 28, 2018
4015d23
fixed validation on MA/MC questions
jonhartm Apr 28, 2018
c7f6229
Make the validation on entering the quiz buttons less aggressive
jonhartm Apr 28, 2018
4963264
validate the entire quiz anytime an object is changed.
jonhartm Apr 28, 2018
2e4d86a
removed hardcoded style for top-nav buttons
jonhartm Apr 29, 2018
41ca75f
Added a confirmation in the event that a quiz has results
jonhartm Apr 29, 2018
4390c74
resize the frame in the event we get a really long error list
jonhartm Apr 29, 2018
ac27f8f
check to make sure the quiz has at least one question
jonhartm Apr 29, 2018
38f9389
set the question type selector to autofocus
jonhartm Apr 29, 2018
00da7ad
validate the quiz when the user clicks either save button
jonhartm Apr 29, 2018
b213073
re-did how buttons are presented
jonhartm Apr 29, 2018
01036a6
fixed issue where user can delete the last possible answer
jonhartm Apr 30, 2018
2e8ea8c
fixed issue with checkboxes dissapearing when removing answers
jonhartm Apr 30, 2018
63766da
hide the html checkbox for the moment - it seems a little risky
jonhartm Apr 30, 2018
2d950f1
set focus on the text field of a newly added question
jonhartm Apr 30, 2018
05fbb55
Display the question type in the question header
jonhartm Apr 30, 2018
09354f9
fixed issue with dissapearing values for new questions
jonhartm Apr 30, 2018
68c5e4e
Re-size the frame window on index
jonhartm Apr 30, 2018
cf366a3
whoops. Changed this earlier and saved it somewhere along the line.
jonhartm Apr 30, 2018
c0d77b7
added ability to change question type
jonhartm Apr 30, 2018
3312fca
added ability to change question type
jonhartm Apr 30, 2018
32e8199
Edited buttons at the base of the form.
jonhartm Apr 30, 2018
ec26eb8
Merge branch 'authoring' of https://github.com/jonhartm/gift into aut…
jonhartm Apr 30, 2018
79ecd25
Fixed bug when trying to change type of a question that was renamed.
jonhartm Apr 30, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
vendor
composer.lock

\.sass-cache
235 changes: 109 additions & 126 deletions configure.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php
require_once "../config.php";
require_once "parse.php";
require_once "sample.php";
require_once "configure_parse.php";

use \Tsugi\Core\Cache;
use \Tsugi\Core\LTIX;
Expand All @@ -13,142 +13,125 @@
// Model
$p = $CFG->dbprefix;

// If they pressed Submit on the quiz content
if ( isset($_POST['gift']) ) {
$gift = $_POST['gift'];
$_SESSION['gift'] = $gift;

// Some sanity checking...
$retval = check_gift($gift);
if ( ! $retval ) {
header( 'Location: '.addSession('configure.php') ) ;
return;
}

// This is not JSON - no one cares
$LINK->setJson($gift);
$_SESSION['success'] = 'Quiz updated';
unset($_SESSION['gift']);
// check to see if there are results from this link already
$results_rows = $PDOX->allRowsDie("SELECT result_id, R.link_id AS link_id, R.user_id AS user_id, M.role as role,
sourcedid, service_id, grade, note, R.json AS json, R.note AS note
FROM lti_result AS R
JOIN lti_link AS L ON L.link_id = R.link_id AND R.link_id = :LI
JOIN lti_context AS C ON L.context_id = C.context_id AND C.context_id = :CI
JOIN lti_membership AS M ON R.user_id = M.user_id AND C.context_id = M.context_id
WHERE L.link_id = :LI AND M.role = 0 AND R.json IS NOT NULL",
array(':LI'=>$LINK->id, ':CI'=>$CONTEXT->id));

if (!empty($_POST)) {

$gift = parse_configure_post();

// Sanity check
$retval = check_gift($gift);
if ( ! $retval ) {
header( 'Location: '.addSession('configure.php') ) ;
return;
}

$LINK->setJson($gift);
$_SESSION['success'] = 'Quiz updated';
if ($_POST['save_quiz'] == "Save and Return") {
header( 'Location: '.addSession('index.php') ) ;
return;
}

// Check to see if we are supposed to preload a quiz
$files = false;
$lock = false;
if ( isset ($CFG->giftquizzes) && is_dir($CFG->giftquizzes) ) {
$files1 = scandir($CFG->giftquizzes);
$files = array();
foreach($files1 as $file) {
if ( $file == '.lock' ) {
$lock = trim(file_get_contents($CFG->giftquizzes.'/'.$file));
continue;
}
if ( strpos($file, '.') === 0 ) continue;
$files[] = $file;
}
sort($files);
}
if ( count($files) < 1 ) {
$_SESSION['error'] = "Found no files in ".$CFG->giftquizzes;
header( 'Location: '.addSession('configure.php') ) ;
return;
}
// print_r($files);
// echo("LOCK = ".$lock);

$default = isset($_SESSION['default_quiz']) ? $_SESSION['default_quiz'] : false;

// Load up the selected file
if ( $files && isset($_POST['file']) ) {
$key = isset($_POST['lock']) ? $_POST['lock'] : false;
if ( $lock && $lock != $key ) {
$_SESSION['error'] = 'Incorrect password';
header( 'Location: '.addSession('configure.php') ) ;
return;
}

$name = $_POST['file'];
if ( ! in_array($name, $files) ) {
$_SESSION['error'] = 'Quiz file not found: '.$_POST['file'];
header( 'Location: '.addSession('configure.php') ) ;
return;
}

$gift = file_get_contents($CFG->giftquizzes.'/'.$name);
$_SESSION['gift'] = $gift;

// Also pre-check for sanity
$retval = check_gift($gift);
if ( ! $retval ) {
header( 'Location: '.addSession('configure.php') ) ;
return;
}

$_SESSION['success'] = 'Preloaded quiz content from file. Make sure to save the quiz below.';
} else {
header( 'Location: '.addSession('configure.php') ) ;
return;
}

// Load up the quiz from session or DB
if ( isset($_SESSION['gift']) ) {
$gift = $_SESSION['gift'];
unset($_SESSION['gift']);
} else {
$gift = $LINK->getJson();
}

// Clean up the JSON for presentation
if ( $gift === false || strlen($gift) < 1 ) {
if ( $default != false && $lock == false && in_array($default, $files) ) {
$gift = file_get_contents($CFG->giftquizzes.'/'.$default);
$_SESSION['success'] = 'Loaded quiz '.$default.' as default';
} else {
$gift = getSampleGIFT();
}
}
return;
}

// View
$OUTPUT->header();
?>
<link rel="stylesheet" type="text/css" href="css/authoring.css">
<?php
$OUTPUT->bodyStart();
$OUTPUT->topNav();
echo('<div class="right">');
echo('<a href="index.php" class="btn btn-default">Cancel</a> ');
echo('<a href="old_configure.php" class="btn btn-default">Input GIFT Quiz Format</a> ');
echo('</div>');
$OUTPUT->flashMessages();
?>
<p>Be careful in making any changes if this quiz has submissions.</p>
<?php
if ( $files !== false ) {
echo("<form method=\"post\">\n");
// echo('<select name="file" onchange="console.dir(this); if(this.value!=0) this.form.submit();">'."\n");
echo('<select name="file">'."\n");
echo('<option value="0">Select Quiz</option>'."\n");
foreach($files as $file) {
if ( $default && $default == $file ) {
echo('<option value="'.htmlentities($file).'" selected>'.htmlentities($file).'</option>'."\n");
} else {
echo('<option value="'.htmlentities($file).'">'.htmlentities($file).'</option>'."\n");
}
}
echo("</select>\n");
if ( $lock != false ) {
echo('<input type="password" name="lock"> Password ');
}
echo('<input type="submit" value="Load Quiz">');
echo("</form>\n");
<form method="post">
<?php
// If we found results rows that weren't empty earlier, show a warning and disable the entire form
if ($results_rows) {
?>
<div id="warning_for_edit_with_results" class="error-list warning">
<p>WARNING: Results have already been recorded for this quiz - are you sure you want to make changes?</p>
<div>
<input type="button" class="btn btn-danger" id="confirm_edit_with_results" value="Yes, I want to make changes...">
</div>
</div>
<fieldset disabled="disabled">
<?php
} else {
// We didn't find any results, so just make the form normally
?>
<fieldset>
<?php
}
?>
<p>
The assignment is configured by carefully editing the gift below.
The documentation for the GIFT format comes from
<a href="https://docs.moodle.org/29/en/GIFT_format" target="_blank">Moodle Documentation</a>.
</p>
<form method="post" style="margin-left:5%;">
<textarea name="gift" rows="25" cols="80" style="width:95%" >
<?php echo(htmlent_utf8($gift)); ?>
</textarea>
<p>
<input type="submit" value="Save">
<input type=submit name=doCancel onclick="location='<?php echo(addSession('index.php'));?>'; return false;" value="Cancel"></p>
<div id="quiz_content"></div>
<div id="validation-error-list" class="error-list warning" style="display:none"></div>
<div class="quiz-controls">
<select class="form-control add-question-type-select" id="question_type_select" autofocus>
<option value=""> -- Add a New Question -- </option>
<option value="true_false_question">True/False Question</option>
<option value="multiple_choice_question">Multiple Choice/Multiple Answer Question</option>
<option value="short_answer_question">Short Answer Question</option>
</select>
<input type="submit" class="btn btn-default" name="save_quiz" value="Save">
<input type="submit" class="btn btn-default" name="save_quiz" value="Save and Return">
<input type=submit name=doCancel class="btn btn-default" onclick="location='<?php echo(addSession('index.php'));?>'; return false;" value="Cancel"></p>
<!-- <input type=submit name=view onclick="location='<?php echo(addSession('quiz_data.php'));?>'; return false;" value="View JSON"></p> -->
</div>
</fieldset>
</form>
<?php

$OUTPUT->footer();
<?php
$OUTPUT->footerStart();
$OUTPUT->templateInclude(array('common', 'tf_authoring', 'mc_authoring', 'sa_authoring'));
?>
<script type="text/javascript" src="js/authoring.js"></script>
<script type="text/javascript" src="js/validation.js"></script>
<script>
$(document).ready(()=> {
// see if there's already a quiz saved in the JSON
$.getJSON("<?= addSession('quiz_data.php') ?>", function(quizData) {
if (!quizData) {
console.log("No quiz is configured");
} else {
for (var q=0; q<quizData.length;q++) {
var context = quizData[q];
// decode htmlentities - from https://stackoverflow.com/a/10715834
context.question = $('<textarea/>').html(context.question).text();
context.count = $("#quiz_content").children().length+1;
addQuestion(context);
}
}
});

$(".save-buttons").click(function() {
validate_quiz();
})

$("#quiz_content").change(function() {
validate_quiz();
});

// In the event the confirmation div appears at the top of the form
// Pressing the button will hide the div and enable the form
$("#confirm_edit_with_results").click(function() {
$("#warning_for_edit_with_results").hide();
$("fieldset").removeAttr("disabled");
});
})
</script>
<?php
$OUTPUT->footerEnd();
98 changes: 98 additions & 0 deletions configure_parse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php
/* -----------------------------------------------------------------------------
// configure_parse.php
// Parse POST data from configure.php in order to pull out the details for each
// question and create a GIFT formatted string that can be read by check_gift()
// -----------------------------------------------------------------------------
*/

// The important one. Roll through the POST data until we run out of questions,
// then return the final GIFT formatted string
function parse_configure_post() {
$questions = array();
$q_num = 1;
$question_details = array("Not Null"); // Make sure the first time we enter the loop, this isn't Null
while ($question_details != Null) {
// pass the current number to question details and see if we can get the details for it
$question_details = get_question($q_num);
// if there is data for a question, create the format. If it's null, we're done and it's time to exit the loop
if ($question_details != Null) {
// if it's not null, just tack it onto the $questions array for later
array_push($questions, create_gift_format($question_details));
}
// increment the question number to get the next one
$q_num++;
}
// join all of the questions array (strings of GIFT format) with a double return between each
return implode("\n\n", $questions);
}

// Get the details of a specific question from the POST data, by question number
function get_question($num) {
$question_details = array('answer'=>array()); // initialize our array
foreach ($_POST as $key => $value) { // Loop through the entire POST
// We're only interested in keys which have "questionX" in them, and which have a value associated with them
if ((strpos($key, "question".$num) > 0) &&($value != Null)) {
// Trim off the "_questionX" part of the key and make that the key name
$key_name = implode("_", explode("_", $key, -1));
// Is this an answer option?
if (strpos($key_name, "answer") !== false) {
// Get the number for this answer from the key name (format "answerX" or "answerX_iscorrect")
$key_parts = explode("_", $key_name); // make an array from the key name (['answerX', 'questionX'])
$answer_index = explode("answer", $key_parts[0])[1];
// is this the text of the answer or an indicator that this is the correct answer?
if (sizeof($key_parts) > 1 && $key_parts[1] == 'iscorrect') {
// T/F questions are a little different. We can identify them because they have an 'iscorrect' key
$question_details['answer'][$answer_index]['iscorrect'] = true;
} else {
$question_details['answer'][$answer_index] = array('text'=>$value, 'iscorrect'=>false);
}
} else {
// It's not an answer option, so jus save the k-v pair
$question_details[$key_name] = $value;
}
}
}

// if we actaully found something return it
if (sizeof($question_details) > 1) {
return $question_details;
} else {
// otherwise, return Null, which will indicate to parse_configure_post that we're done
return Null;
}
}

// Create the GIFT format string from the question details returned by get_question
function create_gift_format($question) {
$answers = Null; // initialize
if ($question['type'] == "true_false_question") {
// if the answer is "true", make answers "T". Otherwise, make it "F"
$answers = (($question['answer'][1]['text'] == 'true') ? "T" : "F");
} elseif (($question['type'] == "multiple_choice_question") || ($question['type'] == "multiple_answers_question")) {
$answers = array();
// iterate through the answers to this question
foreach ($question['answer'] as $answer) {
if ($answer['iscorrect']) {
array_push($answers, "={$answer['text']}"); // GIFT indication for a correct answer
} else {
array_push($answers, "~{$answer['text']}"); // Incorrect answer
}
}
$answers= implode(" ", $answers); // smash all the answers together seperated by spaces
} elseif ($question['type'] == "short_answer_question") {
// only difference between MC/MA and SA answers is that SA answers are only provided if they are "correct"
// ignore the "is correct" bit of the array, and just add every one of the answers
$answers = array();
foreach ($question['answer'] as $answer) {
array_push($answers, "={$answer['text']}");
}
$answers= implode(" ", $answers);
}
// was the html box checked? if so, prepend the text with [html] for the parser
if (isset($question['html'])) {
$question['text'] = '[html]'.$question['text'];
}
// create the formatted string and return it
return "::{$question['title']}:: {$question['text']} {{$answers}}";
}
Loading