} // end: foreach $data if (!empty($_FILES['add_input']['name']) || !empty($_FILES['add_output']['name'])) { $content = array(); $rank = $maxrank + 1; foreach ($FILES as $file) { if (empty($_FILES['add_' . $file]['name'])) { warning("No {$file} file specified for new testcase, ignoring."); } else { checkFileUpload($_FILES['add_' . $file]['error']); $content[$file] = file_get_contents($_FILES['add_' . $file]['tmp_name']); } } $DB->q("INSERT INTO testcase\n\t\t (probid,rank,md5sum_input,md5sum_output,input,output,description,sample)\n\t\t VALUES (%i,%i,%s,%s,%s,%s,%s,%i)", $probid, $rank, md5(@$content['input']), md5(@$content['output']), @$content['input'], @$content['output'], @$_POST['add_desc'], @$_POST['add_sample']); if (!empty($content['image'])) { list($thumb, $type) = get_image_thumb_type($content['image']); $DB->q('UPDATE testcase SET image = %s, image_thumb = %s, image_type = %s WHERE probid = %i AND rank = %i', @$content['image'], $thumb, $type, $probid, $rank); } auditlog('testcase', $probid, 'added', "rank {$rank}"); $result .= "<li>Added new testcase {$rank} from files " . htmlspecialchars($_FILES['add_input']['name']) . " (" . printsize($_FILES['add_input']['size']) . ") and " . htmlspecialchars($_FILES['add_output']['name']) . " (" . printsize($_FILES['add_output']['size']) . ")."; if ($_FILES['add_output']['size'] > dbconfig_get('output_limit') * 1024) { $result .= "<br /><b>Warning: output file size exceeds " . "<code>output_limit</code> of " . dbconfig_get('output_limit') . " kB. This will always result in wrong answers!</b>"; } if (empty($content['input']) || empty($content['output'])) { $result .= "<br /><b>Warning: empty testcase file(s)!</b>"; } $result .= "</li>\n"; } } if (!empty($result)) {
/** * 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; }
function check_add($probid, $rank, $FILES) { global $DB; $result = ''; if (!empty($_FILES['add_input']['name']) || !empty($_FILES['add_output']['name'])) { $content = array(); foreach ($FILES as $file) { if (empty($_FILES['add_' . $file]['name'])) { warning("No {$file} file specified for new testcase, ignoring."); } else { checkFileUpload($_FILES['add_' . $file]['error']); $content[$file] = file_get_contents($_FILES['add_' . $file]['tmp_name']); } } $DB->q("INSERT INTO testcase\n\t\t (probid,rank,md5sum_input,md5sum_output,input,output,description,sample)\n\t\t VALUES (%i,%i,%s,%s,%s,%s,%s,%i)", $probid, $rank, md5(@$content['input']), md5(@$content['output']), @$content['input'], @$content['output'], @$_POST['add_desc'], isset($_POST['add_sample'])); if (!empty($content['image'])) { list($thumb, $type) = get_image_thumb_type($content['image']); $DB->q('UPDATE testcase SET image = %s, image_thumb = %s, image_type = %s WHERE probid = %i AND rank = %i', @$content['image'], $thumb, $type, $probid, $rank); } auditlog('testcase', $probid, 'added', "rank {$rank}"); $result .= "<li>Added new testcase {$rank} from files " . specialchars($_FILES['add_input']['name']) . " (" . printsize($_FILES['add_input']['size']) . ") and " . specialchars($_FILES['add_output']['name']) . " (" . printsize($_FILES['add_output']['size']) . ")."; if ($_FILES['add_output']['size'] > dbconfig_get('output_limit') * 1024) { $result .= "<br /><b>Warning: output file size exceeds " . "<code>output_limit</code> of " . dbconfig_get('output_limit') . " kB. This will always result in wrong answers!</b>"; } if (empty($content['input']) || empty($content['output'])) { $result .= "<br /><b>Warning: empty testcase file(s)!</b>"; } $result .= "</li>\n"; } return $result; }