diff --git a/web/app/controllers/new_remote_problem.php b/web/app/controllers/new_remote_problem.php index 98f049b..6926ff0 100644 --- a/web/app/controllers/new_remote_problem.php +++ b/web/app/controllers/new_remote_problem.php @@ -35,6 +35,14 @@ $new_remote_problem_form->addInput('remote_problem_id', [ $vdata['remote_problem_id'] = $id; + return ''; + } elseif ($remote_oj === 'uoj') { + if (!validateUInt($id)) { + return '不合法的题目 ID'; + } + + $vdata['remote_problem_id'] = $id; + return ''; } diff --git a/web/app/libs/uoj-utility-lib.php b/web/app/libs/uoj-utility-lib.php index ad016ff..5a9f027 100644 --- a/web/app/libs/uoj-utility-lib.php +++ b/web/app/libs/uoj-utility-lib.php @@ -202,10 +202,10 @@ function getProblemCustomTestRequirement($problem) { function sendSystemMsg($username, $title, $content) { DB::insert([ - "insert into user_system_msg", - "(receiver, title, content, send_time)", - "values", DB::tuple([$username, $title, $content, DB::now()]) - ]); + "insert into user_system_msg", + "(receiver, title, content, send_time)", + "values", DB::tuple([$username, $title, $content, DB::now()]) + ]); } function retry_loop(callable $f, $retry = 5, $ms = 10) { @@ -218,3 +218,66 @@ function retry_loop(callable $f, $retry = 5, $ms = 10) { } return $ret; } + +function getAbsoluteUrl($relativeUrl, $baseUrl) { + // if already absolute URL + if (parse_url($relativeUrl, PHP_URL_SCHEME) !== null) { + return $relativeUrl; + } + + // queries and anchors + if ($relativeUrl[0] === '#' || $relativeUrl[0] === '?') { + return $baseUrl . $relativeUrl; + } + + // parse base URL and convert to: $scheme, $host, $path, $query, $port, $user, $pass + extract(parse_url($baseUrl)); + + // if base URL contains a path remove non-directory elements from $path + if (isset($path) === true) { + $path = preg_replace('#/[^/]*$#', '', $path); + } else { + $path = ''; + } + + // if realtive URL starts with // + if (substr($relativeUrl, 0, 2) === '//') { + return $scheme . ':' . $relativeUrl; + } + + // if realtive URL starts with / + if ($relativeUrl[0] === '/') { + $path = null; + } + + $abs = null; + + // if realtive URL contains a user + if (isset($user) === true) { + $abs .= $user; + + // if realtive URL contains a password + if (isset($pass) === true) { + $abs .= ':' . $pass; + } + + $abs .= '@'; + } + + $abs .= $host; + + // if realtive URL contains a port + if (isset($port) === true) { + $abs .= ':' . $port; + } + + $abs .= $path . '/' . $relativeUrl . (isset($query) === true ? '?' . $query : null); + + // replace // or /./ or /foo/../ with / + $re = ['#(/\.?/)#', '#/(?!\.\.)[^/]+/\.\./#']; + for ($n = 1; $n > 0; $abs = preg_replace($re, '/', $abs, -1, $n)) { + } + + // return absolute URL + return $scheme . '://' . $abs; +} diff --git a/web/app/models/UOJRemoteProblem.php b/web/app/models/UOJRemoteProblem.php index ac72cd3..872368c 100644 --- a/web/app/models/UOJRemoteProblem.php +++ b/web/app/models/UOJRemoteProblem.php @@ -23,6 +23,15 @@ class UOJRemoteProblem { ], 'languages' => ['C', 'C++', 'Java11', 'Python3', 'Pascal'], ], + 'uoj' => [ + 'name' => 'UniversalOJ', + 'short_name' => 'UOJ', + 'url' => 'https://uoj.ac', + 'not_exist_texts' => [ + '未找到该页面', + ], + 'languages' => ['C', 'C++03', 'C++11', 'C++14', 'C++17', 'C++20', 'Python3', 'Python2.7', 'Java8', 'Java11', 'Java17', 'Pascal'], + ], ]; static function getCodeforcesProblemUrl($id) { @@ -45,6 +54,10 @@ class UOJRemoteProblem { }, $id); } + static function getUojProblemUrl($id) { + return static::$providers['uoj']['url'] . '/problem/' . $id; + } + static function getCodeforcesProblemBasicInfoFromHtml($id, $html) { $remote_provider = static::$providers['codeforces']; @@ -273,11 +286,59 @@ class UOJRemoteProblem { ]; } + static function getUojProblemBasicInfo($id) { + $remote_provider = static::$providers['uoj']; + $curl = new Curl(); + $curl->setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36 S2OJ/3.1.0'); + + $res = retry_loop(function () use (&$curl, $id) { + $curl->get(static::getUojProblemUrl($id)); + + if ($curl->error) { + return false; + } + + return $curl->response; + }); + + if (!$res) return null; + + $dom = new \IvoPetkov\HTML5DOMDocument(); + $dom->loadHTML($res); + + $title_dom = $dom->querySelector('.page-header'); + $title_matches = []; + preg_match('/^#[1-9][0-9]*\. (.*)$/', trim($title_dom->textContent), $title_matches); + $title = "【{$remote_provider['short_name']}{$id}】{$title_matches[1]}"; + + $statement_dom = $dom->querySelector('.uoj-article'); + $statement = HTML::tag('h3', [], '题目描述'); + + foreach ($statement_dom->querySelectorAll('a') as &$elem) { + $href = $elem->getAttribute('href'); + $href = getAbsoluteUrl($href, $remote_provider['url']); + $elem->setAttribute('href', $href); + } + + $statement .= $statement_dom->innerHTML; + + return [ + 'type' => 'html', + 'title' => $title, + 'time_limit' => null, + 'memory_limit' => null, + 'difficulty' => -1, + 'statement' => $statement, + ]; + } + public static function getProblemRemoteUrl($oj, $id) { if ($oj === 'codeforces') { return static::getCodeforcesProblemUrl($id); } else if ($oj === 'atcoder') { return static::getAtcoderProblemUrl($id); + } else if ($oj === 'uoj') { + return static::getUojProblemUrl($id); } return null; @@ -289,6 +350,8 @@ class UOJRemoteProblem { return static::getCodeforcesProblemBasicInfo($id); } else if ($oj === 'atcoder') { return static::getAtcoderProblemBasicInfo($id); + } else if ($oj === 'uoj') { + return static::getUojProblemBasicInfo($id); } return null;