/**
 * 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;
 }