function offlinequiz_evaluation_cron($jobid = 0, $verbose = false)
{
    global $CFG, $DB;
    raise_memory_limit(MEMORY_EXTRA);
    // Only count the jobs with status processing that have been started in the last 24 hours.
    $expiretime = time() - 86400;
    $runningsql = "SELECT COUNT(*)\n                     FROM {offlinequiz_queue}\n                    WHERE status = 'processing'\n                      AND timestart > :expiretime";
    $runningjobs = $DB->count_records_sql($runningsql, array('expiretime' => $expiretime));
    if ($runningjobs >= OFFLINEQUIZ_MAX_CRON_JOBS) {
        echo "Too many jobs running! Exiting!";
        return;
    }
    // TODO do this properly. Just for testing.
    $sql = "SELECT * FROM {offlinequiz_queue} WHERE status = 'new'";
    $params = array();
    if ($jobid) {
        $sql .= ' AND id = :jobid ';
        $params['jobid'] = $jobid;
    }
    $sql .= " ORDER BY id ASC";
    // If there are no new jobs, we simply exit.
    if (!($jobs = $DB->get_records_sql($sql, $params, 0, OFFLINEQUIZ_TOP_QUEUE_JOBS))) {
        if ($verbose) {
            echo get_string('nothingtodo', 'offlinequiz');
        }
        return;
    }
    $numberofjobs = count($jobs);
    if ($verbose) {
        $pbar = new progress_bar('offlinequizcronbar', 500, true);
        $pbar->create();
        $pbar->update(0, $numberofjobs, "Processing job - {0}/{$numberofjobs}.");
    }
    $numberdone = 0;
    foreach ($jobs as $job) {
        // Check whether the status is still 'new' (might have been changed by other cronjob).
        $transaction = $DB->start_delegated_transaction();
        $status = $DB->get_field('offlinequiz_queue', 'status', array('id' => $job->id));
        if ($status == 'new') {
            $DB->set_field('offlinequiz_queue', 'status', 'processing', array('id' => $job->id));
            $job->timestart = time();
            $DB->set_field('offlinequiz_queue', 'timestart', $job->timestart, array('id' => $job->id));
            $alreadydone = false;
        } else {
            $alreadydone = true;
        }
        $transaction->allow_commit();
        // If the job is still new, process it!
        if (!$alreadydone) {
            // Set up the context for this job.
            if (!($offlinequiz = $DB->get_record('offlinequiz', array('id' => $job->offlinequizid)))) {
                $DB->set_field('offlinequiz_queue', 'status', 'error', array('id' => $job->id));
                $DB->set_field('offlinequiz_queue', 'info', 'offlinequiz not found', array('id' => $job->id));
                continue;
            }
            if (!($course = $DB->get_record('course', array('id' => $offlinequiz->course)))) {
                $DB->set_field('offlinequiz_queue', 'status', 'error', array('id' => $job->id));
                $DB->set_field('offlinequiz_queue', 'info', 'course not found', array('id' => $job->id));
                continue;
            }
            if (!($cm = get_coursemodule_from_instance("offlinequiz", $offlinequiz->id, $course->id))) {
                $DB->set_field('offlinequiz_queue', 'status', 'error', array('id' => $job->id));
                $DB->set_field('offlinequiz_queue', 'info', 'course module found', array('id' => $job->id));
                continue;
            }
            if (!($context = context_module::instance($cm->id))) {
                $DB->set_field('offlinequiz_queue', 'status', 'error', array('id' => $job->id));
                $DB->set_field('offlinequiz_queue', 'info', 'context not found', array('id' => $job->id));
                continue;
            }
            if (!($groups = $DB->get_records('offlinequiz_groups', array('offlinequizid' => $offlinequiz->id), 'number', '*', 0, $offlinequiz->numgroups))) {
                $DB->set_field('offlinequiz_queue', 'status', 'error', array('id' => $job->id));
                $DB->set_field('offlinequiz_queue', 'info', 'no offlinequiz groups found', array('id' => $job->id));
                continue;
            }
            $coursecontext = context_course::instance($course->id);
            offlinequiz_load_useridentification();
            // TODO.
            $jobdata = $DB->get_records_sql("\n                    SELECT *\n                      FROM {offlinequiz_queue_data}\n                     WHERE queueid = :queueid\n                       AND status = 'new'", array('queueid' => $job->id));
            list($maxquestions, $maxanswers, $formtype, $questionsperpage) = offlinequiz_get_question_numbers($offlinequiz, $groups);
            $dirname = '';
            $doubleentry = 0;
            foreach ($jobdata as $data) {
                $starttime = time();
                $DB->set_field('offlinequiz_queue_data', 'status', 'processing', array('id' => $data->id));
                // We remember the directory name to be able to remove it later.
                if (empty($dirname)) {
                    $pathparts = pathinfo($data->filename);
                    $dirname = $pathparts['dirname'];
                }
                set_time_limit(120);
                try {
                    // Create a new scanner for every page.
                    $scanner = new offlinequiz_page_scanner($offlinequiz, $context->id, $maxquestions, $maxanswers);
                    // Try to load the image file.
                    echo 'job ' . $job->id . ': evaluating ' . $data->filename . "\n";
                    $scannedpage = $scanner->load_image($data->filename);
                    if ($scannedpage->status == 'ok') {
                        echo 'job ' . $job->id . ': image loaded ' . $scannedpage->filename . "\n";
                    } else {
                        if ($scannedpage->error == 'filenotfound') {
                            echo 'job ' . $job->id . ': image file not found: ' . $scannedpage->filename . "\n";
                        }
                    }
                    // Unset the origfilename because we don't need it in the DB.
                    unset($scannedpage->origfilename);
                    $scannedpage->offlinequizid = $offlinequiz->id;
                    // If we could load the image file, the status is 'ok', so we can check the page for errors.
                    if ($scannedpage->status == 'ok') {
                        // We autorotate so check_scanned_page will return a potentially new scanner and the scannedpage.
                        list($scanner, $scannedpage) = offlinequiz_check_scanned_page($offlinequiz, $scanner, $scannedpage, $job->importuserid, $coursecontext, true);
                    } else {
                        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);
                        }
                    }
                    echo 'job ' . $job->id . ': scannedpage id ' . $scannedpage->id . "\n";
                    // If the status is still 'ok', we can process the answers. This potentially submits the page and
                    // checks whether the result for a student is complete.
                    if ($scannedpage->status == 'ok') {
                        // We can process the answers and submit them if possible.
                        $scannedpage = offlinequiz_process_scanned_page($offlinequiz, $scanner, $scannedpage, $job->importuserid, $questionsperpage, $coursecontext, true);
                        echo 'job ' . $job->id . ': processed answers for ' . $scannedpage->id . "\n";
                    } else {
                        if ($scannedpage->status == 'error' && $scannedpage->error == 'resultexists') {
                            // Already process the answers but don't submit them.
                            $scannedpage = offlinequiz_process_scanned_page($offlinequiz, $scanner, $scannedpage, $job->importuserid, $questionsperpage, $coursecontext, false);
                            // Compare the old and the new result wrt. the choices.
                            $scannedpage = offlinequiz_check_different_result($scannedpage);
                        }
                    }
                    // If there is something to correct then store the hotspots for retrieval in correct.php.
                    if ($scannedpage->status != 'ok' && $scannedpage->error != 'couldnotgrab' && $scannedpage->error != 'notadjusted' && $scannedpage->error != 'grouperror') {
                        $scanner->store_hotspots($scannedpage->id);
                    }
                    if ($scannedpage->status == 'ok' || $scannedpage->status == 'submitted' || $scannedpage->status == 'suspended' || $scannedpage->error == 'missingpages') {
                        // Mark the file as processed.
                        $DB->set_field('offlinequiz_queue_data', 'status', 'processed', array('id' => $data->id));
                    } else {
                        $DB->set_field('offlinequiz_queue_data', 'status', 'error', array('id' => $data->id));
                        $DB->set_field('offlinequiz_queue_data', 'error', $scannedpage->error, array('id' => $data->id));
                    }
                    if ($scannedpage->error == 'doublepage') {
                        $doubleentry++;
                    }
                } catch (Exception $e) {
                    echo 'job ' . $job->id . ': ' . $e->getMessage() . "\n";
                    $DB->set_field('offlinequiz_queue_data', 'status', 'error', array('id' => $data->id));
                    $DB->set_field('offlinequiz_queue_data', 'error', 'couldnotgrab', array('id' => $data->id));
                    $DB->set_field('offlinequiz_queue_data', 'info', $e->getMessage(), array('id' => $data->id));
                    $scannedpage->status = 'error';
                    $scannedpage->error = 'couldnotgrab';
                    if ($scannedpage->id) {
                        $DB->update_record('offlinequiz_scanned_pages', $scannedpage);
                    } else {
                        $DB->insert_record('offlinequiz_scanned_pages', $scannedpage);
                    }
                }
            }
            // End foreach jobdata.
            offlinequiz_update_grades($offlinequiz);
            $job->timefinish = time();
            $DB->set_field('offlinequiz_queue', 'timefinish', $job->timefinish, array('id' => $job->id));
            $job->status = 'finished';
            $DB->set_field('offlinequiz_queue', 'status', 'finished', array('id' => $job->id));
            echo date('Y-m-d-H:i') . ": Import queue with id {$job->id} imported.\n\n";
            if ($user = $DB->get_record('user', array('id' => $job->importuserid))) {
                $mailtext = get_string('importisfinished', 'offlinequiz', format_text($offlinequiz->name, FORMAT_PLAIN));
                // How many pages have been imported successfully.
                $countsql = "SELECT COUNT(id)\n                               FROM {offlinequiz_queue_data}\n                              WHERE queueid = :queueid\n                                AND status = 'processed'";
                $params = array('queueid' => $job->id);
                $mailtext .= "\n\n" . get_string('importnumberpages', 'offlinequiz', $DB->count_records_sql($countsql, $params));
                // How many pages have an error.
                $countsql = "SELECT COUNT(id)\n                               FROM {offlinequiz_queue_data}\n                              WHERE queueid = :queueid\n                                AND status = 'error'";
                $mailtext .= "\n" . get_string('importnumberverify', 'offlinequiz', $DB->count_records_sql($countsql, $params));
                $mailtext .= "\n" . get_string('importnumberexisting', 'offlinequiz', $doubleentry);
                $linkoverview = "{$CFG->wwwroot}/mod/offlinequiz/report.php?q={$job->offlinequizid}&mode=overview";
                $mailtext .= "\n\n" . get_string('importlinkresults', 'offlinequiz', $linkoverview);
                $linkupload = "{$CFG->wwwroot}/mod/offlinequiz/report.php?q={$job->offlinequizid}&mode=rimport";
                $mailtext .= "\n" . get_string('importlinkverify', 'offlinequiz', $linkupload);
                $mailtext .= "\n\n" . get_string('importtimestart', 'offlinequiz', userdate($job->timestart));
                $mailtext .= "\n" . get_string('importtimefinish', 'offlinequiz', userdate($job->timefinish));
                email_to_user($user, $CFG->noreplyaddress, get_string('importmailsubject', 'offlinequiz'), $mailtext);
            }
        }
        // End !alreadydone.
        $numberdone++;
        if ($verbose) {
            ob_flush();
            $pbar->update($numberdone, $numberofjobs, "Processing job - {$numberdone}/{$numberofjobs}.");
        }
    }
    // End foreach.
}
                            $usernumber = substr($userkey, strlen($offlinequizconfig->ID_prefix), $offlinequizconfig->ID_digits);
                            $groupnumber = intval($scannedpage->groupnumber);
                            $pagenumber = intval($scannedpage->pagenumber);
                        }
                    }
                }
            }
        }
    }
}
// We count the old choices to decide whether to process the page again.
$oldchoices = $DB->count_records('offlinequiz_choices', array('scannedpageid' => $scannedpage->id));
// If we have an OK page and the action was checkuser, setpage, etc. we should process the page.
if (($scannedpage->status == 'ok' || $scannedpage->status == 'suspended') && ($action == 'readjust' || $action == 'checkuser' || $action == 'enrol' || $action == 'setpage' || $action == 'rotate' || $action == 'load' && !$oldchoices)) {
    // Process the scanned page and write the answers in the offlinequiz_choices table.
    $scannedpage = offlinequiz_process_scanned_page($offlinequiz, $scanner, $scannedpage, $USER->id, $questionsperpage, $coursecontext);
}
// Load the choices made before from the database. This might be empty.
$choices = $DB->get_records('offlinequiz_choices', array('scannedpageid' => $scannedpage->id), 'slotnumber, choicenumber');
// Choicesdata contains the choices data from the DB indexed by slotnumber and choicenumber.
$choicesdata = array();
if (!empty($choices)) {
    foreach ($choices as $choice) {
        if (!isset($choicesdata[$choice->slotnumber]) || !is_array($choicesdata[$choice->slotnumber])) {
            $choicesdata[$choice->slotnumber] = array();
        }
        $choicesdata[$choice->slotnumber][$choice->choicenumber] = $choice;
    }
}
// Retrieve the offlinequiz group.
if (is_numeric($groupnumber) && $groupnumber > 0 && $groupnumber <= $offlinequiz->numgroups) {
/**
 * Checks whether the userkey of a scannedpage has been changed. If so, we have to create a new result
 * using the new user ID.
 * Also checks whether the new and the old result differ in terms of markings.
 *
 * @param unknown_type $offlinequiz
 * @param unknown_type $scanner
 * @param unknown_type $scannedpage
 * @param unknown_type $coursecontext
 * @param unknown_type $questionsperpage
 * @param unknown_type $offlinequizconfig
 * @return Ambigous <unknown_type, multitype:unknown_type offlinequiz_page_scanner >
 */
function offlinequiz_check_for_changed_user($offlinequiz, $scanner, $scannedpage, $coursecontext, $questionsperpage, $offlinequizconfig)
{
    global $DB, $USER;
    if (property_exists($scannedpage, 'resultid') and $scannedpage->resultid) {
        if ($result = $DB->get_record('offlinequiz_results', array('id' => $scannedpage->resultid))) {
            if ($newuser = $DB->get_record('user', array($offlinequizconfig->ID_field => $scannedpage->userkey))) {
                if ($newuser->id != $result->userid) {
                    $oldresultid = $scannedpage->resultid;
                    // We have to disconnect the page from its result because we have to create a new result for the new user.
                    unset($scannedpage->resultid);
                    $DB->set_field('offlinequiz_scanned_pages', 'resultid', 0, array('id' => $scannedpage->id));
                    // TODO what do we do with other pages that contributed to the old result?
                    if (!$DB->get_record('offlinequiz_scanned_pages', array('resultid' => $oldresultid))) {
                        // Delete the result if no other pages use this result.
                        $DB->delete_records('offlinequiz_results', array('id' => $oldresultid));
                    }
                    // Now the old result cannot be found and we can check the page again which will produce a new result.
                    $scannedpage = offlinequiz_check_scanned_page($offlinequiz, $scanner, $scannedpage, $USER->id, $coursecontext);
                    if ($scannedpage->status == 'error' && $scannedpage->error == 'resultexists') {
                        // Already process the answers but don't submit them.
                        $scannedpage = offlinequiz_process_scanned_page($offlinequiz, $scanner, $scannedpage, $USER->id, $questionsperpage, $coursecontext, false);
                        // Compare the old and the new result wrt. the choices.
                        $scannedpage = offlinequiz_check_different_result($scannedpage);
                    }
                }
            }
        } else {
            unset($scannedpage->resultid);
            $DB->set_field('offlinequiz_scanned_pages', 'resultid', 0, array('id' => $scannedpage->id));
            if (!$DB->get_record('offlinequiz_scanned_pages', array('resultid' => $oldresultid))) {
                // Delete the result.
                $DB->delete_records('offlinequiz_results', array('id' => $resultid));
            }
        }
    }
    return $scannedpage;
}