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