diff --git a/db/app_uoj233.sql b/db/app_uoj233.sql index 12b362a..48892b5 100644 --- a/db/app_uoj233.sql +++ b/db/app_uoj233.sql @@ -622,8 +622,10 @@ CREATE TABLE `problems` ( `ac_num` int NOT NULL DEFAULT '0', `submit_num` int NOT NULL DEFAULT '0', `difficulty` int NOT NULL DEFAULT '-1', + `type` varchar(20) NOT NULL DEFAULT 'local', `assigned_to_judger` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'any', PRIMARY KEY (`id`), + KEY `type` (`type`), KEY `assigned_to_judger` (`assigned_to_judger`), KEY `uploader` (`uploader`), KEY `difficulty` (`difficulty`), @@ -648,6 +650,7 @@ UNLOCK TABLES; /*!40101 SET character_set_client = utf8mb4 */; CREATE TABLE `problems_contents` ( `id` int NOT NULL, + `remote_content` longtext COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', `statement` longtext COLLATE utf8mb4_unicode_ci NOT NULL, `statement_md` longtext COLLATE utf8mb4_unicode_ci NOT NULL, PRIMARY KEY (`id`) diff --git a/web/Dockerfile b/web/Dockerfile index 94d5602..e9f118a 100644 --- a/web/Dockerfile +++ b/web/Dockerfile @@ -6,7 +6,7 @@ ENV USE_MIRROR $USE_MIRROR SHELL ["/bin/bash", "-c"] ENV DEBIAN_FRONTEND=noninteractive -ENV PKGS="php7.4 php7.4-yaml php7.4-xml php7.4-dev php7.4-zip php7.4-mysql php7.4-mbstring php7.4-gd php7.4-imagick libseccomp-dev git vim ntp zip unzip curl wget apache2 libapache2-mod-xsendfile php-pear mysql-client build-essential fp-compiler re2c libseccomp-dev libyaml-dev python2.7 python3.10 python3-requests openjdk-8-jdk openjdk-11-jdk openjdk-17-jdk" +ENV PKGS="php7.4 php7.4-yaml php7.4-xml php7.4-dev php7.4-zip php7.4-mysql php7.4-mbstring php7.4-gd php7.4-curl php7.4-imagick libseccomp-dev git vim ntp zip unzip curl wget apache2 libapache2-mod-xsendfile php-pear mysql-client build-essential fp-compiler re2c libseccomp-dev libyaml-dev python2.7 python3.10 python3-requests openjdk-8-jdk openjdk-11-jdk openjdk-17-jdk" RUN if [[ "$USE_MIRROR" == "1" ]]; then\ sed -i "s@http://.*archive.ubuntu.com@https://mirrors.aliyun.com@g" /etc/apt/sources.list &&\ sed -i "s@http://.*security.ubuntu.com@https://mirrors.aliyun.com@g" /etc/apt/sources.list ;\ diff --git a/web/app/composer.json b/web/app/composer.json index 8ef8663..7ebf2b0 100644 --- a/web/app/composer.json +++ b/web/app/composer.json @@ -3,7 +3,10 @@ "gregwar/captcha": "^1.1", "phpmailer/phpmailer": "^6.6", "ezyang/htmlpurifier": "^4.16", - "erusev/parsedown": "^1.7" + "erusev/parsedown": "^1.7", + "php-curl-class/php-curl-class": "^2.0", + "ext-dom": "20031129", + "ivopetkov/html5-dom-document-php": "2.*" }, "autoload": { "classmap": [ diff --git a/web/app/controllers/contest_inside.php b/web/app/controllers/contest_inside.php index 3b47fe4..210bd23 100644 --- a/web/app/controllers/contest_inside.php +++ b/web/app/controllers/contest_inside.php @@ -54,10 +54,10 @@ if ($is_manager) { isset($tabs_info[$cur_tab]) || UOJResponse::page404(); if (UOJContest::cur()->userCanStartFinalTest(Auth::user())) { - if (CONTEST_PENDING_FINAL_TEST <= $contest['cur_progress']) { + if (CONTEST_PENDING_FINAL_TEST == $contest['cur_progress']) { $start_test_form = new UOJBs4Form('start_test'); $start_test_form->handle = function () { - UOJContest::finalTest(); + UOJContest::cur()->finalTest(); }; $start_test_form->submit_button_config['class_str'] = 'btn btn-danger d-block w-100'; $start_test_form->submit_button_config['smart_confirm'] = ''; diff --git a/web/app/controllers/contest_manage.php b/web/app/controllers/contest_manage.php index 5ef3f81..0ab7f2a 100644 --- a/web/app/controllers/contest_manage.php +++ b/web/app/controllers/contest_manage.php @@ -596,11 +596,11 @@ EOD); EOD, function ($row) { - $problem = UOJProblem::query($row['problem_id']); + $problem = UOJContestProblem::query($row['problem_id'], UOJContest::cur()); echo ''; echo '', $row['problem_id'], ''; echo '', $problem->getLink(['with' => 'none']), ''; - echo '', isset($contest['extra_config']["problem_{$problem->info['id']}"]) ? $contest['extra_config']["problem_{$problem->info['id']}"] : 'default', ''; + echo '', $problem->getJudgeTypeInContest(), ''; echo ''; echo '
info['id'], ' 从比赛中移除吗?")\'>'; echo ''; diff --git a/web/app/controllers/judge/submit.php b/web/app/controllers/judge/submit.php index d6c7913..59e25c0 100644 --- a/web/app/controllers/judge/submit.php +++ b/web/app/controllers/judge/submit.php @@ -130,17 +130,39 @@ if (isset($_POST['update-status'])) { die(); } -$problem_ban_list = DB::selectAll([ +$assignCond = []; + +$problem_ban_list = array_map(fn ($x) => $x['id'], DB::selectAll([ "select id from problems", "where", [ ["assigned_to_judger", "!=", "any"], ["assigned_to_judger", "!=", $_POST['judger_name']] ] -]); -foreach ($problem_ban_list as &$val) { - $val = $val['id']; +])); + +if ($problem_ban_list) { + $assignCond[] = ["problem_id", "not in", DB::rawtuple($problem_ban_list)]; +} + +if ($_POST['judger_name'] == "remote_judger") { + $problem_ban_list = array_map(fn ($x) => $x['id'], DB::selectAll([ + "select id from problems", + "where", [ + ["type", "!=", "remote"], + ], + ])); +} else { + $problem_ban_list = array_map(fn ($x) => $x['id'], DB::selectAll([ + "select id from problems", + "where", [ + ["type", "!=", "local"], + ], + ])); +} + +if ($problem_ban_list) { + $assignCond[] = ["problem_id", "not in", DB::rawtuple($problem_ban_list)]; } -$assignCond = $problem_ban_list ? [["problem_id", "not in", DB::rawtuple($problem_ban_list)]] : []; $submission = null; $hack = null; diff --git a/web/app/controllers/list.php b/web/app/controllers/list.php index ad1fcf7..5a24d80 100644 --- a/web/app/controllers/list.php +++ b/web/app/controllers/list.php @@ -20,6 +20,9 @@ function getProblemTR($info) { if ($problem->isUserOwnProblem(Auth::user())) { $html .= ' ' . UOJLocale::get('problems::my problem') . ' '; } + if ($info['type'] == 'remote') { + $html .= ' ' . HTML::tag('span', ['class' => 'badge text-bg-success'], '远端评测题'); + } if ($info['is_hidden']) { $html .= ' ' . UOJLocale::get('hidden') . ' '; } diff --git a/web/app/controllers/new_remote_problem.php b/web/app/controllers/new_remote_problem.php new file mode 100644 index 0000000..d76aecd --- /dev/null +++ b/web/app/controllers/new_remote_problem.php @@ -0,0 +1,124 @@ +addSelect('remote_online_judge', [ + 'label' => '远程 OJ', + 'options' => [ + 'codeforces' => 'Codeforces', + ], +]); +$new_remote_problem_form->addInput('remote_problem_id', [ + 'div_class' => 'mt-3', + 'label' => '远程 OJ 上的题目 ID', + 'validator_php' => function ($id, &$vdata) { + if ($_POST['remote_online_judge'] === 'codeforces') { + $id = trim(strtoupper($id)); + + if (!validateCodeforcesProblemId($id)) { + return '不合法的题目 ID'; + } + + $vdata['remote_problem_id'] = $id; + + return ''; + } + + return '不合法的远程 OJ 类型'; + }, +]); +$new_remote_problem_form->handle = function (&$vdata) { + $remote_online_judge = $_POST['remote_online_judge']; + $remote_problem_id = $vdata['remote_problem_id']; + $remote_provider = UOJRemoteProblem::$providers[$remote_online_judge]; + + try { + $data = UOJRemoteProblem::getProblemBasicInfo($remote_online_judge, $remote_problem_id); + } catch (Exception $e) { + $data = null; + UOJLog::error($e->getMessage()); + } + + if ($data === null) { + UOJResponse::page500('题目抓取失败,可能是题目不存在或者没有题面!如果题目没有问题,请稍后再试。返回'); + } + + $submission_requirement = [ + [ + "name" => "answer", + "type" => "source code", + "file_name" => "answer.code", + "languages" => $remote_provider['languages'], + ] + ]; + $enc_submission_requirement = json_encode($submission_requirement); + + $extra_config = [ + 'remote_online_judge' => $remote_online_judge, + 'remote_problem_id' => $remote_problem_id, + 'time_limit' => $data['time_limit'], + 'memory_limit' => $data['memory_limit'], + ]; + $enc_extra_config = json_encode($extra_config); + + DB::insert([ + "insert into problems", + "(title, uploader, is_hidden, submission_requirement, extra_config, difficulty, type)", + "values", DB::tuple([$data['title'], Auth::id(), 1, $enc_submission_requirement, $enc_extra_config, $data['difficulty'] ?: -1, "remote"]) + ]); + + $id = DB::insert_id(); + + DB::insert([ + "insert into problems_contents", + "(id, remote_content, statement, statement_md)", + "values", + DB::tuple([$id, HTML::purifier()->purify($data['statement']), '', '']) + ]); + dataNewProblem($id); + + redirectTo("/problem/{$id}"); + die(); +}; +$new_remote_problem_form->runAtServer(); +?> + + + +

导入远程题库

+ +
+
+
+
+
+
+ printHTML() ?> +
+
+

使用帮助

+
    +
  • +

    目前支持导入以下题库的题目作为远端评测题:

    +
      +
    • Codeforces
    • +
    +
  • +
  • 在导入题目前请先搜索题库中是否已经存在相应题目,避免重复添加。
  • +
+
+
+
+
+
+
+ +
+
+ + diff --git a/web/app/controllers/problem.php b/web/app/controllers/problem.php index 40757b3..c5762c2 100644 --- a/web/app/controllers/problem.php +++ b/web/app/controllers/problem.php @@ -223,7 +223,6 @@ if (UOJContest::cur()) {
-
@@ -233,7 +232,6 @@ if (UOJContest::cur()) {
-

getTitle(['with' => 'letter', 'simplify' => true]) ?> @@ -243,8 +241,13 @@ if (UOJContest::cur()) {

getVal('time_limit', 1) : null; - $memory_limit = $conf instanceof UOJProblemConf ? $conf->getVal('memory_limit', 256) : null; + if (UOJProblem::info('type') == 'local') { + $time_limit = $conf instanceof UOJProblemConf ? $conf->getVal('time_limit', 1) : null; + $memory_limit = $conf instanceof UOJProblemConf ? $conf->getVal('memory_limit', 256) : null; + } else if (UOJProblem::info('type') == 'remote') { + $time_limit = UOJProblem::cur()->getExtraConfig('time_limit'); + $memory_limit = UOJProblem::cur()->getExtraConfig('memory_limit'); + } ?>
时间限制: @@ -259,6 +262,12 @@ if (UOJContest::cur()) {
+ + +
+ queryContent()['remote_content'] ?> +
+
@@ -392,6 +401,14 @@ if (UOJContest::cur()) { getUploaderLink() ?> +
  • + + 题目来源 + + + getProviderLink() ?> + +
  • progress() >= CONTEST_FINISHED) : ?>
  • diff --git a/web/app/controllers/problem_data_manage.php b/web/app/controllers/problem_data_manage.php index 75bd106..91112c7 100644 --- a/web/app/controllers/problem_data_manage.php +++ b/web/app/controllers/problem_data_manage.php @@ -10,6 +10,7 @@ requirePHPLib('data'); UOJProblem::init(UOJRequest::get('id')) || UOJResponse::page404(); UOJProblem::cur()->userCanManage(Auth::user()) || UOJResponse::page403(); +UOJProblem::info('type') === 'local' || UOJResponse::page404(); $problem = UOJProblem::info(); $problem_extra_config = UOJProblem::cur()->getExtraConfig(); @@ -466,93 +467,6 @@ $rejudgege97_form->submit_button_config['class_str'] = 'btn btn-danger d-block w $rejudgege97_form->submit_button_config['text'] = '重测 >=97 的程序'; $rejudgege97_form->submit_button_config['smart_confirm'] = ''; -$view_type_form = new UOJBs4Form('view_type'); -$view_type_form->addVSelect( - 'view_content_type', - array( - 'NONE' => '禁止', - 'ALL_AFTER_AC' => 'AC后', - 'ALL' => '所有人' - ), - '查看提交文件:', - $problem_extra_config['view_content_type'] -); -$view_type_form->addVSelect( - 'view_all_details_type', - array( - 'NONE' => '禁止', - 'SELF' => '仅自己', - 'ALL_AFTER_AC' => 'AC后', - 'ALL' => '所有人' - ), - '查看全部详细信息:', - $problem_extra_config['view_all_details_type'] -); -$view_type_form->addVSelect( - 'view_details_type', - array( - 'NONE' => '禁止', - 'SELF' => '仅自己', - 'ALL_AFTER_AC' => 'AC后', - 'ALL' => '所有人' - ), - '查看测试点详细信息:', - $problem_extra_config['view_details_type'] -); -$view_type_form->handle = function () { - global $problem, $problem_extra_config; - - $config = $problem_extra_config; - $config['view_content_type'] = $_POST['view_content_type']; - $config['view_all_details_type'] = $_POST['view_all_details_type']; - $config['view_details_type'] = $_POST['view_details_type']; - $esc_config = json_encode($config); - - DB::update([ - "update problems", - "set", ["extra_config" => $esc_config], - "where", ["id" => $problem['id']] - ]); -}; -$view_type_form->submit_button_config['class_str'] = 'btn btn-warning d-block w-100 mt-2'; - -$solution_view_type_form = new UOJBs4Form('solution_view_type'); -$solution_view_type_form->addVSelect( - 'view_solution_type', - array( - 'NONE' => '禁止', - 'ALL_AFTER_AC' => 'AC后', - 'ALL' => '所有人' - ), - '查看题解:', - $problem_extra_config['view_solution_type'] -); -$solution_view_type_form->addVSelect( - 'submit_solution_type', - array( - 'NONE' => '禁止', - 'ALL_AFTER_AC' => 'AC后', - 'ALL' => '所有人' - ), - '提交题解:', - $problem_extra_config['submit_solution_type'] -); -$solution_view_type_form->handle = function () { - global $problem, $problem_extra_config; - - $config = $problem_extra_config; - $config['view_solution_type'] = $_POST['view_solution_type']; - $config['submit_solution_type'] = $_POST['submit_solution_type']; - $esc_config = json_encode($config); - - DB::update([ - "update problems", - "set", ["extra_config" => $esc_config], - "where", ["id" => $problem['id']] - ]); -}; -$solution_view_type_form->submit_button_config['class_str'] = 'btn btn-warning d-block w-100 mt-2'; - if ($problem['hackable']) { $test_std_form = new UOJBs4Form('test_std'); $test_std_form->handle = function () use ($problem, $data_disp) { @@ -618,8 +532,6 @@ if ($problem['hackable']) { } $hackable_form->runAtServer(); -$view_type_form->runAtServer(); -$solution_view_type_form->runAtServer(); $data_form->runAtServer(); $clear_data_form->runAtServer(); $rejudge_form->runAtServer(); @@ -753,18 +665,6 @@ $info_form->runAtServer(); printHTML() ?>
  • -
    - - -
    -
    - - -
    printHTML(); ?>
    diff --git a/web/app/controllers/problem_managers_manage.php b/web/app/controllers/problem_managers_manage.php index a93b4a6..9e8ca75 100644 --- a/web/app/controllers/problem_managers_manage.php +++ b/web/app/controllers/problem_managers_manage.php @@ -82,11 +82,13 @@ if (isSuperUser(Auth::user())) { 管理者 - + + +
    diff --git a/web/app/controllers/problem_set.php b/web/app/controllers/problem_set.php index 47623eb..581860d 100644 --- a/web/app/controllers/problem_set.php +++ b/web/app/controllers/problem_set.php @@ -55,7 +55,7 @@ if (UOJProblem::userCanCreateProblem(Auth::user())) { 如有,在此处填写其他于题意或数据相关的说明。 EOD; - $new_problem_form = new UOJBs4Form('new_problem'); + $new_problem_form = new UOJForm('new_problem'); $new_problem_form->handle = function () use ($default_statement) { DB::insert([ "insert into problems", @@ -77,11 +77,10 @@ EOD; redirectTo("/problem/{$id}/manage/statement"); die(); }; - $new_problem_form->submit_button_config['align'] = 'right'; - $new_problem_form->submit_button_config['class_str'] = 'btn btn-primary'; - $new_problem_form->submit_button_config['text'] = UOJLocale::get('problems::add new'); - $new_problem_form->submit_button_config['smart_confirm'] = ''; - + $new_problem_form->config['submit_container']['class'] = ''; + $new_problem_form->config['submit_button']['class'] = 'bg-transparent border-0 d-block w-100 px-3 py-2 text-start'; + $new_problem_form->config['submit_button']['text'] = ' 新建本地题目'; + $new_problem_form->config['confirm']['text'] = '添加新题'; $new_problem_form->runAtServer(); } @@ -95,6 +94,9 @@ function getProblemTR($info) { if ($problem->isUserOwnProblem(Auth::user())) { $html .= ' ' . UOJLocale::get('problems::my problem') . ' '; } + if ($info['type'] == 'remote') { + $html .= ' ' . HTML::tag('span', ['class' => 'badge text-bg-success'], '远端评测题'); + } if ($info['is_hidden']) { $html .= ' ' . UOJLocale::get('hidden') . ' '; } @@ -254,43 +256,35 @@ $pag = new Paginator([
    -
    +

    - -
    - printHTML(); ?> -
    - - +
    + +
    -
    -
    - -
    -
    -
    - /> - -
    + pagination() ?> -
    - /> - -
    +
    +
    + /> + +
    + +
    + /> +
    - pagination() ?> - + +
    +
    + 新建题目 +
    +
    +
    + printHTML() ?> +
    + + + 新建远端评测题目 + +
    +
    + + diff --git a/web/app/controllers/problem_statement_manage.php b/web/app/controllers/problem_statement_manage.php index 3aa2e63..59f4f9e 100644 --- a/web/app/controllers/problem_statement_manage.php +++ b/web/app/controllers/problem_statement_manage.php @@ -65,9 +65,12 @@ $problem_editor->runAtServer(); $difficulty_form = new UOJForm('difficulty'); $difficulty_form->addSelect('difficulty', [ + 'div_class' => 'flex-grow-1', 'options' => [-1 => '暂无评定'] + array_combine(UOJProblem::$difficulty, UOJProblem::$difficulty), 'default_value' => UOJProblem::info('difficulty'), ]); +$difficulty_form->config['form']['class'] = 'd-flex'; +$difficulty_form->config['submit_container']['class'] = 'ms-2'; $difficulty_form->handle = function () { DB::update([ "update problems", @@ -80,6 +83,170 @@ $difficulty_form->handle = function () { ]); }; $difficulty_form->runAtServer(); + +if (UOJProblem::info('type') == 'remote') { + $remote_online_judge = UOJProblem::cur()->getExtraConfig('remote_online_judge'); + $remote_problem_id = UOJProblem::cur()->getExtraConfig('remote_problem_id'); + $remote_provider = UOJRemoteProblem::$providers[$remote_online_judge]; + + $re_crawl_form = new UOJForm('re_crawl'); + $re_crawl_form->appendHTML(<< +
  • 远程题库:{$remote_provider['name']}
  • +
  • 远程题号:{$remote_problem_id}
  • + + EOD); + $re_crawl_form->config['submit_button']['text'] = '重新爬取'; + $re_crawl_form->handle = function () use ($remote_online_judge, $remote_problem_id, $remote_provider) { + try { + $data = UOJRemoteProblem::getProblemBasicInfo($remote_online_judge, $remote_problem_id); + } catch (Exception $e) { + $data = null; + UOJLog::error($e->getMessage()); + } + + if ($data === null) { + UOJResponse::page500('题目抓取失败,可能是题目不存在或者没有题面!如果题目没有问题,请稍后再试。返回'); + } + + $submission_requirement = [ + [ + "name" => "answer", + "type" => "source code", + "file_name" => "answer.code", + "languages" => $remote_provider['languages'], + ] + ]; + $enc_submission_requirement = json_encode($submission_requirement); + + $extra_config = [ + 'remote_online_judge' => $remote_online_judge, + 'remote_problem_id' => $remote_problem_id, + 'time_limit' => $data['time_limit'], + 'memory_limit' => $data['memory_limit'], + ]; + $enc_extra_config = json_encode($extra_config); + + DB::update([ + "update problems", + "set", [ + "title" => $data['title'], + "submission_requirement" => $enc_submission_requirement, + "extra_config" => $enc_extra_config, + "difficulty" => $data['difficulty'] ?: -1, + ], + "where", [ + "id" => UOJProblem::info('id'), + ], + ]); + + DB::update([ + "update problems_contents", + "set", [ + "remote_content" => HTML::purifier()->purify($data['statement']), + ], + "where", [ + "id" => UOJProblem::info('id'), + ], + ]); + + redirectTo(UOJProblem::cur()->getUri()); + }; + $re_crawl_form->runAtServer(); +} + +$view_type_form = new UOJForm('view_type'); +$view_type_form->addSelect('view_content_type', [ + 'div_class' => 'row align-items-center g-0', + 'label_class' => 'form-label col-auto m-0 flex-grow-1', + 'select_class' => 'col-auto form-select w-auto', + 'label' => '查看提交文件', + 'options' => [ + 'NONE' => '禁止', + 'ALL_AFTER_AC' => 'AC 后', + 'ALL' => '所有人', + ], + 'default_value' => $problem_extra_config['view_content_type'], +]); +$view_type_form->addSelect('view_all_details_type', [ + 'div_class' => 'row align-items-center g-0 mt-3', + 'label_class' => 'form-label col-auto m-0 flex-grow-1', + 'select_class' => 'col-auto form-select w-auto', + 'label' => '查看全部详细信息', + 'options' => [ + 'NONE' => '禁止', + 'SELF' => '仅自己', + 'ALL_AFTER_AC' => 'AC 后', + 'ALL' => '所有人' + ], + 'default_value' => $problem_extra_config['view_all_details_type'], +]); +$view_type_form->addSelect('view_details_type', [ + 'div_class' => 'row align-items-center g-0 mt-3', + 'label_class' => 'form-label col-auto m-0 flex-grow-1', + 'select_class' => 'col-auto form-select w-auto', + 'label' => '查看测试点详细信息', + 'options' => [ + 'NONE' => '禁止', + 'SELF' => '仅自己', + 'ALL_AFTER_AC' => 'AC 后', + 'ALL' => '所有人', + ], + 'default_value' => $problem_extra_config['view_details_type'], +]); +$view_type_form->handle = function () { + $config = UOJProblem::cur()->getExtraConfig(); + $config['view_content_type'] = $_POST['view_content_type']; + $config['view_all_details_type'] = $_POST['view_all_details_type']; + $config['view_details_type'] = $_POST['view_details_type']; + $esc_config = json_encode($config); + + DB::update([ + "update problems", + "set", ["extra_config" => $esc_config], + "where", ["id" => UOJProblem::info('id')] + ]); +}; +$view_type_form->runAtServer(); + +$solution_view_type_form = new UOJForm('solution_view_type'); +$solution_view_type_form->addSelect('view_solution_type', [ + 'div_class' => 'row align-items-center g-0', + 'label_class' => 'form-label col-auto m-0 flex-grow-1', + 'select_class' => 'col-auto form-select w-auto', + 'label' => '查看题解', + 'options' => [ + 'NONE' => '禁止', + 'ALL_AFTER_AC' => 'AC 后', + 'ALL' => '所有人', + ], + 'default_value' => $problem_extra_config['view_solution_type'], +]); +$solution_view_type_form->addSelect('submit_solution_type', [ + 'div_class' => 'row align-items-center g-0 mt-3', + 'label_class' => 'form-label col-auto m-0 flex-grow-1', + 'select_class' => 'col-auto form-select w-auto', + 'label' => '提交题解', + 'options' => [ + 'NONE' => '禁止', + 'ALL_AFTER_AC' => 'AC 后', + 'ALL' => '所有人', + ], + 'default_value' => $problem_extra_config['submit_solution_type'], +]); +$solution_view_type_form->handle = function () { + $config = UOJProblem::cur()->getExtraConfig(); + $config['view_solution_type'] = $_POST['view_solution_type']; + $config['submit_solution_type'] = $_POST['submit_solution_type']; + $esc_config = json_encode($config); + + DB::update([ + "update problems", + "set", ["extra_config" => $esc_config], + "where", ["id" => UOJProblem::info('id')] + ]); +}; +$solution_view_type_form->runAtServer(); ?> @@ -102,11 +269,13 @@ $difficulty_form->runAtServer(); 管理者 - + + +
    @@ -261,6 +430,35 @@ $difficulty_form->runAtServer(); printHTML() ?>
    + + +
    +
    + 重新爬取题目信息 +
    +
    + printHTML() ?> +
    +
    + + +
    +
    + 提交记录可视权限 +
    +
    + printHTML() ?> +
    +
    + +
    +
    + 题解可视权限 +
    +
    + printHTML() ?> +
    +
    diff --git a/web/app/libs/uoj-validate-lib.php b/web/app/libs/uoj-validate-lib.php index 4ba22e5..adea031 100644 --- a/web/app/libs/uoj-validate-lib.php +++ b/web/app/libs/uoj-validate-lib.php @@ -75,3 +75,7 @@ function validateUserAndStoreByUsername($username, &$vdata) { function is_short_string($str) { return is_string($str) && strlen($str) <= 256; } + +function validateCodeforcesProblemId($str) { + return preg_match('/[1-9][0-9]{0,5}[A-Z][1-9]?/', $str) !== true; +} diff --git a/web/app/models/HTML.php b/web/app/models/HTML.php index df75d89..7ac1312 100644 --- a/web/app/models/HTML.php +++ b/web/app/models/HTML.php @@ -423,7 +423,7 @@ class HTML { return implode("&", $r); } - public static function purifier() { + public static function purifier($extra_allowed_html = []) { $config = HTMLPurifier_Config::createDefault(); $config->set('Output.Newline', true); $def = $config->getHTMLDefinition(true); @@ -435,14 +435,14 @@ class HTML { $def->addElement('header', 'Block', 'Flow', 'Common'); $def->addElement('footer', 'Block', 'Flow', 'Common'); - $extra_allowed_html = [ + mergeConfig($extra_allowed_html, [ 'span' => [ 'class' => 'Enum#uoj-username', 'data-realname' => 'Text', 'data-color' => 'Color', ], 'img' => ['width' => 'Text'], - ]; + ]); foreach ($extra_allowed_html as $element => $attributes) { foreach ($attributes as $attribute => $type) { diff --git a/web/app/models/UOJContest.php b/web/app/models/UOJContest.php index e7496d9..c9c8d2c 100644 --- a/web/app/models/UOJContest.php +++ b/web/app/models/UOJContest.php @@ -53,65 +53,6 @@ class UOJContest { return isSuperUser($user) || UOJUser::checkPermission($user, 'contests.create'); } - public static function finalTest() { - $contest = self::info(); - - $res = DB::selectAll([ - "select id, problem_id, content, submitter, hide_score_to_others from submissions", - "where", ["contest_id" => $contest['id']] - ]); - foreach ($res as $submission) { - $content = json_decode($submission['content'], true); - if (isset($content['final_test_config'])) { - $content['config'] = $content['final_test_config']; - unset($content['final_test_config']); - } - if (isset($content['first_test_config'])) { - unset($content['first_test_config']); - } - UOJSubmission::rejudgeById($submission['id'], [ - 'reason_text' => HTML::stripTags($contest['name']) . ' 最终测试', - 'reason_url' => HTML::url(UOJContest::cur()->getUri()), - 'set_q' => [ - "content" => json_encode($content) - ] - ]); - } - - // warning: check if this command works well when the database is not MySQL - DB::update([ - "update submissions", - "set", [ - "score = hidden_score", - "hidden_score = NULL", - "hide_score_to_others = 0" - ], "where", [ - "contest_id" => $contest['id'], - "hide_score_to_others" => 1 - ] - ]); - - $updated = []; - foreach ($res as $submission) { - $submitter = $submission['submitter']; - $pid = $submission['problem_id']; - if (isset($updated[$submitter]) && isset($updated[$submitter][$pid])) { - continue; - } - updateBestACSubmissions($submitter, $pid); - if (!isset($updated[$submitter])) { - $updated[$submitter] = []; - } - $updated[$submitter][$pid] = true; - } - - DB::update([ - "update contests", - "set", ["status" => 'testing'], - "where", ["id" => $contest['id']] - ]); - } - public static function announceOfficialResults() { // time config set_time_limit(0); @@ -246,11 +187,121 @@ class UOJContest { $label = '开始最终测试'; } - if ($this->progress() >= CONTEST_TESTING) { - $label = '重新' . $label; + return $label; + } + + public function finalTest() { + ignore_user_abort(true); + set_time_limit(0); + + DB::update([ + "update contests", + "set", ["status" => 'testing'], + "where", ["id" => $this->info['id']] + ]); + + if (DB::affected_rows() !== 1) { + // 已经有其他人开始评测了,不进行任何操作 + return; } - return $label; + $res = DB::selectAll([ + "select id, problem_id, content, result, submitter, hide_score_to_others from submissions", + "where", ["contest_id" => $this->info['id']] + ]); + foreach ($res as $submission) { + $content = json_decode($submission['content'], true); + + if (isset($content['final_test_config'])) { + $content['config'] = $content['final_test_config']; + unset($content['final_test_config']); + } + + if (isset($content['first_test_config'])) { + unset($content['first_test_config']); + } + + $q = [ + 'content' => json_encode($content), + ]; + + $problem_judge_type = $this->info['extra_config']["problem_{$submission['problem_id']}"] ?: $this->defaultProblemJudgeType(); + $result = json_decode($submission['result'], true); + + switch ($problem_judge_type) { + case 'sample': + if (isset($result['final_result']) && $result['final_result']['status'] == 'Judged') { + $q += [ + 'result' => json_encode($result['final_result']), + 'score' => $result['final_result']['score'], + 'used_time' => $result['final_result']['time'], + 'used_memory' => $result['final_result']['memory'], + 'judge_time' => $this->info['end_time_str'], + 'status' => 'Judged', + ]; + + if ($submission['hide_score_to_others']) { + $q['hidden_score'] = $q['score']; + $q['score'] = null; + } + } + + break; + + case 'no-details': + case 'full': + if ($result['status'] == 'Judged') { + $q += [ + 'result' => $submission['result'], + 'score' => $result['score'], + 'used_time' => $result['time'], + 'used_memory' => $result['memory'], + 'judge_time' => $this->info['end_time_str'], + 'status' => 'Judged', + ]; + + if ($submission['hide_score_to_others']) { + $q['hidden_score'] = $q['score']; + $q['score'] = null; + } + } + + break; + } + + UOJSubmission::rejudgeById($submission['id'], [ + 'reason_text' => HTML::stripTags($this->info['name']) . ' 最终测试', + 'reason_url' => HTML::url(UOJContest::cur()->getUri()), + 'set_q' => $q, + ]); + } + + // warning: check if this command works well when the database is not MySQL + DB::update([ + "update submissions", + "set", [ + "score = hidden_score", + "hidden_score = NULL", + "hide_score_to_others = 0" + ], "where", [ + "contest_id" => $this->info['id'], + "hide_score_to_others" => 1 + ] + ]); + + $updated = []; + foreach ($res as $submission) { + $submitter = $submission['submitter']; + $pid = $submission['problem_id']; + if (isset($updated[$submitter]) && isset($updated[$submitter][$pid])) { + continue; + } + updateBestACSubmissions($submitter, $pid); + if (!isset($updated[$submitter])) { + $updated[$submitter] = []; + } + $updated[$submitter][$pid] = true; + } } public function queryJudgeProgress() { diff --git a/web/app/models/UOJForm.php b/web/app/models/UOJForm.php index 7923268..cd3ad47 100644 --- a/web/app/models/UOJForm.php +++ b/web/app/models/UOJForm.php @@ -120,6 +120,53 @@ class UOJForm { $this->add($name, $html, $validator_php, $validator_js); } + public function addInput($name, $config) { + $config += [ + 'type' => 'text', + 'div_class' => '', + 'input_class' => 'form-control', + 'default_value' => '', + 'label' => '', + 'label_class' => 'form-label', + 'placeholder' => '', + 'help' => '', + 'help_class' => 'form-text', + 'validator_php' => function ($x) { + return ''; + }, + 'validator_js' => null, + ]; + + $html = ''; + $html .= HTML::tag_begin('div', ['class' => $config['div_class'], 'id' => "div-$name"]); + + if ($config['label']) { + $html .= HTML::tag('label', [ + 'class' => $config['label_class'], + 'for' => "input-$name", + 'id' => "label-$name" + ], $config['label']); + } + + $html .= HTML::empty_tag('input', [ + 'class' => $config['input_class'], + 'type' => $config['type'], + 'name' => $name, + 'id' => "input-$name", + 'value' => $config['default_value'], + 'placeholder' => $config['placeholder'], + ]); + $html .= HTML::tag('div', ['class' => 'invalid-feedback', 'id' => "help-$name"], ''); + + if ($config['help']) { + $html .= HTML::tag('div', ['class' => $config['help_class']], $config['help']); + } + + $html .= HTML::tag_end('div'); + + $this->add($name, $html, $config['validator_php'], $config['validator_js']); + } + public function addCheckbox($name, $config) { $config += [ 'checked' => false, diff --git a/web/app/models/UOJLang.php b/web/app/models/UOJLang.php index 996fe4b..756549e 100644 --- a/web/app/models/UOJLang.php +++ b/web/app/models/UOJLang.php @@ -65,7 +65,7 @@ class UOJLang { } $is_avail = []; $dep_list = [ - ['C++', 'C++11', 'C++14', 'C++17', 'C++20'], + ['C++98', 'C++03', 'C++11', 'C++', 'C++17', 'C++20'], ['Java8', 'Java11', 'Java17'] ]; foreach ($list as $lang) { diff --git a/web/app/models/UOJProblem.php b/web/app/models/UOJProblem.php index c0bd929..8e4bbce 100644 --- a/web/app/models/UOJProblem.php +++ b/web/app/models/UOJProblem.php @@ -382,6 +382,26 @@ class UOJProblem { return UOJUser::getLink($this->info['uploader'] ?: "root"); } + public function getProviderLink() { + if ($this->info['type'] == 'local') { + return HTML::tag('a', ['href' => HTML::url('/')], UOJConfig::$data['profile']['oj-name-short']); + } + + $remote_oj = $this->getExtraConfig('remote_online_judge'); + $remote_id = $this->getExtraConfig('remote_problem_id'); + + if (!$remote_oj || !array_key_exists($remote_oj, UOJRemoteProblem::$providers)) { + return 'Error'; + } + + $provider = UOJRemoteProblem::$providers[$remote_oj]; + + return HTML::tag('a', [ + 'href' => UOJRemoteProblem::getProblemRemoteUrl($remote_oj, $remote_id), + 'target' => '_blank' + ], $provider['name']); + } + public function getDifficultyHTML() { $difficulty = (int)$this->info['difficulty']; $difficulty_text = in_array($difficulty, static::$difficulty) ? $difficulty : '?'; @@ -440,28 +460,33 @@ class UOJProblem { return $key === null ? $extra_config : $extra_config[$key]; } public function getCustomTestRequirement() { + if ($this->info['type'] == 'remote') { + return []; + } + $extra_config = json_decode($this->info['extra_config'], true); + if (isset($extra_config['custom_test_requirement'])) { return $extra_config['custom_test_requirement']; - } else { - $answer = [ - 'name' => 'answer', - 'type' => 'source code', - 'file_name' => 'answer.code' - ]; - foreach ($this->getSubmissionRequirement() as $req) { - if ($req['name'] == 'answer' && $req['type'] == 'source code' && isset($req['languages'])) { - $answer['languages'] = $req['languages']; - } - } - return [ - $answer, [ - 'name' => 'input', - 'type' => 'text', - 'file_name' => 'input.txt' - ] - ]; } + + $answer = [ + 'name' => 'answer', + 'type' => 'source code', + 'file_name' => 'answer.code' + ]; + foreach ($this->getSubmissionRequirement() as $req) { + if ($req['name'] == 'answer' && $req['type'] == 'source code' && isset($req['languages'])) { + $answer['languages'] = $req['languages']; + } + } + return [ + $answer, [ + 'name' => 'input', + 'type' => 'text', + 'file_name' => 'input.txt' + ] + ]; } public function userCanView(array $user = null, array $cfg = []) { diff --git a/web/app/models/UOJRemoteProblem.php b/web/app/models/UOJRemoteProblem.php new file mode 100644 index 0000000..6074faa --- /dev/null +++ b/web/app/models/UOJRemoteProblem.php @@ -0,0 +1,146 @@ + [ + 'name' => 'Codeforces', + 'short_name' => 'CF', + 'url' => 'https://codeforces.com', + 'not_exists_texts' => [ + 'Actions', + 'Statement is not available on English language', + 'ограничение по времени на тест', + ], + 'languages' => ['C', 'C++', 'C++17', 'C++20', 'Java17', 'Pascal', 'Python2', 'Python3'], + ], + ]; + + static function getCodeforcesProblemUrl($id) { + return static::$providers['codeforces']['url'] . '/problemset/problem/' . preg_replace_callback('/([1-9][0-9]{0,5})([A-Z][1-9]?)/', fn ($matches) => $matches[1] . '/' . $matches[2], $id); + } + + // 传入 ID 需确保有效 + static function getCodeforcesProblemBasicInfo($id) { + $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'); + + $remote_provider = static::$providers['codeforces']; + + $html = retry_loop(function () use ($curl, $id) { + $curl->get(static::getCodeforcesProblemUrl($id)); + + if ($curl->error) { + return false; + } + + return $curl->response; + }); + + if (!$html) return null; + + $html = preg_replace('/\$\$\$/', '$', $html); + $dom = new \IvoPetkov\HTML5DOMDocument(); + $dom->loadHTML($html); + + $judgestatement = $dom->querySelector('html')->innerHTML; + + foreach ($remote_provider['not_exists_texts'] as $text) { + if (str_contains($judgestatement, $text)) { + return null; + } + } + + $statement_dom = $dom->querySelector('.problem-statement'); + $title = explode('. ', trim($statement_dom->querySelector('.title')->innerHTML))[1]; + $title = "【{$remote_provider['short_name']}{$id}】{$title}"; + $time_limit = intval(substr($statement_dom->querySelector('.time-limit')->innerHTML, 53)); + $memory_limit = intval(substr($statement_dom->querySelector('.memory-limit')->innerHTML, 55)); + $difficulty = -1; + + foreach ($dom->querySelectorAll('.tag-box') as &$elem) { + $matches = []; + + if (preg_match('/\*([0-9]{3,4})/', trim($elem->innerHTML), $matches)) { + $difficulty = intval($matches[1]); + + break; + } + } + + if ($difficulty != -1) { + $closest = null; + + foreach (UOJProblem::$difficulty as $val) { + if ($closest === null || abs($val - $difficulty) < abs($closest - $difficulty)) { + $closest = $val; + } + } + + $difficulty = $closest; + } + + $statement_dom->removeChild($statement_dom->querySelector('.header')); + $statement_dom->childNodes->item(0)->insertBefore($dom->createElement('h3', 'Description'), $statement_dom->childNodes->item(0)->childNodes->item(0)); + + foreach ($statement_dom->querySelectorAll('.section-title') as &$elem) { + $elem->outerHTML = '

    ' . $elem->innerHTML . '

    '; + } + + $sample_input_cnt = 0; + $sample_output_cnt = 0; + + foreach ($statement_dom->querySelectorAll('.input') as &$input_dom) { + $sample_input_cnt++; + $input_text = ''; + + if ($input_dom->querySelector('.test-example-line')) { + foreach ($input_dom->querySelectorAll('.test-example-line') as &$line) { + $input_text .= HTML::stripTags($line->innerHTML) . "\n"; + } + } else { + $input_text = HTML::stripTags($input_dom->querySelector('pre')->innerHTML); + } + + $input_dom->outerHTML = HTML::tag('h4', [], "Input #{$sample_input_cnt}") . HTML::tag('pre', [], HTML::tag('code', [], $input_text)); + } + + foreach ($statement_dom->querySelectorAll('.output') as &$output_dom) { + $sample_output_cnt++; + $output_text = ''; + + if ($output_dom->querySelector('.test-example-line')) { + foreach ($output_dom->querySelectorAll('.test-example-line') as &$line) { + $output_text .= HTML::stripTags($line->innerHTML) . "\n"; + } + } else { + $output_text = HTML::stripTags($output_dom->querySelector('pre')->innerHTML); + } + + $output_dom->outerHTML = HTML::tag('h4', [], "Output #{$sample_output_cnt}") . HTML::tag('pre', [], HTML::tag('code', [], $output_text)); + } + + return [ + 'title' => $title, + 'time_limit' => $time_limit, + 'memory_limit' => $memory_limit, + 'difficulty' => $difficulty, + 'statement' => $statement_dom->innerHTML, + ]; + } + + public static function getProblemRemoteUrl($oj, $id) { + if ($oj === 'codeforces') { + return static::getCodeforcesProblemUrl($id); + } + + return null; + } + + public static function getProblemBasicInfo($oj, $id) { + if ($oj === 'codeforces') { + return static::getCodeforcesProblemBasicInfo($id); + } + + return null; + } +} diff --git a/web/app/models/UOJSubmission.php b/web/app/models/UOJSubmission.php index 34570ee..391f64b 100644 --- a/web/app/models/UOJSubmission.php +++ b/web/app/models/UOJSubmission.php @@ -87,11 +87,18 @@ class UOJSubmission { $judge_reason = ''; $content['config'][] = ['problem_id', UOJProblem::info('id')]; + + if (UOJProblem::info('type') == 'remote') { + $content['config'][] = ['remote_online_judge', UOJProblem::cur()->getExtraConfig('remote_online_judge')]; + $content['config'][] = ['remote_problem_id', UOJProblem::cur()->getExtraConfig('remote_problem_id')]; + } + if ($is_contest_submission && UOJContestProblem::cur()->getJudgeTypeInContest() == 'sample') { $content['final_test_config'] = $content['config']; $content['config'][] = ['test_sample_only', 'on']; $judge_reason = json_encode(['text' => '样例测评']); } + $content_json = json_encode($content); $language = static::getAndRememberSubmissionLanguage($content); @@ -407,9 +414,14 @@ class UOJSubmission { } public function userCanRejudge(array $user = null) { + if ($this->problem->info['type'] == 'remote') { + return false; + } + if (isSuperUser($user)) { return true; } + return $this->userCanManageProblemOrContest($user) && $this->hasFullyJudged(); } diff --git a/web/app/route.php b/web/app/route.php index 1f11278a..085cf54 100644 --- a/web/app/route.php +++ b/web/app/route.php @@ -17,6 +17,7 @@ Route::group( Route::any('/', '/index.php'); Route::any('/problems', '/problem_set.php'); Route::any('/problems/template', '/problem_set.php?tab=template'); + Route::any('/problems/new/remote', '/new_remote_problem.php'); Route::any('/problem/{id}', '/problem.php'); Route::any('/problem/{id}/solutions', '/problem_solutions.php'); Route::any('/problem/{id}/statistics', '/problem_statistics.php'); diff --git a/web/app/vendor/composer/ClassLoader.php b/web/app/vendor/composer/ClassLoader.php index fce8549..afef3fa 100644 --- a/web/app/vendor/composer/ClassLoader.php +++ b/web/app/vendor/composer/ClassLoader.php @@ -37,57 +37,130 @@ namespace Composer\Autoload; * * @author Fabien Potencier * @author Jordi Boggiano - * @see http://www.php-fig.org/psr/psr-0/ - * @see http://www.php-fig.org/psr/psr-4/ + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ */ class ClassLoader { + /** @var ?string */ + private $vendorDir; + // PSR-4 + /** + * @var array[] + * @psalm-var array> + */ private $prefixLengthsPsr4 = array(); + /** + * @var array[] + * @psalm-var array> + */ private $prefixDirsPsr4 = array(); + /** + * @var array[] + * @psalm-var array + */ private $fallbackDirsPsr4 = array(); // PSR-0 + /** + * @var array[] + * @psalm-var array> + */ private $prefixesPsr0 = array(); + /** + * @var array[] + * @psalm-var array + */ private $fallbackDirsPsr0 = array(); + /** @var bool */ private $useIncludePath = false; + + /** + * @var string[] + * @psalm-var array + */ private $classMap = array(); + + /** @var bool */ private $classMapAuthoritative = false; + + /** + * @var bool[] + * @psalm-var array + */ private $missingClasses = array(); + + /** @var ?string */ private $apcuPrefix; + /** + * @var self[] + */ + private static $registeredLoaders = array(); + + /** + * @param ?string $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + } + + /** + * @return string[] + */ public function getPrefixes() { if (!empty($this->prefixesPsr0)) { - return call_user_func_array('array_merge', $this->prefixesPsr0); + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); } return array(); } + /** + * @return array[] + * @psalm-return array> + */ public function getPrefixesPsr4() { return $this->prefixDirsPsr4; } + /** + * @return array[] + * @psalm-return array + */ public function getFallbackDirs() { return $this->fallbackDirsPsr0; } + /** + * @return array[] + * @psalm-return array + */ public function getFallbackDirsPsr4() { return $this->fallbackDirsPsr4; } + /** + * @return string[] Array of classname => path + * @psalm-return array + */ public function getClassMap() { return $this->classMap; } /** - * @param array $classMap Class to filename map + * @param string[] $classMap Class to filename map + * @psalm-param array $classMap + * + * @return void */ public function addClassMap(array $classMap) { @@ -102,9 +175,11 @@ class ClassLoader * Registers a set of PSR-0 directories for a given prefix, either * appending or prepending to the ones previously set for this prefix. * - * @param string $prefix The prefix - * @param array|string $paths The PSR-0 root directories - * @param bool $prepend Whether to prepend the directories + * @param string $prefix The prefix + * @param string[]|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void */ public function add($prefix, $paths, $prepend = false) { @@ -147,11 +222,13 @@ class ClassLoader * Registers a set of PSR-4 directories for a given namespace, either * appending or prepending to the ones previously set for this namespace. * - * @param string $prefix The prefix/namespace, with trailing '\\' - * @param array|string $paths The PSR-4 base directories - * @param bool $prepend Whether to prepend the directories + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param string[]|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories * * @throws \InvalidArgumentException + * + * @return void */ public function addPsr4($prefix, $paths, $prepend = false) { @@ -195,8 +272,10 @@ class ClassLoader * Registers a set of PSR-0 directories for a given prefix, * replacing any others previously set for this prefix. * - * @param string $prefix The prefix - * @param array|string $paths The PSR-0 base directories + * @param string $prefix The prefix + * @param string[]|string $paths The PSR-0 base directories + * + * @return void */ public function set($prefix, $paths) { @@ -211,10 +290,12 @@ class ClassLoader * Registers a set of PSR-4 directories for a given namespace, * replacing any others previously set for this namespace. * - * @param string $prefix The prefix/namespace, with trailing '\\' - * @param array|string $paths The PSR-4 base directories + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param string[]|string $paths The PSR-4 base directories * * @throws \InvalidArgumentException + * + * @return void */ public function setPsr4($prefix, $paths) { @@ -234,6 +315,8 @@ class ClassLoader * Turns on searching the include path for class files. * * @param bool $useIncludePath + * + * @return void */ public function setUseIncludePath($useIncludePath) { @@ -256,6 +339,8 @@ class ClassLoader * that have not been registered with the class map. * * @param bool $classMapAuthoritative + * + * @return void */ public function setClassMapAuthoritative($classMapAuthoritative) { @@ -276,6 +361,8 @@ class ClassLoader * APCu prefix to use to cache found/not-found classes, if the extension is enabled. * * @param string|null $apcuPrefix + * + * @return void */ public function setApcuPrefix($apcuPrefix) { @@ -296,25 +383,44 @@ class ClassLoader * Registers this instance as an autoloader. * * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void */ public function register($prepend = false) { spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } } /** * Unregisters this instance as an autoloader. + * + * @return void */ public function unregister() { spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } } /** * Loads the given class or interface. * * @param string $class The name of the class - * @return bool|null True if loaded, null otherwise + * @return true|null True if loaded, null otherwise */ public function loadClass($class) { @@ -323,6 +429,8 @@ class ClassLoader return true; } + + return null; } /** @@ -367,6 +475,21 @@ class ClassLoader return $file; } + /** + * Returns the currently registered loaders indexed by their corresponding vendor directories. + * + * @return self[] + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ private function findFileWithExtension($class, $ext) { // PSR-4 lookup @@ -438,6 +561,10 @@ class ClassLoader * Scope isolated include. * * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + * @private */ function includeFile($file) { diff --git a/web/app/vendor/composer/InstalledVersions.php b/web/app/vendor/composer/InstalledVersions.php new file mode 100644 index 0000000..d50e0c9 --- /dev/null +++ b/web/app/vendor/composer/InstalledVersions.php @@ -0,0 +1,350 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + */ +class InstalledVersions +{ + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array}|array{}|null + */ + private static $installed; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']); + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints($constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + + if (self::$canGetVendors) { + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; + if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { + self::$installed = $installed[count($installed) - 1]; + } + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = require __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + $installed[] = self::$installed; + + return $installed; + } +} diff --git a/web/app/vendor/composer/autoload_classmap.php b/web/app/vendor/composer/autoload_classmap.php index cdc6ae7..e047a6e 100644 --- a/web/app/vendor/composer/autoload_classmap.php +++ b/web/app/vendor/composer/autoload_classmap.php @@ -6,5 +6,13 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( + 'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'CaseInsensitiveArray' => $vendorDir . '/php-curl-class/php-curl-class/src/Curl.class.php', + 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', + 'Curl' => $vendorDir . '/php-curl-class/php-curl-class/src/Curl.class.php', 'ParsedownMath' => $vendorDir . '/parsedown-math/ParsedownMath.php', + 'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', ); diff --git a/web/app/vendor/composer/autoload_files.php b/web/app/vendor/composer/autoload_files.php index c25686b..e4823db 100644 --- a/web/app/vendor/composer/autoload_files.php +++ b/web/app/vendor/composer/autoload_files.php @@ -6,5 +6,8 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( + '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php', + 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', '2cffec82183ee1cea088009cef9a6fc3' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php', + '16eed290c5592c18dc3f16802ad3d0e4' => $vendorDir . '/ivopetkov/html5-dom-document-php/autoload.php', ); diff --git a/web/app/vendor/composer/autoload_psr4.php b/web/app/vendor/composer/autoload_psr4.php index bd5bb53..d7892aa 100644 --- a/web/app/vendor/composer/autoload_psr4.php +++ b/web/app/vendor/composer/autoload_psr4.php @@ -6,6 +6,7 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( + 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), 'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'), 'PHPMailer\\PHPMailer\\' => array($vendorDir . '/phpmailer/phpmailer/src'), 'Gregwar\\' => array($vendorDir . '/gregwar/captcha/src/Gregwar'), diff --git a/web/app/vendor/composer/autoload_real.php b/web/app/vendor/composer/autoload_real.php index a6fbe24..8c78c5f 100644 --- a/web/app/vendor/composer/autoload_real.php +++ b/web/app/vendor/composer/autoload_real.php @@ -22,13 +22,15 @@ class ComposerAutoloaderInit0d7c2cd5c2dbf2120e4372996869e900 return self::$loader; } + require __DIR__ . '/platform_check.php'; + spl_autoload_register(array('ComposerAutoloaderInit0d7c2cd5c2dbf2120e4372996869e900', 'loadClassLoader'), true, true); - self::$loader = $loader = new \Composer\Autoload\ClassLoader(); + self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__))); spl_autoload_unregister(array('ComposerAutoloaderInit0d7c2cd5c2dbf2120e4372996869e900', 'loadClassLoader')); $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if ($useStaticLoader) { - require_once __DIR__ . '/autoload_static.php'; + require __DIR__ . '/autoload_static.php'; call_user_func(\Composer\Autoload\ComposerStaticInit0d7c2cd5c2dbf2120e4372996869e900::getInitializer($loader)); } else { @@ -63,11 +65,16 @@ class ComposerAutoloaderInit0d7c2cd5c2dbf2120e4372996869e900 } } +/** + * @param string $fileIdentifier + * @param string $file + * @return void + */ function composerRequire0d7c2cd5c2dbf2120e4372996869e900($fileIdentifier, $file) { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { - require $file; - $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + + require $file; } } diff --git a/web/app/vendor/composer/autoload_static.php b/web/app/vendor/composer/autoload_static.php index 3fc152b..506d13a 100644 --- a/web/app/vendor/composer/autoload_static.php +++ b/web/app/vendor/composer/autoload_static.php @@ -7,12 +7,16 @@ namespace Composer\Autoload; class ComposerStaticInit0d7c2cd5c2dbf2120e4372996869e900 { public static $files = array ( + '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', + 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', '2cffec82183ee1cea088009cef9a6fc3' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php', + '16eed290c5592c18dc3f16802ad3d0e4' => __DIR__ . '/..' . '/ivopetkov/html5-dom-document-php/autoload.php', ); public static $prefixLengthsPsr4 = array ( 'S' => array ( + 'Symfony\\Polyfill\\Php80\\' => 23, 'Symfony\\Component\\Finder\\' => 25, ), 'P' => @@ -26,6 +30,10 @@ class ComposerStaticInit0d7c2cd5c2dbf2120e4372996869e900 ); public static $prefixDirsPsr4 = array ( + 'Symfony\\Polyfill\\Php80\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php80', + ), 'Symfony\\Component\\Finder\\' => array ( 0 => __DIR__ . '/..' . '/symfony/finder', @@ -58,7 +66,15 @@ class ComposerStaticInit0d7c2cd5c2dbf2120e4372996869e900 ); public static $classMap = array ( + 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'CaseInsensitiveArray' => __DIR__ . '/..' . '/php-curl-class/php-curl-class/src/Curl.class.php', + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'Curl' => __DIR__ . '/..' . '/php-curl-class/php-curl-class/src/Curl.class.php', 'ParsedownMath' => __DIR__ . '/..' . '/parsedown-math/ParsedownMath.php', + 'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', ); public static function getInitializer(ClassLoader $loader) diff --git a/web/app/vendor/composer/installed.json b/web/app/vendor/composer/installed.json index f5ef1af..4b3dccd 100644 --- a/web/app/vendor/composer/installed.json +++ b/web/app/vendor/composer/installed.json @@ -1,303 +1,540 @@ -[ - { - "name": "erusev/parsedown", - "version": "1.7.4", - "version_normalized": "1.7.4.0", - "source": { - "type": "git", - "url": "https://github.com/erusev/parsedown.git", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35" - }, - "time": "2019-12-30T22:54:17+00:00", - "type": "library", - "installation-source": "dist", - "autoload": { - "psr-0": { - "Parsedown": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Emanuil Rusev", - "email": "hello@erusev.com", - "homepage": "http://erusev.com" - } - ], - "description": "Parser for Markdown.", - "homepage": "http://parsedown.org", - "keywords": [ - "markdown", - "parser" - ] - }, - { - "name": "ezyang/htmlpurifier", - "version": "v4.16.0", - "version_normalized": "4.16.0.0", - "source": { - "type": "git", - "url": "https://github.com/ezyang/htmlpurifier.git", - "reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/523407fb06eb9e5f3d59889b3978d5bfe94299c8", - "reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8", - "shasum": "" - }, - "require": { - "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0" - }, - "require-dev": { - "cerdic/css-tidy": "^1.7 || ^2.0", - "simpletest/simpletest": "dev-master" - }, - "suggest": { - "cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.", - "ext-bcmath": "Used for unit conversion and imagecrash protection", - "ext-iconv": "Converts text to and from non-UTF-8 encodings", - "ext-tidy": "Used for pretty-printing HTML" - }, - "time": "2022-09-18T07:06:19+00:00", - "type": "library", - "installation-source": "dist", - "autoload": { - "files": [ - "library/HTMLPurifier.composer.php" +{ + "packages": [ + { + "name": "erusev/parsedown", + "version": "1.7.4", + "version_normalized": "1.7.4.0", + "source": { + "type": "git", + "url": "https://github.com/erusev/parsedown.git", + "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35" + }, + "time": "2019-12-30T22:54:17+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "Parsedown": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" ], - "psr-0": { - "HTMLPurifier": "library/" + "authors": [ + { + "name": "Emanuil Rusev", + "email": "hello@erusev.com", + "homepage": "http://erusev.com" + } + ], + "description": "Parser for Markdown.", + "homepage": "http://parsedown.org", + "keywords": [ + "markdown", + "parser" + ], + "install-path": "../erusev/parsedown" + }, + { + "name": "ezyang/htmlpurifier", + "version": "v4.16.0", + "version_normalized": "4.16.0.0", + "source": { + "type": "git", + "url": "https://github.com/ezyang/htmlpurifier.git", + "reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8" }, - "exclude-from-classmap": [ - "/library/HTMLPurifier/Language/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "LGPL-2.1-or-later" - ], - "authors": [ - { - "name": "Edward Z. Yang", - "email": "admin@htmlpurifier.org", - "homepage": "http://ezyang.com" - } - ], - "description": "Standards compliant HTML filter written in PHP", - "homepage": "http://htmlpurifier.org/", - "keywords": [ - "html" - ] - }, - { - "name": "gregwar/captcha", - "version": "v1.1.9", - "version_normalized": "1.1.9.0", - "source": { - "type": "git", - "url": "https://github.com/Gregwar/Captcha.git", - "reference": "4bb668e6b40e3205a020ca5ee4ca8cff8b8780c5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Gregwar/Captcha/zipball/4bb668e6b40e3205a020ca5ee4ca8cff8b8780c5", - "reference": "4bb668e6b40e3205a020ca5ee4ca8cff8b8780c5", - "shasum": "" - }, - "require": { - "ext-gd": "*", - "ext-mbstring": "*", - "php": ">=5.3.0", - "symfony/finder": "*" - }, - "require-dev": { - "phpunit/phpunit": "^6.4" - }, - "time": "2020-03-24T14:39:05+00:00", - "type": "captcha", - "installation-source": "dist", - "autoload": { - "psr-4": { - "Gregwar\\": "src/Gregwar" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Grégoire Passault", - "email": "g.passault@gmail.com", - "homepage": "http://www.gregwar.com/" + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/523407fb06eb9e5f3d59889b3978d5bfe94299c8", + "reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8", + "shasum": "" }, - { - "name": "Jeremy Livingston", - "email": "jeremy.j.livingston@gmail.com" - } - ], - "description": "Captcha generator", - "homepage": "https://github.com/Gregwar/Captcha", - "keywords": [ - "bot", - "captcha", - "spam" - ] - }, - { - "name": "phpmailer/phpmailer", - "version": "v6.6.5", - "version_normalized": "6.6.5.0", - "source": { - "type": "git", - "url": "https://github.com/PHPMailer/PHPMailer.git", - "reference": "8b6386d7417526d1ea4da9edb70b8352f7543627" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/8b6386d7417526d1ea4da9edb70b8352f7543627", - "reference": "8b6386d7417526d1ea4da9edb70b8352f7543627", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "ext-filter": "*", - "ext-hash": "*", - "php": ">=5.5.0" - }, - "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "doctrine/annotations": "^1.2", - "php-parallel-lint/php-console-highlighter": "^1.0.0", - "php-parallel-lint/php-parallel-lint": "^1.3.2", - "phpcompatibility/php-compatibility": "^9.3.5", - "roave/security-advisories": "dev-latest", - "squizlabs/php_codesniffer": "^3.6.2", - "yoast/phpunit-polyfills": "^1.0.0" - }, - "suggest": { - "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", - "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", - "league/oauth2-google": "Needed for Google XOAUTH2 authentication", - "psr/log": "For optional PSR-3 debug logging", - "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", - "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" - }, - "time": "2022-10-07T12:23:10+00:00", - "type": "library", - "installation-source": "dist", - "autoload": { - "psr-4": { - "PHPMailer\\PHPMailer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "LGPL-2.1-only" - ], - "authors": [ - { - "name": "Marcus Bointon", - "email": "phpmailer@synchromedia.co.uk" + "require": { + "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0" }, - { - "name": "Jim Jagielski", - "email": "jimjag@gmail.com" + "require-dev": { + "cerdic/css-tidy": "^1.7 || ^2.0", + "simpletest/simpletest": "dev-master" }, - { - "name": "Andy Prevost", - "email": "codeworxtech@users.sourceforge.net" + "suggest": { + "cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.", + "ext-bcmath": "Used for unit conversion and imagecrash protection", + "ext-iconv": "Converts text to and from non-UTF-8 encodings", + "ext-tidy": "Used for pretty-printing HTML" }, - { - "name": "Brent R. Matzelle" - } - ], - "description": "PHPMailer is a full-featured email creation and transfer class for PHP", - "funding": [ - { - "url": "https://github.com/Synchro", - "type": "github" - } - ] - }, - { - "name": "symfony/finder", - "version": "v6.1.3", - "version_normalized": "6.1.3.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "39696bff2c2970b3779a5cac7bf9f0b88fc2b709" + "time": "2022-09-18T07:06:19+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "library/HTMLPurifier.composer.php" + ], + "psr-0": { + "HTMLPurifier": "library/" + }, + "exclude-from-classmap": [ + "/library/HTMLPurifier/Language/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "Edward Z. Yang", + "email": "admin@htmlpurifier.org", + "homepage": "http://ezyang.com" + } + ], + "description": "Standards compliant HTML filter written in PHP", + "homepage": "http://htmlpurifier.org/", + "keywords": [ + "html" + ], + "install-path": "../ezyang/htmlpurifier" }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/39696bff2c2970b3779a5cac7bf9f0b88fc2b709", - "reference": "39696bff2c2970b3779a5cac7bf9f0b88fc2b709", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "symfony/filesystem": "^6.0" - }, - "time": "2022-07-29T07:42:06+00:00", - "type": "library", - "installation-source": "dist", - "autoload": { - "psr-4": { - "Symfony\\Component\\Finder\\": "" + { + "name": "gregwar/captcha", + "version": "v1.1.9", + "version_normalized": "1.1.9.0", + "source": { + "type": "git", + "url": "https://github.com/Gregwar/Captcha.git", + "reference": "4bb668e6b40e3205a020ca5ee4ca8cff8b8780c5" }, - "exclude-from-classmap": [ - "/Tests/" - ] + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Gregwar/Captcha/zipball/4bb668e6b40e3205a020ca5ee4ca8cff8b8780c5", + "reference": "4bb668e6b40e3205a020ca5ee4ca8cff8b8780c5", + "shasum": "" + }, + "require": { + "ext-gd": "*", + "ext-mbstring": "*", + "php": ">=5.3.0", + "symfony/finder": "*" + }, + "require-dev": { + "phpunit/phpunit": "^6.4" + }, + "time": "2020-03-24T14:39:05+00:00", + "type": "captcha", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Gregwar\\": "src/Gregwar" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Passault", + "email": "g.passault@gmail.com", + "homepage": "http://www.gregwar.com/" + }, + { + "name": "Jeremy Livingston", + "email": "jeremy.j.livingston@gmail.com" + } + ], + "description": "Captcha generator", + "homepage": "https://github.com/Gregwar/Captcha", + "keywords": [ + "bot", + "captcha", + "spam" + ], + "install-path": "../gregwar/captcha" }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + { + "name": "ivopetkov/html5-dom-document-php", + "version": "v2.4.0", + "version_normalized": "2.4.0.0", + "source": { + "type": "git", + "url": "https://github.com/ivopetkov/html5-dom-document-php.git", + "reference": "32c5ba748d661a9654c190bf70ce2854eaf5ad22" }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Finds files and directories via an intuitive fluent interface", - "homepage": "https://symfony.com", - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ivopetkov/html5-dom-document-php/zipball/32c5ba748d661a9654c190bf70ce2854eaf5ad22", + "reference": "32c5ba748d661a9654c190bf70ce2854eaf5ad22", + "shasum": "" }, - { - "url": "https://github.com/fabpot", - "type": "github" + "require": { + "ext-dom": "*", + "php": "7.0.*|7.1.*|7.2.*|7.3.*|7.4.*|8.0.*|8.1.*|8.2.*" }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ] - } -] + "require-dev": { + "ivopetkov/docs-generator": "1.*" + }, + "time": "2022-12-17T00:20:55+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "autoload.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ivo Petkov", + "email": "ivo@ivopetkov.com", + "homepage": "http://ivopetkov.com" + } + ], + "description": "HTML5 DOMDocument PHP library (extends DOMDocument)", + "support": { + "issues": "https://github.com/ivopetkov/html5-dom-document-php/issues", + "source": "https://github.com/ivopetkov/html5-dom-document-php/tree/v2.4.0" + }, + "install-path": "../ivopetkov/html5-dom-document-php" + }, + { + "name": "php-curl-class/php-curl-class", + "version": "2.0.0", + "version_normalized": "2.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-curl-class/php-curl-class.git", + "reference": "24a93bdc51058ad50d219842b63f7f2e0cb350ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-curl-class/php-curl-class/zipball/24a93bdc51058ad50d219842b63f7f2e0cb350ac", + "reference": "24a93bdc51058ad50d219842b63f7f2e0cb350ac", + "shasum": "" + }, + "time": "2014-04-12T09:46:33+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "description": "PHP Curl Class is an object-oriented wrapper of the PHP cURL extension.", + "support": { + "issues": "https://github.com/php-curl-class/php-curl-class/issues", + "source": "https://github.com/php-curl-class/php-curl-class/tree/2.0.0" + }, + "install-path": "../php-curl-class/php-curl-class" + }, + { + "name": "phpmailer/phpmailer", + "version": "v6.6.5", + "version_normalized": "6.6.5.0", + "source": { + "type": "git", + "url": "https://github.com/PHPMailer/PHPMailer.git", + "reference": "8b6386d7417526d1ea4da9edb70b8352f7543627" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/8b6386d7417526d1ea4da9edb70b8352f7543627", + "reference": "8b6386d7417526d1ea4da9edb70b8352f7543627", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "php": ">=5.5.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "doctrine/annotations": "^1.2", + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^9.3.5", + "roave/security-advisories": "dev-latest", + "squizlabs/php_codesniffer": "^3.6.2", + "yoast/phpunit-polyfills": "^1.0.0" + }, + "suggest": { + "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", + "league/oauth2-google": "Needed for Google XOAUTH2 authentication", + "psr/log": "For optional PSR-3 debug logging", + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", + "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" + }, + "time": "2022-10-07T12:23:10+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "PHPMailer\\PHPMailer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-only" + ], + "authors": [ + { + "name": "Marcus Bointon", + "email": "phpmailer@synchromedia.co.uk" + }, + { + "name": "Jim Jagielski", + "email": "jimjag@gmail.com" + }, + { + "name": "Andy Prevost", + "email": "codeworxtech@users.sourceforge.net" + }, + { + "name": "Brent R. Matzelle" + } + ], + "description": "PHPMailer is a full-featured email creation and transfer class for PHP", + "funding": [ + { + "url": "https://github.com/Synchro", + "type": "github" + } + ], + "install-path": "../phpmailer/phpmailer" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.2", + "version_normalized": "2.5.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "time": "2022-01-02T09:53:40+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/deprecation-contracts" + }, + { + "name": "symfony/finder", + "version": "v5.4.11", + "version_normalized": "5.4.11.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/7872a66f57caffa2916a584db1aa7f12adc76f8c", + "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16" + }, + "time": "2022-07-29T07:37:50+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/finder" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.26.0", + "version_normalized": "1.26.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "time": "2022-05-10T07:21:04+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php80" + } + ], + "dev": true, + "dev-package-names": [] +} diff --git a/web/app/vendor/composer/installed.php b/web/app/vendor/composer/installed.php new file mode 100644 index 0000000..32b055e --- /dev/null +++ b/web/app/vendor/composer/installed.php @@ -0,0 +1,104 @@ + array( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'reference' => 'd6997b84758fbe52d9d90a2d5fe2f2e06806b176', + 'name' => '__root__', + 'dev' => true, + ), + 'versions' => array( + '__root__' => array( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'reference' => 'd6997b84758fbe52d9d90a2d5fe2f2e06806b176', + 'dev_requirement' => false, + ), + 'erusev/parsedown' => array( + 'pretty_version' => '1.7.4', + 'version' => '1.7.4.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../erusev/parsedown', + 'aliases' => array(), + 'reference' => 'cb17b6477dfff935958ba01325f2e8a2bfa6dab3', + 'dev_requirement' => false, + ), + 'ezyang/htmlpurifier' => array( + 'pretty_version' => 'v4.16.0', + 'version' => '4.16.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../ezyang/htmlpurifier', + 'aliases' => array(), + 'reference' => '523407fb06eb9e5f3d59889b3978d5bfe94299c8', + 'dev_requirement' => false, + ), + 'gregwar/captcha' => array( + 'pretty_version' => 'v1.1.9', + 'version' => '1.1.9.0', + 'type' => 'captcha', + 'install_path' => __DIR__ . '/../gregwar/captcha', + 'aliases' => array(), + 'reference' => '4bb668e6b40e3205a020ca5ee4ca8cff8b8780c5', + 'dev_requirement' => false, + ), + 'ivopetkov/html5-dom-document-php' => array( + 'pretty_version' => 'v2.4.0', + 'version' => '2.4.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../ivopetkov/html5-dom-document-php', + 'aliases' => array(), + 'reference' => '32c5ba748d661a9654c190bf70ce2854eaf5ad22', + 'dev_requirement' => false, + ), + 'php-curl-class/php-curl-class' => array( + 'pretty_version' => '2.0.0', + 'version' => '2.0.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../php-curl-class/php-curl-class', + 'aliases' => array(), + 'reference' => '24a93bdc51058ad50d219842b63f7f2e0cb350ac', + 'dev_requirement' => false, + ), + 'phpmailer/phpmailer' => array( + 'pretty_version' => 'v6.6.5', + 'version' => '6.6.5.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../phpmailer/phpmailer', + 'aliases' => array(), + 'reference' => '8b6386d7417526d1ea4da9edb70b8352f7543627', + 'dev_requirement' => false, + ), + 'symfony/deprecation-contracts' => array( + 'pretty_version' => 'v2.5.2', + 'version' => '2.5.2.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', + 'aliases' => array(), + 'reference' => 'e8b495ea28c1d97b5e0c121748d6f9b53d075c66', + 'dev_requirement' => false, + ), + 'symfony/finder' => array( + 'pretty_version' => 'v5.4.11', + 'version' => '5.4.11.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/finder', + 'aliases' => array(), + 'reference' => '7872a66f57caffa2916a584db1aa7f12adc76f8c', + 'dev_requirement' => false, + ), + 'symfony/polyfill-php80' => array( + 'pretty_version' => 'v1.26.0', + 'version' => '1.26.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php80', + 'aliases' => array(), + 'reference' => 'cfa0ae98841b9e461207c13ab093d76b0fa7bace', + 'dev_requirement' => false, + ), + ), +); diff --git a/web/app/vendor/composer/platform_check.php b/web/app/vendor/composer/platform_check.php new file mode 100644 index 0000000..a8b98d5 --- /dev/null +++ b/web/app/vendor/composer/platform_check.php @@ -0,0 +1,26 @@ += 70205)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 7.2.5". You are running ' . PHP_VERSION . '.'; +} + +if ($issues) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); + } elseif (!headers_sent()) { + echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; + } + } + trigger_error( + 'Composer detected issues in your platform: ' . implode(' ', $issues), + E_USER_ERROR + ); +} diff --git a/web/app/vendor/ivopetkov/html5-dom-document-php/LICENSE b/web/app/vendor/ivopetkov/html5-dom-document-php/LICENSE new file mode 100644 index 0000000..c640f81 --- /dev/null +++ b/web/app/vendor/ivopetkov/html5-dom-document-php/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) Ivo Petkov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/web/app/vendor/ivopetkov/html5-dom-document-php/autoload.php b/web/app/vendor/ivopetkov/html5-dom-document-php/autoload.php new file mode 100644 index 0000000..276ed48 --- /dev/null +++ b/web/app/vendor/ivopetkov/html5-dom-document-php/autoload.php @@ -0,0 +1,22 @@ + __DIR__ . '/src/HTML5DOMDocument.php', + 'IvoPetkov\HTML5DOMDocument\Internal\QuerySelectors' => __DIR__ . '/src/HTML5DOMDocument/Internal/QuerySelectors.php', + 'IvoPetkov\HTML5DOMElement' => __DIR__ . '/src/HTML5DOMElement.php', + 'IvoPetkov\HTML5DOMNodeList' => __DIR__ . '/src/HTML5DOMNodeList.php', + 'IvoPetkov\HTML5DOMTokenList' => __DIR__ . '/src/HTML5DOMTokenList.php' +); + +spl_autoload_register(function ($class) use ($classes) { + if (isset($classes[$class])) { + require $classes[$class]; + } +}); diff --git a/web/app/vendor/ivopetkov/html5-dom-document-php/composer.json b/web/app/vendor/ivopetkov/html5-dom-document-php/composer.json new file mode 100644 index 0000000..11aa395 --- /dev/null +++ b/web/app/vendor/ivopetkov/html5-dom-document-php/composer.json @@ -0,0 +1,24 @@ +{ + "name": "ivopetkov/html5-dom-document-php", + "description": "HTML5 DOMDocument PHP library (extends DOMDocument)", + "license": "MIT", + "authors": [ + { + "name": "Ivo Petkov", + "email": "ivo@ivopetkov.com", + "homepage": "http://ivopetkov.com" + } + ], + "require": { + "php": "7.0.*|7.1.*|7.2.*|7.3.*|7.4.*|8.0.*|8.1.*|8.2.*", + "ext-dom": "*" + }, + "require-dev": { + "ivopetkov/docs-generator": "1.*" + }, + "autoload": { + "files": [ + "autoload.php" + ] + } +} \ No newline at end of file diff --git a/web/app/vendor/ivopetkov/html5-dom-document-php/src/HTML5DOMDocument.php b/web/app/vendor/ivopetkov/html5-dom-document-php/src/HTML5DOMDocument.php new file mode 100644 index 0000000..b561f93 --- /dev/null +++ b/web/app/vendor/ivopetkov/html5-dom-document-php/src/HTML5DOMDocument.php @@ -0,0 +1,747 @@ +registerNodeClass('DOMElement', '\IvoPetkov\HTML5DOMElement'); + } + + /** + * Load HTML from a string. + * + * @param string $source The HTML code. + * @param int $options Additional Libxml parameters. + * @return boolean TRUE on success or FALSE on failure. + */ + public function loadHTML($source, $options = 0) + { + // Enables libxml errors handling + $internalErrorsOptionValue = libxml_use_internal_errors(); + if ($internalErrorsOptionValue === false) { + libxml_use_internal_errors(true); + } + + $source = trim($source); + + // Add CDATA around script tags content + $matches = null; + preg_match_all('//', $source, $matches); + if (isset($matches[0])) { + $matches[0] = array_unique($matches[0]); + foreach ($matches[0] as $match) { + if (substr($match, -2, 1) !== '/') { // check if ends with /> + $source = str_replace($match, $match . '', '-html5-dom-document-internal-cdata]]>', $source); // Add CDATA before the end tag + $source = str_replace('', '', $source); // Clean empty script tags + $matches = null; + preg_match_all('/\/s', $source, $matches); + if (isset($matches[0])) { + $matches[0] = array_unique($matches[0]); + foreach ($matches[0] as $match) { + if (strpos($match, '/', $source) === 0 && preg_match('/\/', $source) === 0 && preg_match('/\/', $source) === 0 && preg_match('/\/', $source) === 0) { + $source = '' . $source . ''; + } + + // Add DOCTYPE if missing + if ($autoAddDoctype && strtoupper(substr($source, 0, 9)) !== '\n" . $source; + } + + // Adds temporary head tag + $charsetTag = ''; + $matches = []; + preg_match('/\/', $source, $matches); + $removeHeadTag = false; + $removeHtmlTag = false; + if (isset($matches[0])) { // has head tag + $insertPosition = strpos($source, $matches[0]) + strlen($matches[0]); + $source = substr($source, 0, $insertPosition) . $charsetTag . substr($source, $insertPosition); + } else { + $matches = []; + preg_match('/\/', $source, $matches); + if (isset($matches[0])) { // has html tag + $source = str_replace($matches[0], $matches[0] . '' . $charsetTag . '', $source); + } else { + $source = '' . $charsetTag . '' . $source; + $removeHtmlTag = true; + } + $removeHeadTag = true; + } + + // Preserve html entities + $source = preg_replace('/&([a-zA-Z]*);/', 'html5-dom-document-internal-entity1-$1-end', $source); + $source = preg_replace('/&#([0-9]*);/', 'html5-dom-document-internal-entity2-$1-end', $source); + + $result = parent::loadHTML('' . $source, $options); + if ($internalErrorsOptionValue === false) { + libxml_use_internal_errors(false); + } + if ($result === false) { + return false; + } + $this->encoding = 'utf-8'; + foreach ($this->childNodes as $item) { + if ($item->nodeType === XML_PI_NODE) { + $this->removeChild($item); + break; + } + } + /** @var HTML5DOMElement|null */ + $metaTagElement = $this->getElementsByTagName('meta')->item(0); + if ($metaTagElement !== null) { + if ($metaTagElement->getAttribute('data-html5-dom-document-internal-attribute') === 'charset-meta') { + $headElement = $metaTagElement->parentNode; + $htmlElement = $headElement->parentNode; + $metaTagElement->parentNode->removeChild($metaTagElement); + if ($removeHeadTag && $headElement !== null && $headElement->parentNode !== null && ($headElement->firstChild === null || ($headElement->childNodes->length === 1 && $headElement->firstChild instanceof \DOMText))) { + $headElement->parentNode->removeChild($headElement); + } + if ($removeHtmlTag && $htmlElement !== null && $htmlElement->parentNode !== null && $htmlElement->firstChild === null) { + $htmlElement->parentNode->removeChild($htmlElement); + } + } + } + + if (!$allowDuplicateIDs) { + $matches = []; + preg_match_all('/\sid[\s]*=[\s]*(["\'])(.*?)\1/', $source, $matches); + if (!empty($matches[2]) && max(array_count_values($matches[2])) > 1) { + $elementIDs = []; + $walkChildren = function ($element) use (&$walkChildren, &$elementIDs) { + foreach ($element->childNodes as $child) { + if ($child instanceof \DOMElement) { + if ($child->attributes->length > 0) { // Performance optimization + $id = $child->getAttribute('id'); + if ($id !== '') { + if (isset($elementIDs[$id])) { + throw new \Exception('A DOM node with an ID value "' . $id . '" already exists! Pass the HTML5DOMDocument::ALLOW_DUPLICATE_IDS option to disable this check.'); + } else { + $elementIDs[$id] = true; + } + } + } + $walkChildren($child); + } + } + }; + $walkChildren($this); + } + } + + $this->loaded = true; + return true; + } + + /** + * Load HTML from a file. + * + * @param string $filename The path to the HTML file. + * @param int $options Additional Libxml parameters. + */ + public function loadHTMLFile($filename, $options = 0) + { + return $this->loadHTML(file_get_contents($filename), $options); + } + + /** + * Adds the HTML tag to the document if missing. + * + * @return boolean TRUE on success, FALSE otherwise. + */ + private function addHtmlElementIfMissing(): bool + { + if ($this->getElementsByTagName('html')->length === 0) { + if (!isset(self::$newObjectsCache['htmlelement'])) { + self::$newObjectsCache['htmlelement'] = new \DOMElement('html'); + } + $this->appendChild(clone (self::$newObjectsCache['htmlelement'])); + return true; + } + return false; + } + + /** + * Adds the HEAD tag to the document if missing. + * + * @return boolean TRUE on success, FALSE otherwise. + */ + private function addHeadElementIfMissing(): bool + { + if ($this->getElementsByTagName('head')->length === 0) { + $htmlElement = $this->getElementsByTagName('html')->item(0); + if (!isset(self::$newObjectsCache['headelement'])) { + self::$newObjectsCache['headelement'] = new \DOMElement('head'); + } + $headElement = clone (self::$newObjectsCache['headelement']); + if ($htmlElement->firstChild === null) { + $htmlElement->appendChild($headElement); + } else { + $htmlElement->insertBefore($headElement, $htmlElement->firstChild); + } + return true; + } + return false; + } + + /** + * Adds the BODY tag to the document if missing. + * + * @return boolean TRUE on success, FALSE otherwise. + */ + private function addBodyElementIfMissing(): bool + { + if ($this->getElementsByTagName('body')->length === 0) { + if (!isset(self::$newObjectsCache['bodyelement'])) { + self::$newObjectsCache['bodyelement'] = new \DOMElement('body'); + } + $this->getElementsByTagName('html')->item(0)->appendChild(clone (self::$newObjectsCache['bodyelement'])); + return true; + } + return false; + } + + /** + * Dumps the internal document into a string using HTML formatting. + * + * @param \DOMNode $node Optional parameter to output a subset of the document. + * @return string The document (or node) HTML code as string. + */ + public function saveHTML(\DOMNode $node = null): string + { + $nodeMode = $node !== null; + if ($nodeMode && $node instanceof \DOMDocument) { + $nodeMode = false; + } + + if ($nodeMode) { + if (!isset(self::$newObjectsCache['html5domdocument'])) { + self::$newObjectsCache['html5domdocument'] = new HTML5DOMDocument(); + } + $tempDomDocument = clone (self::$newObjectsCache['html5domdocument']); + if ($node->nodeName === 'html') { + $tempDomDocument->loadHTML(''); + $tempDomDocument->appendChild($tempDomDocument->importNode(clone ($node), true)); + $html = $tempDomDocument->saveHTML(); + $html = substr($html, 16); // remove the DOCTYPE + the new line after + } elseif ($node->nodeName === 'head' || $node->nodeName === 'body') { + $tempDomDocument->loadHTML("\n"); + $tempDomDocument->childNodes[1]->appendChild($tempDomDocument->importNode(clone ($node), true)); + $html = $tempDomDocument->saveHTML(); + $html = substr($html, 22, -7); // remove the DOCTYPE + the new line after + html tag + } else { + $isInHead = false; + $parentNode = $node; + for ($i = 0; $i < 1000; $i++) { + $parentNode = $parentNode->parentNode; + if ($parentNode === null) { + break; + } + if ($parentNode->nodeName === 'body') { + break; + } elseif ($parentNode->nodeName === 'head') { + $isInHead = true; + break; + } + } + $tempDomDocument->loadHTML("\n" . ($isInHead ? '' : '') . ''); + $tempDomDocument->childNodes[1]->childNodes[0]->appendChild($tempDomDocument->importNode(clone ($node), true)); + $html = $tempDomDocument->saveHTML(); + $html = substr($html, 28, -14); // remove the DOCTYPE + the new line + html + body or head tags + } + $html = trim($html); + } else { + $removeHtmlElement = false; + $removeHeadElement = false; + $headElement = $this->getElementsByTagName('head')->item(0); + if ($headElement === null) { + if ($this->addHtmlElementIfMissing()) { + $removeHtmlElement = true; + } + if ($this->addHeadElementIfMissing()) { + $removeHeadElement = true; + } + $headElement = $this->getElementsByTagName('head')->item(0); + } + $meta = $this->createElement('meta'); + $meta->setAttribute('data-html5-dom-document-internal-attribute', 'charset-meta'); + $meta->setAttribute('http-equiv', 'content-type'); + $meta->setAttribute('content', 'text/html; charset=utf-8'); + if ($headElement->firstChild !== null) { + $headElement->insertBefore($meta, $headElement->firstChild); + } else { + $headElement->appendChild($meta); + } + $html = parent::saveHTML(); + $html = rtrim($html, "\n"); + + if ($removeHeadElement) { + $headElement->parentNode->removeChild($headElement); + } else { + $meta->parentNode->removeChild($meta); + } + + if (strpos($html, 'html5-dom-document-internal-entity') !== false) { + $html = preg_replace('/html5-dom-document-internal-entity1-(.*?)-end/', '&$1;', $html); + $html = preg_replace('/html5-dom-document-internal-entity2-(.*?)-end/', '&#$1;', $html); + } + + $codeToRemove = [ + 'html5-dom-document-internal-content', + '', + '', '', '
    ', '', '', '', '', '', '', '', '', '', '', '', '', '', + '', '-html5-dom-document-internal-cdata-endtagfix' + ]; + if ($removeHeadElement) { + $codeToRemove[] = ''; + } + if ($removeHtmlElement) { + $codeToRemove[] = ''; + } + + $html = str_replace($codeToRemove, '', $html); + } + return $html; + } + + /** + * Dumps the internal document into a file using HTML formatting. + * + * @param string $filename The path to the saved HTML document. + * @return int|false the number of bytes written or FALSE if an error occurred. + */ + #[\ReturnTypeWillChange] // Return type "int|false" is invalid in older supported versions. + public function saveHTMLFile($filename) + { + if (!is_writable($filename)) { + return false; + } + $result = $this->saveHTML(); + file_put_contents($filename, $result); + $bytesWritten = filesize($filename); + if ($bytesWritten === strlen($result)) { + return $bytesWritten; + } + return false; + } + + /** + * Returns the first document element matching the selector. + * + * @param string $selector A CSS query selector. Available values: *, tagname, tagname#id, #id, tagname.classname, .classname, tagname.classname.classname2, .classname.classname2, tagname[attribute-selector], [attribute-selector], "div, p", div p, div > p, div + p and p ~ ul. + * @return HTML5DOMElement|null The result DOMElement or null if not found. + * @throws \InvalidArgumentException + */ + public function querySelector(string $selector) + { + return $this->internalQuerySelector($selector); + } + + /** + * Returns a list of document elements matching the selector. + * + * @param string $selector A CSS query selector. Available values: *, tagname, tagname#id, #id, tagname.classname, .classname, tagname.classname.classname2, .classname.classname2, tagname[attribute-selector], [attribute-selector], "div, p", div p, div > p, div + p and p ~ ul. + * @return HTML5DOMNodeList Returns a list of DOMElements matching the criteria. + * @throws \InvalidArgumentException + */ + public function querySelectorAll(string $selector) + { + return $this->internalQuerySelectorAll($selector); + } + + /** + * Creates an element that will be replaced by the new body in insertHTML. + * + * @param string $name The name of the insert target. + * @return HTML5DOMElement A new DOMElement that must be set in the place where the new body will be inserted. + */ + public function createInsertTarget(string $name) + { + if (!$this->loaded) { + $this->loadHTML(''); + } + $element = $this->createElement('html5-dom-document-insert-target'); + $element->setAttribute('name', $name); + return $element; + } + + /** + * Inserts a HTML document into the current document. The elements from the head and the body will be moved to their proper locations. + * + * @param string $source The HTML code to be inserted. + * @param string $target Body target position. Available values: afterBodyBegin, beforeBodyEnd or insertTarget name. + */ + public function insertHTML(string $source, string $target = 'beforeBodyEnd') + { + $this->insertHTMLMulti([['source' => $source, 'target' => $target]]); + } + + /** + * Inserts multiple HTML documents into the current document. The elements from the head and the body will be moved to their proper locations. + * + * @param array $sources An array containing the source of the document to be inserted in the following format: [ ['source'=>'', 'target'=>''], ['source'=>'', 'target'=>''], ... ] + * @throws \Exception + */ + public function insertHTMLMulti(array $sources) + { + if (!$this->loaded) { + $this->loadHTML(''); + } + + if (!isset(self::$newObjectsCache['html5domdocument'])) { + self::$newObjectsCache['html5domdocument'] = new HTML5DOMDocument(); + } + + $currentDomDocument = &$this; + + $copyAttributes = function ($sourceNode, $targetNode) { + foreach ($sourceNode->attributes as $attributeName => $attribute) { + $targetNode->setAttribute($attributeName, $attribute->value); + } + }; + + $currentDomHTMLElement = null; + $currentDomHeadElement = null; + $currentDomBodyElement = null; + + $insertTargetsList = null; + $prepareInsertTargetsList = function () use (&$insertTargetsList) { + if ($insertTargetsList === null) { + $insertTargetsList = []; + $targetElements = $this->getElementsByTagName('html5-dom-document-insert-target'); + foreach ($targetElements as $targetElement) { + $insertTargetsList[$targetElement->getAttribute('name')] = $targetElement; + } + } + }; + + foreach ($sources as $sourceData) { + if (!isset($sourceData['source'])) { + throw new \Exception('Missing source key'); + } + $source = $sourceData['source']; + $target = isset($sourceData['target']) ? $sourceData['target'] : 'beforeBodyEnd'; + + $domDocument = clone (self::$newObjectsCache['html5domdocument']); + $domDocument->loadHTML($source, self::ALLOW_DUPLICATE_IDS); + + $htmlElement = $domDocument->getElementsByTagName('html')->item(0); + if ($htmlElement !== null) { + if ($htmlElement->attributes->length > 0) { + if ($currentDomHTMLElement === null) { + $currentDomHTMLElement = $this->getElementsByTagName('html')->item(0); + if ($currentDomHTMLElement === null) { + $this->addHtmlElementIfMissing(); + $currentDomHTMLElement = $this->getElementsByTagName('html')->item(0); + } + } + $copyAttributes($htmlElement, $currentDomHTMLElement); + } + } + + $headElement = $domDocument->getElementsByTagName('head')->item(0); + if ($headElement !== null) { + if ($currentDomHeadElement === null) { + $currentDomHeadElement = $this->getElementsByTagName('head')->item(0); + if ($currentDomHeadElement === null) { + $this->addHtmlElementIfMissing(); + $this->addHeadElementIfMissing(); + $currentDomHeadElement = $this->getElementsByTagName('head')->item(0); + } + } + foreach ($headElement->childNodes as $headElementChild) { + $newNode = $currentDomDocument->importNode($headElementChild, true); + if ($newNode !== null) { + $currentDomHeadElement->appendChild($newNode); + } + } + if ($headElement->attributes->length > 0) { + $copyAttributes($headElement, $currentDomHeadElement); + } + } + + $bodyElement = $domDocument->getElementsByTagName('body')->item(0); + if ($bodyElement !== null) { + if ($currentDomBodyElement === null) { + $currentDomBodyElement = $this->getElementsByTagName('body')->item(0); + if ($currentDomBodyElement === null) { + $this->addHtmlElementIfMissing(); + $this->addBodyElementIfMissing(); + $currentDomBodyElement = $this->getElementsByTagName('body')->item(0); + } + } + $bodyElementChildren = $bodyElement->childNodes; + if ($target === 'afterBodyBegin') { + $bodyElementChildrenCount = $bodyElementChildren->length; + for ($i = $bodyElementChildrenCount - 1; $i >= 0; $i--) { + $newNode = $currentDomDocument->importNode($bodyElementChildren->item($i), true); + if ($newNode !== null) { + if ($currentDomBodyElement->firstChild === null) { + $currentDomBodyElement->appendChild($newNode); + } else { + $currentDomBodyElement->insertBefore($newNode, $currentDomBodyElement->firstChild); + } + } + } + } elseif ($target === 'beforeBodyEnd') { + foreach ($bodyElementChildren as $bodyElementChild) { + $newNode = $currentDomDocument->importNode($bodyElementChild, true); + if ($newNode !== null) { + $currentDomBodyElement->appendChild($newNode); + } + } + } else { + $prepareInsertTargetsList(); + if (isset($insertTargetsList[$target])) { + $targetElement = $insertTargetsList[$target]; + $targetElementParent = $targetElement->parentNode; + foreach ($bodyElementChildren as $bodyElementChild) { + $newNode = $currentDomDocument->importNode($bodyElementChild, true); + if ($newNode !== null) { + $targetElementParent->insertBefore($newNode, $targetElement); + } + } + $targetElementParent->removeChild($targetElement); + } + } + if ($bodyElement->attributes->length > 0) { + $copyAttributes($bodyElement, $currentDomBodyElement); + } + } else { // clear the insert target when there is no body element + $prepareInsertTargetsList(); + if (isset($insertTargetsList[$target])) { + $targetElement = $insertTargetsList[$target]; + $targetElement->parentNode->removeChild($targetElement); + } + } + } + } + + /** + * Applies the modifications specified to the DOM document. + * + * @param int $modifications The modifications to apply. Available values: + * - HTML5DOMDocument::FIX_MULTIPLE_TITLES - removes all but the last title elements. + * - HTML5DOMDocument::FIX_DUPLICATE_METATAGS - removes all but the last metatags with matching name or property attributes. + * - HTML5DOMDocument::FIX_MULTIPLE_HEADS - merges multiple head elements. + * - HTML5DOMDocument::FIX_MULTIPLE_BODIES - merges multiple body elements. + * - HTML5DOMDocument::OPTIMIZE_HEAD - moves charset metatag and title elements first. + */ + public function modify($modifications = 0) + { + + $fixMultipleTitles = ($modifications & self::FIX_MULTIPLE_TITLES) !== 0; + $fixDuplicateMetatags = ($modifications & self::FIX_DUPLICATE_METATAGS) !== 0; + $fixMultipleHeads = ($modifications & self::FIX_MULTIPLE_HEADS) !== 0; + $fixMultipleBodies = ($modifications & self::FIX_MULTIPLE_BODIES) !== 0; + $optimizeHead = ($modifications & self::OPTIMIZE_HEAD) !== 0; + + /** @var \DOMNodeList */ + $headElements = $this->getElementsByTagName('head'); + + if ($fixMultipleHeads) { // Merges multiple head elements. + if ($headElements->length > 1) { + $firstHeadElement = $headElements->item(0); + while ($headElements->length > 1) { + $nextHeadElement = $headElements->item(1); + $nextHeadElementChildren = $nextHeadElement->childNodes; + $nextHeadElementChildrenCount = $nextHeadElementChildren->length; + for ($i = 0; $i < $nextHeadElementChildrenCount; $i++) { + $firstHeadElement->appendChild($nextHeadElementChildren->item(0)); + } + $nextHeadElement->parentNode->removeChild($nextHeadElement); + } + $headElements = [$firstHeadElement]; + } + } + + foreach ($headElements as $headElement) { + + if ($fixMultipleTitles) { // Remove all title elements except the last one. + $titleTags = $headElement->getElementsByTagName('title'); + $titleTagsCount = $titleTags->length; + for ($i = 0; $i < $titleTagsCount - 1; $i++) { + $node = $titleTags->item($i); + $node->parentNode->removeChild($node); + } + } + + if ($fixDuplicateMetatags) { // Remove all meta tags that has matching name or property attributes. + $metaTags = $headElement->getElementsByTagName('meta'); + if ($metaTags->length > 0) { + $list = []; + $idsList = []; + foreach ($metaTags as $metaTag) { + $id = $metaTag->getAttribute('name'); + if ($id !== '') { + $id = 'name:' . $id; + } else { + $id = $metaTag->getAttribute('property'); + if ($id !== '') { + $id = 'property:' . $id; + } else { + $id = $metaTag->getAttribute('charset'); + if ($id !== '') { + $id = 'charset'; + } + } + } + if (!isset($idsList[$id])) { + $idsList[$id] = 0; + } + $idsList[$id]++; + $list[] = [$metaTag, $id]; + } + foreach ($idsList as $id => $count) { + if ($count > 1 && $id !== '') { + foreach ($list as $i => $item) { + if ($item[1] === $id) { + $node = $item[0]; + $node->parentNode->removeChild($node); + unset($list[$i]); + $count--; + } + if ($count === 1) { + break; + } + } + } + } + } + } + + if ($optimizeHead) { // Moves charset metatag and title elements first. + $titleElement = $headElement->getElementsByTagName('title')->item(0); + $hasTitleElement = false; + if ($titleElement !== null && $titleElement->previousSibling !== null) { + $headElement->insertBefore($titleElement, $headElement->firstChild); + $hasTitleElement = true; + } + $metaTags = $headElement->getElementsByTagName('meta'); + $metaTagsLength = $metaTags->length; + if ($metaTagsLength > 0) { + $charsetMetaTag = null; + $nodesToMove = []; + for ($i = $metaTagsLength - 1; $i >= 0; $i--) { + $nodesToMove[$i] = $metaTags->item($i); + } + for ($i = $metaTagsLength - 1; $i >= 0; $i--) { + $nodeToMove = $nodesToMove[$i]; + if ($charsetMetaTag === null && $nodeToMove->getAttribute('charset') !== '') { + $charsetMetaTag = $nodeToMove; + } + $referenceNode = $headElement->childNodes->item($hasTitleElement ? 1 : 0); + if ($nodeToMove !== $referenceNode) { + $headElement->insertBefore($nodeToMove, $referenceNode); + } + } + if ($charsetMetaTag !== null && $charsetMetaTag->previousSibling !== null) { + $headElement->insertBefore($charsetMetaTag, $headElement->firstChild); + } + } + } + } + + if ($fixMultipleBodies) { // Merges multiple body elements. + $bodyElements = $this->getElementsByTagName('body'); + if ($bodyElements->length > 1) { + $firstBodyElement = $bodyElements->item(0); + while ($bodyElements->length > 1) { + $nextBodyElement = $bodyElements->item(1); + $nextBodyElementChildren = $nextBodyElement->childNodes; + $nextBodyElementChildrenCount = $nextBodyElementChildren->length; + for ($i = 0; $i < $nextBodyElementChildrenCount; $i++) { + $firstBodyElement->appendChild($nextBodyElementChildren->item(0)); + } + $nextBodyElement->parentNode->removeChild($nextBodyElement); + } + } + } + } +} diff --git a/web/app/vendor/ivopetkov/html5-dom-document-php/src/HTML5DOMDocument/Internal/QuerySelectors.php b/web/app/vendor/ivopetkov/html5-dom-document-php/src/HTML5DOMDocument/Internal/QuerySelectors.php new file mode 100644 index 0000000..b99ffd0 --- /dev/null +++ b/web/app/vendor/ivopetkov/html5-dom-document-php/src/HTML5DOMDocument/Internal/QuerySelectors.php @@ -0,0 +1,514 @@ +internalQuerySelectorAll($selector, 1); + return $result->item(0); + } + + /** + * Returns a list of document elements matching the selector. + * + * @param string $selector A CSS query selector. Available values: *, tagname, tagname#id, #id, tagname.classname, .classname, tagname[attribute-selector] and [attribute-selector]. + * @param int|null $preferredLimit Preferred maximum number of elements to return. + * @return DOMNodeList Returns a list of DOMElements matching the criteria. + * @throws \InvalidArgumentException + */ + private function internalQuerySelectorAll(string $selector, $preferredLimit = null) + { + $selector = trim($selector); + + $cache = []; + $walkChildren = function (\DOMNode $context, $tagNames, callable $callback) use (&$cache) { + if (!empty($tagNames)) { + $children = []; + foreach ($tagNames as $tagName) { + $elements = $context->getElementsByTagName($tagName); + foreach ($elements as $element) { + $children[] = $element; + } + } + } else { + $getChildren = function () use ($context) { + $result = []; + $process = function (\DOMNode $node) use (&$process, &$result) { + foreach ($node->childNodes as $child) { + if ($child instanceof \DOMElement) { + $result[] = $child; + $process($child); + } + } + }; + $process($context); + return $result; + }; + if ($this === $context) { + $cacheKey = 'walk_children'; + if (!isset($cache[$cacheKey])) { + $cache[$cacheKey] = $getChildren(); + } + $children = $cache[$cacheKey]; + } else { + $children = $getChildren(); + } + } + foreach ($children as $child) { + if ($callback($child) === true) { + return true; + } + } + }; + + $getElementById = function (\DOMNode $context, $id, $tagName) use (&$walkChildren) { + if ($context instanceof \DOMDocument) { + $element = $context->getElementById($id); + if ($element && ($tagName === null || $element->tagName === $tagName)) { + return $element; + } + } else { + $foundElement = null; + $walkChildren($context, $tagName !== null ? [$tagName] : null, function ($element) use ($id, &$foundElement) { + if ($element->attributes->length > 0 && $element->getAttribute('id') === $id) { + $foundElement = $element; + return true; + } + }); + return $foundElement; + } + return null; + }; + + $simpleSelectors = []; + + // all + $simpleSelectors['\*'] = function (string $mode, array $matches, \DOMNode $context, callable $add = null) use ($walkChildren) { + if ($mode === 'validate') { + return true; + } else { + $walkChildren($context, [], function ($element) use ($add) { + if ($add($element)) { + return true; + } + }); + } + }; + + // tagname + $simpleSelectors['[a-zA-Z0-9\-]+'] = function (string $mode, array $matches, \DOMNode $context, callable $add = null) use ($walkChildren) { + $tagNames = []; + foreach ($matches as $match) { + $tagNames[] = strtolower($match[0]); + } + if ($mode === 'validate') { + return array_search($context->tagName, $tagNames) !== false; + } + $walkChildren($context, $tagNames, function ($element) use ($add) { + if ($add($element)) { + return true; + } + }); + }; + + // tagname[target] or [target] // Available values for targets: attr, attr="value", attr~="value", attr|="value", attr^="value", attr$="value", attr*="value" + $simpleSelectors['(?:[a-zA-Z0-9\-]*)(?:\[.+?\])'] = function (string $mode, array $matches, \DOMNode $context, callable $add = null) use ($walkChildren) { + $run = function ($match) use ($mode, $context, $add, $walkChildren) { + $attributeSelectors = explode('][', substr($match[2], 1, -1)); + foreach ($attributeSelectors as $i => $attributeSelector) { + $attributeSelectorMatches = null; + if (preg_match('/^(.+?)(=|~=|\|=|\^=|\$=|\*=)\"(.+?)\"$/', $attributeSelector, $attributeSelectorMatches) === 1) { + $attributeSelectors[$i] = [ + 'name' => strtolower($attributeSelectorMatches[1]), + 'value' => $attributeSelectorMatches[3], + 'operator' => $attributeSelectorMatches[2] + ]; + } else { + $attributeSelectors[$i] = [ + 'name' => $attributeSelector + ]; + } + } + $tagName = strlen($match[1]) > 0 ? strtolower($match[1]) : null; + $check = function ($element) use ($attributeSelectors) { + if ($element->attributes->length > 0) { + foreach ($attributeSelectors as $attributeSelector) { + $isMatch = false; + $attributeValue = $element->getAttribute($attributeSelector['name']); + if (isset($attributeSelector['value'])) { + $valueToMatch = $attributeSelector['value']; + switch ($attributeSelector['operator']) { + case '=': + if ($attributeValue === $valueToMatch) { + $isMatch = true; + } + break; + case '~=': + $words = preg_split("/[\s]+/", $attributeValue); + if (array_search($valueToMatch, $words) !== false) { + $isMatch = true; + } + break; + + case '|=': + if ($attributeValue === $valueToMatch || strpos($attributeValue, $valueToMatch . '-') === 0) { + $isMatch = true; + } + break; + + case '^=': + if (strpos($attributeValue, $valueToMatch) === 0) { + $isMatch = true; + } + break; + + case '$=': + if (substr($attributeValue, -strlen($valueToMatch)) === $valueToMatch) { + $isMatch = true; + } + break; + + case '*=': + if (strpos($attributeValue, $valueToMatch) !== false) { + $isMatch = true; + } + break; + } + } else { + if ($attributeValue !== '') { + $isMatch = true; + } + } + if (!$isMatch) { + return false; + } + } + return true; + } + return false; + }; + if ($mode === 'validate') { + return ($tagName === null ? true : $context->tagName === $tagName) && $check($context); + } else { + $walkChildren($context, $tagName !== null ? [$tagName] : null, function ($element) use ($check, $add) { + if ($check($element)) { + if ($add($element)) { + return true; + } + } + }); + } + }; + // todo optimize + foreach ($matches as $match) { + if ($mode === 'validate') { + if ($run($match)) { + return true; + } + } else { + $run($match); + } + } + if ($mode === 'validate') { + return false; + } + }; + + // tagname#id or #id + $simpleSelectors['(?:[a-zA-Z0-9\-]*)#(?:[a-zA-Z0-9\-\_]+?)'] = function (string $mode, array $matches, \DOMNode $context, callable $add = null) use ($getElementById) { + $run = function ($match) use ($mode, $context, $add, $getElementById) { + $tagName = strlen($match[1]) > 0 ? strtolower($match[1]) : null; + $id = $match[2]; + if ($mode === 'validate') { + return ($tagName === null ? true : $context->tagName === $tagName) && $context->getAttribute('id') === $id; + } else { + $element = $getElementById($context, $id, $tagName); + if ($element) { + $add($element); + } + } + }; + // todo optimize + foreach ($matches as $match) { + if ($mode === 'validate') { + if ($run($match)) { + return true; + } + } else { + $run($match); + } + } + if ($mode === 'validate') { + return false; + } + }; + + // tagname.classname, .classname, tagname.classname.classname2, .classname.classname2 + $simpleSelectors['(?:[a-zA-Z0-9\-]*)\.(?:[a-zA-Z0-9\-\_\.]+?)'] = function (string $mode, array $matches, \DOMNode $context, callable $add = null) use ($walkChildren) { + $rawData = []; // Array containing [tag, classnames] + $tagNames = []; + foreach ($matches as $match) { + $tagName = strlen($match[1]) > 0 ? $match[1] : null; + $classes = explode('.', $match[2]); + if (empty($classes)) { + continue; + } + $rawData[] = [$tagName, $classes]; + if ($tagName !== null) { + $tagNames[] = $tagName; + } + } + $check = function ($element) use ($rawData) { + if ($element->attributes->length > 0) { + $classAttribute = ' ' . $element->getAttribute('class') . ' '; + $tagName = $element->tagName; + foreach ($rawData as $rawMatch) { + if ($rawMatch[0] !== null && $tagName !== $rawMatch[0]) { + continue; + } + $allClassesFound = true; + foreach ($rawMatch[1] as $class) { + if (strpos($classAttribute, ' ' . $class . ' ') === false) { + $allClassesFound = false; + break; + } + } + if ($allClassesFound) { + return true; + } + } + } + return false; + }; + if ($mode === 'validate') { + return $check($context); + } + $walkChildren($context, $tagNames, function ($element) use ($check, $add) { + if ($check($element)) { + if ($add($element)) { + return true; + } + } + }); + }; + + $isMatchingElement = function (\DOMNode $context, string $selector) use ($simpleSelectors) { + foreach ($simpleSelectors as $simpleSelector => $callback) { + $match = null; + if (preg_match('/^' . (str_replace('?:', '', $simpleSelector)) . '$/', $selector, $match) === 1) { + return call_user_func($callback, 'validate', [$match], $context); + } + } + }; + + $complexSelectors = []; + + $getMatchingElements = function (\DOMNode $context, string $selector, $preferredLimit = null) use (&$simpleSelectors, &$complexSelectors) { + + $processSelector = function (string $mode, string $selector, $operator = null) use (&$processSelector, $simpleSelectors, $complexSelectors, $context, $preferredLimit) { + $supportedSimpleSelectors = array_keys($simpleSelectors); + $supportedSimpleSelectorsExpression = '(?:(?:' . implode(')|(?:', $supportedSimpleSelectors) . '))'; + $supportedSelectors = $supportedSimpleSelectors; + $supportedComplexOperators = array_keys($complexSelectors); + if ($operator === null) { + $operator = ','; + foreach ($supportedComplexOperators as $complexOperator) { + array_unshift($supportedSelectors, '(?:(?:(?:' . $supportedSimpleSelectorsExpression . '\s*\\' . $complexOperator . '\s*))+' . $supportedSimpleSelectorsExpression . ')'); + } + } + $supportedSelectorsExpression = '(?:(?:' . implode(')|(?:', $supportedSelectors) . '))'; + + $vallidationExpression = '/^(?:(?:' . $supportedSelectorsExpression . '\s*\\' . $operator . '\s*))*' . $supportedSelectorsExpression . '$/'; + if (preg_match($vallidationExpression, $selector) !== 1) { + return false; + } + $selector .= $operator; // append the seprator at the back for easier matching below + + $result = []; + if ($mode === 'execute') { + $add = function ($element) use ($preferredLimit, &$result) { + $found = false; + foreach ($result as $addedElement) { + if ($addedElement === $element) { + $found = true; + break; + } + } + if (!$found) { + $result[] = $element; + if ($preferredLimit !== null && sizeof($result) >= $preferredLimit) { + return true; + } + } + return false; + }; + } + + $selectorsToCall = []; + $addSelectorToCall = function ($type, $selector, $argument) use (&$selectorsToCall) { + $previousIndex = sizeof($selectorsToCall) - 1; + // todo optimize complex too + if ($type === 1 && isset($selectorsToCall[$previousIndex]) && $selectorsToCall[$previousIndex][0] === $type && $selectorsToCall[$previousIndex][1] === $selector) { + $selectorsToCall[$previousIndex][2][] = $argument; + } else { + $selectorsToCall[] = [$type, $selector, [$argument]]; + } + }; + for ($i = 0; $i < 100000; $i++) { + $matches = null; + preg_match('/^(?' . $supportedSelectorsExpression . ')\s*\\' . $operator . '\s*/', $selector, $matches); // getting the next subselector + if (isset($matches['subselector'])) { + $subSelector = $matches['subselector']; + $selectorFound = false; + foreach ($simpleSelectors as $simpleSelector => $callback) { + $match = null; + if (preg_match('/^' . (str_replace('?:', '', $simpleSelector)) . '$/', $subSelector, $match) === 1) { // if simple selector + if ($mode === 'parse') { + $result[] = $match[0]; + } else { + $addSelectorToCall(1, $simpleSelector, $match); + //call_user_func($callback, 'execute', $match, $context, $add); + } + $selectorFound = true; + break; + } + } + if (!$selectorFound) { + foreach ($complexSelectors as $complexOperator => $callback) { + $subSelectorParts = $processSelector('parse', $subSelector, $complexOperator); + if ($subSelectorParts !== false) { + $addSelectorToCall(2, $complexOperator, $subSelectorParts); + //call_user_func($callback, $subSelectorParts, $context, $add); + $selectorFound = true; + break; + } + } + } + if (!$selectorFound) { + throw new \Exception('Internal error for selector "' . $selector . '"!'); + } + $selector = substr($selector, strlen($matches[0])); // remove the matched subselector and continue parsing + if (strlen($selector) === 0) { + break; + } + } + } + foreach ($selectorsToCall as $selectorToCall) { + if ($selectorToCall[0] === 1) { // is simple selector + call_user_func($simpleSelectors[$selectorToCall[1]], 'execute', $selectorToCall[2], $context, $add); + } else { // is complex selector + call_user_func($complexSelectors[$selectorToCall[1]], $selectorToCall[2][0], $context, $add); // todo optimize and send all arguments + } + } + return $result; + }; + + return $processSelector('execute', $selector); + }; + + // div p (space between) - all

    elements inside

    elements + $complexSelectors[' '] = function (array $parts, \DOMNode $context, callable $add = null) use (&$getMatchingElements) { + $elements = null; + foreach ($parts as $part) { + if ($elements === null) { + $elements = $getMatchingElements($context, $part); + } else { + $temp = []; + foreach ($elements as $element) { + $temp = array_merge($temp, $getMatchingElements($element, $part)); + } + $elements = $temp; + } + } + foreach ($elements as $element) { + $add($element); + } + }; + + // div > p - all

    elements where the parent is a

    element + $complexSelectors['>'] = function (array $parts, \DOMNode $context, callable $add = null) use (&$getMatchingElements, &$isMatchingElement) { + $elements = null; + foreach ($parts as $part) { + if ($elements === null) { + $elements = $getMatchingElements($context, $part); + } else { + $temp = []; + foreach ($elements as $element) { + foreach ($element->childNodes as $child) { + if ($child instanceof \DOMElement && $isMatchingElement($child, $part)) { + $temp[] = $child; + } + } + } + $elements = $temp; + } + } + foreach ($elements as $element) { + $add($element); + } + }; + + // div + p - all

    elements that are placed immediately after

    elements + $complexSelectors['+'] = function (array $parts, \DOMNode $context, callable $add = null) use (&$getMatchingElements, &$isMatchingElement) { + $elements = null; + foreach ($parts as $part) { + if ($elements === null) { + $elements = $getMatchingElements($context, $part); + } else { + $temp = []; + foreach ($elements as $element) { + if ($element->nextSibling !== null && $isMatchingElement($element->nextSibling, $part)) { + $temp[] = $element->nextSibling; + } + } + $elements = $temp; + } + } + foreach ($elements as $element) { + $add($element); + } + }; + + // p ~ ul - all
      elements that are preceded by a

      element + $complexSelectors['~'] = function (array $parts, \DOMNode $context, callable $add = null) use (&$getMatchingElements, &$isMatchingElement) { + $elements = null; + foreach ($parts as $part) { + if ($elements === null) { + $elements = $getMatchingElements($context, $part); + } else { + $temp = []; + foreach ($elements as $element) { + $nextSibling = $element->nextSibling; + while ($nextSibling !== null) { + if ($isMatchingElement($nextSibling, $part)) { + $temp[] = $nextSibling; + } + $nextSibling = $nextSibling->nextSibling; + } + } + $elements = $temp; + } + } + foreach ($elements as $element) { + $add($element); + } + }; + + $result = $getMatchingElements($this, $selector, $preferredLimit); + if ($result === false) { + throw new \InvalidArgumentException('Unsupported selector (' . $selector . ')'); + } + return new \IvoPetkov\HTML5DOMNodeList($result); + } +} diff --git a/web/app/vendor/ivopetkov/html5-dom-document-php/src/HTML5DOMElement.php b/web/app/vendor/ivopetkov/html5-dom-document-php/src/HTML5DOMElement.php new file mode 100644 index 0000000..6647c8c --- /dev/null +++ b/web/app/vendor/ivopetkov/html5-dom-document-php/src/HTML5DOMElement.php @@ -0,0 +1,240 @@ +firstChild === null) { + return ''; + } + $html = $this->ownerDocument->saveHTML($this); + $nodeName = $this->nodeName; + return preg_replace('@^<' . $nodeName . '[^>]*>|$@', '', $html); + } elseif ($name === 'outerHTML') { + if ($this->firstChild === null) { + $nodeName = $this->nodeName; + $attributes = $this->getAttributes(); + $result = '<' . $nodeName . ''; + foreach ($attributes as $name => $value) { + $result .= ' ' . $name . '="' . htmlentities($value) . '"'; + } + if (array_search($nodeName, ['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr']) === false) { + $result .= '>'; + } else { + $result .= '/>'; + } + return $result; + } + return $this->ownerDocument->saveHTML($this); + } elseif ($name === 'classList') { + if ($this->classList === null) { + $this->classList = new HTML5DOMTokenList($this, 'class'); + } + return $this->classList; + } + throw new \Exception('Undefined property: HTML5DOMElement::$' . $name); + } + + /** + * Sets the value for the property specified. + * + * @param string $name + * @param string $value + * @throws \Exception + */ + public function __set(string $name, $value) + { + if ($name === 'innerHTML') { + while ($this->hasChildNodes()) { + $this->removeChild($this->firstChild); + } + if (!isset(self::$newObjectsCache['html5domdocument'])) { + self::$newObjectsCache['html5domdocument'] = new \IvoPetkov\HTML5DOMDocument(); + } + $tmpDoc = clone (self::$newObjectsCache['html5domdocument']); + $tmpDoc->loadHTML('' . $value . '', HTML5DOMDocument::ALLOW_DUPLICATE_IDS); + foreach ($tmpDoc->getElementsByTagName('body')->item(0)->childNodes as $node) { + $node = $this->ownerDocument->importNode($node, true); + $this->appendChild($node); + } + return; + } elseif ($name === 'outerHTML') { + if (!isset(self::$newObjectsCache['html5domdocument'])) { + self::$newObjectsCache['html5domdocument'] = new \IvoPetkov\HTML5DOMDocument(); + } + $tmpDoc = clone (self::$newObjectsCache['html5domdocument']); + $tmpDoc->loadHTML('' . $value . '', HTML5DOMDocument::ALLOW_DUPLICATE_IDS); + foreach ($tmpDoc->getElementsByTagName('body')->item(0)->childNodes as $node) { + $node = $this->ownerDocument->importNode($node, true); + $this->parentNode->insertBefore($node, $this); + } + $this->parentNode->removeChild($this); + return; + } elseif ($name === 'classList') { + $this->setAttribute('class', $value); + return; + } + throw new \Exception('Undefined property: HTML5DOMElement::$' . $name); + } + + /** + * Updates the result value before returning it. + * + * @param string $value + * @return string The updated value + */ + private function updateResult(string $value): string + { + $value = str_replace(self::$foundEntitiesCache[0], self::$foundEntitiesCache[1], $value); + if (strstr($value, 'html5-dom-document-internal-entity') !== false) { + $search = []; + $replace = []; + $matches = []; + preg_match_all('/html5-dom-document-internal-entity([12])-(.*?)-end/', $value, $matches); + $matches[0] = array_unique($matches[0]); + foreach ($matches[0] as $i => $match) { + $search[] = $match; + $replace[] = html_entity_decode(($matches[1][$i] === '1' ? '&' : '&#') . $matches[2][$i] . ';'); + } + $value = str_replace($search, $replace, $value); + self::$foundEntitiesCache[0] = array_merge(self::$foundEntitiesCache[0], $search); + self::$foundEntitiesCache[1] = array_merge(self::$foundEntitiesCache[1], $replace); + unset($search); + unset($replace); + unset($matches); + } + return $value; + } + + /** + * Returns the updated nodeValue Property + * + * @return string The updated $nodeValue + */ + public function getNodeValue(): string + { + return $this->updateResult($this->nodeValue); + } + + /** + * Returns the updated $textContent Property + * + * @return string The updated $textContent + */ + public function getTextContent(): string + { + return $this->updateResult($this->textContent); + } + + /** + * Returns the value for the attribute name specified. + * + * @param string $name The attribute name. + * @return string The attribute value. + * @throws \InvalidArgumentException + */ + public function getAttribute($name): string + { + if ($this->attributes->length === 0) { // Performance optimization + return ''; + } + $value = parent::getAttribute($name); + return $value !== '' ? (strstr($value, 'html5-dom-document-internal-entity') !== false ? $this->updateResult($value) : $value) : ''; + } + + /** + * Returns an array containing all attributes. + * + * @return array An associative array containing all attributes. + */ + public function getAttributes(): array + { + $attributes = []; + foreach ($this->attributes as $attributeName => $attribute) { + $value = $attribute->value; + $attributes[$attributeName] = $value !== '' ? (strstr($value, 'html5-dom-document-internal-entity') !== false ? $this->updateResult($value) : $value) : ''; + } + return $attributes; + } + + /** + * Returns the element outerHTML. + * + * @return string The element outerHTML. + */ + public function __toString(): string + { + return $this->outerHTML; + } + + /** + * Returns the first child element matching the selector. + * + * @param string $selector A CSS query selector. Available values: *, tagname, tagname#id, #id, tagname.classname, .classname, tagname.classname.classname2, .classname.classname2, tagname[attribute-selector], [attribute-selector], "div, p", div p, div > p, div + p and p ~ ul. + * @return HTML5DOMElement|null The result DOMElement or null if not found. + * @throws \InvalidArgumentException + */ + public function querySelector(string $selector) + { + return $this->internalQuerySelector($selector); + } + + /** + * Returns a list of children elements matching the selector. + * + * @param string $selector A CSS query selector. Available values: *, tagname, tagname#id, #id, tagname.classname, .classname, tagname.classname.classname2, .classname.classname2, tagname[attribute-selector], [attribute-selector], "div, p", div p, div > p, div + p and p ~ ul. + * @return HTML5DOMNodeList Returns a list of DOMElements matching the criteria. + * @throws \InvalidArgumentException + */ + public function querySelectorAll(string $selector) + { + return $this->internalQuerySelectorAll($selector); + } +} diff --git a/web/app/vendor/ivopetkov/html5-dom-document-php/src/HTML5DOMNodeList.php b/web/app/vendor/ivopetkov/html5-dom-document-php/src/HTML5DOMNodeList.php new file mode 100644 index 0000000..dc07236 --- /dev/null +++ b/web/app/vendor/ivopetkov/html5-dom-document-php/src/HTML5DOMNodeList.php @@ -0,0 +1,45 @@ +offsetExists($index) ? $this->offsetGet($index) : null; + } + + /** + * Returns the value for the property specified. + * + * @param string $name The name of the property. + * @return mixed + * @throws \Exception + */ + public function __get(string $name) + { + if ($name === 'length') { + return sizeof($this); + } + throw new \Exception('Undefined property: \IvoPetkov\HTML5DOMNodeList::$' . $name); + } +} diff --git a/web/app/vendor/ivopetkov/html5-dom-document-php/src/HTML5DOMTokenList.php b/web/app/vendor/ivopetkov/html5-dom-document-php/src/HTML5DOMTokenList.php new file mode 100644 index 0000000..e917ab4 --- /dev/null +++ b/web/app/vendor/ivopetkov/html5-dom-document-php/src/HTML5DOMTokenList.php @@ -0,0 +1,266 @@ +element = $element; + $this->attributeName = $attributeName; + $this->previousValue = null; + $this->tokenize(); + } + + /** + * Adds the given tokens to the list. + * + * @param string[] $tokens The tokens you want to add to the list. + * @return void + */ + public function add(string ...$tokens) + { + if (count($tokens) === 0) { + return; + } + foreach ($tokens as $t) { + if (in_array($t, $this->tokens)) { + continue; + } + $this->tokens[] = $t; + } + $this->setAttributeValue(); + } + + /** + * Removes the specified tokens from the list. If the string does not exist in the list, no error is thrown. + * + * @param string[] $tokens The token you want to remove from the list. + * @return void + */ + public function remove(string ...$tokens) + { + if (count($tokens) === 0) { + return; + } + if (count($this->tokens) === 0) { + return; + } + foreach ($tokens as $t) { + $i = array_search($t, $this->tokens); + if ($i === false) { + continue; + } + array_splice($this->tokens, $i, 1); + } + $this->setAttributeValue(); + } + + /** + * Returns an item in the list by its index (returns null if the number is greater than or equal to the length of the list). + * + * @param int $index The zero-based index of the item you want to return. + * @return null|string + */ + public function item(int $index) + { + $this->tokenize(); + if ($index >= count($this->tokens)) { + return null; + } + return $this->tokens[$index]; + } + + /** + * Removes a given token from the list and returns false. If token doesn't exist it's added and the function returns true. + * + * @param string $token The token you want to toggle. + * @param bool $force A Boolean that, if included, turns the toggle into a one way-only operation. If set to false, the token will only be removed but not added again. If set to true, the token will only be added but not removed again. + * @return bool false if the token is not in the list after the call, or true if the token is in the list after the call. + */ + public function toggle(string $token, bool $force = null): bool + { + $this->tokenize(); + $isThereAfter = false; + $i = array_search($token, $this->tokens); + if (is_null($force)) { + if ($i === false) { + $this->tokens[] = $token; + $isThereAfter = true; + } else { + array_splice($this->tokens, $i, 1); + } + } else { + if ($force) { + if ($i === false) { + $this->tokens[] = $token; + } + $isThereAfter = true; + } else { + if ($i !== false) { + array_splice($this->tokens, $i, 1); + } + } + } + $this->setAttributeValue(); + return $isThereAfter; + } + + /** + * Returns true if the list contains the given token, otherwise false. + * + * @param string $token The token you want to check for the existence of in the list. + * @return bool true if the list contains the given token, otherwise false. + */ + public function contains(string $token): bool + { + $this->tokenize(); + return in_array($token, $this->tokens); + } + + /** + * Replaces an existing token with a new token. + * + * @param string $old The token you want to replace. + * @param string $new The token you want to replace $old with. + * @return void + */ + public function replace(string $old, string $new) + { + if ($old === $new) { + return; + } + $this->tokenize(); + $i = array_search($old, $this->tokens); + if ($i !== false) { + $j = array_search($new, $this->tokens); + if ($j === false) { + $this->tokens[$i] = $new; + } else { + array_splice($this->tokens, $i, 1); + } + $this->setAttributeValue(); + } + } + + /** + * + * @return string + */ + public function __toString(): string + { + $this->tokenize(); + return implode(' ', $this->tokens); + } + + /** + * Returns an iterator allowing you to go through all tokens contained in the list. + * + * @return ArrayIterator + */ + public function entries(): ArrayIterator + { + $this->tokenize(); + return new ArrayIterator($this->tokens); + } + + /** + * Returns the value for the property specified + * + * @param string $name The name of the property + * @return string The value of the property specified + * @throws \Exception + */ + public function __get(string $name) + { + if ($name === 'length') { + $this->tokenize(); + return count($this->tokens); + } elseif ($name === 'value') { + return $this->__toString(); + } + throw new \Exception('Undefined property: HTML5DOMTokenList::$' . $name); + } + + /** + * + * @return void + */ + private function tokenize() + { + $current = $this->element->getAttribute($this->attributeName); + if ($this->previousValue === $current) { + return; + } + $this->previousValue = $current; + $tokens = explode(' ', $current); + $finals = []; + foreach ($tokens as $token) { + if ($token === '') { + continue; + } + if (in_array($token, $finals)) { + continue; + } + $finals[] = $token; + } + $this->tokens = $finals; + } + + /** + * + * @return void + */ + private function setAttributeValue() + { + $value = implode(' ', $this->tokens); + if ($this->previousValue === $value) { + return; + } + $this->previousValue = $value; + $this->element->setAttribute($this->attributeName, $value); + } +} diff --git a/web/app/vendor/php-curl-class/php-curl-class/.gitignore b/web/app/vendor/php-curl-class/php-curl-class/.gitignore new file mode 100644 index 0000000..d1502b0 --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/.gitignore @@ -0,0 +1,2 @@ +vendor/ +composer.lock diff --git a/web/app/vendor/php-curl-class/php-curl-class/.travis.yml b/web/app/vendor/php-curl-class/php-curl-class/.travis.yml new file mode 100644 index 0000000..a915771 --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/.travis.yml @@ -0,0 +1,19 @@ +language: php +php: + - 5.3 + - 5.4 + - 5.5 + - 5.6 + - hhvm +matrix: + allow_failures: + - php: 5.6 + - php: hhvm +before_script: + - if [[ "$TRAVIS_PHP_VERSION" == "5.4" ]]; then sh -c "php -S 127.0.0.1:8000 -t tests/PHPCurlClass/ &"; fi + - if [[ "$TRAVIS_PHP_VERSION" == "5.5" ]]; then sh -c "php -S 127.0.0.1:8000 -t tests/PHPCurlClass/ &"; fi + - if [[ "$TRAVIS_PHP_VERSION" == "5.6" ]]; then sh -c "php -S 127.0.0.1:8000 -t tests/PHPCurlClass/ &"; fi + - if [[ "$TRAVIS_PHP_VERSION" == "hhvm" ]]; then sh -c "cd tests && hhvm --mode server --port 8000 --config PHPCurlClass/server.hdf &"; fi +script: + - php -l src/* + - if [[ "$TRAVIS_PHP_VERSION" != "5.3" ]]; then sh -c "cd tests && phpunit --configuration phpunit.xml"; fi diff --git a/web/app/vendor/php-curl-class/php-curl-class/LICENSE b/web/app/vendor/php-curl-class/php-curl-class/LICENSE new file mode 100644 index 0000000..68a49da --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/web/app/vendor/php-curl-class/php-curl-class/README.md b/web/app/vendor/php-curl-class/php-curl-class/README.md new file mode 100644 index 0000000..5934471 --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/README.md @@ -0,0 +1,136 @@ +# php-curl-class + +[![Build Status](https://travis-ci.org/php-curl-class/php-curl-class.png?branch=master)](https://travis-ci.org/php-curl-class/php-curl-class) + +PHP Curl Class is an object-oriented wrapper of the PHP cURL extension. + +### Composer + + $ composer require php-curl-class/php-curl-class + +### Quick Start and Examples + +```php +require 'Curl.class.php'; + +$curl = new Curl(); +$curl->get('http://www.example.com/'); +``` + +```php +$curl = new Curl(); +$curl->get('http://www.example.com/search', array( + 'q' => 'keyword', +)); +``` + +```php +$curl = new Curl(); +$curl->post('http://www.example.com/login/', array( + 'username' => 'myusername', + 'password' => 'mypassword', +)); +``` + +```php +$curl = new Curl(); +$curl->setBasicAuthentication('username', 'password'); +$curl->setUserAgent(''); +$curl->setReferrer(''); +$curl->setHeader('X-Requested-With', 'XMLHttpRequest'); +$curl->setCookie('key', 'value'); +$curl->get('http://www.example.com/'); + +if ($curl->error) { + echo $curl->error_code; +} +else { + echo $curl->response; +} + +var_dump($curl->request_headers); +var_dump($curl->response_headers); +``` + +```php +$curl = new Curl(); +$curl->setOpt(CURLOPT_SSL_VERIFYPEER, false); +$curl->get('https://encrypted.example.com/'); +``` + +```php +$curl = new Curl(); +$curl->put('http://api.example.com/user/', array( + 'first_name' => 'Zach', + 'last_name' => 'Borboa', +)); +``` + +```php +$curl = new Curl(); +$curl->patch('http://api.example.com/profile/', array( + 'image' => '@path/to/file.jpg', +)); +``` + +```php +$curl = new Curl(); +$curl->delete('http://api.example.com/user/', array( + 'id' => '1234', +)); +``` + +```php +// Enable gzip compression. +$curl = new Curl(); +$curl->setOpt(CURLOPT_ENCODING , 'gzip'); +$curl->get('https://www.example.com/image.png'); +``` + +```php +// Case-insensitive access to headers. +$curl = new Curl(); +$curl->get('https://www.example.com/image.png'); +echo $curl->response_headers['Content-Type'] . "\n"; // image/png +echo $curl->response_headers['CoNTeNT-TyPE'] . "\n"; // image/png +``` + +```php +$curl->close(); +``` + +```php +// Example access to curl object. +curl_set_opt($curl->curl, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1'); +curl_close($curl->curl); +``` + +```php +// Requests in parallel with callback functions. +$curl = new Curl(); +$curl->setOpt(CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1'); + +$curl->success(function($instance) { + echo 'call was successful. response was' . "\n"; + echo $instance->response . "\n"; +}); +$curl->error(function($instance) { + echo 'call was unsuccessful.' . "\n"; + echo 'error code:' . $instance->error_code . "\n"; + echo 'error message:' . $instance->error_message . "\n"; +}); +$curl->complete(function($instance) { + echo 'call completed' . "\n"; +}); + +$curl->get(array( + 'https://duckduckgo.com/', + 'https://search.yahoo.com/search', + 'https://www.bing.com/search', + 'http://www.dogpile.com/search/web', + 'https://www.google.com/search', + 'https://www.wolframalpha.com/input/', +), array( + 'q' => 'hello world', +)); +``` diff --git a/web/app/vendor/php-curl-class/php-curl-class/composer.json b/web/app/vendor/php-curl-class/php-curl-class/composer.json new file mode 100644 index 0000000..007da18 --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/composer.json @@ -0,0 +1,7 @@ +{ + "name": "php-curl-class/php-curl-class", + "description": "PHP Curl Class is an object-oriented wrapper of the PHP cURL extension.", + "autoload": { + "classmap": ["src/"] + } +} diff --git a/web/app/vendor/php-curl-class/php-curl-class/examples/coinbase_account_balance.php b/web/app/vendor/php-curl-class/php-curl-class/examples/coinbase_account_balance.php new file mode 100644 index 0000000..b45add8 --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/examples/coinbase_account_balance.php @@ -0,0 +1,22 @@ +setHeader('ACCESS_KEY', API_KEY); +$curl->setHeader('ACCESS_SIGNATURE', $signature); +$curl->setHeader('ACCESS_NONCE', $nonce); +$curl->get($url); + +echo + 'My current account balance at Coinbase is ' . + $curl->response->amount . ' ' . $curl->response->currency . '.' . "\n"; diff --git a/web/app/vendor/php-curl-class/php-curl-class/examples/coinbase_spot_rate.php b/web/app/vendor/php-curl-class/php-curl-class/examples/coinbase_spot_rate.php new file mode 100644 index 0000000..f84e450 --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/examples/coinbase_spot_rate.php @@ -0,0 +1,10 @@ +get('https://coinbase.com/api/v1/prices/spot_rate'); + +echo + 'The current price of bitcoin at Coinbase is ' . + '$' . $curl->response->amount . ' ' . $curl->response->currency . '.' . "\n"; diff --git a/web/app/vendor/php-curl-class/php-curl-class/examples/instagram_search_photos.php b/web/app/vendor/php-curl-class/php-curl-class/examples/instagram_search_photos.php new file mode 100644 index 0000000..dad8d96 --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/examples/instagram_search_photos.php @@ -0,0 +1,17 @@ +get('https://api.instagram.com/v1/media/search', array( + 'client_id' => INSTAGRAM_CLIENT_ID, + 'lat' => '37.8296', + 'lng' => '-122.4832', +)); + +foreach ($curl->response->data as $media) { + $image = $media->images->low_resolution; + echo ''; +} diff --git a/web/app/vendor/php-curl-class/php-curl-class/examples/put.php b/web/app/vendor/php-curl-class/php-curl-class/examples/put.php new file mode 100644 index 0000000..f06e14c --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/examples/put.php @@ -0,0 +1,14 @@ +put('http://httpbin.org/put', array( + 'id' => 1, + 'first_name' => 'Zach', + 'last_name' => 'Borboa', +)); + +echo 'Data server received via PUT:' . "\n"; +var_dump($curl->response->form); diff --git a/web/app/vendor/php-curl-class/php-curl-class/examples/twitter_post_tweet.php b/web/app/vendor/php-curl-class/php-curl-class/examples/twitter_post_tweet.php new file mode 100644 index 0000000..18b7671 --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/examples/twitter_post_tweet.php @@ -0,0 +1,35 @@ + API_KEY, + 'oauth_nonce' => md5(microtime() . mt_rand()), + 'oauth_signature_method' => 'HMAC-SHA1', + 'oauth_timestamp' => time(), + 'oauth_token' => OAUTH_ACCESS_TOKEN, + 'oauth_version' => '1.0', + 'status' => $status, +); + +$url = 'https://api.twitter.com/1.1/statuses/update.json'; +$request = implode('&', array( + 'POST', + rawurlencode($url), + rawurlencode(http_build_query($oauth_data, '', '&', PHP_QUERY_RFC3986)), +)); +$key = implode('&', array(API_SECRET, OAUTH_TOKEN_SECRET)); +$oauth_data['oauth_signature'] = base64_encode(hash_hmac('sha1', $request, $key, true)); +$data = http_build_query($oauth_data, '', '&'); + +$curl = new Curl(); +$curl->post($url, $data); + +echo 'Posted "' . $curl->response->text . '" at ' . $curl->response->created_at . '.' . "\n"; diff --git a/web/app/vendor/php-curl-class/php-curl-class/src/Curl.class.php b/web/app/vendor/php-curl-class/php-curl-class/src/Curl.class.php new file mode 100644 index 0000000..26209ba --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/src/Curl.class.php @@ -0,0 +1,496 @@ +curl = curl_init(); + $this->setUserAgent(self::USER_AGENT); + $this->setOpt(CURLINFO_HEADER_OUT, true); + $this->setOpt(CURLOPT_HEADER, true); + $this->setOpt(CURLOPT_RETURNTRANSFER, true); + } + + public function get($url_mixed, $data = array()) + { + if (is_array($url_mixed)) { + $curl_multi = curl_multi_init(); + $this->multi_parent = true; + + $this->curls = array(); + + foreach ($url_mixed as $url) { + $curl = new Curl(); + $curl->multi_child = true; + $curl->setOpt(CURLOPT_URL, $this->buildURL($url, $data), $curl->curl); + $curl->setOpt(CURLOPT_CUSTOMREQUEST, 'GET'); + $curl->setOpt(CURLOPT_HTTPGET, true); + $this->call($this->before_send_function, $curl); + $this->curls[] = $curl; + + $curlm_error_code = curl_multi_add_handle($curl_multi, $curl->curl); + if (!($curlm_error_code === CURLM_OK)) { + throw new \ErrorException('cURL multi add handle error: ' . curl_multi_strerror($curlm_error_code)); + } + } + + foreach ($this->curls as $ch) { + foreach ($this->options as $key => $value) { + $ch->setOpt($key, $value); + } + } + + do { + $status = curl_multi_exec($curl_multi, $active); + } while ($status === CURLM_CALL_MULTI_PERFORM || $active); + + foreach ($this->curls as $ch) { + $this->exec($ch); + } + } else { + $this->setopt(CURLOPT_URL, $this->buildURL($url_mixed, $data)); + $this->setOpt(CURLOPT_CUSTOMREQUEST, 'GET'); + $this->setopt(CURLOPT_HTTPGET, true); + return $this->exec(); + } + } + + public function post($url, $data = array()) + { + if (is_array($data) && empty($data)) { + $this->setHeader('Content-Length'); + } + + $this->setOpt(CURLOPT_URL, $this->buildURL($url)); + $this->setOpt(CURLOPT_CUSTOMREQUEST, 'POST'); + $this->setOpt(CURLOPT_POST, true); + $this->setOpt(CURLOPT_POSTFIELDS, $this->postfields($data)); + return $this->exec(); + } + + public function put($url, $data = array()) + { + $this->setOpt(CURLOPT_URL, $url); + $this->setOpt(CURLOPT_CUSTOMREQUEST, 'PUT'); + $put_data = http_build_query($data); + if (empty($this->options[CURLOPT_INFILE]) && empty($this->options[CURLOPT_INFILESIZE])) { + $this->setHeader('Content-Length', strlen($put_data)); + } + $this->setOpt(CURLOPT_POSTFIELDS, $put_data); + return $this->exec(); + } + + public function patch($url, $data = array()) + { + $this->setHeader('Content-Length'); + $this->setOpt(CURLOPT_URL, $this->buildURL($url)); + $this->setOpt(CURLOPT_CUSTOMREQUEST, 'PATCH'); + $this->setOpt(CURLOPT_POSTFIELDS, $data); + return $this->exec(); + } + + public function delete($url, $data = array()) + { + $this->setHeader('Content-Length'); + $this->setOpt(CURLOPT_URL, $this->buildURL($url, $data)); + $this->setOpt(CURLOPT_CUSTOMREQUEST, 'DELETE'); + return $this->exec(); + } + + public function head($url, $data = array()) + { + $this->setOpt(CURLOPT_URL, $this->buildURL($url, $data)); + $this->setOpt(CURLOPT_CUSTOMREQUEST, 'HEAD'); + $this->setOpt(CURLOPT_NOBODY, true); + return $this->exec(); + } + + public function options($url, $data = array()) + { + $this->setHeader('Content-Length'); + $this->setOpt(CURLOPT_URL, $this->buildURL($url, $data)); + $this->setOpt(CURLOPT_CUSTOMREQUEST, 'OPTIONS'); + return $this->exec(); + } + + public function setBasicAuthentication($username, $password) + { + $this->setOpt(CURLOPT_HTTPAUTH, CURLAUTH_BASIC); + $this->setOpt(CURLOPT_USERPWD, $username . ':' . $password); + } + + public function setHeader($key, $value = '') + { + $this->headers[$key] = $key . ': ' . $value; + $this->setOpt(CURLOPT_HTTPHEADER, array_values($this->headers)); + } + + public function setUserAgent($user_agent) + { + $this->setOpt(CURLOPT_USERAGENT, $user_agent); + } + + public function setReferrer($referrer) + { + $this->setOpt(CURLOPT_REFERER, $referrer); + } + + public function setCookie($key, $value) + { + $this->cookies[$key] = $value; + $this->setOpt(CURLOPT_COOKIE, http_build_query($this->cookies, '', '; ')); + } + + public function setCookieFile($cookie_file) + { + $this->setOpt(CURLOPT_COOKIEFILE, $cookie_file); + } + + public function setCookieJar($cookie_jar) + { + $this->setOpt(CURLOPT_COOKIEJAR, $cookie_jar); + } + + public function setOpt($option, $value, $_ch = null) + { + $ch = is_null($_ch) ? $this->curl : $_ch; + + $required_options = array( + CURLINFO_HEADER_OUT => 'CURLINFO_HEADER_OUT', + CURLOPT_HEADER => 'CURLOPT_HEADER', + CURLOPT_RETURNTRANSFER => 'CURLOPT_RETURNTRANSFER', + ); + + if (in_array($option, array_keys($required_options), true) && !($value === true)) { + trigger_error($required_options[$option] . ' is a required option', E_USER_WARNING); + } + + $this->options[$option] = $value; + return curl_setopt($ch, $option, $value); + } + + public function verbose($on = true) + { + $this->setOpt(CURLOPT_VERBOSE, $on); + } + + public function close() + { + if ($this->multi_parent) { + foreach ($this->curls as $curl) { + $curl->close(); + } + } + + if (is_resource($this->curl)) { + curl_close($this->curl); + } + } + + public function beforeSend($function) + { + $this->before_send_function = $function; + } + + public function success($callback) + { + $this->success_function = $callback; + } + + public function error($callback) + { + $this->error_function = $callback; + } + + public function complete($callback) + { + $this->complete_function = $callback; + } + + private function buildURL($url, $data = array()) + { + return $url . (empty($data) ? '' : '?' . http_build_query($data)); + } + + private function parseHeaders($raw_headers) + { + $raw_headers = preg_split('/\r\n/', $raw_headers, null, PREG_SPLIT_NO_EMPTY); + $http_headers = new CaseInsensitiveArray(); + + for ($i = 1; $i < count($raw_headers); $i++) { + list($key, $value) = explode(':', $raw_headers[$i], 2); + $key = trim($key); + $value = trim($value); + // Use isset() as array_key_exists() and ArrayAccess are not compatible. + if (isset($http_headers[$key])) { + $http_headers[$key] .= ',' . $value; + } else { + $http_headers[$key] = $value; + } + } + + return array(isset($raw_headers['0']) ? $raw_headers['0'] : '', $http_headers); + } + + private function parseRequestHeaders($raw_headers) + { + $request_headers = new CaseInsensitiveArray(); + list($first_line, $headers) = $this->parseHeaders($raw_headers); + $request_headers['Request-Line'] = $first_line; + foreach ($headers as $key => $value) { + $request_headers[$key] = $value; + } + return $request_headers; + } + + private function parseResponseHeaders($raw_headers) + { + $response_headers = new CaseInsensitiveArray(); + list($first_line, $headers) = $this->parseHeaders($raw_headers); + $response_headers['Status-Line'] = $first_line; + foreach ($headers as $key => $value) { + $response_headers[$key] = $value; + } + return $response_headers; + } + + private function postfields($data) + { + if (is_array($data)) { + if (is_array_multidim($data)) { + $data = http_build_multi_query($data); + } else { + foreach ($data as $key => $value) { + // Fix "Notice: Array to string conversion" when $value in + // curl_setopt($ch, CURLOPT_POSTFIELDS, $value) is an array + // that contains an empty array. + if (is_array($value) && empty($value)) { + $data[$key] = ''; + // Fix "curl_setopt(): The usage of the @filename API for + // file uploading is deprecated. Please use the CURLFile + // class instead". + } elseif (is_string($value) && strpos($value, '@') === 0) { + if (class_exists('CURLFile')) { + $data[$key] = new CURLFile(substr($value, 1)); + } + } + } + } + } + + return $data; + } + + protected function exec($_ch = null) + { + $ch = is_null($_ch) ? $this : $_ch; + + if ($ch->multi_child) { + $ch->response = curl_multi_getcontent($ch->curl); + } else { + $ch->response = curl_exec($ch->curl); + } + + $ch->curl_error_code = curl_errno($ch->curl); + $ch->curl_error_message = curl_error($ch->curl); + $ch->curl_error = !($ch->curl_error_code === 0); + $ch->http_status_code = curl_getinfo($ch->curl, CURLINFO_HTTP_CODE); + $ch->http_error = in_array(floor($ch->http_status_code / 100), array(4, 5)); + $ch->error = $ch->curl_error || $ch->http_error; + $ch->error_code = $ch->error ? ($ch->curl_error ? $ch->curl_error_code : $ch->http_status_code) : 0; + + $ch->request_headers = $this->parseRequestHeaders(curl_getinfo($ch->curl, CURLINFO_HEADER_OUT)); + $ch->response_headers = ''; + if (!(strpos($ch->response, "\r\n\r\n") === false)) { + list($response_header, $ch->response) = explode("\r\n\r\n", $ch->response, 2); + if ($response_header === 'HTTP/1.1 100 Continue') { + list($response_header, $ch->response) = explode("\r\n\r\n", $ch->response, 2); + } + $ch->response_headers = $this->parseResponseHeaders($response_header); + + if (isset($ch->response_headers['Content-Type'])) { + if (preg_match('/^application\/json/i', $ch->response_headers['Content-Type'])) { + $json_obj = json_decode($ch->response, false); + if (!is_null($json_obj)) { + $ch->response = $json_obj; + } + } + } + } + + $ch->http_error_message = ''; + if ($ch->error) { + if (isset($ch->response_headers['Status-Line'])) { + $ch->http_error_message = $ch->response_headers['Status-Line']; + } + } + $ch->error_message = $ch->curl_error ? $ch->curl_error_message : $ch->http_error_message; + + if (!$ch->error) { + $ch->call($this->success_function, $ch); + } else { + $ch->call($this->error_function, $ch); + } + + $ch->call($this->complete_function, $ch); + + return $ch->error_code; + } + + private function call($function) + { + if (is_callable($function)) { + $args = func_get_args(); + array_shift($args); + call_user_func_array($function, $args); + } + } + + public function __destruct() + { + $this->close(); + } +} + +class CaseInsensitiveArray implements ArrayAccess, Countable, Iterator +{ + private $container = array(); + + public function offsetSet($offset, $value) + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $index = array_search(strtolower($offset), array_keys(array_change_key_case($this->container, CASE_LOWER))); + if (!($index === false)) { + $keys = array_keys($this->container); + unset($this->container[$keys[$index]]); + } + $this->container[$offset] = $value; + } + } + + public function offsetExists($offset) + { + return array_key_exists(strtolower($offset), array_change_key_case($this->container, CASE_LOWER)); + } + + public function offsetUnset($offset) + { + unset($this->container[$offset]); + } + + public function offsetGet($offset) + { + $index = array_search(strtolower($offset), array_keys(array_change_key_case($this->container, CASE_LOWER))); + if ($index === false) { + return null; + } + + $values = array_values($this->container); + return $values[$index]; + } + + public function count() + { + return count($this->container); + } + + public function current() + { + return current($this->container); + } + + public function next() + { + return next($this->container); + } + + public function key() + { + return key($this->container); + } + + public function valid() + { + return !($this->current() === false); + } + + public function rewind() + { + reset($this->container); + } +} + +function is_array_assoc($array) +{ + return (bool)count(array_filter(array_keys($array), 'is_string')); +} + +function is_array_multidim($array) +{ + if (!is_array($array)) { + return false; + } + + return !(count($array) === count($array, COUNT_RECURSIVE)); +} + +function http_build_multi_query($data, $key = null) +{ + $query = array(); + + if (empty($data)) { + return $key . '='; + } + + $is_array_assoc = is_array_assoc($data); + + foreach ($data as $k => $value) { + if (is_string($value) || is_numeric($value)) { + $brackets = $is_array_assoc ? '[' . $k . ']' : '[]'; + $query[] = urlencode(is_null($key) ? $k : $key . $brackets) . '=' . rawurlencode($value); + } elseif (is_array($value)) { + $nested = is_null($key) ? $k : $key . '[' . $k . ']'; + $query[] = http_build_multi_query($value, $nested); + } + } + + return implode('&', $query); +} diff --git a/web/app/vendor/php-curl-class/php-curl-class/tests/PHPCurlClass/PHPCurlClassTest.php b/web/app/vendor/php-curl-class/php-curl-class/tests/PHPCurlClass/PHPCurlClassTest.php new file mode 100644 index 0000000..c42bea0 --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/tests/PHPCurlClass/PHPCurlClassTest.php @@ -0,0 +1,740 @@ +assertTrue(extension_loaded('curl')); + } + + public function testArrayAssociative() { + $this->assertTrue(is_array_assoc(array( + 'foo' => 'wibble', + 'bar' => 'wubble', + 'baz' => 'wobble', + ))); + } + + public function testArrayIndexed() { + $this->assertFalse(is_array_assoc(array( + 'wibble', + 'wubble', + 'wobble', + ))); + } + + public function testCaseInsensitiveArrayGet() { + $array = new CaseInsensitiveArray(); + $this->assertTrue(is_object($array)); + $this->assertCount(0, $array); + $this->assertNull($array[(string)rand()]); + + $array['foo'] = 'bar'; + $this->assertNotEmpty($array); + $this->assertCount(1, $array); + } + + public function testCaseInsensitiveArraySet() { + function assertions($array, $count=1) { + PHPUnit_Framework_Assert::assertCount($count, $array); + PHPUnit_Framework_Assert::assertTrue($array['foo'] === 'bar'); + PHPUnit_Framework_Assert::assertTrue($array['Foo'] === 'bar'); + PHPUnit_Framework_Assert::assertTrue($array['FOo'] === 'bar'); + PHPUnit_Framework_Assert::assertTrue($array['FOO'] === 'bar'); + } + + $array = new CaseInsensitiveArray(); + $array['foo'] = 'bar'; + assertions($array); + + $array['Foo'] = 'bar'; + assertions($array); + + $array['FOo'] = 'bar'; + assertions($array); + + $array['FOO'] = 'bar'; + assertions($array); + + $array['baz'] = 'qux'; + assertions($array, 2); + } + + public function testUserAgent() { + $test = new Test(); + $test->curl->setUserAgent(Curl::USER_AGENT); + $this->assertTrue($test->server('server', 'GET', array( + 'key' => 'HTTP_USER_AGENT', + )) === Curl::USER_AGENT); + } + + public function testGet() { + $test = new Test(); + $this->assertTrue($test->server('server', 'GET', array( + 'key' => 'REQUEST_METHOD', + )) === 'GET'); + } + + public function testPostRequestMethod() { + $test = new Test(); + $this->assertTrue($test->server('server', 'POST', array( + 'key' => 'REQUEST_METHOD', + )) === 'POST'); + } + + public function testPostData() { + $test = new Test(); + $this->assertTrue($test->server('post', 'POST', array( + 'key' => 'value', + )) === 'key=value'); + } + + public function testPostAssociativeArrayData() { + $test = new Test(); + $this->assertTrue($test->server('post_multidimensional', 'POST', array( + 'username' => 'myusername', + 'password' => 'mypassword', + 'more_data' => array( + 'param1' => 'something', + 'param2' => 'other thing', + 'param3' => 123, + 'param4' => 3.14, + ), + )) === 'username=myusername&password=mypassword&more_data%5Bparam1%5D=something&more_data%5Bparam2%5D=other%20thing&more_data%5Bparam3%5D=123&more_data%5Bparam4%5D=3.14'); + } + + public function testPostMultidimensionalData() { + $test = new Test(); + $this->assertTrue($test->server('post_multidimensional', 'POST', array( + 'key' => 'file', + 'file' => array( + 'wibble', + 'wubble', + 'wobble', + ), + )) === 'key=file&file%5B%5D=wibble&file%5B%5D=wubble&file%5B%5D=wobble'); + } + + public function testPostFilePathUpload() { + $file_path = get_png(); + + $test = new Test(); + $this->assertTrue($test->server('post_file_path_upload', 'POST', array( + 'key' => 'image', + 'image' => '@' . $file_path, + )) === 'image/png'); + + unlink($file_path); + $this->assertFalse(file_exists($file_path)); + } + + public function testPostCurlFileUpload() { + if (class_exists('CURLFile')) { + $file_path = get_png(); + + $test = new Test(); + $this->assertTrue($test->server('post_file_path_upload', 'POST', array( + 'key' => 'image', + 'image' => new CURLFile($file_path), + )) === 'image/png'); + + unlink($file_path); + $this->assertFalse(file_exists($file_path)); + } + } + + public function testPutRequestMethod() { + $test = new Test(); + $this->assertTrue($test->server('request_method', 'PUT') === 'PUT'); + } + + public function testPutData() { + $test = new Test(); + $this->assertTrue($test->server('put', 'PUT', array( + 'key' => 'value', + )) === 'key=value'); + } + + public function testPutFileHandle() { + $png = create_png(); + $tmp_file = create_tmp_file($png); + + $test = new Test(); + $test->curl->setHeader('X-DEBUG-TEST', 'put_file_handle'); + $test->curl->setOpt(CURLOPT_PUT, true); + $test->curl->setOpt(CURLOPT_INFILE, $tmp_file); + $test->curl->setOpt(CURLOPT_INFILESIZE, strlen($png)); + $test->curl->put(Test::TEST_URL); + + fclose($tmp_file); + + $this->assertTrue($test->curl->response === 'image/png'); + } + + public function testPatchRequestMethod() { + $test = new Test(); + $this->assertTrue($test->server('request_method', 'PATCH') === 'PATCH'); + } + + public function testDelete() { + $test = new Test(); + $this->assertTrue($test->server('server', 'DELETE', array( + 'key' => 'REQUEST_METHOD', + )) === 'DELETE'); + + $test = new Test(); + $this->assertTrue($test->server('delete', 'DELETE', array( + 'test' => 'delete', + 'key' => 'test', + )) === 'delete'); + } + + public function testHeadRequestMethod() { + $test = new Test(); + $test->server('request_method', 'HEAD', array( + 'key' => 'REQUEST_METHOD', + )); + $this->assertEquals($test->curl->response_headers['X-REQUEST-METHOD'], 'HEAD'); + $this->assertEmpty($test->curl->response); + } + + public function testOptionsRequestMethod() { + $test = new Test(); + $test->server('request_method', 'OPTIONS', array( + 'key' => 'REQUEST_METHOD', + )); + $this->assertEquals($test->curl->response_headers['X-REQUEST-METHOD'], 'OPTIONS'); + } + + public function testBasicHttpAuth401Unauthorized() { + $test = new Test(); + $this->assertTrue($test->server('http_basic_auth', 'GET') === 'canceled'); + } + + public function testBasicHttpAuthSuccess() { + $username = 'myusername'; + $password = 'mypassword'; + $test = new Test(); + $test->curl->setBasicAuthentication($username, $password); + $test->server('http_basic_auth', 'GET'); + $json = $test->curl->response; + $this->assertTrue($json->username === $username); + $this->assertTrue($json->password === $password); + } + + public function testReferrer() { + $test = new Test(); + $test->curl->setReferrer('myreferrer'); + $this->assertTrue($test->server('server', 'GET', array( + 'key' => 'HTTP_REFERER', + )) === 'myreferrer'); + } + + public function testCookies() { + $test = new Test(); + $test->curl->setCookie('mycookie', 'yum'); + $this->assertTrue($test->server('cookie', 'GET', array( + 'key' => 'mycookie', + )) === 'yum'); + } + + public function testCookieFile() { + $cookie_file = dirname(__FILE__) . '/cookies.txt'; + $cookie_data = implode("\t", array( + '127.0.0.1', // domain + 'FALSE', // tailmatch + '/', // path + 'FALSE', // secure + '0', // expires + 'mycookie', // name + 'yum', // value + )); + file_put_contents($cookie_file, $cookie_data); + + $test = new Test(); + $test->curl->setCookieFile($cookie_file); + $this->assertTrue($test->server('cookie', 'GET', array( + 'key' => 'mycookie', + )) === 'yum'); + + unlink($cookie_file); + $this->assertFalse(file_exists($cookie_file)); + } + + public function testCookieJar() { + $cookie_file = dirname(__FILE__) . '/cookies.txt'; + + $test = new Test(); + $test->curl->setCookieJar($cookie_file); + $test->server('cookiejar', 'GET'); + $test->curl->close(); + + $this->assertTrue(!(strpos(file_get_contents($cookie_file), "\t" . 'mycookie' . "\t" . 'yum') === false)); + unlink($cookie_file); + $this->assertFalse(file_exists($cookie_file)); + } + + public function testMultipleCookieResponse() { + $expected_response = 'cookie1=scrumptious,cookie2=mouthwatering'; + + // github.com/facebook/hhvm/issues/2345 + if (defined('HHVM_VERSION')) { + $expected_response = 'cookie2=mouthwatering,cookie1=scrumptious'; + } + + $test = new Test(); + $test->server('multiple_cookie', 'GET'); + $this->assertEquals($test->curl->response_headers['Set-Cookie'], $expected_response); + } + + public function testError() { + $test = new Test(); + $test->curl->setOpt(CURLOPT_CONNECTTIMEOUT_MS, 4000); + $test->curl->get(Test::ERROR_URL); + $this->assertTrue($test->curl->error); + $this->assertTrue($test->curl->curl_error); + $this->assertTrue($test->curl->curl_error_code === CURLE_OPERATION_TIMEOUTED); + } + + public function testErrorMessage() { + $test = new Test(); + $test->server('error_message', 'GET'); + + $expected_response = 'HTTP/1.1 401 Unauthorized'; + if (defined('HHVM_VERSION')) { + $expected_response = 'HTTP/1.1 401'; + } + + $this->assertEquals($test->curl->error_message, $expected_response); + } + + public function testHeaders() { + $test = new Test(); + $test->curl->setHeader('Content-Type', 'application/json'); + $test->curl->setHeader('X-Requested-With', 'XMLHttpRequest'); + $test->curl->setHeader('Accept', 'application/json'); + $this->assertTrue($test->server('server', 'GET', array( + 'key' => 'HTTP_CONTENT_TYPE', // OR "CONTENT_TYPE". + )) === 'application/json'); + $this->assertTrue($test->server('server', 'GET', array( + 'key' => 'HTTP_X_REQUESTED_WITH', + )) === 'XMLHttpRequest'); + $this->assertTrue($test->server('server', 'GET', array( + 'key' => 'HTTP_ACCEPT', + )) === 'application/json'); + } + + public function testHeaderCaseSensitivity() { + $content_type = 'application/json'; + $test = new Test(); + $test->curl->setHeader('Content-Type', $content_type); + $test->server('response_header', 'GET'); + + $request_headers = $test->curl->request_headers; + $response_headers = $test->curl->response_headers; + + $this->assertEquals($request_headers['Content-Type'], $content_type); + $this->assertEquals($request_headers['content-type'], $content_type); + $this->assertEquals($request_headers['CONTENT-TYPE'], $content_type); + $this->assertEquals($request_headers['cOnTeNt-TyPe'], $content_type); + + $etag = $response_headers['ETag']; + $this->assertEquals($response_headers['ETAG'], $etag); + $this->assertEquals($response_headers['etag'], $etag); + $this->assertEquals($response_headers['eTAG'], $etag); + $this->assertEquals($response_headers['eTaG'], $etag); + } + + public function testRequestURL() { + $test = new Test(); + $this->assertFalse(substr($test->server('request_uri', 'GET'), -1) === '?'); + $test = new Test(); + $this->assertFalse(substr($test->server('request_uri', 'POST'), -1) === '?'); + $test = new Test(); + $this->assertFalse(substr($test->server('request_uri', 'PUT'), -1) === '?'); + $test = new Test(); + $this->assertFalse(substr($test->server('request_uri', 'PATCH'), -1) === '?'); + $test = new Test(); + $this->assertFalse(substr($test->server('request_uri', 'DELETE'), -1) === '?'); + } + + public function testNestedData() { + $test = new Test(); + $data = array( + 'username' => 'myusername', + 'password' => 'mypassword', + 'more_data' => array( + 'param1' => 'something', + 'param2' => 'other thing', + 'another' => array( + 'extra' => 'level', + 'because' => 'I need it', + ), + ), + ); + $this->assertTrue( + $test->server('post', 'POST', $data) === http_build_query($data) + ); + } + + public function testPostContentTypes() { + $test = new Test(); + $test->server('server', 'POST', 'foo=bar'); + $this->assertEquals($test->curl->request_headers['Content-Type'], 'application/x-www-form-urlencoded'); + + $test = new Test(); + $test->server('server', 'POST', array( + 'foo' => 'bar', + )); + $this->assertEquals($test->curl->request_headers['Expect'], '100-continue'); + preg_match('/^multipart\/form-data; boundary=/', $test->curl->request_headers['Content-Type'], $content_type); + $this->assertTrue(!empty($content_type)); + } + + public function testJSONResponse() { + function assertion($key, $value) { + $test = new Test(); + $test->server('json_response', 'POST', array( + 'key' => $key, + 'value' => $value, + )); + + $response = $test->curl->response; + PHPUnit_Framework_Assert::assertNotNull($response); + PHPUnit_Framework_Assert::assertNull($response->null); + PHPUnit_Framework_Assert::assertTrue($response->true); + PHPUnit_Framework_Assert::assertFalse($response->false); + PHPUnit_Framework_Assert::assertTrue(is_int($response->integer)); + PHPUnit_Framework_Assert::assertTrue(is_float($response->float)); + PHPUnit_Framework_Assert::assertEmpty($response->empty); + PHPUnit_Framework_Assert::assertTrue(is_string($response->string)); + } + + assertion('Content-Type', 'application/json; charset=utf-8'); + assertion('content-type', 'application/json; charset=utf-8'); + assertion('Content-Type', 'application/json'); + assertion('content-type', 'application/json'); + assertion('CONTENT-TYPE', 'application/json'); + assertion('CONTENT-TYPE', 'APPLICATION/JSON'); + } + + public function testArrayToStringConversion() { + $test = new Test(); + $test->server('post', 'POST', array( + 'foo' => 'bar', + 'baz' => array( + ), + )); + $this->assertTrue($test->curl->response === 'foo=bar&baz='); + + $test = new Test(); + $test->server('post', 'POST', array( + 'foo' => 'bar', + 'baz' => array( + 'qux' => array( + ), + ), + )); + $this->assertTrue(urldecode($test->curl->response) === + 'foo=bar&baz[qux]=' + ); + + $test = new Test(); + $test->server('post', 'POST', array( + 'foo' => 'bar', + 'baz' => array( + 'qux' => array( + ), + 'wibble' => 'wobble', + ), + )); + $this->assertTrue(urldecode($test->curl->response) === + 'foo=bar&baz[qux]=&baz[wibble]=wobble' + ); + } + + public function testParallelRequests() { + $test = new Test(); + $curl = $test->curl; + $curl->beforeSend(function($instance) { + $instance->setHeader('X-DEBUG-TEST', 'request_uri'); + }); + $curl->get(array( + Test::TEST_URL . 'a/', + Test::TEST_URL . 'b/', + Test::TEST_URL . 'c/', + ), array( + 'foo' => 'bar', + )); + + $len = strlen('/a/?foo=bar'); + $this->assertTrue(substr($curl->curls['0']->response, - $len) === '/a/?foo=bar'); + $this->assertTrue(substr($curl->curls['1']->response, - $len) === '/b/?foo=bar'); + $this->assertTrue(substr($curl->curls['2']->response, - $len) === '/c/?foo=bar'); + } + + public function testParallelSetOptions() { + $test = new Test(); + $curl = $test->curl; + $curl->setHeader('X-DEBUG-TEST', 'server'); + $curl->setOpt(CURLOPT_USERAGENT, 'useragent'); + $curl->complete(function($instance) { + PHPUnit_Framework_Assert::assertTrue($instance->response === 'useragent'); + }); + $curl->get(array( + Test::TEST_URL, + ), array( + 'key' => 'HTTP_USER_AGENT', + )); + } + + public function testSuccessCallback() { + $success_called = false; + $error_called = false; + $complete_called = false; + + $test = new Test(); + $curl = $test->curl; + $curl->setHeader('X-DEBUG-TEST', 'get'); + + $curl->success(function($instance) use (&$success_called, &$error_called, &$complete_called) { + PHPUnit_Framework_Assert::assertInstanceOf('Curl', $instance); + PHPUnit_Framework_Assert::assertFalse($success_called); + PHPUnit_Framework_Assert::assertFalse($error_called); + PHPUnit_Framework_Assert::assertFalse($complete_called); + $success_called = true; + }); + $curl->error(function($instance) use (&$success_called, &$error_called, &$complete_called, &$curl) { + PHPUnit_Framework_Assert::assertInstanceOf('Curl', $instance); + PHPUnit_Framework_Assert::assertFalse($success_called); + PHPUnit_Framework_Assert::assertFalse($error_called); + PHPUnit_Framework_Assert::assertFalse($complete_called); + $error_called = true; + }); + $curl->complete(function($instance) use (&$success_called, &$error_called, &$complete_called) { + PHPUnit_Framework_Assert::assertInstanceOf('Curl', $instance); + PHPUnit_Framework_Assert::assertTrue($success_called); + PHPUnit_Framework_Assert::assertFalse($error_called); + PHPUnit_Framework_Assert::assertFalse($complete_called); + $complete_called = true; + }); + + $curl->get(Test::TEST_URL); + + $this->assertTrue($success_called); + $this->assertFalse($error_called); + $this->assertTrue($complete_called); + } + + public function testParallelSuccessCallback() { + $success_called = false; + $error_called = false; + $complete_called = false; + + $success_called_once = false; + $error_called_once = false; + $complete_called_once = false; + + $test = new Test(); + $curl = $test->curl; + $curl->setHeader('X-DEBUG-TEST', 'get'); + + $curl->success(function($instance) use (&$success_called, + &$error_called, + &$complete_called, + &$success_called_once) { + PHPUnit_Framework_Assert::assertInstanceOf('Curl', $instance); + PHPUnit_Framework_Assert::assertFalse($success_called); + PHPUnit_Framework_Assert::assertFalse($error_called); + PHPUnit_Framework_Assert::assertFalse($complete_called); + $success_called = true; + $success_called_once = true; + }); + $curl->error(function($instance) use (&$success_called, + &$error_called, + &$complete_called, + &$curl, + &$error_called_once) { + PHPUnit_Framework_Assert::assertInstanceOf('Curl', $instance); + PHPUnit_Framework_Assert::assertFalse($success_called); + PHPUnit_Framework_Assert::assertFalse($error_called); + PHPUnit_Framework_Assert::assertFalse($complete_called); + $error_called = true; + $error_called_once = true; + }); + $curl->complete(function($instance) use (&$success_called, + &$error_called, + &$complete_called, + &$complete_called_once) { + PHPUnit_Framework_Assert::assertInstanceOf('Curl', $instance); + PHPUnit_Framework_Assert::assertTrue($success_called); + PHPUnit_Framework_Assert::assertFalse($error_called); + PHPUnit_Framework_Assert::assertFalse($complete_called); + $complete_called = true; + $complete_called_once = true; + + PHPUnit_Framework_Assert::assertTrue($success_called); + PHPUnit_Framework_Assert::assertFalse($error_called); + PHPUnit_Framework_Assert::assertTrue($complete_called); + + $success_called = false; + $error_called = false; + $complete_called = false; + }); + + $curl->get(array( + Test::TEST_URL . 'a/', + Test::TEST_URL . 'b/', + Test::TEST_URL . 'c/', + )); + + PHPUnit_Framework_Assert::assertTrue($success_called_once || $error_called_once); + PHPUnit_Framework_Assert::assertTrue($complete_called_once); + } + + public function testErrorCallback() { + $success_called = false; + $error_called = false; + $complete_called = false; + + $test = new Test(); + $curl = $test->curl; + $curl->setHeader('X-DEBUG-TEST', 'get'); + $curl->setOpt(CURLOPT_CONNECTTIMEOUT_MS, 2000); + + $curl->success(function($instance) use (&$success_called, &$error_called, &$complete_called) { + PHPUnit_Framework_Assert::assertInstanceOf('Curl', $instance); + PHPUnit_Framework_Assert::assertFalse($success_called); + PHPUnit_Framework_Assert::assertFalse($error_called); + PHPUnit_Framework_Assert::assertFalse($complete_called); + $success_called = true; + }); + $curl->error(function($instance) use (&$success_called, &$error_called, &$complete_called, &$curl) { + PHPUnit_Framework_Assert::assertInstanceOf('Curl', $instance); + PHPUnit_Framework_Assert::assertFalse($success_called); + PHPUnit_Framework_Assert::assertFalse($error_called); + PHPUnit_Framework_Assert::assertFalse($complete_called); + $error_called = true; + }); + $curl->complete(function($instance) use (&$success_called, &$error_called, &$complete_called) { + PHPUnit_Framework_Assert::assertInstanceOf('Curl', $instance); + PHPUnit_Framework_Assert::assertFalse($success_called); + PHPUnit_Framework_Assert::assertTrue($error_called); + PHPUnit_Framework_Assert::assertFalse($complete_called); + $complete_called = true; + }); + + $curl->get(Test::ERROR_URL); + + $this->assertFalse($success_called); + $this->assertTrue($error_called); + $this->assertTrue($complete_called); + } + + public function testClose() { + $test = new Test(); + $curl = $test->curl; + $curl->setHeader('X-DEBUG-TEST', 'post'); + $curl->post(Test::TEST_URL); + $this->assertTrue(is_resource($curl->curl)); + $curl->close(); + $this->assertFalse(is_resource($curl->curl)); + } + + /** + * @expectedException PHPUnit_Framework_Error_Warning + */ + public function testRequiredOptionCurlInfoHeaderOutEmitsWarning() { + $curl = new Curl(); + $curl->setOpt(CURLINFO_HEADER_OUT, false); + } + + /** + * @expectedException PHPUnit_Framework_Error_Warning + */ + public function testRequiredOptionCurlOptHeaderEmitsWarning() { + $curl = new Curl(); + $curl->setOpt(CURLOPT_HEADER, false); + } + + /** + * @expectedException PHPUnit_Framework_Error_Warning + */ + public function testRequiredOptionCurlOptReturnTransferEmitsWarning() { + $curl = new Curl(); + $curl->setOpt(CURLOPT_RETURNTRANSFER, false); + } + + public function testRequestMethodSuccessiveGetRequests() { + $test = new Test(); + test($test, 'GET', 'POST'); + test($test, 'GET', 'PUT'); + test($test, 'GET', 'PATCH'); + test($test, 'GET', 'DELETE'); + test($test, 'GET', 'HEAD'); + test($test, 'GET', 'OPTIONS'); + } + + public function testRequestMethodSuccessivePostRequests() { + $test = new Test(); + test($test, 'POST', 'GET'); + test($test, 'POST', 'PUT'); + test($test, 'POST', 'PATCH'); + test($test, 'POST', 'DELETE'); + test($test, 'POST', 'HEAD'); + test($test, 'POST', 'OPTIONS'); + } + + public function testRequestMethodSuccessivePutRequests() { + $test = new Test(); + test($test, 'PUT', 'GET'); + test($test, 'PUT', 'POST'); + test($test, 'PUT', 'PATCH'); + test($test, 'PUT', 'DELETE'); + test($test, 'PUT', 'HEAD'); + test($test, 'PUT', 'OPTIONS'); + } + + public function testRequestMethodSuccessivePatchRequests() { + $test = new Test(); + test($test, 'PATCH', 'GET'); + test($test, 'PATCH', 'POST'); + test($test, 'PATCH', 'PUT'); + test($test, 'PATCH', 'DELETE'); + test($test, 'PATCH', 'HEAD'); + test($test, 'PATCH', 'OPTIONS'); + } + + public function testRequestMethodSuccessiveDeleteRequests() { + $test = new Test(); + test($test, 'DELETE', 'GET'); + test($test, 'DELETE', 'POST'); + test($test, 'DELETE', 'PUT'); + test($test, 'DELETE', 'PATCH'); + test($test, 'DELETE', 'HEAD'); + test($test, 'DELETE', 'OPTIONS'); + } + + public function testRequestMethodSuccessiveHeadRequests() { + $test = new Test(); + test($test, 'HEAD', 'GET'); + test($test, 'HEAD', 'POST'); + test($test, 'HEAD', 'PUT'); + test($test, 'HEAD', 'PATCH'); + test($test, 'HEAD', 'DELETE'); + test($test, 'HEAD', 'OPTIONS'); + } + + public function testRequestMethodSuccessiveOptionsRequests() { + $test = new Test(); + test($test, 'OPTIONS', 'GET'); + test($test, 'OPTIONS', 'POST'); + test($test, 'OPTIONS', 'PUT'); + test($test, 'OPTIONS', 'PATCH'); + test($test, 'OPTIONS', 'DELETE'); + test($test, 'OPTIONS', 'HEAD'); + } +} diff --git a/web/app/vendor/php-curl-class/php-curl-class/tests/PHPCurlClass/helper.inc.php b/web/app/vendor/php-curl-class/php-curl-class/tests/PHPCurlClass/helper.inc.php new file mode 100644 index 0000000..406a8e1 --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/tests/PHPCurlClass/helper.inc.php @@ -0,0 +1,47 @@ +curl = new Curl(); + $this->curl->setOpt(CURLOPT_SSL_VERIFYPEER, false); + $this->curl->setOpt(CURLOPT_SSL_VERIFYHOST, false); + } + + function server($test, $request_method, $data=array()) { + $this->curl->setHeader('X-DEBUG-TEST', $test); + $request_method = strtolower($request_method); + $this->curl->$request_method(self::TEST_URL, $data); + return $this->curl->response; + } +} + +function test($instance, $before, $after) { + $instance->server('request_method', $before); + PHPUnit_Framework_Assert::assertEquals($instance->curl->response_headers['X-REQUEST-METHOD'], $before); + $instance->server('request_method', $after); + PHPUnit_Framework_Assert::assertEquals($instance->curl->response_headers['X-REQUEST-METHOD'], $after); +} + +function create_png() { + // PNG image data, 1 x 1, 1-bit colormap, non-interlaced + ob_start(); + imagepng(imagecreatefromstring(base64_decode('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'))); + $raw_image = ob_get_contents(); + ob_end_clean(); + return $raw_image; +} + +function create_tmp_file($data) { + $tmp_file = tmpfile(); + fwrite($tmp_file, $data); + rewind($tmp_file); + return $tmp_file; +} + +function get_png() { + $tmp_filename = tempnam('/tmp', 'php-curl-class.'); + file_put_contents($tmp_filename, create_png()); + return $tmp_filename; +} diff --git a/web/app/vendor/php-curl-class/php-curl-class/tests/PHPCurlClass/index.php b/web/app/vendor/php-curl-class/php-curl-class/tests/PHPCurlClass/index.php new file mode 120000 index 0000000..21a87f6 --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/tests/PHPCurlClass/index.php @@ -0,0 +1 @@ +server.php \ No newline at end of file diff --git a/web/app/vendor/php-curl-class/php-curl-class/tests/PHPCurlClass/server.hdf b/web/app/vendor/php-curl-class/php-curl-class/tests/PHPCurlClass/server.hdf new file mode 100644 index 0000000..8e49d88 --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/tests/PHPCurlClass/server.hdf @@ -0,0 +1,7 @@ +Log { + Level = Verbose +} + +Server { + DefaultDocument = index.php +} diff --git a/web/app/vendor/php-curl-class/php-curl-class/tests/PHPCurlClass/server.php b/web/app/vendor/php-curl-class/php-curl-class/tests/PHPCurlClass/server.php new file mode 100644 index 0000000..96e835b --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/tests/PHPCurlClass/server.php @@ -0,0 +1,132 @@ + $_SERVER['PHP_AUTH_USER'], + 'password' => $_SERVER['PHP_AUTH_PW'], + )); + exit; +} +else if ($test === 'get') { + echo http_build_query($_GET); + exit; +} +else if ($test === 'post') { + echo http_build_query($_POST); + exit; +} +else if ($test === 'put') { + echo $http_raw_post_data; + exit; +} +else if ($test === 'post_multidimensional') { + echo $http_raw_post_data; + exit; +} +else if ($test === 'post_file_path_upload') { + echo mime_content_type($_FILES[$key]['tmp_name']); + exit; +} +else if ($test === 'put_file_handle') { + $tmp_filename = tempnam('/tmp', 'php-curl-class.'); + file_put_contents($tmp_filename, $http_raw_post_data); + echo mime_content_type($tmp_filename); + unlink($tmp_filename); + exit; +} +else if ($test === 'request_method') { + header('X-REQUEST-METHOD: ' . $request_method); + echo $request_method; + exit; +} +else if ($test === 'request_uri') { + echo $_SERVER['REQUEST_URI']; + exit; +} +else if ($test === 'cookiejar') { + setcookie('mycookie', 'yum'); + exit; +} +else if ($test === 'multiple_cookie') { + setcookie('cookie1', 'scrumptious'); + setcookie('cookie2', 'mouthwatering'); + exit; +} +else if ($test === 'response_header') { + header('Content-Type: application/json'); + header('ETag: ' . md5('worldpeace')); + exit; +} +else if ($test === 'json_response') { + $key = $_POST['key']; + $value = $_POST['value']; + header($key . ': ' . $value); + echo json_encode(array( + 'null' => null, + 'true' => true, + 'false' => false, + 'integer' => 1, + 'float' => 3.14, + 'empty' => '', + 'string' => 'string', + )); + exit; +} +else if ($test === 'error_message') { + if (function_exists('http_response_code')) { + http_response_code(401); + } + else { + header('HTTP/1.1 401 Unauthorized'); + } + exit; +} + +header('Content-Type: text/plain'); + +$data_mapping = array( + 'cookie' => '_COOKIE', + 'delete' => '_GET', + 'get' => '_GET', + 'patch' => '_PATCH', + 'post' => '_POST', + 'put' => '_PUT', + 'server' => '_SERVER', +); + +$data = $$data_mapping[$test]; +$value = isset($data[$key]) ? $data[$key] : ''; +echo $value; diff --git a/web/app/vendor/php-curl-class/php-curl-class/tests/phpunit.xml b/web/app/vendor/php-curl-class/php-curl-class/tests/phpunit.xml new file mode 100644 index 0000000..8eccd99 --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/tests/phpunit.xml @@ -0,0 +1,8 @@ + + + . + + + + + diff --git a/web/app/vendor/php-curl-class/php-curl-class/tests/run.sh b/web/app/vendor/php-curl-class/php-curl-class/tests/run.sh new file mode 100644 index 0000000..54330ba --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/tests/run.sh @@ -0,0 +1,4 @@ +php -S 127.0.0.1:8000 -t PHPCurlClass/ & +pid=$! +phpunit --configuration phpunit.xml +kill $pid diff --git a/web/app/vendor/php-curl-class/php-curl-class/tests/syntax.sh b/web/app/vendor/php-curl-class/php-curl-class/tests/syntax.sh new file mode 100644 index 0000000..a5a32f1 --- /dev/null +++ b/web/app/vendor/php-curl-class/php-curl-class/tests/syntax.sh @@ -0,0 +1 @@ +phpcs --standard=PSR2 ../src/Curl.class.php diff --git a/web/app/vendor/symfony/deprecation-contracts/.gitignore b/web/app/vendor/symfony/deprecation-contracts/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/web/app/vendor/symfony/deprecation-contracts/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/web/app/vendor/symfony/deprecation-contracts/CHANGELOG.md b/web/app/vendor/symfony/deprecation-contracts/CHANGELOG.md new file mode 100644 index 0000000..7932e26 --- /dev/null +++ b/web/app/vendor/symfony/deprecation-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/web/app/vendor/symfony/deprecation-contracts/LICENSE b/web/app/vendor/symfony/deprecation-contracts/LICENSE new file mode 100644 index 0000000..406242f --- /dev/null +++ b/web/app/vendor/symfony/deprecation-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020-2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/web/app/vendor/symfony/deprecation-contracts/README.md b/web/app/vendor/symfony/deprecation-contracts/README.md new file mode 100644 index 0000000..4957933 --- /dev/null +++ b/web/app/vendor/symfony/deprecation-contracts/README.md @@ -0,0 +1,26 @@ +Symfony Deprecation Contracts +============================= + +A generic function and convention to trigger deprecation notices. + +This package provides a single global function named `trigger_deprecation()` that triggers silenced deprecation notices. + +By using a custom PHP error handler such as the one provided by the Symfony ErrorHandler component, +the triggered deprecations can be caught and logged for later discovery, both on dev and prod environments. + +The function requires at least 3 arguments: + - the name of the Composer package that is triggering the deprecation + - the version of the package that introduced the deprecation + - the message of the deprecation + - more arguments can be provided: they will be inserted in the message using `printf()` formatting + +Example: +```php +trigger_deprecation('symfony/blockchain', '8.9', 'Using "%s" is deprecated, use "%s" instead.', 'bitcoin', 'fabcoin'); +``` + +This will generate the following message: +`Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.` + +While not necessarily recommended, the deprecation notices can be completely ignored by declaring an empty +`function trigger_deprecation() {}` in your application. diff --git a/web/app/vendor/symfony/deprecation-contracts/composer.json b/web/app/vendor/symfony/deprecation-contracts/composer.json new file mode 100644 index 0000000..cc7cc12 --- /dev/null +++ b/web/app/vendor/symfony/deprecation-contracts/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/deprecation-contracts", + "type": "library", + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/web/app/vendor/symfony/deprecation-contracts/function.php b/web/app/vendor/symfony/deprecation-contracts/function.php new file mode 100644 index 0000000..d437150 --- /dev/null +++ b/web/app/vendor/symfony/deprecation-contracts/function.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (!function_exists('trigger_deprecation')) { + /** + * Triggers a silenced deprecation notice. + * + * @param string $package The name of the Composer package that is triggering the deprecation + * @param string $version The version of the package that introduced the deprecation + * @param string $message The message of the deprecation + * @param mixed ...$args Values to insert in the message using printf() formatting + * + * @author Nicolas Grekas + */ + function trigger_deprecation(string $package, string $version, string $message, ...$args): void + { + @trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED); + } +} diff --git a/web/app/vendor/symfony/finder/CHANGELOG.md b/web/app/vendor/symfony/finder/CHANGELOG.md index 9e2fc5a..6a44e87 100644 --- a/web/app/vendor/symfony/finder/CHANGELOG.md +++ b/web/app/vendor/symfony/finder/CHANGELOG.md @@ -1,11 +1,6 @@ CHANGELOG ========= -6.0 ---- - - * Remove `Comparator::setTarget()` and `Comparator::setOperator()` - 5.4.0 ----- diff --git a/web/app/vendor/symfony/finder/Comparator/Comparator.php b/web/app/vendor/symfony/finder/Comparator/Comparator.php index bd68583..3af551f 100644 --- a/web/app/vendor/symfony/finder/Comparator/Comparator.php +++ b/web/app/vendor/symfony/finder/Comparator/Comparator.php @@ -16,47 +16,102 @@ namespace Symfony\Component\Finder\Comparator; */ class Comparator { - private string $target; - private string $operator; + private $target; + private $operator = '=='; - public function __construct(string $target, string $operator = '==') + public function __construct(string $target = null, string $operator = '==') { - if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) { - throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator)); + if (null === $target) { + trigger_deprecation('symfony/finder', '5.4', 'Constructing a "%s" without setting "$target" is deprecated.', __CLASS__); } $this->target = $target; - $this->operator = $operator; + $this->doSetOperator($operator); } /** * Gets the target value. + * + * @return string */ - public function getTarget(): string + public function getTarget() { + if (null === $this->target) { + trigger_deprecation('symfony/finder', '5.4', 'Calling "%s" without initializing the target is deprecated.', __METHOD__); + } + return $this->target; } /** - * Gets the comparison operator. + * @deprecated set the target via the constructor instead */ - public function getOperator(): string + public function setTarget(string $target) + { + trigger_deprecation('symfony/finder', '5.4', '"%s" is deprecated. Set the target via the constructor instead.', __METHOD__); + + $this->target = $target; + } + + /** + * Gets the comparison operator. + * + * @return string + */ + public function getOperator() { return $this->operator; } /** - * Tests against the target. + * Sets the comparison operator. + * + * @throws \InvalidArgumentException + * + * @deprecated set the operator via the constructor instead */ - public function test(mixed $test): bool + public function setOperator(string $operator) { - return match ($this->operator) { - '>' => $test > $this->target, - '>=' => $test >= $this->target, - '<' => $test < $this->target, - '<=' => $test <= $this->target, - '!=' => $test != $this->target, - default => $test == $this->target, - }; + trigger_deprecation('symfony/finder', '5.4', '"%s" is deprecated. Set the operator via the constructor instead.', __METHOD__); + + $this->doSetOperator('' === $operator ? '==' : $operator); + } + + /** + * Tests against the target. + * + * @param mixed $test A test value + * + * @return bool + */ + public function test($test) + { + if (null === $this->target) { + trigger_deprecation('symfony/finder', '5.4', 'Calling "%s" without initializing the target is deprecated.', __METHOD__); + } + + switch ($this->operator) { + case '>': + return $test > $this->target; + case '>=': + return $test >= $this->target; + case '<': + return $test < $this->target; + case '<=': + return $test <= $this->target; + case '!=': + return $test != $this->target; + } + + return $test == $this->target; + } + + private function doSetOperator(string $operator): void + { + if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) { + throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator)); + } + + $this->operator = $operator; } } diff --git a/web/app/vendor/symfony/finder/Comparator/DateComparator.php b/web/app/vendor/symfony/finder/Comparator/DateComparator.php index 159964d..8f651e1 100644 --- a/web/app/vendor/symfony/finder/Comparator/DateComparator.php +++ b/web/app/vendor/symfony/finder/Comparator/DateComparator.php @@ -32,7 +32,7 @@ class DateComparator extends Comparator try { $date = new \DateTime($matches[2]); $target = $date->format('U'); - } catch (\Exception) { + } catch (\Exception $e) { throw new \InvalidArgumentException(sprintf('"%s" is not a valid date.', $matches[2])); } diff --git a/web/app/vendor/symfony/finder/Finder.php b/web/app/vendor/symfony/finder/Finder.php index 4636f05..8cc564c 100644 --- a/web/app/vendor/symfony/finder/Finder.php +++ b/web/app/vendor/symfony/finder/Finder.php @@ -45,27 +45,27 @@ class Finder implements \IteratorAggregate, \Countable public const IGNORE_DOT_FILES = 2; public const IGNORE_VCS_IGNORED_FILES = 4; - private int $mode = 0; - private array $names = []; - private array $notNames = []; - private array $exclude = []; - private array $filters = []; - private array $depths = []; - private array $sizes = []; - private bool $followLinks = false; - private bool $reverseSorting = false; - private \Closure|int|false $sort = false; - private int $ignore = 0; - private array $dirs = []; - private array $dates = []; - private array $iterators = []; - private array $contains = []; - private array $notContains = []; - private array $paths = []; - private array $notPaths = []; - private bool $ignoreUnreadableDirs = false; + private $mode = 0; + private $names = []; + private $notNames = []; + private $exclude = []; + private $filters = []; + private $depths = []; + private $sizes = []; + private $followLinks = false; + private $reverseSorting = false; + private $sort = false; + private $ignore = 0; + private $dirs = []; + private $dates = []; + private $iterators = []; + private $contains = []; + private $notContains = []; + private $paths = []; + private $notPaths = []; + private $ignoreUnreadableDirs = false; - private static array $vcsPatterns = ['.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg']; + private static $vcsPatterns = ['.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg']; public function __construct() { @@ -74,8 +74,10 @@ class Finder implements \IteratorAggregate, \Countable /** * Creates a new Finder. + * + * @return static */ - public static function create(): static + public static function create() { return new static(); } @@ -85,7 +87,7 @@ class Finder implements \IteratorAggregate, \Countable * * @return $this */ - public function directories(): static + public function directories() { $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES; @@ -97,7 +99,7 @@ class Finder implements \IteratorAggregate, \Countable * * @return $this */ - public function files(): static + public function files() { $this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES; @@ -120,7 +122,7 @@ class Finder implements \IteratorAggregate, \Countable * @see DepthRangeFilterIterator * @see NumberComparator */ - public function depth(string|int|array $levels): static + public function depth($levels) { foreach ((array) $levels as $level) { $this->depths[] = new Comparator\NumberComparator($level); @@ -148,7 +150,7 @@ class Finder implements \IteratorAggregate, \Countable * @see DateRangeFilterIterator * @see DateComparator */ - public function date(string|array $dates): static + public function date($dates) { foreach ((array) $dates as $date) { $this->dates[] = new Comparator\DateComparator($date); @@ -173,7 +175,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see FilenameFilterIterator */ - public function name(string|array $patterns): static + public function name($patterns) { $this->names = array_merge($this->names, (array) $patterns); @@ -189,7 +191,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see FilenameFilterIterator */ - public function notName(string|array $patterns): static + public function notName($patterns) { $this->notNames = array_merge($this->notNames, (array) $patterns); @@ -211,7 +213,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see FilecontentFilterIterator */ - public function contains(string|array $patterns): static + public function contains($patterns) { $this->contains = array_merge($this->contains, (array) $patterns); @@ -233,7 +235,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see FilecontentFilterIterator */ - public function notContains(string|array $patterns): static + public function notContains($patterns) { $this->notContains = array_merge($this->notContains, (array) $patterns); @@ -257,7 +259,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see FilenameFilterIterator */ - public function path(string|array $patterns): static + public function path($patterns) { $this->paths = array_merge($this->paths, (array) $patterns); @@ -281,7 +283,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see FilenameFilterIterator */ - public function notPath(string|array $patterns): static + public function notPath($patterns) { $this->notPaths = array_merge($this->notPaths, (array) $patterns); @@ -303,7 +305,7 @@ class Finder implements \IteratorAggregate, \Countable * @see SizeRangeFilterIterator * @see NumberComparator */ - public function size(string|int|array $sizes): static + public function size($sizes) { foreach ((array) $sizes as $size) { $this->sizes[] = new Comparator\NumberComparator($size); @@ -325,7 +327,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see ExcludeDirectoryFilterIterator */ - public function exclude(string|array $dirs): static + public function exclude($dirs) { $this->exclude = array_merge($this->exclude, (array) $dirs); @@ -341,7 +343,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see ExcludeDirectoryFilterIterator */ - public function ignoreDotFiles(bool $ignoreDotFiles): static + public function ignoreDotFiles(bool $ignoreDotFiles) { if ($ignoreDotFiles) { $this->ignore |= static::IGNORE_DOT_FILES; @@ -361,7 +363,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see ExcludeDirectoryFilterIterator */ - public function ignoreVCS(bool $ignoreVCS): static + public function ignoreVCS(bool $ignoreVCS) { if ($ignoreVCS) { $this->ignore |= static::IGNORE_VCS_FILES; @@ -379,7 +381,7 @@ class Finder implements \IteratorAggregate, \Countable * * @return $this */ - public function ignoreVCSIgnored(bool $ignoreVCSIgnored): static + public function ignoreVCSIgnored(bool $ignoreVCSIgnored) { if ($ignoreVCSIgnored) { $this->ignore |= static::IGNORE_VCS_IGNORED_FILES; @@ -397,7 +399,7 @@ class Finder implements \IteratorAggregate, \Countable * * @param string|string[] $pattern VCS patterns to ignore */ - public static function addVCSPattern(string|array $pattern) + public static function addVCSPattern($pattern) { foreach ((array) $pattern as $p) { self::$vcsPatterns[] = $p; @@ -417,7 +419,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see SortableIterator */ - public function sort(\Closure $closure): static + public function sort(\Closure $closure) { $this->sort = $closure; @@ -433,7 +435,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see SortableIterator */ - public function sortByName(bool $useNaturalSort = false): static + public function sortByName(bool $useNaturalSort = false) { $this->sort = $useNaturalSort ? Iterator\SortableIterator::SORT_BY_NAME_NATURAL : Iterator\SortableIterator::SORT_BY_NAME; @@ -449,7 +451,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see SortableIterator */ - public function sortByType(): static + public function sortByType() { $this->sort = Iterator\SortableIterator::SORT_BY_TYPE; @@ -467,7 +469,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see SortableIterator */ - public function sortByAccessedTime(): static + public function sortByAccessedTime() { $this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME; @@ -479,7 +481,7 @@ class Finder implements \IteratorAggregate, \Countable * * @return $this */ - public function reverseSorting(): static + public function reverseSorting() { $this->reverseSorting = true; @@ -499,7 +501,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see SortableIterator */ - public function sortByChangedTime(): static + public function sortByChangedTime() { $this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME; @@ -517,7 +519,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see SortableIterator */ - public function sortByModifiedTime(): static + public function sortByModifiedTime() { $this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME; @@ -534,7 +536,7 @@ class Finder implements \IteratorAggregate, \Countable * * @see CustomFilterIterator */ - public function filter(\Closure $closure): static + public function filter(\Closure $closure) { $this->filters[] = $closure; @@ -546,7 +548,7 @@ class Finder implements \IteratorAggregate, \Countable * * @return $this */ - public function followLinks(): static + public function followLinks() { $this->followLinks = true; @@ -560,7 +562,7 @@ class Finder implements \IteratorAggregate, \Countable * * @return $this */ - public function ignoreUnreadableDirs(bool $ignore = true): static + public function ignoreUnreadableDirs(bool $ignore = true) { $this->ignoreUnreadableDirs = $ignore; @@ -576,7 +578,7 @@ class Finder implements \IteratorAggregate, \Countable * * @throws DirectoryNotFoundException if one of the directories does not exist */ - public function in(string|array $dirs): static + public function in($dirs) { $resolvedDirs = []; @@ -585,7 +587,7 @@ class Finder implements \IteratorAggregate, \Countable $resolvedDirs[] = [$this->normalizeDir($dir)]; } elseif ($glob = glob($dir, (\defined('GLOB_BRACE') ? \GLOB_BRACE : 0) | \GLOB_ONLYDIR | \GLOB_NOSORT)) { sort($glob); - $resolvedDirs[] = array_map($this->normalizeDir(...), $glob); + $resolvedDirs[] = array_map([$this, 'normalizeDir'], $glob); } else { throw new DirectoryNotFoundException(sprintf('The "%s" directory does not exist.', $dir)); } @@ -605,7 +607,8 @@ class Finder implements \IteratorAggregate, \Countable * * @throws \LogicException if the in() method has not been called */ - public function getIterator(): \Iterator + #[\ReturnTypeWillChange] + public function getIterator() { if (0 === \count($this->dirs) && 0 === \count($this->iterators)) { throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.'); @@ -648,7 +651,7 @@ class Finder implements \IteratorAggregate, \Countable * * @throws \InvalidArgumentException when the given argument is not iterable */ - public function append(iterable $iterator): static + public function append(iterable $iterator) { if ($iterator instanceof \IteratorAggregate) { $this->iterators[] = $iterator->getIterator(); @@ -670,8 +673,10 @@ class Finder implements \IteratorAggregate, \Countable /** * Check if any results were found. + * + * @return bool */ - public function hasResults(): bool + public function hasResults() { foreach ($this->getIterator() as $_) { return true; @@ -682,8 +687,11 @@ class Finder implements \IteratorAggregate, \Countable /** * Counts all the results collected by the iterators. + * + * @return int */ - public function count(): int + #[\ReturnTypeWillChange] + public function count() { return iterator_count($this->getIterator()); } diff --git a/web/app/vendor/symfony/finder/Gitignore.php b/web/app/vendor/symfony/finder/Gitignore.php index 070074b..d42cca1 100644 --- a/web/app/vendor/symfony/finder/Gitignore.php +++ b/web/app/vendor/symfony/finder/Gitignore.php @@ -43,7 +43,7 @@ class Gitignore foreach ($gitignoreLines as $line) { $line = preg_replace('~(? $iterator The Iterator to filter @@ -45,8 +45,11 @@ class CustomFilterIterator extends \FilterIterator /** * Filters the iterator values. + * + * @return bool */ - public function accept(): bool + #[\ReturnTypeWillChange] + public function accept() { $fileinfo = $this->current(); diff --git a/web/app/vendor/symfony/finder/Iterator/DateRangeFilterIterator.php b/web/app/vendor/symfony/finder/Iterator/DateRangeFilterIterator.php index 718d42b..f592e19 100644 --- a/web/app/vendor/symfony/finder/Iterator/DateRangeFilterIterator.php +++ b/web/app/vendor/symfony/finder/Iterator/DateRangeFilterIterator.php @@ -22,7 +22,7 @@ use Symfony\Component\Finder\Comparator\DateComparator; */ class DateRangeFilterIterator extends \FilterIterator { - private array $comparators = []; + private $comparators = []; /** * @param \Iterator $iterator @@ -37,8 +37,11 @@ class DateRangeFilterIterator extends \FilterIterator /** * Filters the iterator values. + * + * @return bool */ - public function accept(): bool + #[\ReturnTypeWillChange] + public function accept() { $fileinfo = $this->current(); diff --git a/web/app/vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php b/web/app/vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php index 1cddb5f..f593a3f 100644 --- a/web/app/vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php +++ b/web/app/vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php @@ -23,7 +23,7 @@ namespace Symfony\Component\Finder\Iterator; */ class DepthRangeFilterIterator extends \FilterIterator { - private int $minDepth = 0; + private $minDepth = 0; /** * @param \RecursiveIteratorIterator<\RecursiveIterator> $iterator The Iterator to filter @@ -40,8 +40,11 @@ class DepthRangeFilterIterator extends \FilterIterator /** * Filters the iterator values. + * + * @return bool */ - public function accept(): bool + #[\ReturnTypeWillChange] + public function accept() { return $this->getInnerIterator()->getDepth() >= $this->minDepth; } diff --git a/web/app/vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php b/web/app/vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php index efe9364..d9e182c 100644 --- a/web/app/vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php +++ b/web/app/vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php @@ -11,27 +11,24 @@ namespace Symfony\Component\Finder\Iterator; -use Symfony\Component\Finder\SplFileInfo; - /** * ExcludeDirectoryFilterIterator filters out directories. * * @author Fabien Potencier * - * @extends \FilterIterator - * @implements \RecursiveIterator + * @extends \FilterIterator + * @implements \RecursiveIterator */ class ExcludeDirectoryFilterIterator extends \FilterIterator implements \RecursiveIterator { - /** @var \Iterator */ - private \Iterator $iterator; - private bool $isRecursive; - private array $excludedDirs = []; - private ?string $excludedPattern = null; + private $iterator; + private $isRecursive; + private $excludedDirs = []; + private $excludedPattern; /** - * @param \Iterator $iterator The Iterator to filter - * @param string[] $directories An array of directories to exclude + * @param \Iterator $iterator The Iterator to filter + * @param string[] $directories An array of directories to exclude */ public function __construct(\Iterator $iterator, array $directories) { @@ -55,8 +52,11 @@ class ExcludeDirectoryFilterIterator extends \FilterIterator implements \Recursi /** * Filters the iterator values. + * + * @return bool */ - public function accept(): bool + #[\ReturnTypeWillChange] + public function accept() { if ($this->isRecursive && isset($this->excludedDirs[$this->getFilename()]) && $this->isDir()) { return false; @@ -72,12 +72,20 @@ class ExcludeDirectoryFilterIterator extends \FilterIterator implements \Recursi return true; } - public function hasChildren(): bool + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function hasChildren() { return $this->isRecursive && $this->iterator->hasChildren(); } - public function getChildren(): self + /** + * @return self + */ + #[\ReturnTypeWillChange] + public function getChildren() { $children = new self($this->iterator->getChildren(), []); $children->excludedDirs = $this->excludedDirs; diff --git a/web/app/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php b/web/app/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php index 2130378..793ae35 100644 --- a/web/app/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php +++ b/web/app/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php @@ -23,11 +23,11 @@ class FileTypeFilterIterator extends \FilterIterator public const ONLY_FILES = 1; public const ONLY_DIRECTORIES = 2; - private int $mode; + private $mode; /** - * @param \Iterator $iterator The Iterator to filter - * @param int $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES) + * @param \Iterator $iterator The Iterator to filter + * @param int $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES) */ public function __construct(\Iterator $iterator, int $mode) { @@ -38,8 +38,11 @@ class FileTypeFilterIterator extends \FilterIterator /** * Filters the iterator values. + * + * @return bool */ - public function accept(): bool + #[\ReturnTypeWillChange] + public function accept() { $fileinfo = $this->current(); if (self::ONLY_DIRECTORIES === (self::ONLY_DIRECTORIES & $this->mode) && $fileinfo->isFile()) { diff --git a/web/app/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php b/web/app/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php index bdc71ff..79f8c29 100644 --- a/web/app/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php +++ b/web/app/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php @@ -11,22 +11,23 @@ namespace Symfony\Component\Finder\Iterator; -use Symfony\Component\Finder\SplFileInfo; - /** * FilecontentFilterIterator filters files by their contents using patterns (regexps or strings). * * @author Fabien Potencier * @author Włodzimierz Gajda * - * @extends MultiplePcreFilterIterator + * @extends MultiplePcreFilterIterator */ class FilecontentFilterIterator extends MultiplePcreFilterIterator { /** * Filters the iterator values. + * + * @return bool */ - public function accept(): bool + #[\ReturnTypeWillChange] + public function accept() { if (!$this->matchRegexps && !$this->noMatchRegexps) { return true; @@ -50,8 +51,10 @@ class FilecontentFilterIterator extends MultiplePcreFilterIterator * Converts string to regexp if necessary. * * @param string $str Pattern: string or regexp + * + * @return string */ - protected function toRegex(string $str): string + protected function toRegex(string $str) { return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; } diff --git a/web/app/vendor/symfony/finder/Iterator/FilenameFilterIterator.php b/web/app/vendor/symfony/finder/Iterator/FilenameFilterIterator.php index 05d9535..77b3b24 100644 --- a/web/app/vendor/symfony/finder/Iterator/FilenameFilterIterator.php +++ b/web/app/vendor/symfony/finder/Iterator/FilenameFilterIterator.php @@ -24,8 +24,11 @@ class FilenameFilterIterator extends MultiplePcreFilterIterator { /** * Filters the iterator values. + * + * @return bool */ - public function accept(): bool + #[\ReturnTypeWillChange] + public function accept() { return $this->isAccepted($this->current()->getFilename()); } @@ -37,8 +40,10 @@ class FilenameFilterIterator extends MultiplePcreFilterIterator * Glob strings are transformed with Glob::toRegex(). * * @param string $str Pattern: glob or regexp + * + * @return string */ - protected function toRegex(string $str): string + protected function toRegex(string $str) { return $this->isRegex($str) ? $str : Glob::toRegex($str); } diff --git a/web/app/vendor/symfony/finder/Iterator/LazyIterator.php b/web/app/vendor/symfony/finder/Iterator/LazyIterator.php index 5b5806b..32cc37f 100644 --- a/web/app/vendor/symfony/finder/Iterator/LazyIterator.php +++ b/web/app/vendor/symfony/finder/Iterator/LazyIterator.php @@ -18,11 +18,11 @@ namespace Symfony\Component\Finder\Iterator; */ class LazyIterator implements \IteratorAggregate { - private \Closure $iteratorFactory; + private $iteratorFactory; public function __construct(callable $iteratorFactory) { - $this->iteratorFactory = $iteratorFactory(...); + $this->iteratorFactory = $iteratorFactory; } public function getIterator(): \Traversable diff --git a/web/app/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php b/web/app/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php index 82a9df3..564765d 100644 --- a/web/app/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php +++ b/web/app/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php @@ -27,9 +27,9 @@ abstract class MultiplePcreFilterIterator extends \FilterIterator protected $noMatchRegexps = []; /** - * @param \Iterator $iterator The Iterator to filter - * @param string[] $matchPatterns An array of patterns that need to match - * @param string[] $noMatchPatterns An array of patterns that need to not match + * @param \Iterator $iterator The Iterator to filter + * @param string[] $matchPatterns An array of patterns that need to match + * @param string[] $noMatchPatterns An array of patterns that need to not match */ public function __construct(\Iterator $iterator, array $matchPatterns, array $noMatchPatterns) { @@ -50,8 +50,10 @@ abstract class MultiplePcreFilterIterator extends \FilterIterator * If there is no regexps defined in the class, this method will accept the string. * Such case can be handled by child classes before calling the method if they want to * apply a different behavior. + * + * @return bool */ - protected function isAccepted(string $string): bool + protected function isAccepted(string $string) { // should at least not match one rule to exclude foreach ($this->noMatchRegexps as $regex) { @@ -77,8 +79,10 @@ abstract class MultiplePcreFilterIterator extends \FilterIterator /** * Checks whether the string is a regex. + * + * @return bool */ - protected function isRegex(string $str): bool + protected function isRegex(string $str) { $availableModifiers = 'imsxuADU'; @@ -106,6 +110,8 @@ abstract class MultiplePcreFilterIterator extends \FilterIterator /** * Converts string into regexp. + * + * @return string */ - abstract protected function toRegex(string $str): string; + abstract protected function toRegex(string $str); } diff --git a/web/app/vendor/symfony/finder/Iterator/PathFilterIterator.php b/web/app/vendor/symfony/finder/Iterator/PathFilterIterator.php index c6d5813..7974c4e 100644 --- a/web/app/vendor/symfony/finder/Iterator/PathFilterIterator.php +++ b/web/app/vendor/symfony/finder/Iterator/PathFilterIterator.php @@ -11,22 +11,23 @@ namespace Symfony\Component\Finder\Iterator; -use Symfony\Component\Finder\SplFileInfo; - /** * PathFilterIterator filters files by path patterns (e.g. some/special/dir). * * @author Fabien Potencier * @author Włodzimierz Gajda * - * @extends MultiplePcreFilterIterator + * @extends MultiplePcreFilterIterator */ class PathFilterIterator extends MultiplePcreFilterIterator { /** * Filters the iterator values. + * + * @return bool */ - public function accept(): bool + #[\ReturnTypeWillChange] + public function accept() { $filename = $this->current()->getRelativePathname(); @@ -48,8 +49,10 @@ class PathFilterIterator extends MultiplePcreFilterIterator * Use only / as directory separator (on Windows also). * * @param string $str Pattern: regexp or dirname + * + * @return string */ - protected function toRegex(string $str): string + protected function toRegex(string $str) { return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; } diff --git a/web/app/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php b/web/app/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php index c321aee..27589cd 100644 --- a/web/app/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php +++ b/web/app/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php @@ -18,17 +18,23 @@ use Symfony\Component\Finder\SplFileInfo; * Extends the \RecursiveDirectoryIterator to support relative paths. * * @author Victor Berchet - * @extends \RecursiveDirectoryIterator */ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator { - private bool $ignoreUnreadableDirs; - private ?bool $rewindable = null; + /** + * @var bool + */ + private $ignoreUnreadableDirs; + + /** + * @var bool + */ + private $rewindable; // these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations - private string $rootPath; - private string $subPath; - private string $directorySeparator = '/'; + private $rootPath; + private $subPath; + private $directorySeparator = '/'; /** * @throws \RuntimeException @@ -49,15 +55,17 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator /** * Return an instance of SplFileInfo with support for relative paths. + * + * @return SplFileInfo */ - public function current(): SplFileInfo + #[\ReturnTypeWillChange] + public function current() { // the logic here avoids redoing the same work in all iterations - if (!isset($this->subPath)) { - $this->subPath = $this->getSubPath(); + if (null === $subPathname = $this->subPath) { + $subPathname = $this->subPath = $this->getSubPath(); } - $subPathname = $this->subPath; if ('' !== $subPathname) { $subPathname .= $this->directorySeparator; } @@ -70,7 +78,13 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator return new SplFileInfo($basePath.$subPathname, $this->subPath, $subPathname); } - public function hasChildren(bool $allowLinks = false): bool + /** + * @param bool $allowLinks + * + * @return bool + */ + #[\ReturnTypeWillChange] + public function hasChildren($allowLinks = false) { $hasChildren = parent::hasChildren($allowLinks); @@ -82,16 +96,19 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator parent::getChildren(); return true; - } catch (\UnexpectedValueException) { + } catch (\UnexpectedValueException $e) { // If directory is unreadable and finder is set to ignore it, skip children return false; } } /** + * @return \RecursiveDirectoryIterator + * * @throws AccessDeniedException */ - public function getChildren(): \RecursiveDirectoryIterator + #[\ReturnTypeWillChange] + public function getChildren() { try { $children = parent::getChildren(); @@ -113,8 +130,11 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator /** * Do nothing for non rewindable stream. + * + * @return void */ - public function rewind(): void + #[\ReturnTypeWillChange] + public function rewind() { if (false === $this->isRewindable()) { return; @@ -125,8 +145,10 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator /** * Checks if the stream is rewindable. + * + * @return bool */ - public function isRewindable(): bool + public function isRewindable() { if (null !== $this->rewindable) { return $this->rewindable; diff --git a/web/app/vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php b/web/app/vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php index 925830a..575bf29 100644 --- a/web/app/vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php +++ b/web/app/vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php @@ -22,7 +22,7 @@ use Symfony\Component\Finder\Comparator\NumberComparator; */ class SizeRangeFilterIterator extends \FilterIterator { - private array $comparators = []; + private $comparators = []; /** * @param \Iterator $iterator @@ -37,8 +37,11 @@ class SizeRangeFilterIterator extends \FilterIterator /** * Filters the iterator values. + * + * @return bool */ - public function accept(): bool + #[\ReturnTypeWillChange] + public function accept() { $fileinfo = $this->current(); if (!$fileinfo->isFile()) { diff --git a/web/app/vendor/symfony/finder/Iterator/SortableIterator.php b/web/app/vendor/symfony/finder/Iterator/SortableIterator.php index cc4dbd6..9afde5c 100644 --- a/web/app/vendor/symfony/finder/Iterator/SortableIterator.php +++ b/web/app/vendor/symfony/finder/Iterator/SortableIterator.php @@ -28,9 +28,8 @@ class SortableIterator implements \IteratorAggregate public const SORT_BY_MODIFIED_TIME = 5; public const SORT_BY_NAME_NATURAL = 6; - /** @var \Traversable */ - private \Traversable $iterator; - private \Closure|int $sort; + private $iterator; + private $sort; /** * @param \Traversable $iterator @@ -38,7 +37,7 @@ class SortableIterator implements \IteratorAggregate * * @throws \InvalidArgumentException */ - public function __construct(\Traversable $iterator, int|callable $sort, bool $reverseOrder = false) + public function __construct(\Traversable $iterator, $sort, bool $reverseOrder = false) { $this->iterator = $iterator; $order = $reverseOrder ? -1 : 1; @@ -76,13 +75,17 @@ class SortableIterator implements \IteratorAggregate } elseif (self::SORT_BY_NONE === $sort) { $this->sort = $order; } elseif (\is_callable($sort)) { - $this->sort = $reverseOrder ? static function (\SplFileInfo $a, \SplFileInfo $b) use ($sort) { return -$sort($a, $b); } : $sort(...); + $this->sort = $reverseOrder ? static function (\SplFileInfo $a, \SplFileInfo $b) use ($sort) { return -$sort($a, $b); } : $sort; } else { throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.'); } } - public function getIterator(): \Traversable + /** + * @return \Traversable + */ + #[\ReturnTypeWillChange] + public function getIterator() { if (1 === $this->sort) { return $this->iterator; diff --git a/web/app/vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.php b/web/app/vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.php index 29fc2d9..e27158c 100644 --- a/web/app/vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.php +++ b/web/app/vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.php @@ -13,9 +13,6 @@ namespace Symfony\Component\Finder\Iterator; use Symfony\Component\Finder\Gitignore; -/** - * @extends \FilterIterator - */ final class VcsIgnoredFilterIterator extends \FilterIterator { /** @@ -33,20 +30,10 @@ final class VcsIgnoredFilterIterator extends \FilterIterator */ private $ignoredPathsCache = []; - /** - * @param \Iterator $iterator - */ public function __construct(\Iterator $iterator, string $baseDir) { $this->baseDir = $this->normalizePath($baseDir); - foreach ($this->parentDirectoriesUpwards($this->baseDir) as $parentDirectory) { - if (@is_dir("{$parentDirectory}/.git")) { - $this->baseDir = $parentDirectory; - break; - } - } - parent::__construct($iterator); } @@ -71,7 +58,7 @@ final class VcsIgnoredFilterIterator extends \FilterIterator $ignored = false; - foreach ($this->parentDirectoriesDownwards($fileRealPath) as $parentDirectory) { + foreach ($this->parentsDirectoryDownward($fileRealPath) as $parentDirectory) { if ($this->isIgnored($parentDirectory)) { // rules in ignored directories are ignored, no need to check further. break; @@ -102,11 +89,11 @@ final class VcsIgnoredFilterIterator extends \FilterIterator /** * @return list */ - private function parentDirectoriesUpwards(string $from): array + private function parentsDirectoryDownward(string $fileRealPath): array { $parentDirectories = []; - $parentDirectory = $from; + $parentDirectory = $fileRealPath; while (true) { $newParentDirectory = \dirname($parentDirectory); @@ -116,30 +103,16 @@ final class VcsIgnoredFilterIterator extends \FilterIterator break; } - $parentDirectories[] = $parentDirectory = $newParentDirectory; + $parentDirectory = $newParentDirectory; + + if (0 !== strpos($parentDirectory, $this->baseDir)) { + break; + } + + $parentDirectories[] = $parentDirectory; } - return $parentDirectories; - } - - private function parentDirectoriesUpTo(string $from, string $upTo): array - { - return array_filter( - $this->parentDirectoriesUpwards($from), - static function (string $directory) use ($upTo): bool { - return str_starts_with($directory, $upTo); - } - ); - } - - /** - * @return list - */ - private function parentDirectoriesDownwards(string $fileRealPath): array - { - return array_reverse( - $this->parentDirectoriesUpTo($fileRealPath, $this->baseDir) - ); + return array_reverse($parentDirectories); } /** diff --git a/web/app/vendor/symfony/finder/SplFileInfo.php b/web/app/vendor/symfony/finder/SplFileInfo.php index 867e8e8..11604a2 100644 --- a/web/app/vendor/symfony/finder/SplFileInfo.php +++ b/web/app/vendor/symfony/finder/SplFileInfo.php @@ -18,8 +18,8 @@ namespace Symfony\Component\Finder; */ class SplFileInfo extends \SplFileInfo { - private string $relativePath; - private string $relativePathname; + private $relativePath; + private $relativePathname; /** * @param string $file The file name @@ -37,8 +37,10 @@ class SplFileInfo extends \SplFileInfo * Returns the relative path. * * This path does not contain the file name. + * + * @return string */ - public function getRelativePath(): string + public function getRelativePath() { return $this->relativePath; } @@ -47,8 +49,10 @@ class SplFileInfo extends \SplFileInfo * Returns the relative path name. * * This path contains the file name. + * + * @return string */ - public function getRelativePathname(): string + public function getRelativePathname() { return $this->relativePathname; } @@ -63,9 +67,11 @@ class SplFileInfo extends \SplFileInfo /** * Returns the contents of the file. * + * @return string + * * @throws \RuntimeException */ - public function getContents(): string + public function getContents() { set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); try { diff --git a/web/app/vendor/symfony/finder/composer.json b/web/app/vendor/symfony/finder/composer.json index 06d129c..ef19911 100644 --- a/web/app/vendor/symfony/finder/composer.json +++ b/web/app/vendor/symfony/finder/composer.json @@ -16,10 +16,9 @@ } ], "require": { - "php": ">=8.1" - }, - "require-dev": { - "symfony/filesystem": "^6.0" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16" }, "autoload": { "psr-4": { "Symfony\\Component\\Finder\\": "" }, diff --git a/web/app/vendor/symfony/polyfill-php80/LICENSE b/web/app/vendor/symfony/polyfill-php80/LICENSE new file mode 100644 index 0000000..5593b1d --- /dev/null +++ b/web/app/vendor/symfony/polyfill-php80/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/web/app/vendor/symfony/polyfill-php80/Php80.php b/web/app/vendor/symfony/polyfill-php80/Php80.php new file mode 100644 index 0000000..362dd1a --- /dev/null +++ b/web/app/vendor/symfony/polyfill-php80/Php80.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php80; + +/** + * @author Ion Bazan + * @author Nico Oelgart + * @author Nicolas Grekas + * + * @internal + */ +final class Php80 +{ + public static function fdiv(float $dividend, float $divisor): float + { + return @($dividend / $divisor); + } + + public static function get_debug_type($value): string + { + switch (true) { + case null === $value: return 'null'; + case \is_bool($value): return 'bool'; + case \is_string($value): return 'string'; + case \is_array($value): return 'array'; + case \is_int($value): return 'int'; + case \is_float($value): return 'float'; + case \is_object($value): break; + case $value instanceof \__PHP_Incomplete_Class: return '__PHP_Incomplete_Class'; + default: + if (null === $type = @get_resource_type($value)) { + return 'unknown'; + } + + if ('Unknown' === $type) { + $type = 'closed'; + } + + return "resource ($type)"; + } + + $class = \get_class($value); + + if (false === strpos($class, '@')) { + return $class; + } + + return (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous'; + } + + public static function get_resource_id($res): int + { + if (!\is_resource($res) && null === @get_resource_type($res)) { + throw new \TypeError(sprintf('Argument 1 passed to get_resource_id() must be of the type resource, %s given', get_debug_type($res))); + } + + return (int) $res; + } + + public static function preg_last_error_msg(): string + { + switch (preg_last_error()) { + case \PREG_INTERNAL_ERROR: + return 'Internal error'; + case \PREG_BAD_UTF8_ERROR: + return 'Malformed UTF-8 characters, possibly incorrectly encoded'; + case \PREG_BAD_UTF8_OFFSET_ERROR: + return 'The offset did not correspond to the beginning of a valid UTF-8 code point'; + case \PREG_BACKTRACK_LIMIT_ERROR: + return 'Backtrack limit exhausted'; + case \PREG_RECURSION_LIMIT_ERROR: + return 'Recursion limit exhausted'; + case \PREG_JIT_STACKLIMIT_ERROR: + return 'JIT stack limit exhausted'; + case \PREG_NO_ERROR: + return 'No error'; + default: + return 'Unknown error'; + } + } + + public static function str_contains(string $haystack, string $needle): bool + { + return '' === $needle || false !== strpos($haystack, $needle); + } + + public static function str_starts_with(string $haystack, string $needle): bool + { + return 0 === strncmp($haystack, $needle, \strlen($needle)); + } + + public static function str_ends_with(string $haystack, string $needle): bool + { + if ('' === $needle || $needle === $haystack) { + return true; + } + + if ('' === $haystack) { + return false; + } + + $needleLength = \strlen($needle); + + return $needleLength <= \strlen($haystack) && 0 === substr_compare($haystack, $needle, -$needleLength); + } +} diff --git a/web/app/vendor/symfony/polyfill-php80/PhpToken.php b/web/app/vendor/symfony/polyfill-php80/PhpToken.php new file mode 100644 index 0000000..fe6e691 --- /dev/null +++ b/web/app/vendor/symfony/polyfill-php80/PhpToken.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php80; + +/** + * @author Fedonyuk Anton + * + * @internal + */ +class PhpToken implements \Stringable +{ + /** + * @var int + */ + public $id; + + /** + * @var string + */ + public $text; + + /** + * @var int + */ + public $line; + + /** + * @var int + */ + public $pos; + + public function __construct(int $id, string $text, int $line = -1, int $position = -1) + { + $this->id = $id; + $this->text = $text; + $this->line = $line; + $this->pos = $position; + } + + public function getTokenName(): ?string + { + if ('UNKNOWN' === $name = token_name($this->id)) { + $name = \strlen($this->text) > 1 || \ord($this->text) < 32 ? null : $this->text; + } + + return $name; + } + + /** + * @param int|string|array $kind + */ + public function is($kind): bool + { + foreach ((array) $kind as $value) { + if (\in_array($value, [$this->id, $this->text], true)) { + return true; + } + } + + return false; + } + + public function isIgnorable(): bool + { + return \in_array($this->id, [\T_WHITESPACE, \T_COMMENT, \T_DOC_COMMENT, \T_OPEN_TAG], true); + } + + public function __toString(): string + { + return (string) $this->text; + } + + /** + * @return static[] + */ + public static function tokenize(string $code, int $flags = 0): array + { + $line = 1; + $position = 0; + $tokens = token_get_all($code, $flags); + foreach ($tokens as $index => $token) { + if (\is_string($token)) { + $id = \ord($token); + $text = $token; + } else { + [$id, $text, $line] = $token; + } + $tokens[$index] = new static($id, $text, $line, $position); + $position += \strlen($text); + } + + return $tokens; + } +} diff --git a/web/app/vendor/symfony/polyfill-php80/README.md b/web/app/vendor/symfony/polyfill-php80/README.md new file mode 100644 index 0000000..3816c55 --- /dev/null +++ b/web/app/vendor/symfony/polyfill-php80/README.md @@ -0,0 +1,25 @@ +Symfony Polyfill / Php80 +======================== + +This component provides features added to PHP 8.0 core: + +- [`Stringable`](https://php.net/stringable) interface +- [`fdiv`](https://php.net/fdiv) +- [`ValueError`](https://php.net/valueerror) class +- [`UnhandledMatchError`](https://php.net/unhandledmatcherror) class +- `FILTER_VALIDATE_BOOL` constant +- [`get_debug_type`](https://php.net/get_debug_type) +- [`PhpToken`](https://php.net/phptoken) class +- [`preg_last_error_msg`](https://php.net/preg_last_error_msg) +- [`str_contains`](https://php.net/str_contains) +- [`str_starts_with`](https://php.net/str_starts_with) +- [`str_ends_with`](https://php.net/str_ends_with) +- [`get_resource_id`](https://php.net/get_resource_id) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/web/app/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php b/web/app/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php new file mode 100644 index 0000000..7ea6d27 --- /dev/null +++ b/web/app/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php @@ -0,0 +1,22 @@ +flags = $flags; + } +} diff --git a/web/app/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php b/web/app/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php new file mode 100644 index 0000000..72f1081 --- /dev/null +++ b/web/app/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php @@ -0,0 +1,7 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php80 as p; + +if (\PHP_VERSION_ID >= 80000) { + return; +} + +if (!defined('FILTER_VALIDATE_BOOL') && defined('FILTER_VALIDATE_BOOLEAN')) { + define('FILTER_VALIDATE_BOOL', \FILTER_VALIDATE_BOOLEAN); +} + +if (!function_exists('fdiv')) { + function fdiv(float $num1, float $num2): float { return p\Php80::fdiv($num1, $num2); } +} +if (!function_exists('preg_last_error_msg')) { + function preg_last_error_msg(): string { return p\Php80::preg_last_error_msg(); } +} +if (!function_exists('str_contains')) { + function str_contains(?string $haystack, ?string $needle): bool { return p\Php80::str_contains($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('str_starts_with')) { + function str_starts_with(?string $haystack, ?string $needle): bool { return p\Php80::str_starts_with($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('str_ends_with')) { + function str_ends_with(?string $haystack, ?string $needle): bool { return p\Php80::str_ends_with($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('get_debug_type')) { + function get_debug_type($value): string { return p\Php80::get_debug_type($value); } +} +if (!function_exists('get_resource_id')) { + function get_resource_id($resource): int { return p\Php80::get_resource_id($resource); } +} diff --git a/web/app/vendor/symfony/polyfill-php80/composer.json b/web/app/vendor/symfony/polyfill-php80/composer.json new file mode 100644 index 0000000..cd3e9b6 --- /dev/null +++ b/web/app/vendor/symfony/polyfill-php80/composer.json @@ -0,0 +1,40 @@ +{ + "name": "symfony/polyfill-php80", + "type": "library", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php80\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/web/app/views/page-footer.php b/web/app/views/page-footer.php index 5d806ce..8d1b32e 100644 --- a/web/app/views/page-footer.php +++ b/web/app/views/page-footer.php @@ -13,25 +13,29 @@ if (!isset($ShowPageFooter)) {

      diff --git a/web/css/uoj-bs5.css b/web/css/uoj-bs5.css index 1019f61..80ac420 100644 --- a/web/css/uoj-bs5.css +++ b/web/css/uoj-bs5.css @@ -463,3 +463,8 @@ form.uoj-bs4-form-compressed button { --bs-btn-hover-bg: #d3d4d570; --bs-btn-hover-border-color: transparent; } + +.remote-content center > img + span { + display: block; + font-size: 90%; +}