uoj-html-lib.php 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509
  1. <?php
  2. function uojHandleAtSign($str, $uri) {
  3. $referrers = array();
  4. $res = preg_replace_callback('/@(@|[a-zA-Z0-9_]{1,20})/', function($matches) use (&$referrers) {
  5. if ($matches[1] === '@') {
  6. return '@';
  7. } else {
  8. $user = queryUser($matches[1]);
  9. if ($user == null) {
  10. return $matches[0];
  11. } else {
  12. $referrers[$user['username']] = '';
  13. return '<span class="uoj-username" data-rating="'.$user['rating'].'">@'.$user['username'].'</span>';
  14. }
  15. }
  16. }, $str);
  17. $referrers_list = array();
  18. foreach ($referrers as $referrer => $val) {
  19. $referrers_list[] = $referrer;
  20. }
  21. return array($res, $referrers_list);
  22. }
  23. function uojFilePreview($file_name, $output_limit, $file_type = 'text') {
  24. switch ($file_type) {
  25. case 'text':
  26. return strOmit(file_get_contents($file_name, false, null, 0, $output_limit + 4), $output_limit);
  27. default:
  28. return strOmit(shell_exec('xxd -g 4 -l 5000 ' . escapeshellarg($file_name) . ' | head -c ' . ($output_limit + 4)), $output_limit);
  29. }
  30. }
  31. function uojIncludeView($name, $view_params = array()) {
  32. extract($view_params);
  33. include $_SERVER['DOCUMENT_ROOT'].'/app/views/'.$name.'.php';
  34. }
  35. function redirectTo($url) {
  36. header('Location: '.$url);
  37. die();
  38. }
  39. function permanentlyRedirectTo($url) {
  40. header("HTTP/1.1 301 Moved Permanently");
  41. header('Location: '.$url);
  42. die();
  43. }
  44. function redirectToLogin() {
  45. if (UOJContext::isAjax()) {
  46. die('please <a href="'.HTML::url('/login').'">login</a>');
  47. } else {
  48. header('Location: '.HTML::url('/login'));
  49. die();
  50. }
  51. }
  52. function becomeMsgPage($msg, $title = '消息') {
  53. if (UOJContext::isAjax()) {
  54. die($msg);
  55. } else {
  56. echoUOJPageHeader($title);
  57. echo $msg;
  58. echoUOJPageFooter();
  59. die();
  60. }
  61. }
  62. function become404Page() {
  63. header($_SERVER['SERVER_PROTOCOL'] . " 404 Not Found", true, 404);
  64. becomeMsgPage('<div class="text-center"><div style="font-size:233px">404</div><p>唔……未找到该页面……你是从哪里点进来的……&gt;_&lt;……</p></div>', '404');
  65. }
  66. function become403Page() {
  67. header($_SERVER['SERVER_PROTOCOL'] . " 403 Forbidden", true, 403);
  68. becomeMsgPage('<div class="text-center"><div style="font-size:233px">403</div><p>禁止入内! T_T</p></div>', '403');
  69. }
  70. function getUserLink($username, $rating = null, $certs = null) {
  71. if (validateUsername($username) && ($user = queryUser($username))) {
  72. if ($rating == null) {
  73. $rating = $user['rating'];
  74. }
  75. if ($cert == null) {
  76. $certs = $user['certs'];
  77. }
  78. return '<span class="uoj-username" data-rating="'.$rating.'" data-certs="'.$certs.'">'.$username.'</span>';
  79. } else {
  80. $esc_username = HTML::escape($username);
  81. return '<span>'.$esc_username.'</span>';
  82. }
  83. }
  84. function getRemoteProblemLink($rpid) {
  85. $rp = queryRemoteProblemBrief($rpid);
  86. return '<a href="/rp/' . $rpid . '">' . HTML::escape($rp['title']) . '</a>';
  87. }
  88. function getProblemLink($problem, $problem_title = '!title_only') {
  89. if ($problem_title == '!title_only') {
  90. $problem_title = HTML::escape($problem['title']);
  91. } elseif ($problem_title == '!id_and_title') {
  92. $problem_title = "#${problem['id']}. " . HTML::escape($problem['title']);
  93. }
  94. return '<a href="/problem/'.$problem['id'].'">'.$problem_title.'</a>';
  95. }
  96. function getContestProblemLink($problem, $contest_id, $problem_title = '!title_only') {
  97. if ($problem_title == '!title_only') {
  98. $problem_title = HTML::escape($problem['title']);
  99. } elseif ($problem_title == '!id_and_title') {
  100. $problem_title = "#{$problem['id']}. " . HTML::escape($problem['title']);
  101. }
  102. return '<a href="/contest/'.$contest_id.'/problem/'.$problem['id'].'">'.$problem_title.'</a>';
  103. }
  104. function getBlogLink($id) {
  105. if (validateUInt($id) && $blog = queryBlog($id)) {
  106. return '<a href="/blogs/'.$id.'">'.$blog['title'].'</a>';
  107. }
  108. }
  109. function getClickZanBlock($type, $id, $cnt, $val = null) {
  110. if ($val == null) {
  111. $val = queryZanVal($id, $type, Auth::user());
  112. }
  113. return '<div class="uoj-click-zan-block" data-id="'.$id.'" data-type="'.$type.'" data-val="'.$val.'" data-cnt="'.$cnt.'"></div>';
  114. }
  115. function getLongTablePageRawUri($page) {
  116. $path = strtok(UOJContext::requestURI(), '?');
  117. $query_string = strtok('?');
  118. parse_str($query_string, $param);
  119. $param['page'] = $page;
  120. if ($page == 1) {
  121. unset($param['page']);
  122. }
  123. if ($param) {
  124. return $path . '?' . http_build_query($param);
  125. } else {
  126. return $path;
  127. }
  128. }
  129. function getLongTablePageUri($page) {
  130. return HTML::escape(getLongTablePageRawUri($page));
  131. }
  132. function echoLongTable($col_names, $table_name, $cond, $tail, $header_row, $print_row, $config) {
  133. $pag_config = $config;
  134. $pag_config['col_names'] = $col_names;
  135. $pag_config['table_name'] = $table_name;
  136. $pag_config['cond'] = $cond;
  137. $pag_config['tail'] = $tail;
  138. $pag = new Paginator($pag_config);
  139. $div_classes = isset($config['div_classes']) ? $config['div_classes'] : array('table-responsive', 'shadow', 'my-4');
  140. $table_classes = isset($config['table_classes']) ? $config['table_classes'] : array('table', 'table-bordered', 'table-hover', 'table-striped', 'table-text-center');
  141. echo '<div class="', join($div_classes, ' '), '">';
  142. echo '<table class="', join($table_classes, ' '), '">';
  143. echo '<thead>';
  144. echo $header_row;
  145. echo '</thead>';
  146. echo '<tbody>';
  147. foreach ($pag->get() as $idx => $row) {
  148. if (isset($config['get_row_index'])) {
  149. $print_row($row, $idx);
  150. } else {
  151. $print_row($row);
  152. }
  153. }
  154. if ($pag->isEmpty()) {
  155. echo '<tr><td colspan="233">'.UOJLocale::get('none').'</td></tr>';
  156. }
  157. echo '</tbody>';
  158. echo '</table>';
  159. echo '</div>';
  160. if (isset($config['print_after_table'])) {
  161. $fun = $config['print_after_table'];
  162. $fun();
  163. }
  164. echo $pag->pagination();
  165. }
  166. function getSubmissionStatusDetails($submission) {
  167. $html = '<td colspan="233" style="vertical-align: middle">';
  168. $out_status = explode(', ', $submission['status'])[0];
  169. $fly = '<img src="/images/utility/qpx_n/b37.gif" alt="小熊像超人一样飞" class="img-rounded" />';
  170. $think = '<img src="/images/utility/qpx_n/b29.gif" alt="小熊像在思考" class="img-rounded" />';
  171. if ($out_status == 'Judged') {
  172. $status_text = '<strong>Judged!</strong>';
  173. $status_img = $fly;
  174. } else {
  175. if ($submission['status_details'] !== '') {
  176. $status_img = $fly;
  177. $status_text = HTML::escape($submission['status_details']);
  178. } else {
  179. $status_img = $think;
  180. $status_text = $out_status;
  181. }
  182. }
  183. $html .= '<div class="uoj-status-details-img-div">' . $status_img . '</div>';
  184. $html .= '<div class="uoj-status-details-text-div">' . $status_text . '</div>';
  185. $html .= '</td>';
  186. return $html;
  187. }
  188. function echoSubmission($submission, $config, $user) {
  189. $problem = queryProblemBrief($submission['problem_id']);
  190. $submitterLink = getUserLink($submission['submitter']);
  191. if ($submission['score'] == null) {
  192. $used_time_str = "/";
  193. $used_memory_str = "/";
  194. } else {
  195. $used_time_str = $submission['used_time'] . 'ms';
  196. $used_memory_str = $submission['used_memory'] . 'kb';
  197. }
  198. $status = explode(', ', $submission['status'])[0];
  199. $show_status_details = Auth::check() && ($submission['submitter'] === Auth::id() || isSuperUser(queryUser(Auth::id()))) && $status !== 'Judged';
  200. if (!$show_status_details) {
  201. echo '<tr>';
  202. } else {
  203. echo '<tr class="warning">';
  204. }
  205. if (!isset($config['id_hidden'])) {
  206. echo '<td><a href="/submission/', $submission['id'], '">#', $submission['id'], '</a></td>';
  207. }
  208. if (!isset($config['problem_hidden'])) {
  209. if ($submission['contest_id']) {
  210. echo '<td>', getContestProblemLink($problem, $submission['contest_id'], '!id_and_title'), '</td>';
  211. } else if ($submission['problem_id'] != 260328) {
  212. echo '<td>', getProblemLink($problem, '!id_and_title'), '</td>';
  213. } else {
  214. foreach (json_decode($submission['content'], true)['config'] as $row)
  215. if ($row[0] == 'rpid') {
  216. $rpid = $row[1];
  217. break;
  218. }
  219. echo '<td>' . getRemoteProblemLink($rpid) . '</td>';
  220. }
  221. }
  222. if (!isset($config['submitter_hidden'])) {
  223. echo '<td>', $submitterLink, '</td>';
  224. }
  225. if (!isset($config['result_hidden'])) {
  226. echo '<td>';
  227. if ($status == 'Judged') {
  228. if ($submission['score'] == null) {
  229. echo '<a href="/submission/', $submission['id'], '" class="small">', $submission['result_error'], '</a>';
  230. } else {
  231. echo '<a href="/submission/', $submission['id'], '" class="uoj-score">', $submission['score'], '</a>';
  232. }
  233. } else {
  234. echo '<a href="/submission/', $submission['id'], '" class="small">', $status, '</a>';
  235. }
  236. echo '</td>';
  237. }
  238. if (!isset($config['used_time_hidden'])) {
  239. echo '<td>', $used_time_str, '</td>';
  240. }
  241. if (!isset($config['used_memory_hidden'])) {
  242. echo '<td>', $used_memory_str, '</td>';
  243. }
  244. echo '<td>', '<a href="/submission/', $submission['id'], '">', $submission['language'], '</a>', '</td>';
  245. if ($submission['tot_size'] < 1024) {
  246. $size_str = $submission['tot_size'] . 'b';
  247. } else {
  248. $size_str = sprintf("%.1f", $submission['tot_size'] / 1024) . 'kb';
  249. }
  250. echo '<td>', $size_str, '</td>';
  251. if (!isset($config['submit_time_hidden'])) {
  252. echo '<td><small>', $submission['submit_time'], '</small></td>';
  253. }
  254. if (!isset($config['judge_time_hidden'])) {
  255. echo '<td><small>', $submission['judge_time'], '</small></td>';
  256. }
  257. echo '</tr>';
  258. if ($show_status_details) {
  259. echo '<tr id="', "status_details_{$submission['id']}", '" class="info">';
  260. echo getSubmissionStatusDetails($submission);
  261. echo '</tr>';
  262. echo '<script type="text/javascript">update_judgement_status_details('.$submission['id'].')</script>';
  263. }
  264. }
  265. function echoSubmissionsListOnlyOne($submission, $config, $user) {
  266. echo '<div class="table-responsive shadow my-4">';
  267. echo '<table class="table table-bordered table-text-center">';
  268. echo '<thead>';
  269. echo '<tr>';
  270. if (!isset($config['id_hidden'])) {
  271. echo '<th>ID</th>';
  272. }
  273. if (!isset($config['problem_hidden'])) {
  274. echo '<th>'.UOJLocale::get('problems::problem').'</th>';
  275. }
  276. if (!isset($config['submitter_hidden'])) {
  277. echo '<th>'.UOJLocale::get('problems::submitter').'</th>';
  278. }
  279. if (!isset($config['result_hidden'])) {
  280. echo '<th>'.UOJLocale::get('problems::result').'</th>';
  281. }
  282. if (!isset($config['used_time_hidden'])) {
  283. echo '<th>'.UOJLocale::get('problems::used time').'</th>';
  284. }
  285. if (!isset($config['used_memory_hidden'])) {
  286. echo '<th>'.UOJLocale::get('problems::used memory').'</th>';
  287. }
  288. echo '<th>'.UOJLocale::get('problems::language').'</th>';
  289. echo '<th>'.UOJLocale::get('problems::file size').'</th>';
  290. if (!isset($config['submit_time_hidden'])) {
  291. echo '<th>'.UOJLocale::get('problems::submit time').'</th>';
  292. }
  293. if (!isset($config['judge_time_hidden'])) {
  294. echo '<th>'.UOJLocale::get('problems::judge time').'</th>';
  295. }
  296. echo '</tr>';
  297. echo '</thead>';
  298. echo '<tbody>';
  299. echoSubmission($submission, $config, $user);
  300. echo '</tbody>';
  301. echo '</table>';
  302. echo '</div>';
  303. }
  304. function echoSubmissionsList($cond, $tail, $config, $user) {
  305. $header_row = '<tr>';
  306. $col_names = array();
  307. $col_names[] = 'submissions.status_details';
  308. $col_names[] = 'submissions.status';
  309. $col_names[] = 'submissions.result_error';
  310. $col_names[] = 'submissions.score';
  311. $col_names[] = 'submissions.content';
  312. if (!isset($config['id_hidden'])) {
  313. $header_row .= '<th>ID</th>';
  314. $col_names[] = 'submissions.id';
  315. }
  316. if (!isset($config['problem_hidden'])) {
  317. $header_row .= '<th>'.UOJLocale::get('problems::problem').'</th>';
  318. $col_names[] = 'submissions.problem_id';
  319. $col_names[] = 'submissions.contest_id';
  320. }
  321. if (!isset($config['submitter_hidden'])) {
  322. $header_row .= '<th>'.UOJLocale::get('problems::submitter').'</th>';
  323. $col_names[] = 'submissions.submitter';
  324. }
  325. if (!isset($config['result_hidden'])) {
  326. $header_row .= '<th>'.UOJLocale::get('problems::result').'</th>';
  327. }
  328. if (!isset($config['used_time_hidden'])) {
  329. $header_row .= '<th>'.UOJLocale::get('problems::used time').'</th>';
  330. $col_names[] = 'submissions.used_time';
  331. }
  332. if (!isset($config['used_memory_hidden'])) {
  333. $header_row .= '<th>'.UOJLocale::get('problems::used memory').'</th>';
  334. $col_names[] = 'submissions.used_memory';
  335. }
  336. $header_row .= '<th>'.UOJLocale::get('problems::language').'</th>';
  337. $col_names[] = 'submissions.language';
  338. $header_row .= '<th>'.UOJLocale::get('problems::file size').'</th>';
  339. $col_names[] = 'submissions.tot_size';
  340. if (!isset($config['submit_time_hidden'])) {
  341. $header_row .= '<th>'.UOJLocale::get('problems::submit time').'</th>';
  342. $col_names[] = 'submissions.submit_time';
  343. }
  344. if (!isset($config['judge_time_hidden'])) {
  345. $header_row .= '<th>'.UOJLocale::get('problems::judge time').'</th>';
  346. $col_names[] = 'submissions.judge_time';
  347. }
  348. $header_row .= '</tr>';
  349. $table_name = isset($config['table_name']) ? $config['table_name'] : 'submissions';
  350. if (!isSuperUser($user)) {
  351. if ($user != null) {
  352. $permission_cond = "submissions.is_hidden = false or (submissions.is_hidden = true and submissions.problem_id in (select problem_id from problems_permissions where username = '{$user['username']}')) or (submissions.is_hidden = 1 and (select max(find_in_set(submissions.problem_id, privs)) as nr from user_info where username like 'group_%' and find_in_set(username, '{$user['extgroups']}')))";
  353. } else {
  354. $permission_cond = "submissions.is_hidden = false";
  355. }
  356. if ($cond !== '1') {
  357. $cond = "($cond) and ($permission_cond)";
  358. } else {
  359. $cond = $permission_cond;
  360. }
  361. }
  362. $table_config = isset($config['table_config']) ? $config['table_config'] : null;
  363. echoLongTable($col_names, $table_name, $cond, $tail, $header_row,
  364. function($submission) use ($config, $user) {
  365. echoSubmission($submission, $config, $user);
  366. }, $table_config);
  367. }
  368. function echoShortList($header, $rows, $print_row) {
  369. echo '<div class="table-responsive"><table class="table table-bordered table-hover table-striped table-text-center">';
  370. echo '<thead>';
  371. echo $header;
  372. echo '</thead>';
  373. echo '<tbody>';
  374. foreach ($rows as $idx => $row)
  375. $print_row($row);
  376. if (empty($rows))
  377. echo '<tr><td colspan="233">'.UOJLocale::get('none').'</td></tr>';
  378. echo '</tbody>';
  379. echo '</table>';
  380. echo '</div>';
  381. }
  382. function echoBelongGroupList($user) {
  383. echoShortList('<tr><th>组名</th><th>显示名</th><th>题目</th></tr>', queryUserExtgroups($user), function($grp) {
  384. $qp = queryUser($grp);
  385. $link = getUserLink($qp['username']);
  386. $name = HTML::escape($qp['email']);
  387. echo <<<HTML
  388. <tr>
  389. <td>$link</td>
  390. <td>$name</td>
  391. <td><a href="/problems/group/{$grp}">题目</a></td>
  392. </tr>
  393. HTML;
  394. });
  395. }
  396. function echoGroupMemberList($grp) {
  397. echoShortList('<tr><th>用户名</th><th>管理</th></tr>', queryGroupMembers($grp),
  398. function($usern) use ($grp) {
  399. $username = $usern['username'];
  400. $user = queryUser($username);
  401. $link = getUserLink($username);
  402. if (hasGroupCtrlPermission($user, $grp)) $adminp = '<b>是</b>';
  403. else $adminp = '否';
  404. echo <<<HTML
  405. <tr>
  406. <td>$link</td>
  407. <td>$adminp</td>
  408. </tr>
  409. HTML;
  410. });
  411. }
  412. function echoGroupProblemList($grp) {
  413. echoShortList('<tr><th>题号</th><th>题名</th><th>控制</th><th>压缩包大小</th></tr>', queryGroupProblems($grp),
  414. function($pid) use ($grp) {
  415. $problem = queryProblemBrief($pid);
  416. $pid = $pid;
  417. $title = HTML::escape($problem['title']);
  418. if ($problem['ctrl'] && $problem['ctrl'] == $grp['username']) $adminp = '<b>是</b>';
  419. else $adminp = '否';
  420. $size = printSize(queryProblemSize($pid));
  421. echo <<<HTML
  422. <tr><td>$pid</td>
  423. <td><a href="/problem/{$pid}">$title</a></td>
  424. <td>$adminp</td>
  425. <td>$size</td></tr>
  426. HTML;
  427. });
  428. }
  429. function echoGroupProblemSizeList($grp) {
  430. echoShortList('<tr><th>题号</th><th>题名</th><th>压缩包大小</th></tr>', queryGroupCtrlProblems($grp),
  431. function($pid) {
  432. $problem = queryProblemBrief($pid);
  433. $pid = $pid;
  434. $title = HTML::escape($problem['title']);
  435. $size = printSize(queryProblemSize($pid));
  436. echo <<<HTML
  437. <tr><td>$pid</td>
  438. <td><a href="/problem/{$pid}">$title</a></td>
  439. <td>$size</td></tr>
  440. HTML;
  441. });
  442. }
  443. function echoControlGroupList($user) {
  444. echoShortList('<tr><th>组名</th><th>显示名</th><th>管理</th></tr>', queryUserCtrlgroups($user), function($grp) {
  445. $qp = queryUser($grp);
  446. $link = getUserLink($qp['username']);
  447. $name = HTML::escape($qp['email']);
  448. echo <<<HTML
  449. <tr>
  450. <td>$link</td>
  451. <td>$name</td>
  452. <td><a href="/group-manage/{$grp}">管理</a></td>
  453. </tr>
  454. HTML;
  455. });
  456. }
  457. function echoGroupsList() {
  458. $header_row = '<tr>';
  459. $col_names = ['username', 'privs'];
  460. $header_row .= '<th>组名</th>';
  461. $header_row .= '<th>组权限</th>';
  462. $header_row .= '</tr>';
  463. $table_name = 'user_info';
  464. echoLongTable($col_names, $table_name, "username like 'group_%'", '', $header_row,
  465. function ($group) {
  466. $link = getUserLink($group['username']);
  467. $privs = $group['privs'];
  468. echo <<<HTML
  469. <tr>
  470. <td>
  471. {$link}
  472. </td>
  473. <td>
  474. {$privs_pretty}
  475. </td>
  476. </tr>
  477. HTML;
  478. }, []);
  479. }
  480. function echoCertsList($user) {
  481. echoShortList('<tr><th>勋章</th><th>展示</th></tr>', explode(',', $user['certslist']), function($certs) {
  482. $token = HTML::hiddenToken();
  483. $col = explode('#', $certs)[0];
  484. $text = HTML::escape(explode('#', $certs)[1]);
  485. $cert = '<span class="uoj-certs uoj-certs-' . $col . '">' . $text . '</span>';
  486. echo <<<HTML
  487. <tr>
  488. <td>$cert</td>
  489. <td>
  490. <form action="/certs-manage" method="post">
  491. {$token}
  492. <input type="hidden" name="certs" value="$certs">
  493. <button type="submit" class="btn btn-primary">展示这个</button>
  494. </form>
  495. </td>
  496. </tr>
  497. HTML;
  498. });
  499. }
  500. function echoPastesList() {
  501. $header_row = '<tr>';
  502. $col_names = ['`index`','creator','created_at'];
  503. $header_row .= '<th>ID</th>';
  504. $header_row .= '<th>'.UOJLocale::get("problems::submitter").'</th>';
  505. $header_row .= '<th>'.UOJLocale::get('problems::submit time').'</th>';
  506. $header_row .= '<th> 操作 </th>';
  507. $header_row .= '</tr>';
  508. $table_name = 'pastes';
  509. echoLongTable($col_names, $table_name, "1", 'order by created_at desc', $header_row,
  510. function($paste) {
  511. $user = getUserLink($paste['creator']);
  512. $token = HTML::hiddenToken();
  513. echo <<<HTML
  514. <tr>
  515. <td>
  516. <a href="/pastes/{$paste['index']}">{$paste['index']}</a>
  517. </td>
  518. <td>
  519. {$user}
  520. </td>
  521. <td>
  522. {$paste['created_at']}
  523. </td>
  524. <td>
  525. <form action="/super-manage/paste" method="post" class="form-horizontal">
  526. {$token}
  527. <input type="text" class="form-control" name="paste_deleter_name" id="input-paste_deleter_name" value="{$paste['index']}" style="display: none;">
  528. <button type="submit" name="submit-paste_deleter" value="paste_deleter" class="btn btn-sm btn-danger" style="margin: 0">删除</button>
  529. </form>
  530. </td>
  531. </tr>
  532. HTML;
  533. }, []);
  534. }
  535. function echoPasteContent($paste) {
  536. $zip_file = new ZipArchive();
  537. $submission_content = json_decode($paste['content'], true);
  538. $zip_file->open(UOJContext::storagePath().$submission_content['file_name']);
  539. $config = array();
  540. foreach ($submission_content['config'] as $config_key => $config_val) {
  541. $config[$config_val[0]] = $config_val[1];
  542. }
  543. $file_content = $zip_file->getFromName("paste.code");
  544. $file_content = uojTextEncode($file_content, array('allow_CR' => true, 'html_escape' => true));
  545. $file_language = htmlspecialchars($config["paste_language"]);
  546. $footer_text = UOJLocale::get('problems::source code').', '.UOJLocale::get('problems::language').': '.$file_language;
  547. $footer_text .= ", ".UOJLocale::get("problems::submitter") . <<<HTML
  548. : <a href="/user/profile/${paste['creator']}">${paste['creator']}</a>
  549. HTML;
  550. $footer_text .= ", ".UOJLocale::get("problems::submit time").": ".$paste['created_at'];
  551. switch ($file_language) {
  552. case 'C++14':
  553. case 'C++17':
  554. case 'C++17ubsan':
  555. $sh_class = 'sh_cpp';
  556. break;
  557. case 'Python2':
  558. case 'Python3':
  559. $sh_class = 'sh_python';
  560. break;
  561. case 'Java8':
  562. case 'Java11':
  563. $sh_class = 'sh_java';
  564. break;
  565. case 'C':
  566. $sh_class = 'sh_c';
  567. break;
  568. case 'Pascal':
  569. $sh_class = 'sh_pascal';
  570. break;
  571. default:
  572. $sh_class = '';
  573. break;
  574. }
  575. echo '<div class="card mb-3">';
  576. echo '<div class="card-header">';
  577. echo '<h4 class="card-title">Paste!</h4>';
  578. echo '</div>';
  579. echo '<div class="card-body">';
  580. echo '<pre><code class="'.$sh_class.'">'.$file_content."\n".'</code></pre>';
  581. echo '</div>';
  582. echo '<div class="card-footer">'.$footer_text.'</div>';
  583. echo '</div>';
  584. $zip_file->close();
  585. }
  586. function echoSubmissionContent($submission, $requirement, $options = array()) {
  587. static $uoj_submission_content_seq = 0;
  588. $collapsible = isset($options['collapsible']) && $options['collapsible'];
  589. $zip_file = new ZipArchive();
  590. $submission_content = json_decode($submission['content'], true);
  591. $zip_file->open(UOJContext::storagePath().$submission_content['file_name']);
  592. $config = array();
  593. foreach ($submission_content['config'] as $config_key => $config_val) {
  594. $config[$config_val[0]] = $config_val[1];
  595. }
  596. foreach ($requirement as $req) {
  597. if ($req['type'] == "source code") {
  598. $file_content = $zip_file->getFromName("{$req['name']}.code");
  599. $file_content = uojTextEncode($file_content, array('allow_CR' => true, 'html_escape' => true));
  600. $file_language = htmlspecialchars($config["{$req['name']}_language"]);
  601. $footer_text = UOJLocale::get('problems::source code').', '.UOJLocale::get('problems::language').': '.$file_language;
  602. switch ($file_language) {
  603. case 'C++14':
  604. case 'C++17':
  605. case 'C++17ubsan':
  606. $sh_class = 'sh_cpp';
  607. break;
  608. case 'Python2':
  609. case 'Python3':
  610. $sh_class = 'sh_python';
  611. break;
  612. case 'Java8':
  613. case 'Java11':
  614. $sh_class = 'sh_java';
  615. break;
  616. case 'C':
  617. $sh_class = 'sh_c';
  618. break;
  619. case 'Pascal':
  620. $sh_class = 'sh_pascal';
  621. break;
  622. default:
  623. $sh_class = '';
  624. break;
  625. }
  626. echo '<div class="card mb-3">';
  627. echo '<div class="card-header">';
  628. echo '<h4 class="card-title">源代码</h4>';
  629. echo '</div>';
  630. echo '<div class="card-body">';
  631. if ($collapsible) {
  632. $uoj_submission_content_seq++;
  633. $fold_id = 'uoj-code-'.$submission['id'].'-'.$uoj_submission_content_seq;
  634. echo '<button type="button" class="btn btn-sm btn-outline-secondary uoj-code-toggle" id="', $fold_id, '_toggle" aria-haspopup="dialog">', '查看源代码', '</button>';
  635. echo '<div class="uoj-modal" id="', $fold_id, '_modal" hidden>';
  636. echo '<div class="uoj-modal-backdrop" data-uoj-modal-close></div>';
  637. echo '<div class="uoj-modal-window" role="dialog" aria-modal="true" aria-label="', '源代码', '">';
  638. echo '<button type="button" class="uoj-modal-close" data-uoj-modal-close aria-label="', '关闭', '">×</button>';
  639. echo '<div class="uoj-modal-body">';
  640. echo '<div class="text-muted uoj-code-meta">'.$footer_text.'</div>';
  641. echo '<pre><code class="'.$sh_class.'">'.$file_content."\n".'</code></pre>';
  642. echo '</div>';
  643. echo '</div>';
  644. echo '</div>';
  645. echoUOJModalHelper();
  646. echo '<script type="text/javascript">';
  647. echo '(function(){var b=document.getElementById(', json_encode($fold_id.'_toggle'), '),m=document.getElementById(', json_encode($fold_id.'_modal'), ');';
  648. echo 'if(!b||!m||!window.uojModalInit){return;}';
  649. echo 'var api=window.uojModalInit(m);';
  650. echo 'b.addEventListener("click",function(){api.open();});';
  651. echo '})();';
  652. echo '</script>';
  653. } else {
  654. echo '<pre><code class="'.$sh_class.'">'.$file_content."\n".'</code></pre>';
  655. }
  656. echo '</div>';
  657. echo '<div class="card-footer">'.$footer_text.'</div>';
  658. echo '</div>';
  659. } elseif ($req['type'] == "text") {
  660. $file_content = $zip_file->getFromName("{$req['file_name']}", 504);
  661. $file_content = strOmit($file_content, 500);
  662. $file_content = uojTextEncode($file_content, array('allow_CR' => true, 'html_escape' => true));
  663. $footer_text = UOJLocale::get('problems::text file');
  664. echo '<div class="card mb-3">';
  665. echo '<div class="card-header">';
  666. echo '<h4 class="card-title">'.HTML::escape($req['file_name']).'</h4>';
  667. echo '</div>';
  668. echo '<div class="card-body">';
  669. echo '<pre>'."\n".$file_content."\n".'</pre>';
  670. echo '</div>';
  671. echo '<div class="card-footer">'.$footer_text.'</div>';
  672. echo '</div>';
  673. }
  674. }
  675. $zip_file->close();
  676. }
  677. // Shared vanilla-JS modal helper. Emitted at most once per request, and it is
  678. // also guarded in JS, so both the test-detail modal and the source-code modal
  679. // can use it independently (a page with only one of them still works).
  680. function echoUOJModalHelper() {
  681. static $emitted = false;
  682. if ($emitted) {
  683. return;
  684. }
  685. $emitted = true;
  686. echo <<<'UOJMODAL'
  687. <script type="text/javascript">
  688. if (typeof window.uojModalInit !== 'function') {
  689. window.uojModalInit = function (modal) {
  690. var win = modal.querySelector('.uoj-modal-window');
  691. var api = {
  692. lastFocus: null,
  693. body: modal.querySelector('.uoj-modal-body'),
  694. open: function () {
  695. if (!modal.hidden) {
  696. return;
  697. }
  698. if (window.uojModalCurrent && window.uojModalCurrent !== api) {
  699. window.uojModalCurrent.close();
  700. }
  701. api.lastFocus = document.activeElement;
  702. modal.hidden = false;
  703. document.body.classList.add('uoj-modal-open');
  704. window.uojModalCurrent = api;
  705. var btn = modal.querySelector('.uoj-modal-close');
  706. if (btn && btn.focus) {
  707. btn.focus();
  708. }
  709. },
  710. close: function () {
  711. if (modal.hidden) {
  712. return;
  713. }
  714. modal.hidden = true;
  715. if (window.uojModalCurrent === api) {
  716. window.uojModalCurrent = null;
  717. }
  718. if (!window.uojModalCurrent) {
  719. document.body.classList.remove('uoj-modal-open');
  720. }
  721. if (api.lastFocus && api.lastFocus.focus) {
  722. api.lastFocus.focus();
  723. }
  724. }
  725. };
  726. modal.addEventListener('click', function (e) {
  727. if (e.target === modal || (e.target.getAttribute && e.target.getAttribute('data-uoj-modal-close') !== null)) {
  728. api.close();
  729. }
  730. });
  731. document.addEventListener('keydown', function (e) {
  732. if ((e.key === 'Escape' || e.keyCode === 27) && !modal.hidden) {
  733. api.close();
  734. }
  735. });
  736. return api;
  737. };
  738. }
  739. </script>
  740. UOJMODAL;
  741. }
  742. class JudgementDetailsPrinter {
  743. private $name;
  744. private $styler;
  745. private $dom;
  746. private $subtask_num;
  747. private $prefix;
  748. private $blocks = array();
  749. private $pending_tests = array();
  750. private $bodies = array();
  751. private $body_seq = 0;
  752. private $rect_seq = 0;
  753. private $auto_select_id = null;
  754. private static $instance_seq = 0;
  755. private function _print_c($node) {
  756. foreach ($node->childNodes as $child) {
  757. if ($child->nodeName == '#text') {
  758. echo htmlspecialchars($child->nodeValue);
  759. } else {
  760. $this->_print($child);
  761. }
  762. }
  763. }
  764. private function _print($node) {
  765. if ($node->nodeName == 'error') {
  766. echo "<pre>\n";
  767. $this->_print_c($node);
  768. echo "\n</pre>";
  769. } elseif ($node->nodeName == 'in') {
  770. echo "<h4>标准输入:</h4><pre>\n";
  771. $this->_print_c($node);
  772. echo "\n</pre>";
  773. } elseif ($node->nodeName == 'out') {
  774. echo "<h4>标准输出:</h4><pre>\n";
  775. $this->_print_c($node);
  776. echo "\n</pre>";
  777. } elseif ($node->nodeName == 'res') {
  778. echo "<h4>检验结果:</h4><pre>\n";
  779. $this->_print_c($node);
  780. echo "\n</pre>";
  781. } else {
  782. echo '<', $node->nodeName;
  783. foreach ($node->attributes as $attr) {
  784. echo ' ', $attr->name, '="', htmlspecialchars($attr->value), '"';
  785. }
  786. echo '>';
  787. $this->_print_c($node);
  788. echo '</', $node->nodeName, '>';
  789. }
  790. }
  791. private function getVerdictAbbr($info) {
  792. $info = trim($info);
  793. $map = array(
  794. 'Accepted' => 'AC',
  795. 'Extra Test Passed' => 'AC',
  796. 'Success' => 'AC',
  797. 'Acceptable Answer' => 'AC',
  798. 'Wrong Answer' => 'WA',
  799. 'Time Limit Exceeded' => 'TLE',
  800. 'Memory Limit Exceeded' => 'MLE',
  801. 'Runtime Error' => 'RE',
  802. 'Output Limit Exceeded' => 'OLE',
  803. 'Dangerous Syscalls' => 'DGS',
  804. 'Judgement Failed' => 'JGF',
  805. 'No Comment' => '?',
  806. );
  807. if ($info === '') {
  808. return '?';
  809. }
  810. if (isset($map[$info])) {
  811. return $map[$info];
  812. }
  813. $words = preg_split('/\s+/', $info, -1, PREG_SPLIT_NO_EMPTY);
  814. if (count($words) > 1) {
  815. $abbr = '';
  816. foreach ($words as $word) {
  817. $abbr .= strtoupper(substr($word, 0, 1));
  818. if (strlen($abbr) >= 4) {
  819. break;
  820. }
  821. }
  822. if ($abbr !== '') {
  823. return substr($abbr, 0, 4);
  824. }
  825. }
  826. if (function_exists('mb_substr')) {
  827. return strtoupper(mb_substr($info, 0, 4, 'UTF-8'));
  828. }
  829. return strtoupper(substr($info, 0, 4));
  830. }
  831. private function getVerdictClass($info) {
  832. $info = trim($info);
  833. if ($info == 'Accepted' || $info == 'Extra Test Passed' || $info == 'Success' || $info == 'Acceptable Answer') {
  834. return 'uoj-test-rect-ac';
  835. }
  836. if ($info == 'Wrong Answer') {
  837. return 'uoj-test-rect-wa';
  838. }
  839. return 'uoj-test-rect-other';
  840. }
  841. private function collectBlocks($node) {
  842. foreach ($node->childNodes as $child) {
  843. if ($child->nodeType != XML_ELEMENT_NODE) {
  844. continue;
  845. }
  846. if ($child->nodeName == 'subtask') {
  847. $this->flushPendingTests();
  848. $this->blocks[] = array('type' => 'subtask', 'node' => $child);
  849. } elseif ($child->nodeName == 'test' || $child->nodeName == 'custom-test') {
  850. $this->pending_tests[] = $child;
  851. } elseif ($child->nodeName == 'error') {
  852. $this->flushPendingTests();
  853. $this->blocks[] = array('type' => 'error', 'node' => $child);
  854. } else {
  855. $this->collectBlocks($child);
  856. }
  857. }
  858. }
  859. private function flushPendingTests() {
  860. if (count($this->pending_tests)) {
  861. $this->blocks[] = array('type' => 'tests', 'nodes' => $this->pending_tests);
  862. $this->pending_tests = array();
  863. }
  864. }
  865. private function collectRoot($node) {
  866. if ($node->nodeName == 'subtask') {
  867. $this->blocks[] = array('type' => 'subtask', 'node' => $node);
  868. } elseif ($node->nodeName == 'test' || $node->nodeName == 'custom-test') {
  869. $this->pending_tests[] = $node;
  870. } elseif ($node->nodeName == 'error') {
  871. $this->blocks[] = array('type' => 'error', 'node' => $node);
  872. } else {
  873. $this->collectBlocks($node);
  874. }
  875. }
  876. private function renderRect($node) {
  877. $info = trim($node->getAttribute('info'));
  878. $num = $node->getAttribute('num');
  879. $time = $node->getAttribute('time');
  880. $memory = $node->getAttribute('memory');
  881. $this->rect_seq++;
  882. if ($num !== '' && is_numeric($num)) {
  883. $num_text = ((int)$num > 0) ? ('#' . (int)$num) : 'Extra';
  884. } else {
  885. $num_text = '#' . $this->rect_seq;
  886. }
  887. // The verdict abbreviation is always the label. UOJ stores a per-test
  888. // point value in score="..." (e.g. 10 on every test of an ACM problem),
  889. // so it is NOT a partial-credit signal and must never replace the
  890. // verdict (see the subtask header for the per-subtask score instead).
  891. $label = $this->getVerdictAbbr($info);
  892. $meta = array();
  893. if ($time !== '' && is_numeric($time) && $time >= 0) {
  894. $meta[] = $time . 'ms';
  895. }
  896. if ($memory !== '' && is_numeric($memory) && $memory >= 0) {
  897. $meta[] = $memory . 'kb';
  898. }
  899. $class = $this->getVerdictClass($info);
  900. $title = (strlen($num_text) && $num_text[0] === '#') ? trim($num_text . ' ' . $info) : $info;
  901. if ($this->styler->shouldFadeDetails($info)) {
  902. echo '<div class="uoj-test-rect ', $class, ' is-disabled" title="', htmlspecialchars($title), '">';
  903. } else {
  904. $body_id = $this->prefix . '_body_' . (++$this->body_seq);
  905. $this->bodies[$body_id] = $node;
  906. $auto_attr = '';
  907. if ($this->styler->collapse_in && $this->auto_select_id === null) {
  908. $this->auto_select_id = $body_id;
  909. $auto_attr = ' data-auto="1"';
  910. }
  911. echo '<div class="uoj-test-rect ', $class, '" role="button" tabindex="0" data-target="', $body_id, '"', $auto_attr, ' title="', htmlspecialchars($title), '">';
  912. }
  913. echo '<span class="uoj-test-rect-index">', htmlspecialchars($num_text), '</span>';
  914. echo '<span class="uoj-test-rect-verdict">', htmlspecialchars($label), '</span>';
  915. foreach ($meta as $meta_part) {
  916. echo '<span class="uoj-test-rect-meta">', htmlspecialchars($meta_part), '</span>';
  917. }
  918. echo '</div>';
  919. }
  920. private function renderSubtask($node) {
  921. $num = $node->getAttribute('num');
  922. $score = $node->getAttribute('score');
  923. $tests = array();
  924. $sum = 0;
  925. $has_test_score = false;
  926. foreach ($node->childNodes as $child) {
  927. if ($child->nodeType != XML_ELEMENT_NODE) {
  928. continue;
  929. }
  930. if ($child->nodeName != 'test' && $child->nodeName != 'custom-test') {
  931. continue;
  932. }
  933. $tests[] = $child;
  934. $test_score = $child->getAttribute('score');
  935. if ($test_score !== '' && is_numeric($test_score)) {
  936. $sum += $test_score + 0;
  937. $has_test_score = true;
  938. }
  939. }
  940. echo '<div class="uoj-test-group uoj-test-group-subtask">';
  941. echo '<div class="uoj-test-subtask-head">Subtask #', htmlspecialchars($num);
  942. if ($this->styler->show_score) {
  943. if ($score !== '') {
  944. echo ' · ', htmlspecialchars($score);
  945. }
  946. if ($has_test_score) {
  947. echo ' / ', htmlspecialchars(rtrim(rtrim(sprintf('%.2f', $sum), '0'), '.'));
  948. }
  949. }
  950. echo '</div>';
  951. echo '<div class="uoj-test-rects">';
  952. foreach ($tests as $test) {
  953. $this->renderRect($test);
  954. }
  955. echo '</div>';
  956. echo '</div>';
  957. }
  958. private function renderGrid() {
  959. echo '<div class="uoj-details-wrap" id="', $this->prefix, '_wrap">';
  960. // Only once per page: a two-phase submission (pretest + final test) runs
  961. // this printer twice, and showing the same tip above both grids was
  962. // confusing. static so it spans every instance in this request.
  963. static $tip_shown = false;
  964. if (!$tip_shown && $this->styler->show_small_tip) {
  965. $tip_shown = true;
  966. echo '<div class="text-right text-muted uoj-details-tip">', '小提示:点击方块,在弹出的窗口中查看该测试点的详细信息', '</div>';
  967. } elseif (!$tip_shown && !empty($this->styler->ioi_contest_is_running)) {
  968. $tip_shown = true;
  969. echo '<div class="text-right text-muted uoj-details-tip">', 'IOI 赛制比赛中不支持显示详细信息', '</div>';
  970. }
  971. echo '<div class="uoj-test-grid">';
  972. foreach ($this->blocks as $block) {
  973. if ($block['type'] == 'error') {
  974. echo '<div class="uoj-details-error">';
  975. $this->_print($block['node']);
  976. echo '</div>';
  977. } elseif ($block['type'] == 'subtask') {
  978. $this->renderSubtask($block['node']);
  979. } else {
  980. echo '<div class="uoj-test-group">';
  981. foreach ($block['nodes'] as $test) {
  982. $this->renderRect($test);
  983. }
  984. echo '</div>';
  985. }
  986. }
  987. echo '</div>';
  988. if (count($this->bodies)) {
  989. echo '<div class="uoj-modal" id="', $this->prefix, '_modal" hidden>';
  990. echo '<div class="uoj-modal-backdrop" data-uoj-modal-close></div>';
  991. echo '<div class="uoj-modal-window" role="dialog" aria-modal="true" aria-label="', '测试点详细信息', '">';
  992. echo '<button type="button" class="uoj-modal-close" data-uoj-modal-close aria-label="', '关闭', '">×</button>';
  993. echo '<div class="uoj-modal-body" id="', $this->prefix, '_modal_body">';
  994. foreach ($this->bodies as $body_id => $test) {
  995. echo '<div class="uoj-test-body" id="', $body_id, '" hidden>';
  996. $this->_print_c($test);
  997. echo '</div>';
  998. }
  999. echo '</div>';
  1000. echo '</div>';
  1001. echo '</div>';
  1002. }
  1003. echo '</div>';
  1004. }
  1005. private function renderScript() {
  1006. if (!count($this->bodies)) {
  1007. return;
  1008. }
  1009. echoUOJModalHelper();
  1010. $wrap = $this->prefix . '_wrap';
  1011. $modal = $this->prefix . '_modal';
  1012. echo '<script type="text/javascript">';
  1013. echo '(function(){';
  1014. echo 'var wrap=document.getElementById(', json_encode($wrap), ');';
  1015. echo 'var modal=document.getElementById(', json_encode($modal), ');';
  1016. echo 'if(!wrap||!modal||!window.uojModalInit){return;}';
  1017. echo 'var api=window.uojModalInit(modal);';
  1018. echo 'var body=api.body;';
  1019. echo 'var rects=wrap.querySelectorAll(".uoj-test-rect[data-target]");';
  1020. echo 'function show(rect){';
  1021. echo 'var target=document.getElementById(rect.getAttribute("data-target"));';
  1022. echo 'if(!target){return;}';
  1023. echo 'var bodies=body.querySelectorAll(".uoj-test-body");';
  1024. echo 'for(var i=0;i<bodies.length;i++){bodies[i].hidden=true;}';
  1025. echo 'target.hidden=false;';
  1026. echo 'for(var i=0;i<rects.length;i++){';
  1027. echo 'if(rects[i]===rect){rects[i].classList.add("is-selected");}else{rects[i].classList.remove("is-selected");}';
  1028. echo '}';
  1029. echo '}';
  1030. echo 'for(var i=0;i<rects.length;i++){(function(rect){';
  1031. echo 'rect.addEventListener("click",function(){show(rect);api.open();});';
  1032. echo 'rect.addEventListener("keydown",function(e){if(e.key==="Enter"||e.key===" "){e.preventDefault();show(rect);api.open();}});';
  1033. echo '})(rects[i]);}';
  1034. echo 'var auto=wrap.querySelector(".uoj-test-rect[data-auto=\"1\"]");';
  1035. echo 'if(auto){show(auto);api.open();}';
  1036. echo '})();';
  1037. echo '</script>';
  1038. }
  1039. public function __construct($details, $styler, $name) {
  1040. $this->name = $name;
  1041. $this->styler = $styler;
  1042. $this->details = $details;
  1043. $this->dom = new DOMDocument();
  1044. if (!$this->dom->loadXML($this->details)) {
  1045. throw new Exception("XML syntax error");
  1046. }
  1047. $this->details = '';
  1048. }
  1049. public function printHTML() {
  1050. $this->subtask_num = null;
  1051. $this->prefix = $this->name . '_' . (++self::$instance_seq);
  1052. $root = $this->dom->documentElement;
  1053. if ($root->nodeName == 'error') {
  1054. $this->_print($root);
  1055. return;
  1056. }
  1057. $this->collectRoot($root);
  1058. $this->flushPendingTests();
  1059. $this->renderGrid();
  1060. $this->renderScript();
  1061. }
  1062. }
  1063. function echoJudgementDetails($raw_details, $styler, $name) {
  1064. try {
  1065. $printer = new JudgementDetailsPrinter($raw_details, $styler, $name);
  1066. $printer->printHTML();
  1067. } catch (Exception $e) {
  1068. echo 'Failed to show details';
  1069. }
  1070. }
  1071. class SubmissionDetailsStyler {
  1072. public $show_score = true;
  1073. public $show_small_tip = true;
  1074. public $collapse_in = false;
  1075. public $fade_all_details = false;
  1076. public function shouldFadeDetails($info) {
  1077. return $this->fade_all_details || $info == 'Extra Test Passed';
  1078. }
  1079. }
  1080. class CustomTestSubmissionDetailsStyler {
  1081. public $show_score = true;
  1082. public $show_small_tip = false;
  1083. public $collapse_in = true;
  1084. public $fade_all_details = false;
  1085. public $ioi_contest_is_running = false;
  1086. public function shouldFadeDetails($info) {
  1087. return $this->fade_all_details;
  1088. }
  1089. }
  1090. class HackDetailsStyler {
  1091. public $show_score = false;
  1092. public $show_small_tip = false;
  1093. public $collapse_in = true;
  1094. public $fade_all_details = false;
  1095. public function shouldFadeDetails($info) {
  1096. return $this->fade_all_details;
  1097. }
  1098. }
  1099. function echoSubmissionDetails($submission_details, $name) {
  1100. echoJudgementDetails($submission_details, new SubmissionDetailsStyler(), $name);
  1101. }
  1102. function echoCustomTestSubmissionDetails($submission_details, $name) {
  1103. echoJudgementDetails($submission_details, new CustomTestSubmissionDetailsStyler(), $name);
  1104. }
  1105. function echoHackDetails($hack_details, $name) {
  1106. echoJudgementDetails($hack_details, new HackDetailsStyler(), $name);
  1107. }
  1108. function echoHack($hack, $config, $user) {
  1109. $problem = queryProblemBrief($hack['problem_id']);
  1110. echo '<tr>';
  1111. if (!isset($config['id_hidden'])) {
  1112. echo '<td><a href="/hack/', $hack['id'], '">#', $hack['id'], '</a></td>';
  1113. }
  1114. if (!isset($config['submission_hidden'])) {
  1115. echo '<td><a href="/submission/', $hack['submission_id'], '">#', $hack['submission_id'], '</a></td>';
  1116. }
  1117. if (!isset($config['problem_hidden'])) {
  1118. if ($hack['contest_id']) {
  1119. echo '<td>', getContestProblemLink($problem, $hack['contest_id'], '!id_and_title'), '</td>';
  1120. } else {
  1121. echo '<td>', getProblemLink($problem, '!id_and_title'), '</td>';
  1122. }
  1123. }
  1124. if (!isset($config['hacker_hidden'])) {
  1125. echo '<td>', getUserLink($hack['hacker']), '</td>';
  1126. }
  1127. if (!isset($config['owner_hidden'])) {
  1128. echo '<td>', getUserLink($hack['owner']), '</td>';
  1129. }
  1130. if (!isset($config['result_hidden'])) {
  1131. if ($hack['judge_time'] == null) {
  1132. echo '<td><a href="/hack/', $hack['id'], '">Waiting</a></td>';
  1133. } elseif ($hack['success'] == null) {
  1134. echo '<td><a href="/hack/', $hack['id'], '">Judging</a></td>';
  1135. } elseif ($hack['success']) {
  1136. echo '<td><a href="/hack/', $hack['id'], '" class="uoj-status" data-success="1"><strong>Success!</strong></a></td>';
  1137. } else {
  1138. echo '<td><a href="/hack/', $hack['id'], '" class="uoj-status" data-success="0"><strong>Failed.</strong></a></td>';
  1139. }
  1140. } else {
  1141. echo '<td>Hidden</td>';
  1142. }
  1143. if (!isset($config['submit_time_hidden'])) {
  1144. echo '<td>', $hack['submit_time'], '</td>';
  1145. }
  1146. if (!isset($config['judge_time_hidden'])) {
  1147. echo '<td>', $hack['judge_time'], '</td>';
  1148. }
  1149. echo '</tr>';
  1150. }
  1151. function echoHackListOnlyOne($hack, $config, $user) {
  1152. echo '<div class="table-responsive shadow my-4">';
  1153. echo '<table class="table table-bordered table-text-center">';
  1154. echo '<thead>';
  1155. echo '<tr>';
  1156. if (!isset($config['id_hidden'])) {
  1157. echo '<th>ID</th>';
  1158. }
  1159. if (!isset($config['submission_id_hidden'])) {
  1160. echo '<th>'.UOJLocale::get('problems::submission id').'</th>';
  1161. }
  1162. if (!isset($config['problem_hidden'])) {
  1163. echo '<th>'.UOJLocale::get('problems::problem').'</th>';
  1164. }
  1165. if (!isset($config['hacker_hidden'])) {
  1166. echo '<th>'.UOJLocale::get('problems::hacker').'</th>';
  1167. }
  1168. if (!isset($config['owner_hidden'])) {
  1169. echo '<th>'.UOJLocale::get('problems::owner').'</th>';
  1170. }
  1171. if (!isset($config['result_hidden'])) {
  1172. echo '<th>'.UOJLocale::get('problems::result').'</th>';
  1173. }
  1174. if (!isset($config['submit_time_hidden'])) {
  1175. echo '<th>'.UOJLocale::get('problems::submit time').'</th>';
  1176. }
  1177. if (!isset($config['judge_time_hidden'])) {
  1178. echo '<th>'.UOJLocale::get('problems::judge time').'</th>';
  1179. }
  1180. echo '</tr>';
  1181. echo '</thead>';
  1182. echo '<tbody>';
  1183. echoHack($hack, $config, $user);
  1184. echo '</tbody>';
  1185. echo '</table>';
  1186. echo '</div>';
  1187. }
  1188. function echoHacksList($cond, $tail, $config, $user) {
  1189. $header_row = '<tr>';
  1190. $col_names = array();
  1191. $col_names[] = 'id';
  1192. $col_names[] = 'success';
  1193. $col_names[] = 'judge_time';
  1194. if (!isset($config['id_hidden'])) {
  1195. $header_row .= '<th>ID</th>';
  1196. }
  1197. if (!isset($config['submission_id_hidden'])) {
  1198. $header_row .= '<th>'.UOJLocale::get('problems::submission id').'</th>';
  1199. $col_names[] = 'submission_id';
  1200. }
  1201. if (!isset($config['problem_hidden'])) {
  1202. $header_row .= '<th>'.UOJLocale::get('problems::problem').'</th>';
  1203. $col_names[] = 'problem_id';
  1204. }
  1205. if (!isset($config['hacker_hidden'])) {
  1206. $header_row .= '<th>'.UOJLocale::get('problems::hacker').'</th>';
  1207. $col_names[] = 'hacker';
  1208. }
  1209. if (!isset($config['owner_hidden'])) {
  1210. $header_row .= '<th>'.UOJLocale::get('problems::owner').'</th>';
  1211. $col_names[] = 'owner';
  1212. }
  1213. if (!isset($config['result_hidden'])) {
  1214. $header_row .= '<th>'.UOJLocale::get('problems::result').'</th>';
  1215. }
  1216. if (!isset($config['submit_time_hidden'])) {
  1217. $header_row .= '<th>'.UOJLocale::get('problems::submit time').'</th>';
  1218. $col_names[] = 'submit_time';
  1219. }
  1220. if (!isset($config['judge_time_hidden'])) {
  1221. $header_row .= '<th>'.UOJLocale::get('problems::judge time').'</th>';
  1222. }
  1223. $header_row .= '</tr>';
  1224. if (!isSuperUser($user)) {
  1225. if ($user != null) {
  1226. $permission_cond = "is_hidden = false or (is_hidden = true and problem_id in (select problem_id from problems_permissions where username = '{$user['username']}'))";
  1227. } else {
  1228. $permission_cond = "is_hidden = false";
  1229. }
  1230. if ($cond !== '1') {
  1231. $cond = "($cond) and ($permission_cond)";
  1232. } else {
  1233. $cond = $permission_cond;
  1234. }
  1235. }
  1236. echoLongTable($col_names, 'hacks', $cond, $tail, $header_row,
  1237. function($hacks) use ($config, $user) {
  1238. echoHack($hacks, $config, $user);
  1239. }, null);
  1240. }
  1241. function echoDatum($datum) {
  1242. $pid = $datum['pid'];
  1243. $title = queryProblemBrief($pid)['title'];
  1244. echo '<tr>';
  1245. echo '<td>' . $datum['id'] . '</td>';
  1246. echo '<td>', getProblemLink(queryProblemBrief($pid), '!id_and_title'), '</td>';
  1247. if ($datum['status'] == 'Done')
  1248. echo '<td><a href="/datum/' . $datum['id'] . '" class="uoj-status" data-success="1"><strong>Done</strong></a></td>';
  1249. else if ($datum['status'] == 'Failed')
  1250. echo '<td><a href="/datum/' . $datum['id'] . '" class="uoj-status" data-success="0"><strong>Failed</strong></a></td>';
  1251. else echo '<td><a href="/datum/' . $datum['id'] . '">' . $datum['status'] . '</a></td>';
  1252. echo '<td>' . $datum['request_time'] . '</td>';
  1253. echo '<td>' . $datum['finish_time'] . '</td>';
  1254. echo '<td>' . $datum['timelimit'] . ' s</td>';
  1255. echo '<td>' . $datum['memlimit'] . ' MB</td>';
  1256. echo '</tr>';
  1257. }
  1258. function echoDatumlistOnlyOne($datum) {
  1259. $header_row = '<tr><th>ID</th><th>题目</th><th>状态</th><th>请求时间</th><th>完成时间</th><th>时间限制</th><th>空间限制</th></tr>';
  1260. echo '<div class="table-responsive shadow my-4">';
  1261. echo '<table class="table table-bordered table-text-center">';
  1262. echo '<thead>';
  1263. echo $header_row;
  1264. echo '</thead>';
  1265. echo '<tbody>';
  1266. echoDatum($datum);
  1267. echo '</tbody>';
  1268. echo '</table></div>';
  1269. }
  1270. function echoDatumList() {
  1271. $col_names = array('id', 'pid', 'request_time', 'status', 'finish_time', 'timelimit', 'memlimit');
  1272. $header_row = '<tr><th>ID</th><th>题目</th><th>状态</th><th>请求时间</th><th>完成时间</th><th>时间限制</th><th>空间限制</th></tr>';
  1273. echoLongTable($col_names, 'datum_requests', '1', '', $header_row,
  1274. function($datum) { echoDatum($datum); }, null);
  1275. }
  1276. function getBlogPreview($str) {
  1277. return '';
  1278. }
  1279. function echoBlogCard($blog) {
  1280. if ($blog == null)
  1281. return;
  1282. $bloghref = "/blogs/" . $blog['id'];
  1283. $blogtitle = $blog['title'];
  1284. $poster = queryUser($blog['poster']);
  1285. $userlink = getUserLink($blog['poster']);
  1286. $posttime = $blog['post_time'];
  1287. $usermotto = HTML::escape($poster['motto']);
  1288. $zanblock = getClickZanBlock('B', $blog['id'], $blog['zan']);
  1289. $avatar = "https://q.qlogo.cn/g?b=qq&nk=" . $poster['qq'] . "&s=100";
  1290. return <<<EOF
  1291. <div class="col-12 mb-3">
  1292. <div class="card wyoj-blog-card border-0 shadow-sm">
  1293. <div class="card-body p-3">
  1294. <a href="$bloghref" class="text-decoration-none text-dark">
  1295. <h5 class="mb-3 wyoj-blog-title">$blogtitle</h5>
  1296. </a>
  1297. <div class="d-flex align-items-center justify-content-between">
  1298. <div class="d-flex align-items-center" style="min-width:0;">
  1299. <img class="uoj-avatar rounded-circle flex-shrink-0 mr-2"
  1300. style="width:28px;height:28px;object-fit:cover;"
  1301. src="$avatar" alt="...">
  1302. <span class="text-truncate">$userlink</span>
  1303. <span class="text-muted ml-2 text-nowrap" style="font-size:0.85rem;">$posttime</span>
  1304. <span class="ml-3 text-truncate" style="font-size:1rem;font-family:'SimSun','宋体','Songti SC','STSong',serif;">$usermotto</span>
  1305. </div>
  1306. <div class="flex-shrink-0 ml-2">$zanblock</div>
  1307. </div>
  1308. </div>
  1309. </div>
  1310. </div>
  1311. EOF;
  1312. }
  1313. function echoRecommendProblem($pid, $link, $name, $descr) {
  1314. return <<<EOF
  1315. <div class="col-md-3 mb-4">
  1316. <div class="card h-100 p-3 text-center">
  1317. <a class="mb-2" href="$link"><h4>$pid</h4></a>
  1318. <h4 class="mb-2">$name</h4>
  1319. <span class="text-bottom">$descr</span>
  1320. </div>
  1321. </div>
  1322. EOF;
  1323. }
  1324. function echoBlog($blog, $config = array()) {
  1325. $default_config = array(
  1326. 'blog' => $blog,
  1327. 'show_title_only' => false,
  1328. 'show_content_only' => false,
  1329. 'is_preview' => false,
  1330. 'outside_blog_domain' => false,
  1331. );
  1332. foreach ($default_config as $key => $val) {
  1333. if (!isset($config[$key])) {
  1334. $config[$key] = $val;
  1335. }
  1336. }
  1337. uojIncludeView('blog-preview', $config);
  1338. }
  1339. function echoBlogTag($tag) {
  1340. echo '<a class="uoj-blog-tag"><span class="badge badge-pill badge-secondary">', HTML::escape($tag), '</span></a>';
  1341. }
  1342. function echoUOJPageHeader($page_title, $extra_config = array()) {
  1343. global $REQUIRE_LIB;
  1344. $config = UOJContext::pageConfig();
  1345. $config['REQUIRE_LIB'] = $REQUIRE_LIB;
  1346. $config['PageTitle'] = $page_title;
  1347. $config = array_merge($config, $extra_config);
  1348. uojIncludeView('page-header', $config);
  1349. }
  1350. function echoUOJPageFooter($config = array()) {
  1351. uojIncludeView('page-footer', $config);
  1352. }
  1353. function getAvatar($user) {
  1354. return '<img class="media-object img-rounded" height="50" width="50" src="' . HTML::avatar_addr($user, 50) . '">';
  1355. }
  1356. function echoRanklist($config = array()) {
  1357. $header_row = '';
  1358. $header_row .= '<tr>';
  1359. $header_row .= '<th style="width: 5em;">#</th>';
  1360. $header_row .= '<th style="width: 14em";>'.'头像'.'</th>';
  1361. $header_row .= '<th style="width: 14em;">'.UOJLocale::get('username').'</th>';
  1362. $header_row .= '<th style="width: 50em;">'.UOJLocale::get('motto').'</th>';
  1363. $header_row .= '<th style="width: 5em;">'.UOJLocale::get('rating').'</th>';
  1364. $header_row .= '</tr>';
  1365. $users = array();
  1366. $relcnt = 0;
  1367. $print_row = function($user, $now_cnt) use (&$users, &$relcnt) {
  1368. #if (!$users) {
  1369. $rank = DB::selectCount("select count(*) from user_info where rating > {$user['rating']}") + 1;
  1370. # } elseif ($user['rating'] == $users[count($users) - 1]['rating']) {
  1371. # $rank = $users[count($users) - 1]['rank'];
  1372. # } else {
  1373. # $rank = $now_cnt;
  1374. # }
  1375. $user['rank'] = $rank;
  1376. $relcnt++;
  1377. echo '<tr>';
  1378. echo '<td>' . $user['rank'] . ' (' . $relcnt . ')</td>';
  1379. echo '<td>' . getAvatar($user) . '</td>';
  1380. echo '<td>' . getUserLink($user['username']) . '</td>';
  1381. echo '<td>' . HTML::escape($user['motto']) . '</td>';
  1382. echo '<td>' . $user['rating'] . '</td>';
  1383. echo '</tr>';
  1384. $users[] = $user;
  1385. };
  1386. $col_names = array('username', 'email', 'qq', 'rating', 'motto');
  1387. $tail = 'inner join contests_registrants c on user_info.username = c.username inner join contests s on s.id = c.contest_id where datediff(now(), s.start_time) <= 180 group by c.username having cnt >= 3 order by rating desc, username asc';
  1388. # $tail = 'order by rating desc, username asc';
  1389. #
  1390. $config['raw_sql'] = 'select u.username, u.email, u.rating, u.rating_rd, u.rating_vol, u.motto, u.qq, count(*) cnt from user_info u inner join contests_registrants c on u.username = c.username inner join contests s on s.id = c.contest_id where datediff(now(), s.start_time) <= 180 group by c.username having cnt >= 3 order by rating desc, username asc';
  1391. if (isset($config['top10'])) {
  1392. $tail .= ' limit 10';
  1393. }
  1394. $config['get_row_index'] = '';
  1395. echoLongTable($col_names, 'user_info', '1', $tail, $header_row, $print_row, $config);
  1396. }