echo "<li>{$row}</li>\n"; } echo "</ul>\n\n"; if ($collapse) { echo "<script type=\"text/javascript\">\n<!--\n\tcollapse({$section});\n// -->\n</script>\n\n"; } flush(); } while (!empty($cids) && ($row = $res->next())) { $sid = $row['submitid']; // Try to find the verification match string in one of the source // files. The first match is used. $files = $DB->q("KEYVALUETABLE SELECT rank, sourcecode\n\t FROM submission_file WHERE submitid = %i", $sid); $results = NULL; foreach ($files as $rank => $source) { if (($results = getExpectedResults($source)) !== NULL) { break; } } if ($results !== NULL && $row['verified'] == 0) { $nchecked++; $result = mb_strtoupper($row['result']); if (!in_array($result, $results)) { $unexpected[] = "<a href=\"submission.php?id=" . $sid . "\">s{$sid}</a> has unexpected result '{$result}', " . "should be one of: " . implode(', ', $results); } else { if (count($results) > 1) { if ($verify_multiple) { // Judging result is as expected, set judging to verified: $DB->q('UPDATE judging SET verified = 1, jury_member = %s WHERE judgingid = %i', $verifier, $row['judgingid']); $multiple[] = "<a href=\"submission.php?id=" . $sid . "\">s{$sid}</a> verified as {$result}, " . "out of multiple possible outcomes (" . implode(', ', $results) . ")";
/** * Read problem description file and testdata from zip archive * and update problem with it, or insert new problem when probid=NULL. * Returns probid on success, or generates error on failure. */ function importZippedProblem($zip, $probid = NULL, $cid = -1) { global $DB, $teamid, $cdatas, $matchstrings; $prop_file = 'domjudge-problem.ini'; $yaml_file = 'problem.yaml'; $ini_keys_problem = array('name', 'timelimit', 'special_run', 'special_compare'); $ini_keys_contest_problem = array('probid', 'allow_submit', 'allow_judge', 'points', 'color'); $def_timelimit = 10; // Read problem properties $ini_array = parse_ini_string($zip->getFromName($prop_file)); if (empty($ini_array)) { if ($probid === NULL) { error("Need '" . $prop_file . "' file when adding a new problem."); } } else { // Only preserve valid keys: $ini_array_problem = array_intersect_key($ini_array, array_flip($ini_keys_problem)); $ini_array_contest_problem = array_intersect_key($ini_array, array_flip($ini_keys_contest_problem)); // Set default of 1 point for a problem if not specified if (!isset($ini_array_contest_problem['points'])) { $ini_array_contest_problem['points'] = 1; } if ($probid === NULL) { if (!isset($ini_array_contest_problem['probid'])) { error("Need 'probid' in '" . $prop_file . "' when adding a new problem."); } // Set sensible defaults for name and timelimit if not specified: if (!isset($ini_array_problem['name'])) { $ini_array_problem['name'] = $ini_array_contest_problem['probid']; } if (!isset($ini_array_problem['timelimit'])) { $ini_array_problem['timelimit'] = $def_timelimit; } // rename probid to shortname $shortname = $ini_array_contest_problem['probid']; unset($ini_array_contest_problem['probid']); $ini_array_contest_problem['shortname'] = $shortname; $probid = $DB->q('RETURNID INSERT INTO problem (' . implode(', ', array_keys($ini_array_problem)) . ') VALUES (%As)', $ini_array_problem); if ($cid != -1) { $ini_array_contest_problem['cid'] = $cid; $ini_array_contest_problem['probid'] = $probid; $DB->q('INSERT INTO contestproblem (' . implode(', ', array_keys($ini_array_contest_problem)) . ') VALUES (%As)', $ini_array_contest_problem); } } else { if (count($ini_array_problem) > 0) { $DB->q('UPDATE problem SET %S WHERE probid = %i', $ini_array_problem, $probid); } if ($cid != -1) { if ($DB->q("MAYBEVALUE SELECT probid FROM contestproblem\n\t\t\t\t WHERE probid = %i AND cid = %i", $probid, $cid)) { // Remove keys that cannot be modified: unset($ini_array_contest_problem['probid']); if (count($ini_array_contest_problem) != 0) { $DB->q('UPDATE contestproblem SET %S WHERE probid = %i AND cid = %i', $ini_array_contest_problem, $probid, $cid); } } else { $shortname = $ini_array_contest_problem['probid']; unset($ini_array_contest_problem['probid']); $ini_array_contest_problem['shortname'] = $shortname; $ini_array_contest_problem['cid'] = $cid; $ini_array_contest_problem['probid'] = $probid; $DB->q('INSERT INTO contestproblem (' . implode(', ', array_keys($ini_array_contest_problem)) . ') VALUES (%As)', $ini_array_contest_problem); } } } } // parse problem.yaml $problem_yaml = $zip->getFromName($yaml_file); if ($problem_yaml !== FALSE) { $problem_yaml_data = spyc_load($problem_yaml); if (!empty($problem_yaml_data)) { if (isset($problem_yaml_data['uuid']) && $cid != -1) { $DB->q('UPDATE contestproblem SET shortname=%s WHERE cid=%i AND probid=%i', $problem_yaml_data['uuid'], $cid, $probid); } $yaml_array_problem = array(); if (isset($problem_yaml_data['name'])) { if (is_array($problem_yaml_data['name'])) { foreach ($problem_yaml_data['name'] as $lang => $name) { // TODO: select a specific instead of the first language $yaml_array_problem['name'] = $name; break; } } else { $yaml_array_problem['name'] = $problem_yaml_data['name']; } } if (isset($problem_yaml_data['validator_flags'])) { $yaml_array_problem['special_compare_args'] = $problem_yaml_data['validator_flags']; } if (isset($problem_yaml_data['validation']) && $problem_yaml_data['validation'] == 'custom') { // search for validator $validator_files = array(); for ($j = 0; $j < $zip->numFiles; $j++) { $filename = $zip->getNameIndex($j); if (starts_with($filename, "output_validators/") && !ends_with($filename, "/")) { $validator_files[] = $filename; } } if (sizeof($validator_files) == 0) { echo "<p>Custom validator specified but not found.</p>\n"; } else { // file(s) have to share common directory $validator_dir = mb_substr($validator_files[0], 0, mb_strrpos($validator_files[0], "/")) . "/"; $same_dir = TRUE; foreach ($validator_files as $validator_file) { if (!starts_with($validator_file, $validator_dir)) { $same_dir = FALSE; echo "<p>{$validator_file} does not start with {$validator_dir}</p>\n"; break; } } if (!$same_dir) { echo "<p>Found multiple custom output validators.</p>\n"; } else { $tmpzipfiledir = exec("mktemp -d --tmpdir=" . TMPDIR, $dontcare, $retval); if ($retval != 0) { error("failed to create temporary directory"); } chmod($tmpzipfiledir, 0700); foreach ($validator_files as $validator_file) { $content = $zip->getFromName($validator_file); $filebase = basename($validator_file); $newfilename = $tmpzipfiledir . "/" . $filebase; file_put_contents($newfilename, $content); if ($filebase === 'build' || $filebase === 'run') { // mark special files as executable chmod($newfilename, 0755); } } exec("zip -r -j '{$tmpzipfiledir}/outputvalidator.zip' '{$tmpzipfiledir}'", $dontcare, $retval); if ($retval != 0) { error("failed to create zip file for output validator."); } $ovzip = file_get_contents("{$tmpzipfiledir}/outputvalidator.zip"); $probname = $DB->q("VALUE SELECT name FROM problem\n\t\t\t\t\t\t WHERE probid=%i", $probid); $ovname = preg_replace('/[^a-zA-Z0-9]/', '_', $probname) . "_cmp"; if ($DB->q("MAYBEVALUE SELECT execid FROM executable\n\t\t\t\t\t\t WHERE execid=%s", $ovname)) { // avoid name clash $clashcnt = 2; while ($DB->q("MAYBEVALUE SELECT execid FROM executable\n\t\t\t\t\t\t\t WHERE execid=%s", $ovname . "_" . $clashcnt)) { $clashcnt++; } $ovname = $ovname . "_" . $clashcnt; } $DB->q("INSERT INTO executable (execid, md5sum, zipfile,\n\t\t\t\t\t\t description, type) VALUES (%s, %s, %s, %s, %s)", $ovname, md5($ovzip), $ovzip, 'output validator for ' . $probname, 'compare'); $DB->q("UPDATE problem SET special_compare=%s\n\t\t\t\t\t\t WHERE probid=%i", $ovname, $probid); echo "<p>Added output validator '{$ovname}'.</p>\n"; } } } if (isset($problem_yaml_data['limits'])) { if (isset($problem_yaml_data['limits']['memory'])) { $yaml_array_problem['memlimit'] = 1024 * $problem_yaml_data['limits']['memory']; } if (isset($problem_yaml_data['limits']['output'])) { $yaml_array_problem['outputlimit'] = 1024 * $problem_yaml_data['limits']['output']; } } if (sizeof($yaml_array_problem) > 0) { $DB->q('UPDATE problem SET %S WHERE probid = %i', $yaml_array_problem, $probid); } } } // Add problem statement foreach (array('pdf', 'html', 'txt') as $type) { $text = $zip->getFromName('problem.' . $type); if ($text !== FALSE) { $DB->q('UPDATE problem SET problemtext = %s, problemtext_type = %s WHERE probid = %i', $text, $type, $probid); echo "<p>Added problem statement from: <tt>problem.{$type}</tt></p>\n"; break; } } // Insert/update testcases $maxrank = 1 + $DB->q('VALUE SELECT max(rank) FROM testcase WHERE probid = %i', $probid); // first insert sample, then secret data in alphabetical order foreach (array('sample', 'secret') as $type) { $ncases = 0; $datafiles = array(); for ($j = 0; $j < $zip->numFiles; $j++) { $filename = $zip->getNameIndex($j); if (starts_with($filename, "data/{$type}/") && ends_with($filename, ".in")) { $basename = basename($filename, ".in"); $fileout = "data/{$type}/" . $basename . ".ans"; if ($zip->locateName($fileout) !== FALSE) { $datafiles[] = $basename; } } } asort($datafiles); echo "<ul>\n"; foreach ($datafiles as $datafile) { $testin = $zip->getFromName("data/{$type}/{$datafile}.in"); $testout = $zip->getFromName("data/{$type}/{$datafile}.ans"); $description = $datafile; if (($descfile = $zip->getFromName("data/{$type}/{$datafile}.desc")) !== FALSE) { $description .= ": \n" . $descfile; } $image_file = $image_type = $image_thumb = FALSE; foreach (array('png', 'jpg', 'jpeg', 'gif') as $img_ext) { if (($image_file = $zip->getFromName("data/{$type}/{$datafile}" . "." . $img_ext)) !== FALSE) { list($image_thumb, $image_type) = get_image_thumb_type($image_file); break; } } $md5in = md5($testin); $md5out = md5($testout); // Skip testcases that already exist identically $id = $DB->q('MAYBEVALUE SELECT testcaseid FROM testcase WHERE md5sum_input = %s AND md5sum_output = %s AND description = %s AND sample = %i AND probid = %i', $md5in, $md5out, $description, $type == 'sample' ? 1 : 0, $probid); if (isset($id)) { echo "<li>Skipped {$type} testcase <tt>{$datafile}</tt>: already exists</li>\n"; continue; } $DB->q('INSERT INTO testcase (probid, rank, sample, md5sum_input, md5sum_output, input, output, description' . ($image_file !== FALSE ? ', image, image_thumb, image_type' : '') . ')' . 'VALUES (%i, %i, %i, %s, %s, %s, %s, %s' . ($image_file !== FALSE ? ', %s, %s, %s' : '%_ %_ %_') . ')', $probid, $maxrank, $type == 'sample' ? 1 : 0, $md5in, $md5out, $testin, $testout, $description, $image_file, $image_thumb, $image_type); $maxrank++; $ncases++; echo "<li>Added {$type} testcase from: <tt>{$datafile}.{in,ans}</tt></li>\n"; } echo "</ul>\n<p>Added {$ncases} {$type} testcase(s).</p>\n"; } // submit reference solutions if ($cid == -1) { echo "<p>No jury solutions added: problem is not linked to a contest (yet).</p>\n"; } else { if (empty($teamid)) { echo "<p>No jury solutions added: must associate team with your user first.</p>\n"; } else { if ($DB->q('VALUE SELECT allow_submit FROM problem INNER JOIN contestproblem using (probid) WHERE probid = %i AND cid = %i', $probid, $cid)) { // First find all submittable languages: $langs = $DB->q('KEYVALUETABLE SELECT langid, extensions FROM language WHERE allow_submit = 1'); $njurysols = 0; echo "<ul>\n"; for ($j = 0; $j < $zip->numFiles; $j++) { $filename = $zip->getNameIndex($j); $filename_parts = explode(".", $filename); $extension = end($filename_parts); if (!starts_with($filename, 'submissions/') || ends_with($filename, '/')) { // skipping non-submission files and directories silently continue; } unset($langid); foreach ($langs as $key => $exts) { if (in_array($extension, json_decode($exts))) { $langid = $key; break; } } if (empty($langid)) { echo "<li>Could not add jury solution <tt>{$filename}</tt>: unknown language.</li>\n"; } else { if (!($tmpfname = tempnam(TMPDIR, "ref_solution-"))) { error("Could not create temporary file in directory " . TMPDIR); } $offset = mb_strlen('submissions/'); $expectedResult = normalizeExpectedResult(mb_substr($filename, $offset, mb_strpos($filename, '/', $offset) - $offset)); $source = $zip->getFromIndex($j); $results = getExpectedResults($source); if ($results === NULL) { // annotate source code with expected result $source = "// added by import: " . $matchstrings[0] . $expectedResult . "\n" . $source; } else { if (!in_array($expectedResult, $results)) { warning("annotated result '" . implode(', ', $results) . "' does not match directory for {$filename}"); } } file_put_contents($tmpfname, $source); if (filesize($tmpfname) <= dbconfig_get('sourcesize_limit') * 1024) { submit_solution($teamid, $probid, $cid, $langid, array($tmpfname), array(basename($filename))); echo "<li>Added jury solution from: <tt>{$filename}</tt></li>\n"; $njurysols++; } else { echo "<li>Could not add jury solution <tt>{$filename}</tt>: too large.</li>\n"; } unlink($tmpfname); } } echo "</ul>\n<p>Added {$njurysols} jury solution(s).</p>\n"; } else { echo "<p>No jury solutions added: problem not submittable</p>\n"; } } } if (!in_array($cid, array_keys($cdatas))) { echo "<p>The corresponding contest is not activated yet." . "To view the submissions in the submissions list, you have to activate the contest first.</p>\n"; } return $probid; }
/** * POST a new submission */ function submissions_POST($args) { global $userdata, $DB, $api; checkargs($args, array('shortname', 'langid')); checkargs($userdata, array('teamid')); $contests = getCurContests(TRUE, $userdata['teamid'], false, 'shortname'); $contest_shortname = null; if (isset($args['contest'])) { if (isset($contests[$args['contest']])) { $contest_shortname = $args['contest']; } else { $api->createError("Cannot find active contest '{$args['contest']}', or you are not part of it."); } } else { if (count($contests) == 1) { $contest_shortname = key($contests); } else { $api->createError("No contest specified while multiple active contests found."); } } $cid = $contests[$contest_shortname]['cid']; $probid = $DB->q('MAYBEVALUE SELECT probid FROM problem INNER JOIN contestproblem USING (probid) WHERE shortname = %s AND cid = %i AND allow_submit = 1', $args['shortname'], $cid); if (empty($probid)) { error("Problem " . $args['shortname'] . " not found or or not submittable"); } // rebuild array of filenames, paths to get rid of empty upload fields $FILEPATHS = $FILENAMES = array(); foreach ($_FILES['code']['tmp_name'] as $fileid => $tmpname) { if (!empty($tmpname)) { checkFileUpload($_FILES['code']['error'][$fileid]); $FILEPATHS[] = $_FILES['code']['tmp_name'][$fileid]; $FILENAMES[] = $_FILES['code']['name'][$fileid]; } } $sid = submit_solution($userdata['teamid'], $probid, $cid, $args['langid'], $FILEPATHS, $FILENAMES); if (checkrole('jury')) { $results = getExpectedResults(file_get_contents($FILEPATHS[0])); if (!empty($results)) { $DB->q('UPDATE submission SET expected_results=%s WHERE submitid=%i', json_encode($results), $sid); } } auditlog('submission', $sid, 'added', 'via api', null, $cid); return safe_int($sid); }