if (!($course = $DB->get_record("course", array('id' => $offlinequiz->course)))) {
    print_error("The course with id {$offlinequiz->course} that the offlinequiz with id {$offlinequiz->id} belongs to is missing");
}
if (!($cm = get_coursemodule_from_instance("offlinequiz", $offlinequiz->id, $course->id))) {
    print_error("The course module for the offlinequiz with id {$offlinequiz->id} is missing");
}
// Can only grade finished results.
if ($result->status != 'complete') {
    print_error('resultnotcomplete', 'offlinequiz');
}
// Check login and permissions.
require_login($course->id, false, $cm);
$context = context_module::instance($cm->id);
require_capability('mod/offlinequiz:grade', $context);
// Load the questions needed by page.
if (!($quba = question_engine::load_questions_usage_by_activity($result->usageid))) {
    print_error('Could not load question usage');
}
$slotquestion = $quba->get_question($slot);
// Print the page header.
$PAGE->set_pagelayout('popup');
echo $OUTPUT->header();
echo $OUTPUT->heading(format_string($slotquestion->name));
// Process any data that was submitted.
if (data_submitted() && confirm_sesskey()) {
    if (optional_param('submit', false, PARAM_BOOL)) {
        // Set the mark in the quba's slot.
        $transaction = $DB->start_delegated_transaction();
        $quba->process_all_actions(time());
        question_engine::save_questions_usage_by_activity($quba);
        $transaction->allow_commit();
Example #2
0
 protected function display_grading_interface($slot, $questionid, $grade, $pagesize, $page, $shownames, $showidnumbers, $order, $counts)
 {
     global $OUTPUT;
     if ($pagesize * $page >= $counts->{$grade}) {
         $page = 0;
     }
     list($qubaids, $count) = $this->get_usage_ids_where_question_in_state($grade, $slot, $questionid, $order, $page, $pagesize);
     $attempts = $this->load_attempts_by_usage_ids($qubaids);
     // Prepare the form.
     $hidden = array('id' => $this->cm->id, 'mode' => 'grading', 'slot' => $slot, 'qid' => $questionid, 'page' => $page);
     if (array_key_exists('includeauto', $this->viewoptions)) {
         $hidden['includeauto'] = $this->viewoptions['includeauto'];
     }
     $mform = new quiz_grading_settings_form($hidden, $counts, $shownames, $showidnumbers);
     // Tell the form the current settings.
     $settings = new stdClass();
     $settings->grade = $grade;
     $settings->pagesize = $pagesize;
     $settings->order = $order;
     $mform->set_data($settings);
     // Print the heading and form.
     echo question_engine::initialise_js();
     $a = new stdClass();
     $a->number = $this->questions[$slot]->number;
     $a->questionname = format_string($counts->name);
     echo $OUTPUT->heading(get_string('gradingquestionx', 'quiz_grading', $a), 3);
     echo html_writer::tag('p', html_writer::link($this->list_questions_url(), get_string('backtothelistofquestions', 'quiz_grading')), array('class' => 'mdl-align'));
     $mform->display();
     // Paging info.
     $a = new stdClass();
     $a->from = $page * $pagesize + 1;
     $a->to = min(($page + 1) * $pagesize, $count);
     $a->of = $count;
     echo $OUTPUT->heading(get_string('gradingattemptsxtoyofz', 'quiz_grading', $a), 3);
     if ($count > $pagesize && $order != 'random') {
         echo $OUTPUT->paging_bar($count, $page, $pagesize, $this->grade_question_url($slot, $questionid, $grade, false));
     }
     // Display the form with one section for each attempt.
     $sesskey = sesskey();
     $qubaidlist = implode(',', $qubaids);
     echo html_writer::start_tag('form', array('method' => 'post', 'action' => $this->grade_question_url($slot, $questionid, $grade, $page), 'class' => 'mform', 'id' => 'manualgradingform')) . html_writer::start_tag('div') . html_writer::input_hidden_params(new moodle_url('', array('qubaids' => $qubaidlist, 'slots' => $slot, 'sesskey' => $sesskey)));
     foreach ($qubaids as $qubaid) {
         $attempt = $attempts[$qubaid];
         $quba = question_engine::load_questions_usage_by_activity($qubaid);
         $displayoptions = quiz_get_review_options($this->quiz, $attempt, $this->context);
         $displayoptions->hide_all_feedback();
         $displayoptions->history = question_display_options::HIDDEN;
         $displayoptions->manualcomment = question_display_options::EDITABLE;
         $heading = $this->get_question_heading($attempt, $shownames, $showidnumbers);
         if ($heading) {
             echo $OUTPUT->heading($heading, 4);
         }
         echo $quba->render_question($slot, $displayoptions, $this->questions[$slot]->number);
     }
     echo html_writer::tag('div', html_writer::empty_tag('input', array('type' => 'submit', 'value' => get_string('saveandnext', 'quiz_grading'))), array('class' => 'mdl-align')) . html_writer::end_tag('div') . html_writer::end_tag('form');
 }
 /**
  * Constructor assuming we already have the necessary data loaded.
  *
  * @param object $attempt the row of the quiz_attempts table.
  * @param object $quiz the quiz object for this attempt and user.
  * @param object $cm the course_module object for this quiz.
  * @param object $course the row from the course table for the course we belong to.
  * @param bool $loadquestions (optional) if true, the default, load all the details
  *      of the state of each question. Else just set up the basic details of the attempt.
  */
 public function __construct($attempt, $quiz, $cm, $course, $loadquestions = true)
 {
     $this->attempt = $attempt;
     $this->quizobj = new quiz($quiz, $cm, $course);
     if (!$loadquestions) {
         return;
     }
     $this->quba = question_engine::load_questions_usage_by_activity($this->attempt->uniqueid);
     $this->determine_layout();
     $this->number_questions();
 }
Example #4
0
 /**
  * Constructor assuming we already have the necessary data loaded.
  *
  * @param object $attempt the row of the quiz_attempts table.
  * @param object $quiz the quiz object for this attempt and user.
  * @param object $cm the course_module object for this quiz.
  * @param object $course the row from the course table for the course we belong to.
  * @param bool $loadquestions (optional) if true, the default, load all the details
  *      of the state of each question. Else just set up the basic details of the attempt.
  */
 public function __construct($attempt, $quiz, $cm, $course, $loadquestions = true)
 {
     global $DB;
     $this->attempt = $attempt;
     $this->quizobj = new quiz($quiz, $cm, $course);
     if (!$loadquestions) {
         return;
     }
     $this->quba = question_engine::load_questions_usage_by_activity($this->attempt->uniqueid);
     $this->slots = $DB->get_records('quiz_slots', array('quizid' => $this->get_quizid()), 'slot', 'slot, requireprevious, questionid');
     $this->sections = array_values($DB->get_records('quiz_sections', array('quizid' => $this->get_quizid()), 'firstslot'));
     $this->link_sections_and_slots();
     $this->determine_layout();
     $this->number_questions();
 }
Example #5
0
 /**
  * Constructor assuming we already have the necessary data loaded.
  *
  * @param object $attempt the row of the reader_attempts table.
  * @param object $reader the reader object for this attempt and user.
  * @param object $cm the course_module object for this reader.
  * @param object $course the row from the course table for the course we belong to.
  */
 public function __construct($attempt, $reader, $cm, $course)
 {
     $this->attempt = $attempt;
     $this->readerobj = reader::create($reader->id, $attempt->userid, $attempt->quizid);
     $this->quba = question_engine::load_questions_usage_by_activity($this->attempt->uniqueid);
     $this->determine_layout();
     $this->number_questions();
 }
 /**
  * (non-PHPdoc)
  *
  * @see offlinequiz_default_report::display()
  */
 public function display($offlinequiz, $cm, $course)
 {
     global $CFG, $OUTPUT, $SESSION, $DB;
     // Define some strings.
     $strtimeformat = get_string('strftimedatetime');
     $letterstr = ' ABCDEFGHIJKLMNOPQRSTUVWXYZ';
     offlinequiz_load_useridentification();
     $offlinequizconfig = get_config('offlinequiz');
     // Deal with actions.
     $action = optional_param('action', '', PARAM_ACTION);
     // Only print headers if not asked to download data or delete data.
     if (!($download = optional_param('download', null, PARAM_TEXT)) && !$action == 'delete') {
         $this->print_header_and_tabs($cm, $course, $offlinequiz, 'overview');
         echo $OUTPUT->box_start('linkbox');
         echo $OUTPUT->heading(format_string($offlinequiz->name));
         echo $OUTPUT->heading(get_string('results', 'offlinequiz'));
         require_once $CFG->libdir . '/grouplib.php';
         echo groups_print_activity_menu($cm, $CFG->wwwroot . '/mod/offlinequiz/report.php?id=' . $cm->id . '&mode=overview', true);
         echo $OUTPUT->box_end();
         echo '<br/>';
     }
     $context = context_module::instance($cm->id);
     $systemcontext = context_system::instance();
     // Set table options.
     $noresults = optional_param('noresults', 0, PARAM_INT);
     $pagesize = optional_param('pagesize', 10, PARAM_INT);
     $groupid = optional_param('group', 0, PARAM_INT);
     if ($pagesize < 1) {
         $pagesize = 10;
     }
     $answerletters = 'abcdefghijklmnopqrstuvwxyz';
     if ($action == 'delete' && confirm_sesskey()) {
         $selectedresultids = array();
         $params = (array) data_submitted();
         foreach ($params as $key => $value) {
             if (preg_match('!^s([0-9]+)$!', $key, $matches)) {
                 $selectedresultids[] = $matches[1];
             }
         }
         if ($selectedresultids) {
             foreach ($selectedresultids as $resultid) {
                 if ($resultid && ($todelete = $DB->get_record('offlinequiz_results', array('id' => $resultid)))) {
                     offlinequiz_delete_result($resultid, $context);
                     // Log this event.
                     $params = array('objectid' => $resultid, 'relateduserid' => $todelete->userid, 'context' => context_module::instance($cm->id), 'other' => array('mode' => 'overview'));
                     $event = \mod_offlinequiz\event\attempt_deleted::create($params);
                     $event->trigger();
                     // Change the status of all related pages with error 'resultexists' to 'suspended'.
                     $user = $DB->get_record('user', array('id' => $todelete->userid));
                     $group = $DB->get_record('offlinequiz_groups', array('id' => $todelete->offlinegroupid));
                     $sql = "SELECT id\n                                  FROM {offlinequiz_scanned_pages}\n                                 WHERE offlinequizid = :offlinequizid\n                                   AND userkey = :userkey\n                                   AND groupnumber = :groupnumber\n                                   AND status = 'error'\n                                   AND (error = 'resultexists' OR error = 'differentresultexists')";
                     $params = array('offlinequizid' => $offlinequiz->id, 'userkey' => $user->{$offlinequizconfig->ID_field}, 'groupnumber' => $group->number);
                     $otherpages = $DB->get_records_sql($sql, $params);
                     foreach ($otherpages as $page) {
                         $DB->set_field('offlinequiz_scanned_pages', 'status', 'suspended', array('id' => $page->id));
                         $DB->set_field('offlinequiz_scanned_pages', 'error', '', array('id' => $page->id));
                     }
                 }
             }
             offlinequiz_grade_item_update($offlinequiz, 'reset');
             offlinequiz_update_grades($offlinequiz);
             redirect(new moodle_url('/mod/offlinequiz/report.php', array('mode' => 'overview', 'id' => $cm->id, 'noresults' => $noresults, 'pagesize' => $pagesize)));
         }
     }
     // Now check if asked download of data.
     if ($download) {
         $filename = clean_filename("{$course->shortname} " . format_string($offlinequiz->name, true));
         $sort = '';
     }
     // Fetch the group data.
     $groups = $DB->get_records('offlinequiz_groups', array('offlinequizid' => $offlinequiz->id), 'number', '*', 0, $offlinequiz->numgroups);
     // Define table columns.
     $tablecolumns = array('checkbox', 'picture', 'fullname', $offlinequizconfig->ID_field, 'timestart', 'offlinegroupid', 'sumgrades');
     $tableheaders = array('<input type="checkbox" name="toggle" onClick="if (this.checked) {select_all_in(\'DIV\',null,\'tablecontainer\');}
             else {deselect_all_in(\'DIV\',null,\'tablecontainer\');}"/>', '', get_string('fullname'), get_string($offlinequizconfig->ID_field), get_string('importedon', 'offlinequiz'), get_string('group'), get_string('grade', 'offlinequiz'));
     $checked = array();
     // Get participants list.
     $withparticipants = false;
     if ($lists = $DB->get_records('offlinequiz_p_lists', array('offlinequizid' => $offlinequiz->id))) {
         $withparticipants = true;
         $tablecolumns[] = 'checked';
         $tableheaders[] = get_string('present', 'offlinequiz');
         foreach ($lists as $list) {
             $participants = $DB->get_records('offlinequiz_participants', array('listid' => $list->id));
             foreach ($participants as $participant) {
                 $checked[$participant->userid] = $participant->checked;
             }
         }
     }
     if (!$download) {
         // Set up the table.
         $params = array('offlinequiz' => $offlinequiz, 'noresults' => $noresults, 'pagesize' => $pagesize);
         $table = new offlinequiz_results_table('mod-offlinequiz-report-overview-report', $params);
         $table->define_columns($tablecolumns);
         $table->define_headers($tableheaders);
         $table->define_baseurl($CFG->wwwroot . '/mod/offlinequiz/report.php?mode=overview&amp;id=' . $cm->id . '&amp;noresults=' . $noresults . '&amp;pagesize=' . $pagesize);
         $table->sortable(true);
         $table->no_sorting('checkbox');
         if ($withparticipants) {
             $table->no_sorting('checked');
         }
         $table->column_suppress('picture');
         $table->column_suppress('fullname');
         $table->column_class('picture', 'picture');
         $table->column_class($offlinequizconfig->ID_field, 'userkey');
         $table->column_class('timestart', 'timestart');
         $table->column_class('offlinegroupid', 'offlinegroupid');
         $table->column_class('sumgrades', 'sumgrades');
         $table->set_attribute('cellpadding', '2');
         $table->set_attribute('id', 'attempts');
         $table->set_attribute('class', 'generaltable generalbox');
         // Start working -- this is necessary as soon as the niceties are over.
         $table->setup();
     } else {
         if ($download == 'ODS') {
             require_once "{$CFG->libdir}/odslib.class.php";
             $filename .= ".ods";
             // Creating a workbook.
             $workbook = new MoodleODSWorkbook("-");
             // Sending HTTP headers.
             $workbook->send($filename);
             // Creating the first worksheet.
             $sheettitle = get_string('reportoverview', 'offlinequiz');
             $myxls = $workbook->add_worksheet($sheettitle);
             // Format types.
             $format = $workbook->add_format();
             $format->set_bold(0);
             $formatbc = $workbook->add_format();
             $formatbc->set_bold(1);
             $formatbc->set_align('center');
             $formatb = $workbook->add_format();
             $formatb->set_bold(1);
             $formaty = $workbook->add_format();
             $formaty->set_bg_color('yellow');
             $formatc = $workbook->add_format();
             $formatc->set_align('center');
             $formatr = $workbook->add_format();
             $formatr->set_bold(1);
             $formatr->set_color('red');
             $formatr->set_align('center');
             $formatg = $workbook->add_format();
             $formatg->set_bold(1);
             $formatg->set_color('green');
             $formatg->set_align('center');
             // Here starts workshhet headers.
             $headers = array(get_string($offlinequizconfig->ID_field), get_string('firstname'), get_string('lastname'), get_string('importedon', 'offlinequiz'), get_string('group'), get_string('grade', 'offlinequiz'));
             if (!empty($withparticipants)) {
                 $headers[] = get_string('present', 'offlinequiz');
             }
             $colnum = 0;
             foreach ($headers as $item) {
                 $myxls->write(0, $colnum, $item, $formatbc);
                 $colnum++;
             }
             $rownum = 1;
         } else {
             if ($download == 'Excel') {
                 require_once "{$CFG->libdir}/excellib.class.php";
                 $filename .= ".xls";
                 // Creating a workbook.
                 $workbook = new MoodleExcelWorkbook("-");
                 // Sending HTTP headers.
                 $workbook->send($filename);
                 // Creating the first worksheet.
                 $sheettitle = get_string('results', 'offlinequiz');
                 $myxls = $workbook->add_worksheet($sheettitle);
                 // Format types.
                 $format = $workbook->add_format();
                 $format->set_bold(0);
                 $formatbc = $workbook->add_format();
                 $formatbc->set_bold(1);
                 $formatbc->set_align('center');
                 $formatb = $workbook->add_format();
                 $formatb->set_bold(1);
                 $formaty = $workbook->add_format();
                 $formaty->set_bg_color('yellow');
                 $formatc = $workbook->add_format();
                 $formatc->set_align('center');
                 $formatr = $workbook->add_format();
                 $formatr->set_bold(1);
                 $formatr->set_color('red');
                 $formatr->set_align('center');
                 $formatg = $workbook->add_format();
                 $formatg->set_bold(1);
                 $formatg->set_color('green');
                 $formatg->set_align('center');
                 // Here starts worksheet headers.
                 $headers = array(get_string($offlinequizconfig->ID_field), get_string('firstname'), get_string('lastname'), get_string('importedon', 'offlinequiz'), get_string('group'), get_string('grade', 'offlinequiz'));
                 if (!empty($withparticipants)) {
                     $headers[] = get_string('present', 'offlinequiz');
                 }
                 $colnum = 0;
                 foreach ($headers as $item) {
                     $myxls->write(0, $colnum, $item, $formatbc);
                     $colnum++;
                 }
                 $rownum = 1;
             } else {
                 if ($download == 'CSV') {
                     $filename .= ".csv";
                     header("Content-Encoding: UTF-8");
                     header("Content-Type: text/csv; charset=utf-8");
                     header("Content-Disposition: attachment; filename=\"{$filename}\"");
                     header("Expires: 0");
                     header("Cache-Control: must-revalidate,post-check=0,pre-check=0");
                     header("Pragma: public");
                     echo "";
                     // UTF-8 BOM.
                     $headers = get_string($offlinequizconfig->ID_field) . ", " . get_string('fullname') . ", " . get_string('importedon', 'offlinequiz') . ", " . get_string('group') . ", " . get_string('grade', 'offlinequiz');
                     if (!empty($withparticipants)) {
                         $headers .= ", " . get_string('present', 'offlinequiz');
                     }
                     echo $headers . " \n";
                 } else {
                     if ($download == 'CSVplus1' || $download == 'CSVpluspoints') {
                         $filename .= ".csv";
                         header("Content-Encoding: UTF-8");
                         header("Content-Type: text/csv; charset=utf-8");
                         header("Content-Disposition: attachment; filename=\"{$filename}\"");
                         header("Expires: 0");
                         header("Cache-Control: must-revalidate,post-check=0,pre-check=0");
                         header("Pragma: public");
                         echo "";
                         // UTF-8 BOM.
                         // Print the table headers.
                         echo get_string('firstname') . ',' . get_string('lastname') . ',' . get_string($offlinequizconfig->ID_field) . ',' . get_string('group');
                         $maxquestions = offlinequiz_get_maxquestions($offlinequiz, $groups);
                         for ($i = 0; $i < $maxquestions; $i++) {
                             echo ', ' . get_string('question') . ' ' . ($i + 1);
                         }
                         echo "\n";
                         // Print the correct answer bit-strings.
                         foreach ($groups as $group) {
                             if ($group->templateusageid) {
                                 $quba = question_engine::load_questions_usage_by_activity($group->templateusageid);
                                 $slots = $quba->get_slots();
                                 echo ', ,' . get_string('correct', 'offlinequiz');
                                 echo ',' . $group->number;
                                 foreach ($slots as $slot) {
                                     $slotquestion = $quba->get_question($slot);
                                     $qtype = $slotquestion->get_type_name();
                                     if ($qtype == 'multichoice' || $qtype == 'multichoiceset') {
                                         $attempt = $quba->get_question_attempt($slot);
                                         $order = $slotquestion->get_order($attempt);
                                         // Order of the answers.
                                         $tempstr = ",";
                                         $letters = array();
                                         $counter = 0;
                                         foreach ($order as $key => $answerid) {
                                             $fraction = $DB->get_field('question_answers', 'fraction', array('id' => $answerid));
                                             if ($fraction > 0) {
                                                 $letters[] = $answerletters[$counter];
                                             }
                                             $counter++;
                                         }
                                         if (empty($letters)) {
                                             $tempstr .= '99';
                                         } else {
                                             $tempstr .= implode('/', $letters);
                                         }
                                         echo $tempstr;
                                     }
                                 }
                                 echo "\n";
                             }
                         }
                     }
                 }
             }
         }
     }
     $coursecontext = context_course::instance($course->id);
     $contextids = $coursecontext->get_parent_context_ids(true);
     // Construct the SQL
     // First get roleids for students from leagcy.
     if (!($roles = get_roles_with_capability('mod/offlinequiz:attempt', CAP_ALLOW, $systemcontext))) {
         error("No roles with capability 'moodle/offlinequiz:attempt' defined in system context");
     }
     $roleids = array();
     foreach ($roles as $role) {
         $roleids[] = $role->id;
     }
     $rolelist = implode(',', $roleids);
     $select = "SELECT " . $DB->sql_concat('u.id', "'#'", "COALESCE(qa.usageid, 0)") . " AS uniqueid,\n        qa.id AS resultid, u.id, qa.usageid, qa.offlinegroupid, qa.status,\n        u.id AS userid, u.firstname, u.lastname,\n        u.alternatename, u.middlename, u.firstnamephonetic, u.lastnamephonetic,\n        u.picture, u." . $offlinequizconfig->ID_field . ",\n        qa.sumgrades, qa.timefinish, qa.timestart, qa.timefinish - qa.timestart AS duration ";
     $result = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'ctx');
     list($contexttest, $cparams) = $result;
     list($roletest, $rparams) = $DB->get_in_or_equal($roleids, SQL_PARAMS_NAMED, 'role');
     $from = "FROM {user} u\n                  JOIN {role_assignments} ra ON ra.userid = u.id\n             LEFT JOIN {offlinequiz_results} qa ON u.id = qa.userid AND qa.offlinequizid = :offlinequizid\n             ";
     $where = " WHERE ra.contextid {$contexttest} AND ra.roleid {$roletest} ";
     $params = array('offlinequizid' => $offlinequiz->id);
     $params = array_merge($params, $cparams, $rparams);
     if ($groupid) {
         $from .= " JOIN {groups_members} gm ON gm.userid = u.id ";
         $where .= " AND gm.groupid = :groupid ";
         $params['groupid'] = $groupid;
     }
     if (empty($noresults)) {
         $where = $where . " AND qa.userid IS NOT NULL\n                                AND qa.status = 'complete'";
         // Show ONLY students with results.
     } else {
         if ($noresults == 1) {
             // The value noresults = 1 means only no results, so make the left join ask for only records
             // where the right is null (no results).
             $where .= ' AND qa.userid IS NULL';
             // Show ONLY students without results.
         } else {
             if ($noresults == 3) {
                 // We want all results, also the partial ones.
                 $from = "FROM {user} u\n                      JOIN {offlinequiz_results} qa ON u.id = qa.userid ";
                 $where = " WHERE qa.offlinequizid = :offlinequizid";
             }
         }
     }
     // The value noresults = 2 means we want all students, with or without results.
     $countsql = 'SELECT COUNT(DISTINCT(u.id)) ' . $from . $where;
     if (!$download) {
         // Count the records NOW, before funky question grade sorting messes up $from.
         $totalinitials = $DB->count_records_sql($countsql, $params);
         // Add extra limits due to initials bar.
         list($ttest, $tparams) = $table->get_sql_where();
         if (!empty($ttest)) {
             $where .= ' AND ' . $ttest;
             $countsql .= ' AND ' . $ttest;
             $params = array_merge($params, $tparams);
         }
         $total = $DB->count_records_sql($countsql, $params);
         // Add extra limits due to sorting by question grade.
         $sort = $table->get_sql_sort();
         // Fix some wired sorting.
         if (empty($sort)) {
             $sort = ' ORDER BY u.lastname';
         } else {
             $sort = ' ORDER BY ' . $sort;
         }
         $table->pagesize($pagesize, $total);
     }
     // Fetch the results.
     if (!$download) {
         $results = $DB->get_records_sql($select . $from . $where . $sort, $params, $table->get_page_start(), $table->get_page_size());
     } else {
         $results = $DB->get_records_sql($select . $from . $where . $sort, $params);
     }
     // Build table rows.
     if (!$download) {
         $table->initialbars(true);
     }
     if (!empty($results) || !empty($noresults)) {
         foreach ($results as $result) {
             $user = $DB->get_record('user', array('id' => $result->userid));
             $picture = $OUTPUT->user_picture($user, array('courseid' => $course->id));
             if (!empty($result->resultid)) {
                 $checkbox = '<input type="checkbox" name="s' . $result->resultid . '" value="' . $result->resultid . '" />';
             } else {
                 $checkbox = '';
             }
             if (!empty($result) && (empty($result->resultid) || $result->timefinish == 0)) {
                 $resultdate = '-';
             } else {
                 $resultdate = userdate($result->timefinish, $strtimeformat);
             }
             if (!empty($result) && $result->offlinegroupid) {
                 $groupletter = $letterstr[$groups[$result->offlinegroupid]->number];
             } else {
                 $groupletter = '-';
             }
             $userlink = '<a href="' . $CFG->wwwroot . '/user/view.php?id=' . $result->userid . '&amp;course=' . $course->id . '">' . fullname($result) . '</a>';
             if (!$download) {
                 $row = array($checkbox, $picture, $userlink, $result->{$offlinequizconfig->ID_field}, $resultdate, $groupletter);
             } else {
                 $row = array($result->{$offlinequizconfig->ID_field}, $result->firstname, $result->lastname, $resultdate, $groupletter);
             }
             if (!empty($result) && $result->offlinegroupid) {
                 $outputgrade = format_float($result->sumgrades / $groups[$result->offlinegroupid]->sumgrades * $offlinequiz->grade, $offlinequiz->decimalpoints);
             } else {
                 $outputgrade = '-';
             }
             if (!$download) {
                 if ($result->status == 'partial') {
                     $row[] = get_string('partial', 'offlinequiz');
                 } else {
                     if ($result->sumgrades === null) {
                         $row[] = '-';
                     } else {
                         $row[] = '<a href="review.php?q=' . $offlinequiz->id . '&amp;resultid=' . $result->resultid . '">' . $outputgrade . '</a>';
                     }
                 }
                 if ($withparticipants) {
                     $row[] = !empty($checked[$result->userid]) ? "<img src=\"{$CFG->wwwroot}/mod/offlinequiz/pix/tick.gif\" alt=\"" . get_string('ischecked', 'offlinequiz') . "\">" : "<img src=\"{$CFG->wwwroot}/mod/offlinequiz/pix/cross.gif\" alt=\"" . get_string('isnotchecked', 'offlinequiz') . "\">";
                 }
             } else {
                 if ($download != 'CSVplus1' || $download == 'CSVpluspoints') {
                     $row[] = $result->sumgrades === null ? '-' : $outputgrade;
                     if ($withparticipants) {
                         if (array_key_exists($result->userid, $checked)) {
                             $row[] = $checked[$result->userid] ? get_string('ok') : '-';
                         } else {
                             $row[] = '-';
                         }
                     }
                 }
             }
             if (!$download) {
                 $table->add_data($row);
             } else {
                 if ($download == 'Excel' or $download == 'ODS') {
                     $colnum = 0;
                     foreach ($row as $item) {
                         $myxls->write($rownum, $colnum, $item, $format);
                         $colnum++;
                     }
                     $rownum++;
                 } else {
                     if ($download == 'CSV') {
                         $text = implode(',', $row);
                         echo $text . "\n";
                     } else {
                         if ($download == 'CSVplus1' || $download == 'CSVpluspoints') {
                             $text = $row[1] . ',' . $row[2] . ',' . $row[0] . ',' . $groups[$result->offlinegroupid]->number;
                             if ($pages = $DB->get_records('offlinequiz_scanned_pages', array('resultid' => $result->resultid), 'pagenumber ASC')) {
                                 foreach ($pages as $page) {
                                     if ($page->status == 'ok' || $page->status == 'submitted') {
                                         $choices = $DB->get_records('offlinequiz_choices', array('scannedpageid' => $page->id), 'slotnumber, choicenumber');
                                         $counter = 0;
                                         $oldslot = -1;
                                         $letters = array();
                                         foreach ($choices as $choice) {
                                             if ($oldslot == -1) {
                                                 $oldslot = $choice->slotnumber;
                                             } else {
                                                 if ($oldslot != $choice->slotnumber) {
                                                     if (empty($letters)) {
                                                         $text .= ',99';
                                                     } else {
                                                         $text .= ',' . implode('/', $letters);
                                                     }
                                                     $counter = 0;
                                                     $oldslot = $choice->slotnumber;
                                                     $letters = array();
                                                 }
                                             }
                                             if ($choice->value == 1) {
                                                 $letters[] = $answerletters[$counter];
                                             }
                                             $counter++;
                                         }
                                         if (empty($letters)) {
                                             $text .= ',99';
                                         } else {
                                             $text .= ',' . implode('/', $letters);
                                         }
                                     }
                                 }
                             }
                             echo $text . "\n";
                             if ($download == 'CSVpluspoints') {
                                 $text = $row[1] . ',' . $row[2] . ',' . $row[0] . ',' . $groups[$result->offlinegroupid]->number;
                                 $quba = question_engine::load_questions_usage_by_activity($result->usageid);
                                 $slots = $quba->get_slots();
                                 foreach ($slots as $slot) {
                                     $slotquestion = $quba->get_question($slot);
                                     $attempt = $quba->get_question_attempt($slot);
                                     $text .= ',' . format_float($attempt->get_mark(), $offlinequiz->decimalpoints, false);
                                 }
                                 echo $text . "\n";
                             }
                         }
                     }
                 }
             }
         }
         // End foreach ($results...
     } else {
         if (!$download) {
             $table->print_initials_bar();
         }
     }
     if (!$download) {
         // Print table.
         $table->finish_html();
         if (!empty($results)) {
             echo '<form id="downloadoptions" action="report.php" method="get">';
             echo ' <input type="hidden" name="id" value="' . $cm->id . '" />';
             echo ' <input type="hidden" name="q" value="' . $offlinequiz->id . '" />';
             echo ' <input type="hidden" name="mode" value="overview" />';
             echo ' <input type="hidden" name="noheader" value="yes" />';
             echo ' <table class="boxaligncenter"><tr><td>';
             $options = array('Excel' => get_string('excelformat', 'offlinequiz'), 'ODS' => get_string('odsformat', 'offlinequiz'), 'CSV' => get_string('csvformat', 'offlinequiz'), 'CSVplus1' => get_string('csvplus1format', 'offlinequiz'), 'CSVpluspoints' => get_string('csvpluspointsformat', 'offlinequiz'));
             print_string('downloadresultsas', 'offlinequiz');
             echo "</td><td>";
             echo html_writer::select($options, 'download', '', false);
             echo ' <input type="submit" value="' . get_string('download') . '" />';
             echo ' <script type="text/javascript">' . "\n<!--\n" . 'document.getElementById("noscriptmenuaction").style.display = "none";' . "\n-->\n" . '</script>';
             echo " </td>\n";
             echo "<td>";
             echo "</td>\n";
             echo '</tr></table></form>';
         }
     } else {
         if ($download == 'Excel' || $download == 'ODS') {
             $workbook->close();
             exit;
         } else {
             if ($download == 'CSV' || $download == 'CSVplus1' || $download == 'CSVpluspoints') {
                 exit;
             }
         }
     }
     // Print display options.
     echo '<div class="controls">';
     echo '<form id="options" action="report.php" method="get">';
     echo '<div class=centerbox>';
     echo '<p>' . get_string('displayoptions', 'offlinequiz') . ': </p>';
     echo '<input type="hidden" name="id" value="' . $cm->id . '" />';
     echo '<input type="hidden" name="q" value="' . $offlinequiz->id . '" />';
     echo '<input type="hidden" name="mode" value="overview" />';
     echo '<input type="hidden" name="detailedmarks" value="0" />';
     echo '<table id="overview-options" class="boxaligncenter">';
     echo '<tr align="left">';
     echo '<td><label for="pagesize">' . get_string('pagesizeparts', 'offlinequiz') . '</label></td>';
     echo '<td><input type="text" id="pagesize" name="pagesize" size="3" value="' . $pagesize . '" /></td>';
     echo '</tr>';
     echo '<tr align="left">';
     echo '<td colspan="2">';
     $options = array();
     $options[] = get_string('attemptsonly', 'offlinequiz');
     $options[] = get_string('noattemptsonly', 'offlinequiz');
     $options[] = get_string('allstudents', 'offlinequiz');
     $options[] = get_string('allresults', 'offlinequiz');
     echo html_writer::select($options, 'noresults', $noresults, '');
     echo '</td></tr>';
     echo '<tr><td colspan="2" align="center">';
     echo '<input type="submit" value="' . get_string('go') . '" />';
     echo '</td></tr></table>';
     echo '</div>';
     echo '</form>';
     echo '</div>';
     echo "\n";
     return true;
 }
/**
 * Called via pluginfile.php -> question_pluginfile to serve files belonging to
 * a question in a question_attempt when that attempt is a preview.
 *
 * @param object $course course settings object
 * @param object $context context object
 * @param string $component the name of the component we are serving files for.
 * @param string $filearea the name of the file area.
 * @param int $qubaid the question_usage this image belongs to.
 * @param int $slot the relevant slot within the usage.
 * @param array $args the remaining bits of the file path.
 * @param bool $forcedownload whether the user must be forced to download the file.
 * @return bool false if file not found, does not return if found - justsend the file
 */
function question_preview_question_pluginfile($course, $context, $component, $filearea, $qubaid, $slot, $args, $forcedownload)
{
    global $USER, $DB, $CFG;
    $quba = question_engine::load_questions_usage_by_activity($qubaid);
    if (!question_has_capability_on($quba->get_question($slot), 'use')) {
        send_file_not_found();
    }
    $options = new question_display_options();
    $options->feedback = question_display_options::VISIBLE;
    $options->numpartscorrect = question_display_options::VISIBLE;
    $options->generalfeedback = question_display_options::VISIBLE;
    $options->rightanswer = question_display_options::VISIBLE;
    $options->manualcomment = question_display_options::VISIBLE;
    $options->history = question_display_options::VISIBLE;
    if (!$quba->check_file_access($slot, $options, $component, $filearea, $args, $forcedownload)) {
        send_file_not_found();
    }
    $fs = get_file_storage();
    $relativepath = implode('/', $args);
    $fullpath = "/{$context->id}/{$component}/{$filearea}/{$relativepath}";
    if (!($file = $fs->get_file_by_hash(sha1($fullpath))) or $file->is_directory()) {
        send_file_not_found();
    }
    send_stored_file($file, 0, 0, $forcedownload);
}
 /**
  * Regrade a particular offlinequiz result. Either for real ($dryrun = false), or
  * as a pretend regrade to see which fractions would change. The outcome is
  * stored in the offlinequiz_overview_regrades table.
  *
  * Note, $result is not upgraded in the database. The caller needs to do that.
  * However, $result->sumgrades is updated, if this is not a dry run.
  *
  * @param object $result the offlinequiz result to regrade.
  * @param bool $dryrun if true, do a pretend regrade, otherwise do it for real.
  * @param array $slots if null, regrade all questions, otherwise, just regrade
  *      the quetsions with those slots.
  */
 protected function regrade_result($result, $questions, $dryrun = false, $slots = null)
 {
     global $DB;
     $transaction = $DB->start_delegated_transaction();
     $quba = question_engine::load_questions_usage_by_activity($result->usageid);
     if (is_null($slots)) {
         $slots = $quba->get_slots();
     }
     $changed = false;
     $finished = true;
     foreach ($slots as $slot) {
         $qqr = new stdClass();
         $qqr->oldfraction = $quba->get_question_fraction($slot);
         $slotquestion = $quba->get_question($slot);
         $newmaxmark = $questions[$slotquestion->id]->maxmark;
         $quba->regrade_question($slot, $finished, $newmaxmark);
         $qqr->newfraction = $quba->get_question_fraction($slot);
         if (abs($qqr->oldfraction - $qqr->newfraction) > 1.0E-7) {
             $changed = true;
         }
     }
     if (!$dryrun) {
         question_engine::save_questions_usage_by_activity($quba);
     }
     $transaction->allow_commit();
     return $changed;
 }
 /**
  * Processes a set of Scantron-formatted responses, creating a quiz attempt, as though the user had entered these answers into Moodle directly.
  *
  * @param array   $set              An associative array of data read off of a Scantron form. Known to work for the scantron form 223127; likely works for others.
  * @param bool    $overwrite        If set, imported responses will be allowed to overwrite existsing quiz attempts with the same unique id (QUBA id).
  * @param bool    $allow_cross_user If set, allows a quiz attempt to move from one user to another (i.e. if the student had entered in the wrong ID number.)
  */
 protected function enter_scantron_responses($set, $overwrite = false, $allow_cross_user = false, $finish = true, $error_if_not_first = false, $force_first = false)
 {
     global $DB;
     //if no usage ID has been specified, throw an exception
     if (!array_key_exists('Special Codes', $set)) {
         if (array_key_exists('Student Name', $set) || array_key_exists('ID', $set)) {
             throw new quiz_papercopy_invalid_usage_id_exception();
         } else {
             throw new quiz_papercopy_benign_row_exception();
         }
     }
     //get the usage ID from the Special Codes field on the scantron
     $usage_id = intval($set['Special Codes']);
     //get the ID for the attempt that would be created
     $new_id = $this->user_id_from_scantron($set);
     //if we need this to be the first attempt, check for an existing attempt by this user at the current quiz
     if ($error_if_not_first || $force_first) {
         //if an attempt exists with both this user_id and quiz ID
         $existing_attempt = $DB->get_record('quiz_attempts', array('userid' => $new_id, 'quiz' => $this->quiz->id));
         //if this isn't allowed to be a subsequent attempt, throw an exception
         if ($error_if_not_first) {
             throw new quiz_papercopy_not_first_attempt_when_required();
         } elseif ($force_first) {
             $DB->delete_records('quiz_attempts', array('userid' => $new_id, 'quiz' => $this->quiz->id));
         }
     }
     //check for any attempt that uses this usage
     $existing_record = $DB->get_record('quiz_attempts', array('uniqueid' => $usage_id), 'userid', IGNORE_MISSING);
     //if one exists, handle the overwrite cases
     if ($existing_record) {
         //if we're trying to assign the same record to a different usage, and we haven't explicitly allowed cross-user overwrites, throw an exception
         if ($new_id != $existing_record->userid && !$allow_cross_user) {
             throw new quiz_papercopy_conflicting_users_exception();
         }
         //if overwrite is enabled, remove the existing attempt
         if ($overwrite) {
             $DB->delete_records('quiz_attempts', array('uniqueid' => $usage_id));
         } else {
             throw new quiz_papercopy_attempt_exists_exception();
         }
     }
     try {
         //get a usage object from the Special Codes usage ID
         $usage = question_engine::load_questions_usage_by_activity($usage_id);
     } catch (coding_exception $e) {
         //if we couldn't load that usage, throw an "invalid usage id" error
         throw new quiz_papercopy_invalid_usage_id_exception();
     }
     //get an associative array, which indicates the order in which questions were
     $slots = $usage->get_slots();
     //enter the student's answers for each of the questions
     foreach ($slots as $slot) {
         $letter2number = array('A' => 1, 'B' => 2, 'C' => 3, 'D' => 4);
         if (array_key_exists($set['Question' . $slot], $letter2number)) {
             $set['Question' . $slot] = $letter2number[$set['Question' . $slot]];
         }
         $usage->process_action($slot, array('answer' => $set['Question' . $slot] - 1));
     }
     //set the attempt's owner to reflect the student who filled out the scantron
     $target_user = $this->user_id_from_scantron($set);
     //create a new attempt object, if requested, immediately close it, grading the attempt
     // $attempt = $this->build_attempt_from_usage($usage, $target_user, $finish, true);
     $attempt = $this->build_attempt_from_usage($usage, $target_user, true, true);
     //return the user's grade and id, on success
     return array('grade' => $attempt->sumgrades, 'user' => $attempt->userid);
 }
/**
 * Retrieves a template question usage for an offline group. Creates a new template if there is none.
 * While creating question usage it shuffles the group questions if shuffleanswers is created.
 *
 * @param object $offlinequiz
 * @param object $group
 * @param object $context
 * @return question_usage_by_activity
 */
function offlinequiz_get_group_template_usage($offlinequiz, $group, $context)
{
    global $CFG, $DB;
    if (!empty($group->templateusageid) && $group->templateusageid > 0) {
        $templateusage = question_engine::load_questions_usage_by_activity($group->templateusageid);
    } else {
        $questionids = offlinequiz_get_group_question_ids($offlinequiz, $group->id);
        if ($offlinequiz->shufflequestions) {
            $offlinequiz->groupid = $group->id;
            $questionids = offlinequiz_shuffle_questions($questionids);
        }
        // We have to use our own class s.t. we can use the clone function to create results.
        $templateusage = offlinequiz_make_questions_usage_by_activity('mod_offlinequiz', $context);
        $templateusage->set_preferred_behaviour('immediatefeedback');
        if (!$questionids) {
            print_error(get_string('noquestionsfound', 'offlinequiz'), 'view.php?q=' . $offlinequiz->id);
        }
        // Gets database raw data for the questions.
        $questiondata = question_load_questions($questionids);
        // Get the question instances for initial markmarks.
        $sql = "SELECT questionid, maxmark\n                  FROM {offlinequiz_group_questions}\n                 WHERE offlinequizid = :offlinequizid\n                   AND offlinegroupid = :offlinegroupid ";
        $groupquestions = $DB->get_records_sql($sql, array('offlinequizid' => $offlinequiz->id, 'offlinegroupid' => $group->id));
        foreach ($questionids as $questionid) {
            if ($questionid) {
                // Convert the raw data of multichoice questions to a real question definition object.
                if (!$offlinequiz->shuffleanswers) {
                    $questiondata[$questionid]->options->shuffleanswers = false;
                }
                $question = question_bank::make_question($questiondata[$questionid]);
                // We only add multichoice questions which are needed for grading.
                if ($question->get_type_name() == 'multichoice' || $question->get_type_name() == 'multichoiceset') {
                    $templateusage->add_question($question, $groupquestions[$question->id]->maxmark);
                }
            }
        }
        // Create attempts for all questions (fixes order of the answers if shuffleanswers is active).
        $templateusage->start_all_questions();
        // Save the template question usage to the DB.
        question_engine::save_questions_usage_by_activity($templateusage);
        // Save the templateusage-ID in the offlinequiz_groups table.
        $group->templateusageid = $templateusage->get_id();
        $DB->set_field('offlinequiz_groups', 'templateusageid', $group->templateusageid, array('id' => $group->id));
    }
    // End else.
    return $templateusage;
}
 /**
  * Construct the class.  if a dbattempt object is passed in set it, otherwise initialize empty class
  *
  * @param questionmanager $questionmanager
  * @param \stdClass
  * @param \context_module $context
  */
 public function __construct($questionmanager, $dbattempt = null, $context = null)
 {
     $this->questionmanager = $questionmanager;
     $this->context = $context;
     // if empty create new attempt
     if (empty($dbattempt)) {
         $this->attempt = new \stdClass();
         // create a new quba since we're creating a new attempt
         $this->quba = \question_engine::make_questions_usage_by_activity('mod_activequiz', $this->questionmanager->getRTQ()->getContext());
         $this->quba->set_preferred_behaviour('immediatefeedback');
         $attemptlayout = $this->questionmanager->add_questions_to_quba($this->quba);
         // add the attempt layout to this instance
         $this->attempt->qubalayout = implode(',', $attemptlayout);
     } else {
         // else load it up in this class instance
         $this->attempt = $dbattempt;
         $this->quba = \question_engine::load_questions_usage_by_activity($this->attempt->questionengid);
     }
 }
/**
 * Checks whether a given result is complete, i.e. all contributing scanned pages have been submitted.
 * Updates the result in the DB if it is complete. Also updates the scanned pages that were duplicates from
 * 'doublepage' to 'resultexists'
 *
 * @param object $offlinequiz
 * @param object $group
 * @param object $result
 * @return boolean
 */
function offlinequiz_check_result_completed($offlinequiz, $group, $result)
{
    global $DB;
    $resultpages = $DB->get_records_sql("SELECT *\n               FROM {offlinequiz_scanned_pages}\n              WHERE resultid = :resultid\n                AND status = 'submitted'", array('resultid' => $result->id));
    if (count($resultpages) == $group->numberofpages) {
        $transaction = $DB->start_delegated_transaction();
        $quba = question_engine::load_questions_usage_by_activity($result->usageid);
        $quba->finish_all_questions(time());
        $totalmark = $quba->get_total_mark();
        question_engine::save_questions_usage_by_activity($quba);
        $result->sumgrades = $totalmark;
        $result->status = 'complete';
        $result->timestart = time();
        $result->timefinish = time();
        $result->timemodified = time();
        $DB->update_record('offlinequiz_results', $result);
        $transaction->allow_commit();
        offlinequiz_update_grades($offlinequiz, $result->userid);
        // Change the error of all submitted pages of the result to '' (was 'missingpages' before).
        foreach ($resultpages as $page) {
            $DB->set_field('offlinequiz_scanned_pages', 'error', '', array('id' => $page->id));
        }
        // Change the status of all double pages of the user to 'resultexists'.
        $offlinequizconfig = get_config('offlinequiz');
        $user = $DB->get_record('user', array('id' => $result->userid));
        $sql = "SELECT id\n                  FROM {offlinequiz_scanned_pages}\n                 WHERE offlinequizid = :offlinequizid\n                   AND userkey = :userkey\n                   AND groupnumber = :groupnumber\n                   AND error = 'doublepage'";
        $params = array('offlinequizid' => $offlinequiz->id, 'userkey' => $user->{$offlinequizconfig->ID_field}, 'groupnumber' => $group->number);
        $doublepages = $DB->get_records_sql($sql, $params);
        foreach ($doublepages as $page) {
            $DB->set_field('offlinequiz_scanned_pages', 'error', 'resultexists', array('id' => $page->id));
            $DB->set_field('offlinequiz_scanned_pages', 'resultid', 0, array('id' => $page->id));
        }
        return true;
    }
    return false;
}
 public function update_all_results_and_logs($offlinequiz)
 {
     global $DB, $CFG;
     $this->prevent_timeout();
     // Now we have to migrate offlinequiz_attempts to offlinequiz_results because
     // we need the new result IDs for the scannedpages.
     // Get all attempts that have already been migrated to the new question engine.
     $attempts = $DB->get_records('offlinequiz_attempts', array('offlinequiz' => $offlinequiz->id, 'needsupgradetonewqe' => 0, 'sheet' => 0));
     $groups = $DB->get_records('offlinequiz_groups', array('offlinequizid' => $offlinequiz->id), 'number', '*', 0, $offlinequiz->numgroups);
     list($maxquestions, $maxanswers, $formtype, $questionsperpage) = offlinequiz_get_question_numbers($offlinequiz, $groups);
     $transaction = $DB->start_delegated_transaction();
     foreach ($attempts as $attempt) {
         $group = $DB->get_record('offlinequiz_groups', array('offlinequizid' => $offlinequiz->id, 'number' => $attempt->groupid));
         $attemptlog = $DB->get_record('offlinequiz_i_log', array('offlinequiz' => $offlinequiz->id, 'attempt' => $attempt->id, 'page' => 0));
         $result = new StdClass();
         $result->offlinequizid = $offlinequiz->id;
         if ($group) {
             $result->offlinegroupid = $group->id;
         }
         $teacherid = $attemptlog->importadmin;
         if (empty($teacherid)) {
             $teacherid = 2;
         }
         $result->userid = $attempt->userid;
         $result->sumgrades = $attempt->sumgrades;
         $result->usageid = $attempt->uniqueid;
         $result->teacherid = $teacherid;
         $result->offlinegroupid = $group->id;
         $result->status = 'complete';
         $result->timestart = $attempt->timestart;
         $result->timefinish = $attempt->timefinish;
         $result->timemodified = $attempt->timemodified;
         if (!($oldresult = $DB->get_record('offlinequiz_results', array('offlinequizid' => $result->offlinequizid, 'userid' => $result->userid)))) {
             $result->id = $DB->insert_record('offlinequiz_results', $result);
         } else {
             $result->id = $oldresult->id;
             $DB->update_record('offlinequiz_results', $result);
         }
         // Save the resultid, s.t. we can still reconstruct the data later.
         $DB->set_field('offlinequiz_attempts', 'resultid', $result->id, array('id' => $attempt->id));
         if ($quba = question_engine::load_questions_usage_by_activity($result->usageid)) {
             $quba->finish_all_questions();
             $slots = $quba->get_slots();
             // Get all the page logs that have contributed to the attempt.
             if ($group->numberofpages == 1) {
                 $pagelogs = array($attemptlog);
             } else {
                 $sql = "SELECT *\n                    FROM {offlinequiz_i_log}\n                    WHERE offlinequiz = :offlinequizid\n                    AND attempt = :attemptid\n                    AND page > 0";
                 $params = array('offlinequizid' => $offlinequiz->id, 'attemptid' => $attempt->id);
                 $pagelogs = $DB->get_records_sql($sql, $params);
             }
             foreach ($pagelogs as $pagelog) {
                 $rawdata = $pagelog->rawdata;
                 $scannedpage = new StdClass();
                 $scannedpage->offlinequizid = $offlinequiz->id;
                 $scannedpage->resultid = $result->id;
                 $scannedpage->filename = $this->get_pic_name($rawdata);
                 $scannedpage->groupnumber = $this->get_group($rawdata);
                 $scannedpage->userkey = $this->get_user_name($rawdata);
                 if ($group->numberofpages == 1) {
                     $scannedpage->pagenumber = 1;
                 } else {
                     $scannedpage->pagenumber = $pagelog->page;
                 }
                 $scannedpage->time = $pagelog->time ? $pagelog->time : time();
                 $scannedpage->status = 'submitted';
                 $scannedpage->error = '';
                 $scannedpage->id = $DB->insert_record('offlinequiz_scanned_pages', $scannedpage);
                 $itemdata = $this->get_item_data($rawdata);
                 $items = explode(',', $itemdata);
                 if (!empty($items)) {
                     // Determine the slice of slots we are interested in.
                     // we start at the top of the page (e.g. 0, 96, etc).
                     $startindex = min(($scannedpage->pagenumber - 1) * $questionsperpage, count($slots));
                     // We end on the bottom of the page or when the questions are gone (e.g., 95, 105).
                     $endindex = min($scannedpage->pagenumber * $questionsperpage, count($slots));
                     $questioncounter = 0;
                     for ($slotindex = $startindex; $slotindex < $endindex; $slotindex++) {
                         $slot = $slots[$slotindex];
                         if (array_key_exists($questioncounter, $items)) {
                             $item = $items[$questioncounter];
                             for ($key = 0; $key < strlen($item); $key++) {
                                 $itemchoice = substr($item, $key, 1);
                                 $choice = new stdClass();
                                 $choice->scannedpageid = $scannedpage->id;
                                 $choice->slotnumber = $slot;
                                 $choice->choicenumber = $key;
                                 if ($itemchoice == '1') {
                                     $choice->value = 1;
                                 } else {
                                     if ($itemchoice == '0') {
                                         $choice->value = 0;
                                     } else {
                                         $choice->value = -1;
                                     }
                                 }
                                 $choice->id = $DB->insert_record('offlinequiz_choices', $choice);
                             }
                         }
                         $questioncounter++;
                     }
                 }
                 $rawcorners = explode(',', $pagelog->corners);
                 if (!empty($rawcorners) && count($rawcorners) > 8) {
                     for ($i = 0; $i < count($rawcorners); $i++) {
                         if ($rawcorners[$i] < 0) {
                             $rawcorners[$i] = 0;
                         }
                         if ($rawcorners[$i] > 2000) {
                             $rawcorners[$i] = 2000;
                         }
                     }
                     $corners = array();
                     $corners[0] = new oq_point($rawcorners[1], $rawcorners[2]);
                     $corners[1] = new oq_point($rawcorners[3], $rawcorners[4]);
                     $corners[2] = new oq_point($rawcorners[5], $rawcorners[6]);
                     $corners[3] = new oq_point($rawcorners[7], $rawcorners[8]);
                     offlinequiz_save_page_corners($scannedpage, $corners);
                 }
             }
         }
     }
     $DB->set_field('offlinequiz', 'needsilogupgrade', 0, array('id' => $offlinequiz->id));
     $transaction->allow_commit();
     // We start a new transaction for the remaining i_log entries.
     $otherlogs = $DB->get_records('offlinequiz_i_log', array('offlinequiz' => $offlinequiz->id, 'attempt' => 0));
     $transaction = $DB->start_delegated_transaction();
     foreach ($otherlogs as $pagelog) {
         list($status, $error) = $this->get_status_and_error($pagelog->error);
         $rawdata = $pagelog->rawdata;
         $groupnumber = $this->get_group($rawdata);
         $intgroup = intval($groupnumber);
         if ($intgroup > 0 && $intgroup <= $offlinequiz->numgroups) {
             $groupnumber = $intgroup;
         } else {
             $groupnumber = 0;
         }
         $scannedpage = new StdClass();
         $scannedpage->offlinequizid = $offlinequiz->id;
         $scannedpage->filename = $this->get_pic_name($rawdata);
         $scannedpage->groupnumber = $groupnumber;
         $scannedpage->userkey = $this->get_user_name($rawdata);
         $scannedpage->pagenumber = $pagelog->page;
         if ($pagelog->time) {
             $scannedpage->time = $pagelog->time;
         } else {
             $scannedpage->time = time();
         }
         $scannedpage->status = $status;
         $scannedpage->error = $error;
         $scannedpage->id = $DB->insert_record('offlinequiz_scanned_pages', $scannedpage);
         // We do not migrate itemdata for the scanned pages with error.
         // We do store the corners though.
         $rawcorners = explode(',', $pagelog->corners);
         if (!empty($rawcorners) && count($rawcorners) > 8) {
             $corners = array();
             $corners[0] = new oq_point($rawcorners[1], $rawcorners[2]);
             $corners[1] = new oq_point($rawcorners[3], $rawcorners[4]);
             $corners[2] = new oq_point($rawcorners[5], $rawcorners[6]);
             $corners[3] = new oq_point($rawcorners[7], $rawcorners[8]);
             offlinequiz_save_page_corners($scannedpage, $corners);
         }
     }
     $transaction->allow_commit();
     return true;
 }
Example #14
0
 private static function quiz_attempt_stepone($attempt, $lastattempt, $quizobj, $timenow, $quba, $attemptnumber)
 {
     //global $CFG, $DB;
     if (!($quizobj->get_quiz()->attemptonlast && $lastattempt)) {
         // Starting a normal, new, quiz attempt.
         // Fully load all the questions in this quiz.
         $quizobj->preload_questions();
         $quizobj->load_questions();
         // Add them all to the $quba.
         $questionsinuse = array_keys($quizobj->get_questions());
         self::quiz_question_process($attempt, $quizobj, $quba, $questionsinuse, $attemptnumber, $timenow);
     } else {
         // Starting a subsequent attempt in each attempt builds on last mode.
         $oldquba = question_engine::load_questions_usage_by_activity($lastattempt->uniqueid);
         $oldnumberstonew = array();
         foreach ($oldquba->get_attempt_iterator() as $oldslot => $oldqa) {
             $newslot = $quba->add_question($oldqa->get_question(), $oldqa->get_max_mark());
             $quba->start_question_based_on($newslot, $oldqa);
             $oldnumberstonew[$oldslot] = $newslot;
         }
     }
 }
Example #15
0
// Get and validate display options.
$maxvariant = $question->get_num_variants();
$options = new question_preview_options($question);
$options->load_user_defaults();
$options->set_from_request();
$PAGE->set_url(question_preview_url($id, $options->behaviour, $options->maxmark, $options));

// Get and validate exitsing preview, or start a new one.
$previewid = optional_param('previewid', 0, PARAM_INT);

if ($previewid) {
    if (!isset($SESSION->question_previews[$previewid])) {
        print_error('notyourpreview', 'question');
    }
    try {
        $quba = question_engine::load_questions_usage_by_activity($previewid);
    } catch (Exception $e) {
        print_error('submissionoutofsequencefriendlymessage', 'question',
                question_preview_url($question->id, $options->behaviour,
                $options->maxmark, $options), null, $e);
    }
    $slot = $quba->get_first_question_number();
    $usedquestion = $quba->get_question($slot);
    if ($usedquestion->id != $question->id) {
        print_error('questionidmismatch', 'question');
    }
    $question = $usedquestion;
    $options->variant = $quba->get_variant($slot);

} else {
    $quba = question_engine::make_questions_usage_by_activity('core_question_preview',
 /**
  * Makes an attempt for one student in this quiz, answering all the questions.
  *
  * @param $student
  * @param $quiz
  * @return int How many questions answers were made.
  */
 public function make_student_quiz_atttempt($student, $quiz)
 {
     $submissioncount = 0;
     $attempt = $this->start_quiz_attempt($quiz->id, $student->id);
     $quba = question_engine::load_questions_usage_by_activity($attempt->uniqueid);
     // This bit strips out bits of quiz_attempt::process_submitted_actions()
     // Simulates data from the form.
     // TODO iterate over the questions.
     $formdata = array('answer' => 'Sample essay answer text', 'answerformat' => FORMAT_MOODLE);
     $slot = 1;
     // Only 1 question so far.
     $quba->process_action($slot, $formdata, time());
     $submissioncount++;
     question_engine::save_questions_usage_by_activity($quba);
     $this->end_quiz_attmept($attempt);
     return $submissioncount;
 }
Example #17
0
 protected function load_quba(moodle_database $db = null)
 {
     $this->quba = question_engine::load_questions_usage_by_activity($this->quba->get_id(), $db);
 }
Example #18
0
 /**
  * Regrade a particular quiz attempt. Either for real ($dryrun = false), or
  * as a pretend regrade to see which fractions would change. The outcome is
  * stored in the quiz_overview_regrades table.
  *
  * Note, $attempt is not upgraded in the database. The caller needs to do that.
  * However, $attempt->sumgrades is updated, if this is not a dry run.
  *
  * @param object $attempt the quiz attempt to regrade.
  * @param bool $dryrun if true, do a pretend regrade, otherwise do it for real.
  * @param array $slots if null, regrade all questions, otherwise, just regrade
  *      the quetsions with those slots.
  */
 protected function regrade_attempt($attempt, $dryrun = false, $slots = null)
 {
     global $DB;
     // Need more time for a quiz with many questions.
     set_time_limit(300);
     $transaction = $DB->start_delegated_transaction();
     $quba = question_engine::load_questions_usage_by_activity($attempt->uniqueid);
     if (is_null($slots)) {
         $slots = $quba->get_slots();
     }
     $finished = $attempt->state == quiz_attempt::FINISHED;
     foreach ($slots as $slot) {
         $qqr = new stdClass();
         $qqr->oldfraction = $quba->get_question_fraction($slot);
         $quba->regrade_question($slot, $finished);
         $qqr->newfraction = $quba->get_question_fraction($slot);
         if (abs($qqr->oldfraction - $qqr->newfraction) > 1.0E-7) {
             $qqr->questionusageid = $quba->get_id();
             $qqr->slot = $slot;
             $qqr->regraded = empty($dryrun);
             $qqr->timemodified = time();
             $DB->insert_record('quiz_overview_regrades', $qqr, false);
         }
     }
     if (!$dryrun) {
         question_engine::save_questions_usage_by_activity($quba);
     }
     $transaction->allow_commit();
     // Really, PHP should not need this hint, but without this, we just run out of memory.
     $quba = null;
     $transaction = null;
     gc_collect_cycles();
 }
Example #19
0
        $variantoffset = $attemptnumber;
    }
    $quba->start_all_questions(new question_variant_pseudorandom_no_repeats_strategy($variantoffset), $timenow);
    // Update attempt layout.
    $newlayout = array();
    foreach (explode(',', $attempt->layout) as $qid) {
        if ($qid != 0) {
            $newlayout[] = $idstoslots[$qid];
        } else {
            $newlayout[] = 0;
        }
    }
    $attempt->layout = implode(',', $newlayout);
} else {
    // Starting a subsequent attempt in each attempt builds on last mode.
    $oldquba = question_engine::load_questions_usage_by_activity($lastattempt->uniqueid);
    $oldnumberstonew = array();
    foreach ($oldquba->get_attempt_iterator() as $oldslot => $oldqa) {
        $newslot = $quba->add_question($oldqa->get_question(), $oldqa->get_max_mark());
        $quba->start_question_based_on($newslot, $oldqa);
        $oldnumberstonew[$oldslot] = $newslot;
    }
    // Update attempt layout.
    $newlayout = array();
    foreach (explode(',', $lastattempt->layout) as $oldslot) {
        if ($oldslot != 0) {
            $newlayout[] = $oldnumberstonew[$oldslot];
        } else {
            $newlayout[] = 0;
        }
    }
Example #20
0
/**
 * Start a subsequent new attempt, in each attempt builds on last mode.
 *
 * @param question_usage_by_activity    $quba         this question usage
 * @param object                        $attempt      this attempt
 * @param object                        $lastattempt  last attempt
 * @return object                       modified attempt object
 *
 */
function quiz_start_attempt_built_on_last($quba, $attempt, $lastattempt) {
    $oldquba = question_engine::load_questions_usage_by_activity($lastattempt->uniqueid);

    $oldnumberstonew = array();
    foreach ($oldquba->get_attempt_iterator() as $oldslot => $oldqa) {
        $newslot = $quba->add_question($oldqa->get_question(), $oldqa->get_max_mark());

        $quba->start_question_based_on($newslot, $oldqa);

        $oldnumberstonew[$oldslot] = $newslot;
    }

    // Update attempt layout.
    $newlayout = array();
    foreach (explode(',', $lastattempt->layout) as $oldslot) {
        if ($oldslot != 0) {
            $newlayout[] = $oldnumberstonew[$oldslot];
        } else {
            $newlayout[] = 0;
        }
    }
    $attempt->layout = implode(',', $newlayout);
    return $attempt;
}
Example #21
0
 /**
  * We create two usages, each with two questions, a short-answer marked
  * out of 5, and and essay marked out of 10. We just start these attempts.
  *
  * Then we change the max mark for the short-answer question in one of the
  * usages to 20, using a qubaid_list, and verify.
  *
  * Then we change the max mark for the essay question in the other
  * usage to 2, using a qubaid_join, and verify.
  */
 public function test_set_max_mark_in_attempts()
 {
     // Set up some things the tests will need.
     $this->resetAfterTest();
     $dm = new question_engine_data_mapper();
     // Create the questions.
     $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
     $cat = $generator->create_question_category();
     $sa = $generator->create_question('shortanswer', null, array('category' => $cat->id));
     $essay = $generator->create_question('essay', null, array('category' => $cat->id));
     // Create the first usage.
     $q = question_bank::load_question($sa->id);
     $this->start_attempt_at_question($q, 'interactive', 5);
     $q = question_bank::load_question($essay->id);
     $this->start_attempt_at_question($q, 'interactive', 10);
     $this->finish();
     $this->save_quba();
     $usage1id = $this->quba->get_id();
     // Create the second usage.
     $this->quba = question_engine::make_questions_usage_by_activity('unit_test', context_system::instance());
     $q = question_bank::load_question($sa->id);
     $this->start_attempt_at_question($q, 'interactive', 5);
     $this->process_submission(array('answer' => 'fish'));
     $q = question_bank::load_question($essay->id);
     $this->start_attempt_at_question($q, 'interactive', 10);
     $this->finish();
     $this->save_quba();
     $usage2id = $this->quba->get_id();
     // Test set_max_mark_in_attempts with a qubaid_list.
     $usagestoupdate = new qubaid_list(array($usage1id));
     $dm->set_max_mark_in_attempts($usagestoupdate, 1, 20.0);
     $quba1 = question_engine::load_questions_usage_by_activity($usage1id);
     $quba2 = question_engine::load_questions_usage_by_activity($usage2id);
     $this->assertEquals(20, $quba1->get_question_max_mark(1));
     $this->assertEquals(10, $quba1->get_question_max_mark(2));
     $this->assertEquals(5, $quba2->get_question_max_mark(1));
     $this->assertEquals(10, $quba2->get_question_max_mark(2));
     // Test set_max_mark_in_attempts with a qubaid_join.
     $usagestoupdate = new qubaid_join('{question_usages} qu', 'qu.id', 'qu.id = :usageid', array('usageid' => $usage2id));
     $dm->set_max_mark_in_attempts($usagestoupdate, 2, 2.0);
     $quba1 = question_engine::load_questions_usage_by_activity($usage1id);
     $quba2 = question_engine::load_questions_usage_by_activity($usage2id);
     $this->assertEquals(20, $quba1->get_question_max_mark(1));
     $this->assertEquals(10, $quba1->get_question_max_mark(2));
     $this->assertEquals(5, $quba2->get_question_max_mark(1));
     $this->assertEquals(2, $quba2->get_question_max_mark(2));
     // Test the nothing to do case.
     $usagestoupdate = new qubaid_join('{question_usages} qu', 'qu.id', 'qu.id = :usageid', array('usageid' => -1));
     $dm->set_max_mark_in_attempts($usagestoupdate, 2, 2.0);
     $quba1 = question_engine::load_questions_usage_by_activity($usage1id);
     $quba2 = question_engine::load_questions_usage_by_activity($usage2id);
     $this->assertEquals(20, $quba1->get_question_max_mark(1));
     $this->assertEquals(10, $quba1->get_question_max_mark(2));
     $this->assertEquals(5, $quba2->get_question_max_mark(1));
     $this->assertEquals(2, $quba2->get_question_max_mark(2));
 }
Example #22
0
 protected function process_submitted_data()
 {
     global $DB;
     $qubaids = optional_param('qubaids', null, PARAM_SEQUENCE);
     if (!$qubaids) {
         return;
     }
     $qubaids = clean_param(explode(',', $qubaids), PARAM_INT);
     $attempts = $this->load_attempts_by_usage_ids($qubaids);
     $transaction = $DB->start_delegated_transaction();
     foreach ($qubaids as $qubaid) {
         $attempt = $attempts[$qubaid];
         $quba = question_engine::load_questions_usage_by_activity($qubaid);
         $attemptobj = new quiz_attempt($attempt, $this->quiz, $this->cm, $this->course);
         $attemptobj->process_all_actions(time());
     }
     $transaction->allow_commit();
 }
Example #23
0
 /**
  * Regrade a particular quiz attempt. Either for real ($dryrun = false), or
  * as a pretend regrade to see which fractions would change. The outcome is
  * stored in the quiz_overview_regrades table.
  *
  * Note, $attempt is not upgraded in the database. The caller needs to do that.
  * However, $attempt->sumgrades is updated, if this is not a dry run.
  *
  * @param object $attempt the quiz attempt to regrade.
  * @param bool $dryrun if true, do a pretend regrade, otherwise do it for real.
  * @param array $slots if null, regrade all questions, otherwise, just regrade
  *      the quetsions with those slots.
  */
 protected function regrade_attempt($attempt, $dryrun = false, $slots = null)
 {
     global $DB;
     $transaction = $DB->start_delegated_transaction();
     $quba = question_engine::load_questions_usage_by_activity($attempt->uniqueid);
     if (is_null($slots)) {
         $slots = $quba->get_slots();
     }
     $finished = $attempt->timefinish > 0;
     foreach ($slots as $slot) {
         $qqr = new stdClass();
         $qqr->oldfraction = $quba->get_question_fraction($slot);
         $quba->regrade_question($slot, $finished);
         $qqr->newfraction = $quba->get_question_fraction($slot);
         if (abs($qqr->oldfraction - $qqr->newfraction) > 1.0E-7) {
             $qqr->questionusageid = $quba->get_id();
             $qqr->slot = $slot;
             $qqr->regraded = empty($dryrun);
             $qqr->timemodified = time();
             $DB->insert_record('quiz_overview_regrades', $qqr, false);
         }
     }
     if (!$dryrun) {
         question_engine::save_questions_usage_by_activity($quba);
     }
     $transaction->allow_commit();
 }
 protected function print_quba($quba_id, $batch_mode, $include_barcodes = true, $include_intro = true)
 {
     //get the default question display information
     $options = new question_display_options_pdf();
     //get a question usage object from the database
     $usage = question_engine::load_questions_usage_by_activity($quba_id);
     //get an associative array, which indicates the questions which should be rendered
     $slots = $usage->get_slots();
     //if we're not _only_ outputting a key, output the core of the quesiton
     if ($batch_mode !== quiz_papercopy_batch_mode::KEY_ONLY) {
         //start a new copy with the given margins
         echo html_writer::start_tag('page', array('backtop' => '9mm', 'backbottom' => '0mm', 'backleft' => '0mm', 'backright' => '8mm'));
         if ($include_barcodes) {
             //echo html_writer::start_tag('page_header');
             echo self::render_barcode($quba_id);
             //echo html_writer::end_tag('page_header');
         } else {
             echo html_writer::tag('div', $quba_id, array('class' => 'qubaid'));
         }
         //bookmark, for easy access from a PDF viewer
         echo html_writer::tag('bookmark', '', array('title' => get_string('copynumber', 'quiz_papercopy', $quba_id), 'level' => '0'));
         //print the quiz's introduction
         if (!empty($this->quiz->intro) && $include_intro) {
             $introtext = $this->quiz->intro;
             $introtext = file_rewrite_pluginfile_urls($this->quiz->intro, 'pluginfile.php', $this->context->id, 'mod_quiz', 'intro', null);
             echo html_writer::tag('div', self::insert_ids($introtext, $quba_id), array('class' => 'introduction'));
         }
         //output each question
         foreach ($slots as $slot => $question) {
             $qbuf = $usage->render_question($question, $options, $slot + 1);
             //if the core PDF renderer has purification turned off, purify the question locally
             if (core_pdf_renderer::$do_not_purify) {
                 $qbuf = core_pdf_renderer::clean_with_htmlpurify($qbuf);
             }
             echo $qbuf;
         }
         echo html_writer::end_tag('page');
     }
     //if a key has been requested, output it as well
     if ($batch_mode == quiz_papercopy_batch_mode::KEY_ONLY || $batch_mode == quiz_papercopy_batch_mode::WITH_KEY) {
         //start a new copy with the given margins
         echo html_writer::start_tag('page', array('backtop' => '9mm', 'backbottom' => '0mm', 'backleft' => '0mm', 'backright' => '8mm'));
         //start of page headers
         if ($include_barcodes) {
             echo html_writer::start_tag('page_header');
             echo html_writer::start_tag('div', array('align' => 'center'));
             echo html_writer::tag('barcode', '', array('value' => $id, 'style' => 'width: 40mm; height: 7mm;', 'label' => 'label'));
             echo html_writer::end_tag('div');
             echo html_writer::end_tag('page_header');
         }
         //bookmark, for easy access from a PDF viewer
         echo html_writer::tag('bookmark', '', array('title' => get_string('answerkeynumber', 'quiz_papercopy', $quba_id), 'level' => $batch_mode !== quiz_papercopy_batch_mode::KEY_ONLY));
         //print the quiz's introduction
         echo html_writer::tag('p', get_string('answerkeyfortestid', 'quiz_papercopy', $quba_id), array('style' => 'font-weight:bold;'));
         echo html_writer::start_tag('table');
         //output each question
         foreach ($slots as $slot => $question) {
             echo html_writer::start_tag('tr');
             //echo the answer key contents
             echo html_writer::tag('td', $slot + 1 . '. ', array('style' => 'padding-right: 10px;'));
             echo html_writer::tag('td', $usage->get_right_answer_summary($question));
             echo html_writer::end_tag('tr');
         }
         echo html_writer::start_tag('table');
         echo html_writer::end_tag('page');
     }
 }