/** * Checks groupnumber, userkey, and pagenumber of a scanned answer form * * @param unknown_type $offlinequiz * @param offlinequiz_page_scanner $scanner * @param unknown_type $scannedpage * @param unknown_type $teacherid * @param unknown_type $coursecontext */ function offlinequiz_check_scanned_page($offlinequiz, offlinequiz_page_scanner $scanner, $scannedpage, $teacherid, $coursecontext, $autorotate = false, $recheckresult = false, $ignoremaxanswers = false) { global $DB, $CFG; $offlinequizconfig = get_config('offlinequiz'); if (!$scanner->check_deleted()) { $scannedpage->status = 'error'; $scannedpage->error = 'notadjusted'; } if ($scannedpage->status == 'error' && $scanner->ontop && $autorotate) { echo 'rotating...' . "\n"; $oldfilename = $scannedpage->filename; if ($newfile = $scanner->rotate_180()) { $scannedpage->status = 'ok'; $scannedpage->error = ''; $scannedpage->userkey = null; $scannedpage->pagenumber = null; $scannedpage->groupnumber = null; $scannedpage->filename = $newfile->get_filename(); $corners = $scanner->get_corners(); $newcorners = array(); // Create a completely new scanner. $scanner = new offlinequiz_page_scanner($offlinequiz, $scanner->contextid, $scanner->maxquestions, $scanner->maxanswers); $sheetloaded = $scanner->load_stored_image($scannedpage->filename, $newcorners); if (!$sheetloaded) { $scannedpage->status = 'error'; $scannedpage->error = 'fatalerror'; } else { if (!$scanner->check_deleted()) { $scannedpage->status = 'error'; $scannedpage->error = 'notadjusted'; } else { $scannedpage->status = 'ok'; $scannedpage->error = ''; } } } } // Check the group number. $groupnumber = $scanner->calibrate_and_get_group(); // Call group first for callibration, such a crap! if (!property_exists($scannedpage, 'groupnumber') || $scannedpage->groupnumber == 0) { $scannedpage->groupnumber = $groupnumber; } $group = null; if ($scannedpage->status == 'ok' || $scannedpage->status == 'suspended') { if (!($group = $DB->get_record('offlinequiz_groups', array('offlinequizid' => $offlinequiz->id, 'number' => $scannedpage->groupnumber)))) { $scannedpage->status = 'error'; $scannedpage->error = 'grouperror'; } } // Adjust the maxanswers of the scanner according to the offlinequiz group deterimined above. if ($group && !$ignoremaxanswers) { $maxanswers = offlinequiz_get_maxanswers($offlinequiz, array($group)); if ($maxanswers != $scanner->maxanswers) { // Create a completely new scanner. $corners = $scanner->get_corners(); $scanner = new offlinequiz_page_scanner($offlinequiz, $scanner->contextid, $scanner->maxquestions, $maxanswers); $sheetloaded = $scanner->load_stored_image($scannedpage->filename, $corners); // Recursively call this method this time ignoring the maxanswers change. return offlinequiz_check_scanned_page($offlinequiz, $scanner, $scannedpage, $teacherid, $coursecontext, $autorotate, $recheckresult, true); } } // Check the user key (username, or userid, or other). $usernumber = $scanner->get_usernumber(); if (empty($scannedpage->userkey)) { $scannedpage->userkey = $offlinequizconfig->ID_prefix . $usernumber . $offlinequizconfig->ID_postfix; } if ($scannedpage->status == 'ok' || $scannedpage->status == 'suspended') { if (!($user = $DB->get_record('user', array($offlinequizconfig->ID_field => $scannedpage->userkey)))) { $scannedpage->status = 'error'; $scannedpage->error = 'nonexistinguser'; } else { $coursestudents = get_enrolled_users($coursecontext, 'mod/offlinequiz:attempt'); if (empty($coursestudents[$user->id])) { $scannedpage->status = 'error'; $scannedpage->error = 'usernotincourse'; } } } // Check the pagenumber. // Patch for old answer forms that did not have the page barcode. // With this patch the old forms can be uploaded in Moodle 2.x anyway. $pagenumber = $scanner->get_page(); if ($group && $group->numberofpages == 1) { $scannedpage->pagenumber = 1; $scanner->set_page(1); $page = 1; } else { if (!property_exists($scannedpage, 'pagenumber') || $scannedpage->pagenumber == 0 || $scannedpage->pagenumber == null) { $scannedpage->pagenumber = $pagenumber; } else { // This is neede because otherwise the scanner doesn't return answers (questionsonpage not set). $scanner->set_page($scannedpage->pagenumber); } $page = $scannedpage->pagenumber; if ($scannedpage->status == 'ok' || $scannedpage->status == 'suspended') { if ($page < 1 || $page > $group->numberofpages) { $scannedpage->status = 'error'; $scannedpage->error = 'invalidpagenumber'; } } } // If we have a valid userkey, a group and a page number then we can // check whether there is already a scanned page or even a completed result with the same group, userid, etc. if (($scannedpage->status == 'ok' || $scannedpage->status == 'suspended') && $user && $group && $page) { $resultexists = false; if (!property_exists($scannedpage, 'resultid') || !$scannedpage->resultid || $recheckresult) { $sql = "SELECT id\n FROM {offlinequiz_results}\n WHERE offlinequizid = :offlinequizid\n AND userid = :userid\n AND status = 'complete'"; $params = array('offlinequizid' => $offlinequiz->id, 'offlinegroupid' => $group->id, 'userid' => $user->id); if ($DB->get_record_sql($sql, $params)) { $resultexists = true; } } if ($resultexists) { $scannedpage->status = 'error'; $scannedpage->error = 'resultexists'; } else { if (!property_exists($scannedpage, 'id') || !$scannedpage->id) { $otherpages = $DB->get_records('offlinequiz_scanned_pages', array('offlinequizid' => $offlinequiz->id, 'userkey' => $user->{$offlinequizconfig->ID_field}, 'groupnumber' => $group->number, 'pagenumber' => $page)); } else { $sql = "SELECT id\n FROM {offlinequiz_scanned_pages}\n WHERE offlinequizid = :offlinequizid\n AND userkey = :userkey\n AND groupnumber = :groupnumber\n AND pagenumber = :pagenumber\n AND (status = 'ok' OR status = 'submitted')\n AND id <> :id"; $params = array('offlinequizid' => $offlinequiz->id, 'userkey' => $user->{$offlinequizconfig->ID_field}, 'groupnumber' => $group->number, 'pagenumber' => $page, 'id' => $scannedpage->id); $otherpages = $DB->get_records_sql($sql, $params); } if ($otherpages) { $scannedpage->status = 'error'; $scannedpage->error = 'doublepage'; } } } // Still everything OK, so we have a user and a group. Thus we can get/create the associated result // we also do that if another result exists, s.t. we have the answers later. if (empty($scannedpage->resultid)) { if ($scannedpage->status == 'ok' || $scannedpage->status == 'suspended' || $scannedpage->status == 'error' && $scannedpage->error == 'resultexists' || $scannedpage->status == 'error' && $scannedpage->error == 'usernotincourse') { // We have a group and a userid, so we can check if there is a matching partial result in the offlinequiz_results table. // The problem with this is that we could have several partial results with several pages. $sql = "SELECT *\n FROM {offlinequiz_results}\n WHERE offlinequizid = :offlinequizid\n AND offlinegroupid = :offlinegroupid\n AND userid = :userid\n AND status = 'partial'\n ORDER BY id ASC"; $params = array('offlinequizid' => $offlinequiz->id, 'offlinegroupid' => $group->id, 'userid' => $user->id); if (!($result = $DB->get_record_sql($sql, $params))) { // There is no result. First we have to clone the template question usage of the offline group. // We have to use our own loading function in order to get the right class. $templateusage = offlinequiz_load_questions_usage_by_activity($group->templateusageid); // Get the question instances for initial maxmarks. $sql = "SELECT questionid, maxmark\n FROM {offlinequiz_group_questions}\n WHERE offlinequizid = :offlinequizid\n AND offlinegroupid = :offlinegroupid"; $qinstances = $DB->get_records_sql($sql, array('offlinequizid' => $offlinequiz->id, 'offlinegroupid' => $group->id)); // Clone it... $quba = $templateusage->get_clone($qinstances); // And save it. The clone contains the same question in the same order and the same order of the answers. question_engine::save_questions_usage_by_activity($quba); $result = new stdClass(); $result->offlinequizid = $offlinequiz->id; $result->offlinegroupid = $group->id; $result->userid = $user->id; $result->teacherid = $teacherid; $result->usageid = $quba->get_id(); $result->attendant = 'scanonly'; $result->status = 'partial'; $result->timecreated = time(); $result->timemodified = time(); $newid = $DB->insert_record('offlinequiz_results', $result); if ($newid) { $result->id = $newid; $scannedpage->resultid = $result->id; } else { $scannedpage->status = 'error'; $scannedpage->error = 'noresult'; } } else { // There is a partial result, so we can just load the user's question usage. // From now on we can use the default question usage class. $scannedpage->resultid = $result->id; } } } // We insert the scanned page into the database in any case. $scannedpage->time = time(); if (property_exists($scannedpage, 'id') && !empty($scannedpage->id)) { $DB->update_record('offlinequiz_scanned_pages', $scannedpage); } else { $scannedpage->id = $DB->insert_record('offlinequiz_scanned_pages', $scannedpage); } // Now the scanned page definitely has an ID, so we can store the corners. if (!$DB->get_records('offlinequiz_page_corners', array('scannedpageid' => $scannedpage->id))) { $corners = $scanner->get_corners(); offlinequiz_save_page_corners($scannedpage, $corners); } if ($autorotate) { return array($scanner, $scannedpage); } else { return $scannedpage; } }
if ($submitfilename = optional_param('filename', '', PARAM_RAW)) { $scannedpage->filename = $submitfilename; $filename = $submitfilename; } $origfilename = required_param('origfilename', PARAM_FILE); $origuserkey = required_param('origuserkey', PARAM_ALPHANUM); $origgroupnumber = required_param('origgroupnumber', PARAM_INT); $origpagenumber = required_param('origpagenumber', PARAM_INT); $origstatus = required_param('origstatus', PARAM_ALPHA); $origerror = required_param('origerror', PARAM_ALPHA); $origtime = required_param('origtime', PARAM_INT); } // If we still don't have corners, get them from the scanner. if ($nodbcorners) { $corners = $scanner->get_corners(); offlinequiz_save_page_corners($scannedpage, $corners); } // O=======================================. // O Step 2. The user might have submitted data. // O=======================================. // O=============================================. // O Action cancel. // O=============================================. if ($action == 'cancel') { $scannedpage->filename = $origfilename; $scannedpage->userkey = $origuserkey; $scannedpage->groupnumber = $origgroupnumber; $scannedpage->pagenumber = $origpagenumber; $scannedpage->status = $origstatus; $scannedpage->error = $origerror; $scannedpage->time = $origtime;
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; }