summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRaindropsSys <contact@minteck.org>2023-05-28 22:23:55 +0200
committerRaindropsSys <contact@minteck.org>2023-05-28 22:23:55 +0200
commit16c315ca4791538bc6d21626183cff3a648ddb2d (patch)
treead5a386ce203cf0ff7f5e7b1a1d43be450918a5f
parentf90bff2e7e6ecae78c786f22dd63a895e2c3d6ee (diff)
downloaddelta-16c315ca4791538bc6d21626183cff3a648ddb2d.tar.gz
delta-16c315ca4791538bc6d21626183cff3a648ddb2d.tar.bz2
delta-16c315ca4791538bc6d21626183cff3a648ddb2d.zip
Updated 16 files and added 26 files (automated)
-rw-r--r--.gitignore2
-rw-r--r--.idea/delta.iml1
-rw-r--r--.idea/jsLibraryMappings.xml1
-rw-r--r--_edit/index.php2
-rw-r--r--_icons/id.svg1
-rw-r--r--_icons/magic-register.svg1
-rw-r--r--_icons/name.svg1
-rw-r--r--_icons/upload-register.svg1
-rw-r--r--_profile/index.php2
-rw-r--r--admin/create/index.php25
-rw-r--r--admin/index.php1
-rw-r--r--admin/register_approve/index.php29
-rw-r--r--admin/register_reject/index.php29
-rw-r--r--admin/registrations/index.php82
-rw-r--r--includes/email.html8
-rw-r--r--includes/email.php99
-rw-r--r--includes/header.php8
-rw-r--r--includes/lang.php2
-rwxr-xr-xincludes/recaptcha/LICENSE29
-rwxr-xr-xincludes/recaptcha/README.md147
-rwxr-xr-xincludes/recaptcha/app.yaml8
-rwxr-xr-xincludes/recaptcha/composer.json39
-rwxr-xr-xincludes/recaptcha/src/ReCaptcha/ReCaptcha.php275
-rwxr-xr-xincludes/recaptcha/src/ReCaptcha/RequestMethod.php49
-rwxr-xr-xincludes/recaptcha/src/ReCaptcha/RequestMethod/Curl.php81
-rwxr-xr-xincludes/recaptcha/src/ReCaptcha/RequestMethod/CurlPost.php104
-rwxr-xr-xincludes/recaptcha/src/ReCaptcha/RequestMethod/Post.php88
-rwxr-xr-xincludes/recaptcha/src/ReCaptcha/RequestMethod/Socket.php112
-rwxr-xr-xincludes/recaptcha/src/ReCaptcha/RequestMethod/SocketPost.php110
-rwxr-xr-xincludes/recaptcha/src/ReCaptcha/RequestParameters.php111
-rwxr-xr-xincludes/recaptcha/src/ReCaptcha/Response.php218
-rwxr-xr-xincludes/recaptcha/src/autoload.php69
-rw-r--r--includes/rules.php74
-rw-r--r--lang/en.json119
-rw-r--r--lang/fr.json127
-rw-r--r--login/index.php2
-rw-r--r--logo.pngbin0 -> 22357 bytes
-rw-r--r--plus/index.php4
-rw-r--r--register/complete/index.php57
-rw-r--r--register/index.php347
-rw-r--r--register/submit/index.php122
-rw-r--r--support/rules/index.php75
42 files changed, 2572 insertions, 90 deletions
diff --git a/.gitignore b/.gitignore
index c595cdf..60d8436 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,4 +5,6 @@ uploads
includes/fcm/firebase.json
includes/phone
includes/email
+includes/recaptcha-secret
+includes/recaptcha-site
includes/kiosk.json \ No newline at end of file
diff --git a/.idea/delta.iml b/.idea/delta.iml
index c956989..71e3dd1 100644
--- a/.idea/delta.iml
+++ b/.idea/delta.iml
@@ -4,5 +4,6 @@
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="library" name="api" level="application" />
</component>
</module> \ No newline at end of file
diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml
index d23208f..21ec924 100644
--- a/.idea/jsLibraryMappings.xml
+++ b/.idea/jsLibraryMappings.xml
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
+ <file url="PROJECT" libraries="{api}" />
<includedPredefinedLibrary name="Node.js Core" />
</component>
</project> \ No newline at end of file
diff --git a/_edit/index.php b/_edit/index.php
index 330e8b3..59a5fc0 100644
--- a/_edit/index.php
+++ b/_edit/index.php
@@ -59,7 +59,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/navigation.php";
<?php else: ?>
<span><?= $data["nick_name"] ?? $data["first_name"] . " " . $data["last_name"] ?><?php if (isset($data["nick_name"]) && trim($data["nick_name"]) !== ""): ?> <small><small><small>(<?= $data["first_name"] . " " . $data["last_name"] ?>)</small></small></small><?php endif; ?></span>
<?php endif; ?>
- <span id="btn-area" class="btn-group"><a style="height: 38px;" id="form-btn" title="<?= l("lang_edit_save") ?>" class="btn btn-outline-primary btn-with-img disabled" data-bs-toggle="tooltip"><img id="save-icon" src="/icons/save.svg" style="width: 24px;"></a><a style="height: 38px;" onclick="preview();" class="btn btn-outline-dark btn-with-img" title="<?= l("lang_edit_preview") ?>" data-bs-toggle="tooltip"><img src="/icons/preview.svg" style="width: 24px;"></a><a style="height: 38px;" href="/<?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/people/" . $id . ".json") ? "people" : "articles" ?>/<?= $id ?>" class="btn btn-outline-dark btn-with-img" title="<?= l("lang_edit_cancel") ?>" data-bs-toggle="tooltip"><img src="/icons/close.svg" style="width: 24px;"></a></span>
+ <span id="btn-area" class="btn-group"><a style="height: 38px;" id="form-btn" title="<?= l("lang_edit_save") ?>" class="btn btn-outline-primary btn-with-img disabled" data-bs-toggle="tooltip"><img id="save-icon" src="/icons/save.svg" style="width: 24px;"></a><a style="height: 38px;" onclick="preview();" class="btn btn-outline-dark btn-with-img" title="<?= l("lang_edit_preview") ?>" data-bs-toggle="tooltip"><img src="/icons/preview.svg" style="width: 24px;"></a><a style="height: 38px;" href="<?= getUrlFromId($id) ?>" class="btn btn-outline-dark btn-with-img" title="<?= l("lang_edit_cancel") ?>" data-bs-toggle="tooltip"><img src="/icons/close.svg" style="width: 24px;"></a></span>
</h1>
<?php if (isset($_PROFILE["requests"][$id])): ?>
diff --git a/_icons/id.svg b/_icons/id.svg
new file mode 100644
index 0000000..7e2bbdd
--- /dev/null
+++ b/_icons/id.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M186-116q-27.5 0-47.25-19.75T119-183v-393q0-27.5 19.75-47.25T186-643h222v-167q0-13.5 10.777-24.25Q429.555-845 443.504-845h73.272q13.949 0 24.586 10.75Q552-823.5 552-810v167h222q27.5 0 47.25 19.75T841-576v393q0 27.5-19.75 47.25T774-116H186Zm0-35h588q14 0 23-9t9-23v-393q0-14-9-23t-23-9H548v49q-7 9-14 12t-17.411 3h-73.178Q433-544 426-547q-7-3-14-12v-49H186q-14 0-23 9t-9 23v393q0 14 9 23t23 9Zm62-112h217v-4q0-14.478-7.5-26.239Q450-305 437.872-309.692q-24.975-9.654-44.423-13.481Q374-327 357-327q-17 0-37.5 4.5T276-310q-13 6-20.5 17.022Q248-281.957 248-267v4Zm315-64h169v-33H563v33Zm-205.765-33q19.206 0 32.486-13.515Q403-387.029 403-406.265q0-19.235-13.044-32.985T357.706-453q-19.206 0-32.956 13.473Q311-426.054 311-406.235q0 18.735 13.515 32.485Q338.029-360 357.235-360ZM563-416h169v-34H563v34ZM443-579h74v-231h-74v231Zm37 199Z" fill="#000000"/></svg> \ No newline at end of file
diff --git a/_icons/magic-register.svg b/_icons/magic-register.svg
new file mode 100644
index 0000000..8841d91
--- /dev/null
+++ b/_icons/magic-register.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="m391.769 696.615 54.488-119.197 118.512-54.803-118.512-54.246-54.488-117.984-54.561 117.984-118.669 54.246 118.669 54.803 54.561 119.197Zm0 105.231-86.923-191.538-192.308-87.693 192.308-86.923 86.923-192.308 87.692 192.308L671 522.615l-191.539 87.693-87.692 191.538Zm316.693 105.77-43.51-96.25-95.721-43.75 95.385-43.077 43.846-96.154 43.846 96.154 96.154 43.077-96.25 43.75-43.75 96.25ZM391.769 522.615Z" fill="#084298"/></svg> \ No newline at end of file
diff --git a/_icons/name.svg b/_icons/name.svg
new file mode 100644
index 0000000..5f26356
--- /dev/null
+++ b/_icons/name.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M511-430q89-60 142.5-133T707-705q0-42-20-73.5T635-810q-62 0-98.5 78.5T500-525q0 29 2.5 52.5T511-430ZM155-156v-59h59v59h-59Zm148 0v-59h59v59h-59Zm147 0v-59h59v59h-59Zm148 0v-59h59v59h-59Zm147 0v-59h59v59h-59ZM143-309l-24-24 64-64-64-64 24-24 64 64 64-64 24 24-64 64 64 64-24 24-64-64-64 64Zm449 0q-34 0-57.5-16.5T496-373q-25 16-51.5 29.5T390-318l-12-32q26-12 52.5-26t50.5-30q-8-23-12-52t-4-62q0-147 46-236t124-89q49 0 78 40t29 98q0 82-60 165.5T523-392q10 24 26.5 36t38.5 12q37 0 75-30t69-82l30 13q-4 40-.5 60t14.5 36q14-8 24-15t26-25l28 23q-29 27-48.5 41T768-309q-18 0-29.5-22.5T725-387q-24 36-61 57t-72 21Z" fill="#000000"/></svg> \ No newline at end of file
diff --git a/_icons/upload-register.svg b/_icons/upload-register.svg
new file mode 100644
index 0000000..f49a5d0
--- /dev/null
+++ b/_icons/upload-register.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="M255.384 856q-23.057 0-39.221-16.163Q200 823.673 200 800.616V694.538h30.769v106.078q0 9.23 7.692 16.923 7.693 7.692 16.923 7.692h449.232q9.23 0 16.923-7.692 7.692-7.693 7.692-16.923V694.538H760v106.078q0 23.057-16.163 39.221Q727.673 856 704.616 856H255.384Zm209.231-147.615V342L363.846 442l-22.231-21.461L480 282.154l138.385 138.385L596.154 442 495.385 342v366.385h-30.77Z" fill="#084298"/></svg> \ No newline at end of file
diff --git a/_profile/index.php b/_profile/index.php
index eacb9ff..733ea0e 100644
--- a/_profile/index.php
+++ b/_profile/index.php
@@ -53,7 +53,7 @@ foreach (array_filter(scandir($_SERVER["DOCUMENT_ROOT"] . "/includes/data/people
<?php badges($data) ?>
</div>
<div class="btn-group">
- <a style="height: 38px;" href="/edit/<?= $id ?>" class="btn btn-outline-dark btn-with-img <?= $id !== $_USER ? "disabled" : "" ?>" title="<?= l("lang_articles_rename") ?>" data-bs-toggle="tooltip"><img src="/icons/edit.svg" style="width: 24px;"></a><a style="height: 38px;" onclick="copy('<?= uuidToId($id) ?>', true)" class="btn btn-outline-dark btn-with-img" title="<?= l("lang_shortener_copy") ?>" data-bs-toggle="tooltip"><img src="/icons/copy.svg" style="width: 24px;"></a>
+ <a style="height: 38px;" href="/edit/<?= $id ?>" class="btn btn-outline-dark btn-with-img <?= $id !== $_USER ? "disabled" : "" ?>" title="<?= l("lang_people_edit") ?>" data-bs-toggle="tooltip"><img src="/icons/edit.svg" style="width: 24px;"></a><a style="height: 38px;" onclick="copy('<?= uuidToId($id) ?>', true)" class="btn btn-outline-dark btn-with-img" title="<?= l("lang_shortener_copy") ?>" data-bs-toggle="tooltip"><img src="/icons/copy.svg" style="width: 24px;"></a>
<button style="height: 38px;" type="button" class="btn btn-outline-dark dropdown-toggle btn-with-img" title="<?= l("lang_profile_options") ?>" data-bs-toggle2="tooltip" data-bs-toggle="dropdown"><img src="/icons/admin.svg" style="width: 24px;"></button>
<ul class="dropdown-menu">
<li><a class="dropdown-item <?= $id === $_USER ? "disabled" : "" ?>" href="/request/?type=userreport&id=<?= $id ?>">
diff --git a/admin/create/index.php b/admin/create/index.php
index 1ab8573..61f3e45 100644
--- a/admin/create/index.php
+++ b/admin/create/index.php
@@ -36,7 +36,30 @@ require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/navigation.php";
<form action="/admin/create/save/?skel=<?= $_GET['skel'] ?>" method="post">
<p>
<input type="hidden" name="contents">
- <div id="editor"><?= $_POST["contents"] ?? json_encode($skel, JSON_PRETTY_PRINT) ?></div>
+ <div id="editor"><?php
+
+ if (isset($_GET["registration"])) {
+ if (preg_match("/^[a-z0-9-]*$/m", $_GET["registration"]) === false || !file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/registrations/" . $_GET["registration"] . ".json")) {
+ header("Location: /admin");
+ die();
+ }
+
+ $reg = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/registrations/" . $_GET["registration"] . ".json"), true);
+
+ $skel["language"] = $reg["lang"];
+ $skel["kiosk"] = true;
+ $skel["first_name"] = $reg["use_name"] ?? $reg["first_name"];
+ $skel["last_name"] = $reg["last_name"];
+ $skel["email"] = $reg["email"];
+ $skel["birth"] = $reg["birth_date"];
+ $skel["phone"] = $reg["phone"];
+
+ echo(json_encode($skel, JSON_PRETTY_PRINT));
+ } else {
+ echo($_POST["contents"] ?? json_encode($skel, JSON_PRETTY_PRINT));
+ }
+
+ ?></div>
<style media="screen">
#editor {
diff --git a/admin/index.php b/admin/index.php
index 0846067..6d14926 100644
--- a/admin/index.php
+++ b/admin/index.php
@@ -13,6 +13,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/navigation.php";
<div class="list-group">
<a href="/admin/requests" class="list-group-item list-group-item-action"><?= l("lang_admin_titles_requests") ?></a>
+ <a href="/admin/registrations" class="list-group-item list-group-item-action"><?= l("lang_admin_titles_registrations") ?></a>
<a href="/admin/objects" class="list-group-item list-group-item-action"><?= l("lang_admin_titles_objects") ?></a>
<a href="/admin/handoff" class="list-group-item list-group-item-action"><?= l("lang_admin_titles_handoff") ?></a>
<a href="/admin/support" class="list-group-item list-group-item-action"><?= l("lang_admin_titles_support") ?></a>
diff --git a/admin/register_approve/index.php b/admin/register_approve/index.php
new file mode 100644
index 0000000..78e719b
--- /dev/null
+++ b/admin/register_approve/index.php
@@ -0,0 +1,29 @@
+<?php
+
+require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php";
+require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/functions.php";
+require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/email.php";
+
+$id = $_GET['id'] ?? null;
+
+if (isset($id)) {
+ if (!preg_match("/[a-zA-Z0-9]/m", $id)) {
+ die();
+ }
+
+ if (!file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/registrations/" . $id . ".json")) {
+ die();
+ }
+} else {
+ die();
+}
+
+$reg = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/registrations/" . $id . ".json"), true);
+
+genLang($reg["lang"]);
+sendRegistrationApproval($reg["email"], $reg["use_name"] ?? $reg["first_name"], $reg["id"]);
+
+rename($_SERVER['DOCUMENT_ROOT'] . "/includes/data/registrations/" . $id . ".json", $_SERVER['DOCUMENT_ROOT'] . "/includes/data/archive/" . $id . ".json");
+
+header("Location: /admin/registrations");
+die(); \ No newline at end of file
diff --git a/admin/register_reject/index.php b/admin/register_reject/index.php
new file mode 100644
index 0000000..5610197
--- /dev/null
+++ b/admin/register_reject/index.php
@@ -0,0 +1,29 @@
+<?php
+
+require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php";
+require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/functions.php";
+require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/email.php";
+
+$id = $_GET['id'] ?? null;
+
+if (isset($id)) {
+ if (!preg_match("/[a-zA-Z0-9]/m", $id)) {
+ die();
+ }
+
+ if (!file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/registrations/" . $id . ".json")) {
+ die();
+ }
+} else {
+ die();
+}
+
+$reg = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/registrations/" . $id . ".json"), true);
+
+genLang($reg["lang"]);
+sendRegistrationRejection($reg["email"], $reg["use_name"] ?? $reg["first_name"], $reg["id"], $_GET["reason"]);
+
+rename($_SERVER['DOCUMENT_ROOT'] . "/includes/data/registrations/" . $id . ".json", $_SERVER['DOCUMENT_ROOT'] . "/includes/data/archive/" . $id . ".json");
+
+header("Location: /admin/registrations");
+die(); \ No newline at end of file
diff --git a/admin/registrations/index.php b/admin/registrations/index.php
new file mode 100644
index 0000000..7c7fe66
--- /dev/null
+++ b/admin/registrations/index.php
@@ -0,0 +1,82 @@
+<?php
+
+require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php";
+$title = "lang_admin_title";
+$title_pre = l("lang_admin_titles_registrations");
+require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/header.php";
+require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/navigation.php";
+
+?>
+
+<div class="container">
+ <br><br>
+ <a href="/admin">← <?= l("lang_admin_title") ?></a>
+ <h1><?= l("lang_admin_titles_registrations") ?></h1>
+
+ <?php
+
+ if (!file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/registrations")) mkdir($_SERVER['DOCUMENT_ROOT'] . "/includes/data/registrations");
+
+ $requestsPre = array_filter(scandir($_SERVER['DOCUMENT_ROOT'] . "/includes/data/registrations"), function ($i) { return !str_starts_with($i, "."); });
+
+ usort($requestsPre, function ($a, $b) {
+ return strtotime(json_decode(pf_utf8_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/registrations/$a")), true)["date"]) - strtotime(json_decode(pf_utf8_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/registrations/$b")), true)["date"]);
+ });
+
+ $requests = [...array_map(function ($i) {
+ $id = $i;
+ $i = json_decode(pf_utf8_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/registrations/$i")), true);
+ $i["_id"] = explode(".", $id)[0];
+ $i["_original"] = json_decode(pf_utf8_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/registrations/$id")), true);
+ return $i;
+ }, $requestsPre)];
+
+ ?>
+
+ <div class="list-group">
+ <?php foreach ($requests as $request): ?>
+ <details class="list-group-item list-group-item-action">
+ <summary><?= $request["id"] ?> (<?= l("lang_admin_requests_preview_author") ?> <?= ($request["use_name"] ?? $request["first_name"]) . " " . $request["last_name"] ?>; <?= timeAgo($request["date"]) ?>)</summary>
+
+ <div class="list-group-item" style="margin-top: 10px;">
+ <p>
+ <b><?= l("lang_admin_registrations_view_0") ?></b> <?= $request["_id"] ?><br>
+ <b><?= l("lang_admin_registrations_view_1") ?></b> <?= $request["id"] ?><br>
+ <b><?= l("lang_admin_registrations_view_2") ?></b> <?= date('r', strtotime($request["date"])) ?><br>
+ <b><?= l("lang_admin_registrations_view_3") ?></b> <?= ($request["use_name"] ?? $request["first_name"]) . " " . $request["last_name"] ?> (<?= $request["first_name"] . " " . $request["last_name"] ?>)<br>
+ <b><?= l("lang_admin_registrations_view_4") ?></b> <?= $request["email"] ?><?= isset($request["phone"]) ? ", " . $request["phone"] : "" ?><br>
+ <b><?= l("lang_admin_registrations_view_5") ?></b> <?= formatDate($request["birth_date"]) ?> (<?= trim(timeAgo($request["birth_date"], false, true) . " " . l("lang_profile_old")) ?>)<br>
+ <b><?= l("lang_admin_registrations_view_6") ?></b> <?= $request["underage_email"] ?? "" ?><br>
+ <b><?= l("lang_admin_registrations_view_7") ?></b> <?= locale_get_display_language($request["lang"], l("lang__name")) ?><br>
+ </p>
+
+ <details style="margin-bottom: 1rem;">
+ <summary><?= l("lang_admin_requests_full") ?></summary>
+ <pre style="margin-bottom: 0;"><?= str_replace(">", "&gt;", str_replace("<", "&lt;", json_encode($request, JSON_PRETTY_PRINT))) ?></pre>
+ </details>
+
+ <a href="/admin/register_approve/?id=<?= $request["_id"] ?>" class="btn btn-outline-success"><?= l("lang_admin_requests_mark") ?></a> <a onclick="reject('<?= $request["_id"] ?>');" class="btn btn-outline-danger"><?= l("lang_admin_requests_reject") ?></a><br>
+ <small class="text-muted">*<?= l("lang_admin_requests_notify") ?> <span class="text-warning">*<?= l("lang_admin_requests_manual") ?>, <a style="color: inherit;" href="/admin/create/?skel=profiles&registration=<?= $request["_id"] ?>"><?= l("lang_admin_registrations_start") ?></a></span></small>
+ </div>
+ </details>
+ <?php endforeach; ?>
+ </div>
+
+ <?php if (count($requests) === 0): ?>
+ <div class="text-muted"><?= l("lang_admin_registrations_nothing") ?></div>
+ <?php endif; ?>
+
+ <br><br>
+
+ <script>
+ function reject(id) {
+ let reason = prompt("<?= str_replace("%1", locale_get_display_language($request["lang"], l("lang__name")), l("lang_admin_registrations_reject")) ?>");
+
+ if (reason !== null) {
+ location.href = "/admin/register_reject/?id=" + id + "&reason=" + encodeURIComponent(reason);
+ }
+ }
+ </script>
+</div>
+
+<?php require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/footer.php"; ?> \ No newline at end of file
diff --git a/includes/email.html b/includes/email.html
index 7e3606d..8030214 100644
--- a/includes/email.html
+++ b/includes/email.html
@@ -1,5 +1,5 @@
<div style="background-color:#eee;margin:10px;border-radius: 10px;padding:50px 0;">
- <img src="data:image/png" style="width: 64px; height: 64px; display: block; margin-left: auto; margin-right: auto;">
+ <img src="cid:logo" alt="Logo" style="width: 64px; height: 64px; display: block; margin-left: auto; margin-right: auto;">
</div>
<div style="font-family: sans-serif; margin-top: 50px;padding:50px;" class="container">
@@ -18,6 +18,12 @@
--bs-gutter-x: 1.5rem;
}
+ blockquote {
+ border-left: 5px solid #ddd;
+ margin-left: 10px;
+ padding: 10px;
+ }
+
@media (min-width: 576px) {
.container {
max-width: 540px;
diff --git a/includes/email.php b/includes/email.php
index 2bee82a..a85d5f6 100644
--- a/includes/email.php
+++ b/includes/email.php
@@ -39,6 +39,102 @@ function sendCode($email, $code) {
$mail->send();
}
+function sendRegistration($email, $name, $id) {
+ global $emailConfig;
+
+ $mail = new PHPMailer(true);
+ $mail->isSMTP();
+ $mail->Host = 'in-v3.mailjet.com';
+ $mail->SMTPAuth = true;
+ $mail->Username = $emailConfig["username"];
+ $mail->Password = $emailConfig["password"];
+ $mail->SMTPSecure = "none";
+ $mail->Port = 587;
+
+ $mail->setFrom('delta@auto.minteck.org', 'Delta');
+ $mail->addAddress($email);
+ $mail->addReplyTo(trim(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/email")), 'Equestria.dev');
+
+ $mail->isHTML();
+ $mail->Subject = str_replace("%1", $id, l("lang_register_email_title"));
+ $mail->addEmbeddedImage($_SERVER['DOCUMENT_ROOT'] . "/logo.png", "logo", "logo.png");
+
+ $body = file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/email.html");
+ $body = str_replace("%3", date('Y'), $body);
+ $body = str_replace("%2", l("lang_register_email_reason"), $body);
+ $body = str_replace("%1", "<p>" . str_replace("%1", strip_tags($name), l("lang_register_email_content_0")) . "</p><p>" . l("lang_register_email_content_1") . "</p><p>" . str_replace("%1", strip_tags($id), l("lang_register_email_content_2")) . "</p><p>" . l("lang_register_email_content_3") . "</p>", $body);
+
+ $mail->CharSet = 'UTF-8';
+ $mail->Body = $body;
+ $mail->AltBody = strip_tags($body);
+
+ $mail->send();
+}
+
+function sendRegistrationApproval($email, $name, $id) {
+ global $emailConfig;
+
+ $mail = new PHPMailer(true);
+ $mail->isSMTP();
+ $mail->Host = 'in-v3.mailjet.com';
+ $mail->SMTPAuth = true;
+ $mail->Username = $emailConfig["username"];
+ $mail->Password = $emailConfig["password"];
+ $mail->SMTPSecure = "none";
+ $mail->Port = 587;
+
+ $mail->setFrom('delta@auto.minteck.org', 'Delta');
+ $mail->addAddress($email);
+ $mail->addReplyTo(trim(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/email")), 'Equestria.dev');
+
+ $mail->isHTML();
+ $mail->Subject = str_replace("%1", $id, l("lang_register_approved_title"));
+ $mail->addEmbeddedImage($_SERVER['DOCUMENT_ROOT'] . "/logo.png", "logo", "logo.png");
+
+ $body = file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/email.html");
+ $body = str_replace("%3", date('Y'), $body);
+ $body = str_replace("%2", l("lang_register_email_reason"), $body);
+ $body = str_replace("%1", "<p>" . str_replace("%1", strip_tags($name), l("lang_register_approved_content_0")) . "</p><p>" . l("lang_register_approved_content_1") . "</p><p>" . l("lang_register_approved_content_2") . "</p><p>" . l("lang_register_approved_content_3") . "</p>", $body);
+
+ $mail->CharSet = 'UTF-8';
+ $mail->Body = $body;
+ $mail->AltBody = strip_tags($body);
+
+ $mail->send();
+}
+
+function sendRegistrationRejection($email, $name, $id, $reason) {
+ global $emailConfig;
+
+ $mail = new PHPMailer(true);
+ $mail->isSMTP();
+ $mail->Host = 'in-v3.mailjet.com';
+ $mail->SMTPAuth = true;
+ $mail->Username = $emailConfig["username"];
+ $mail->Password = $emailConfig["password"];
+ $mail->SMTPSecure = "none";
+ $mail->Port = 587;
+
+ $mail->setFrom('delta@auto.minteck.org', 'Delta');
+ $mail->addAddress($email);
+ $mail->addReplyTo(trim(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/email")), 'Equestria.dev');
+
+ $mail->isHTML();
+ $mail->Subject = str_replace("%1", $id, l("lang_register_rejected_title"));
+ $mail->addEmbeddedImage($_SERVER['DOCUMENT_ROOT'] . "/logo.png", "logo", "logo.png");
+
+ $body = file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/email.html");
+ $body = str_replace("%3", date('Y'), $body);
+ $body = str_replace("%2", l("lang_register_email_reason"), $body);
+ $body = str_replace("%1", "<p>" . str_replace("%1", strip_tags($name), l("lang_register_rejected_content_0")) . "</p><p>" . str_replace("%1", (isset($reason) && trim($reason) !== "" ? l("lang_register_rejected_content_5") . "<blockquote>" . strip_tags($reason) . "</blockquote>" : l("lang_register_rejected_content_4")), l("lang_register_rejected_content_1")) . "</p><p>" . l("lang_register_rejected_content_2") . "</p><p>" . l("lang_register_rejected_content_3") . "</p>", $body);
+
+ $mail->CharSet = 'UTF-8';
+ $mail->Body = $body;
+ $mail->AltBody = strip_tags($body);
+
+ $mail->send();
+}
+
function sendPlanUpdate($email, $plan) {
global $emailConfig;
@@ -77,6 +173,7 @@ function sendPlanUpdate($email, $plan) {
$mail->isHTML();
$mail->Subject = l("lang_email_plan_title");
+ $mail->addEmbeddedImage($_SERVER['DOCUMENT_ROOT'] . "/logo.png", "logo", "logo.png");
$body = file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/email.html");
$body = str_replace("%3", date('Y'), $body);
@@ -113,6 +210,7 @@ function sendAlerts($email, $alerts) {
} else {
$mail->Subject = l("lang_email_alerts_title_1");
}
+ $mail->addEmbeddedImage($_SERVER['DOCUMENT_ROOT'] . "/logo.png", "logo", "logo.png");
$body = file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/email.html");
$body = str_replace("%3", date('Y'), $body);
@@ -151,6 +249,7 @@ function sendLogin($email) {
$mail->isHTML();
$mail->Subject = l("lang_email_login_title");
+ $mail->addEmbeddedImage($_SERVER['DOCUMENT_ROOT'] . "/logo.png", "logo", "logo.png");
$location = json_decode(file_get_contents("https://api.iplocation.net/?ip=" . $_SERVER['HTTP_X_FORWARDED_FOR']), true);
diff --git a/includes/header.php b/includes/header.php
index b850bd3..b175bdc 100644
--- a/includes/header.php
+++ b/includes/header.php
@@ -693,7 +693,7 @@ if (isset($_GET["__"])) {
--bs-list-group-action-active-bg: #<?= $palette[1] ?> !important;
}
- .btn-outline-light, .btn-outline-dark, .btn-outline-secondary {
+ .btn-outline-dark, .btn-outline-secondary {
--bs-btn-color: var(--bs-body-color);
--bs-btn-border-color: var(--bs-body-color);
--bs-btn-hover-color: var(--bs-body-bg);
@@ -798,17 +798,17 @@ if (isset($_GET["__"])) {
--bs-gradient: none;
}
- .form-control, .form-control:focus {
+ .form-control, .form-control:focus, .form-select, .form-select:focus {
color: var(--bs-body-color);
background-color: #<?= $palette[2] ?>;
border-color: #<?= $palette[3] ?>;
}
- .form-control:focus {
+ .form-control:focus, .form-select:focus {
box-shadow: 0 0 0 0.25rem rgba(<?= rgb($palette[9]) ?>, .25);
}
- .form-control::placeholder {
+ .form-control::placeholder, .form-select::placeholder {
color: #<?= $palette[6] ?>77;
}
diff --git a/includes/lang.php b/includes/lang.php
index 3c4d219..2b7bbd7 100644
--- a/includes/lang.php
+++ b/includes/lang.php
@@ -28,5 +28,5 @@ function genLang($lf): void {
function l($entry) {
global $_lang;
- return $_lang[$entry] ?? $entry;
+ return $_lang[$entry] ?? strip_tags($entry);
} \ No newline at end of file
diff --git a/includes/recaptcha/LICENSE b/includes/recaptcha/LICENSE
new file mode 100755
index 0000000..d147b35
--- /dev/null
+++ b/includes/recaptcha/LICENSE
@@ -0,0 +1,29 @@
+BSD 3-Clause License
+
+Copyright (c) 2019, Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/includes/recaptcha/README.md b/includes/recaptcha/README.md
new file mode 100755
index 0000000..67044a7
--- /dev/null
+++ b/includes/recaptcha/README.md
@@ -0,0 +1,147 @@
+# reCAPTCHA PHP client library
+
+[![Build Status](https://travis-ci.org/google/recaptcha.svg)](https://travis-ci.org/google/recaptcha)
+[![Coverage Status](https://coveralls.io/repos/github/google/recaptcha/badge.svg)](https://coveralls.io/github/google/recaptcha)
+[![Latest Stable Version](https://poser.pugx.org/google/recaptcha/v/stable.svg)](https://packagist.org/packages/google/recaptcha)
+[![Total Downloads](https://poser.pugx.org/google/recaptcha/downloads.svg)](https://packagist.org/packages/google/recaptcha)
+
+reCAPTCHA is a free CAPTCHA service that protects websites from spam and abuse.
+This is a PHP library that wraps up the server-side verification step required
+to process responses from the reCAPTCHA service. This client supports both v2
+and v3.
+
+- reCAPTCHA: https://www.google.com/recaptcha
+- This repo: https://github.com/google/recaptcha
+- Hosted demo: https://recaptcha-demo.appspot.com/
+- Version: 1.3.0
+- License: BSD, see [LICENSE](LICENSE)
+
+## Installation
+
+### Composer (recommended)
+
+Use [Composer](https://getcomposer.org) to install this library from Packagist:
+[`google/recaptcha`](https://packagist.org/packages/google/recaptcha)
+
+Run the following command from your project directory to add the dependency:
+
+```sh
+composer require google/recaptcha "^1.3"
+```
+
+Alternatively, add the dependency directly to your `composer.json` file:
+
+```json
+"require": {
+ "google/recaptcha": "^1.3"
+}
+```
+
+### Support for earlier versions of PHP
+
+The 1.3 release moves to PHP 8 and up. For earlier versions, you will need to
+stay with the 1.2 releases.
+
+### Direct download
+
+Download the [ZIP file](https://github.com/google/recaptcha/archive/master.zip)
+and extract into your project. An autoloader script is provided in
+`src/autoload.php` which you can require into your script. For example:
+
+```php
+require_once '/path/to/recaptcha/src/autoload.php';
+$recaptcha = new \ReCaptcha\ReCaptcha($secret);
+```
+
+The classes in the project are structured according to the
+[PSR-4](https://www.php-fig.org/psr/psr-4/) standard, so you can also use your
+own autoloader or require the needed files directly in your code.
+
+## Usage
+
+First obtain the appropriate keys for the type of reCAPTCHA you wish to
+integrate for v2 at https://www.google.com/recaptcha/admin or v3 at
+https://g.co/recaptcha/v3.
+
+Then follow the [integration guide on the developer
+site](https://developers.google.com/recaptcha/intro) to add the reCAPTCHA
+functionality into your frontend.
+
+This library comes in when you need to verify the user's response. On the PHP
+side you need the response from the reCAPTCHA service and secret key from your
+credentials. Instantiate the `ReCaptcha` class with your secret key, specify any
+additional validation rules, and then call `verify()` with the reCAPTCHA
+response (usually in `$_POST['g-recaptcha-response']` or the response from
+`grecaptcha.execute()` in JS which is in `$gRecaptchaResponse` in the example)
+and user's IP address. For example:
+
+```php
+<?php
+$recaptcha = new \ReCaptcha\ReCaptcha($secret);
+$resp = $recaptcha->setExpectedHostname('recaptcha-demo.appspot.com')
+ ->verify($gRecaptchaResponse, $remoteIp);
+if ($resp->isSuccess()) {
+ // Verified!
+} else {
+ $errors = $resp->getErrorCodes();
+}
+```
+
+The following methods are available:
+
+- `setExpectedHostname($hostname)`: ensures the hostname matches. You must do
+ this if you have disabled "Domain/Package Name Validation" for your
+ credentials.
+- `setExpectedApkPackageName($apkPackageName)`: if you're verifying a response
+ from an Android app. Again, you must do this if you have disabled
+ "Domain/Package Name Validation" for your credentials.
+- `setExpectedAction($action)`: ensures the action matches for the v3 API.
+- `setScoreThreshold($threshold)`: set a score threshold for responses from the
+ v3 API
+- `setChallengeTimeout($timeoutSeconds)`: set a timeout between the user passing
+ the reCAPTCHA and your server processing it.
+
+Each of the `set`\*`()` methods return the `ReCaptcha` instance so you can chain
+them together. For example:
+
+```php
+<?php
+$recaptcha = new \ReCaptcha\ReCaptcha($secret);
+$resp = $recaptcha->setExpectedHostname('recaptcha-demo.appspot.com')
+ ->setExpectedAction('homepage')
+ ->setScoreThreshold(0.5)
+ ->verify($gRecaptchaResponse, $remoteIp);
+
+if ($resp->isSuccess()) {
+ // Verified!
+} else {
+ $errors = $resp->getErrorCodes();
+}
+```
+
+You can find the constants for the libraries error codes in the `ReCaptcha`
+class constants, e.g. `ReCaptcha::E_HOSTNAME_MISMATCH`
+
+For more details on usage and structure, see [ARCHITECTURE](ARCHITECTURE.md).
+
+### Examples
+
+You can see examples of each reCAPTCHA type in [examples/](examples/). You can
+run the examples locally by using the Composer script:
+
+```sh
+composer run-script serve-examples
+```
+
+This makes use of the in-built PHP dev server to host the examples at
+http://localhost:8080/
+
+These are also hosted on Google AppEngine Flexible environment at
+https://recaptcha-demo.appspot.com/. This is configured by
+[`app.yaml`](./app.yaml) which you can also use to [deploy to your own AppEngine
+project](https://cloud.google.com/appengine/docs/flexible/php/download).
+
+## Contributing
+
+No one ever has enough engineers, so we're very happy to accept contributions
+via Pull Requests. For details, see [CONTRIBUTING](CONTRIBUTING.md)
diff --git a/includes/recaptcha/app.yaml b/includes/recaptcha/app.yaml
new file mode 100755
index 0000000..b6ccaf1
--- /dev/null
+++ b/includes/recaptcha/app.yaml
@@ -0,0 +1,8 @@
+runtime: php
+env: flex
+
+skip_files:
+- tests
+
+runtime_config:
+ document_root: examples
diff --git a/includes/recaptcha/composer.json b/includes/recaptcha/composer.json
new file mode 100755
index 0000000..7f199fc
--- /dev/null
+++ b/includes/recaptcha/composer.json
@@ -0,0 +1,39 @@
+{
+ "name": "google/recaptcha",
+ "description": "Client library for reCAPTCHA, a free service that protects websites from spam and abuse.",
+ "type": "library",
+ "keywords": ["recaptcha", "captcha", "spam", "abuse"],
+ "homepage": "https://www.google.com/recaptcha/",
+ "license": "BSD-3-Clause",
+ "support": {
+ "forum": "https://groups.google.com/forum/#!forum/recaptcha",
+ "source": "https://github.com/google/recaptcha"
+ },
+ "require": {
+ "php": ">=8"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10",
+ "friendsofphp/php-cs-fixer": "^3.14",
+ "php-coveralls/php-coveralls": "^2.5"
+ },
+ "autoload": {
+ "psr-4": {
+ "ReCaptcha\\": "src/ReCaptcha"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.3.x-dev"
+ }
+ },
+ "scripts": {
+ "lint": "PHP_CS_FIXER_IGNORE_ENV=1 vendor/bin/php-cs-fixer -vvv fix --using-cache=no --dry-run .",
+ "lint-fix": "PHP_CS_FIXER_IGNORE_ENV=1 vendor/bin/php-cs-fixer -vvv fix --using-cache=no .",
+ "test": "XDEBUG_MODE=coverage vendor/bin/phpunit",
+ "serve-examples": "@php -S localhost:8080 -t examples"
+ },
+ "config": {
+ "process-timeout": 0
+ }
+}
diff --git a/includes/recaptcha/src/ReCaptcha/ReCaptcha.php b/includes/recaptcha/src/ReCaptcha/ReCaptcha.php
new file mode 100755
index 0000000..d75ce1f
--- /dev/null
+++ b/includes/recaptcha/src/ReCaptcha/ReCaptcha.php
@@ -0,0 +1,275 @@
+<?php
+/**
+ * This is a PHP library that handles calling reCAPTCHA.
+ *
+ * BSD 3-Clause License
+ * @copyright (c) 2019, Google Inc.
+ * @link https://www.google.com/recaptcha
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace ReCaptcha;
+
+/**
+ * reCAPTCHA client.
+ */
+class ReCaptcha
+{
+ /**
+ * Version of this client library.
+ * @const string
+ */
+ public const VERSION = 'php_1.3.0';
+
+ /**
+ * URL for reCAPTCHA siteverify API
+ * @const string
+ */
+ public const SITE_VERIFY_URL = 'https://www.google.com/recaptcha/api/siteverify';
+
+ /**
+ * Invalid JSON received
+ * @const string
+ */
+ public const E_INVALID_JSON = 'invalid-json';
+
+ /**
+ * Could not connect to service
+ * @const string
+ */
+ public const E_CONNECTION_FAILED = 'connection-failed';
+
+ /**
+ * Did not receive a 200 from the service
+ * @const string
+ */
+ public const E_BAD_RESPONSE = 'bad-response';
+
+ /**
+ * Not a success, but no error codes received!
+ * @const string
+ */
+ public const E_UNKNOWN_ERROR = 'unknown-error';
+
+ /**
+ * ReCAPTCHA response not provided
+ * @const string
+ */
+ public const E_MISSING_INPUT_RESPONSE = 'missing-input-response';
+
+ /**
+ * Expected hostname did not match
+ * @const string
+ */
+ public const E_HOSTNAME_MISMATCH = 'hostname-mismatch';
+
+ /**
+ * Expected APK package name did not match
+ * @const string
+ */
+ public const E_APK_PACKAGE_NAME_MISMATCH = 'apk_package_name-mismatch';
+
+ /**
+ * Expected action did not match
+ * @const string
+ */
+ public const E_ACTION_MISMATCH = 'action-mismatch';
+
+ /**
+ * Score threshold not met
+ * @const string
+ */
+ public const E_SCORE_THRESHOLD_NOT_MET = 'score-threshold-not-met';
+
+ /**
+ * Challenge timeout
+ * @const string
+ */
+ public const E_CHALLENGE_TIMEOUT = 'challenge-timeout';
+
+ /**
+ * Shared secret for the site.
+ * @var string
+ */
+ private $secret;
+
+ /**
+ * Method used to communicate with service. Defaults to POST request.
+ * @var RequestMethod
+ */
+ private $requestMethod;
+
+ private $hostname;
+ private $apkPackageName;
+ private $action;
+ private $threshold;
+ private $timeoutSeconds;
+
+ /**
+ * Create a configured instance to use the reCAPTCHA service.
+ *
+ * @param string $secret The shared key between your site and reCAPTCHA.
+ * @param RequestMethod $requestMethod method used to send the request. Defaults to POST.
+ * @throws \RuntimeException if $secret is invalid
+ */
+ public function __construct($secret, RequestMethod $requestMethod = null)
+ {
+ if (empty($secret)) {
+ throw new \RuntimeException('No secret provided');
+ }
+
+ if (!is_string($secret)) {
+ throw new \RuntimeException('The provided secret must be a string');
+ }
+
+ $this->secret = $secret;
+ $this->requestMethod = (is_null($requestMethod)) ? new RequestMethod\Post() : $requestMethod;
+ }
+
+ /**
+ * Calls the reCAPTCHA siteverify API to verify whether the user passes
+ * CAPTCHA test and additionally runs any specified additional checks
+ *
+ * @param string $response The user response token provided by reCAPTCHA, verifying the user on your site.
+ * @param string $remoteIp The end user's IP address.
+ * @return Response Response from the service.
+ */
+ public function verify($response, $remoteIp = null)
+ {
+ // Discard empty solution submissions
+ if (empty($response)) {
+ $recaptchaResponse = new Response(false, array(self::E_MISSING_INPUT_RESPONSE));
+ return $recaptchaResponse;
+ }
+
+ $params = new RequestParameters($this->secret, $response, $remoteIp, self::VERSION);
+ $rawResponse = $this->requestMethod->submit($params);
+ $initialResponse = Response::fromJson($rawResponse);
+ $validationErrors = array();
+
+ if (isset($this->hostname) && strcasecmp($this->hostname, $initialResponse->getHostname()) !== 0) {
+ $validationErrors[] = self::E_HOSTNAME_MISMATCH;
+ }
+
+ if (isset($this->apkPackageName) && strcasecmp($this->apkPackageName, $initialResponse->getApkPackageName()) !== 0) {
+ $validationErrors[] = self::E_APK_PACKAGE_NAME_MISMATCH;
+ }
+
+ if (isset($this->action) && strcasecmp($this->action, $initialResponse->getAction()) !== 0) {
+ $validationErrors[] = self::E_ACTION_MISMATCH;
+ }
+
+ if (isset($this->threshold) && $this->threshold > $initialResponse->getScore()) {
+ $validationErrors[] = self::E_SCORE_THRESHOLD_NOT_MET;
+ }
+
+ if (isset($this->timeoutSeconds)) {
+ $challengeTs = strtotime($initialResponse->getChallengeTs());
+
+ if ($challengeTs > 0 && time() - $challengeTs > $this->timeoutSeconds) {
+ $validationErrors[] = self::E_CHALLENGE_TIMEOUT;
+ }
+ }
+
+ if (empty($validationErrors)) {
+ return $initialResponse;
+ }
+
+ return new Response(
+ false,
+ array_merge($initialResponse->getErrorCodes(), $validationErrors),
+ $initialResponse->getHostname(),
+ $initialResponse->getChallengeTs(),
+ $initialResponse->getApkPackageName(),
+ $initialResponse->getScore(),
+ $initialResponse->getAction()
+ );
+ }
+
+ /**
+ * Provide a hostname to match against in verify()
+ * This should be without a protocol or trailing slash, e.g. www.google.com
+ *
+ * @param string $hostname Expected hostname
+ * @return ReCaptcha Current instance for fluent interface
+ */
+ public function setExpectedHostname($hostname)
+ {
+ $this->hostname = $hostname;
+ return $this;
+ }
+
+ /**
+ * Provide an APK package name to match against in verify()
+ *
+ * @param string $apkPackageName Expected APK package name
+ * @return ReCaptcha Current instance for fluent interface
+ */
+ public function setExpectedApkPackageName($apkPackageName)
+ {
+ $this->apkPackageName = $apkPackageName;
+ return $this;
+ }
+
+ /**
+ * Provide an action to match against in verify()
+ * This should be set per page.
+ *
+ * @param string $action Expected action
+ * @return ReCaptcha Current instance for fluent interface
+ */
+ public function setExpectedAction($action)
+ {
+ $this->action = $action;
+ return $this;
+ }
+
+ /**
+ * Provide a threshold to meet or exceed in verify()
+ * Threshold should be a float between 0 and 1 which will be tested as response >= threshold.
+ *
+ * @param float $threshold Expected threshold
+ * @return ReCaptcha Current instance for fluent interface
+ */
+ public function setScoreThreshold($threshold)
+ {
+ $this->threshold = floatval($threshold);
+ return $this;
+ }
+
+ /**
+ * Provide a timeout in seconds to test against the challenge timestamp in verify()
+ *
+ * @param int $timeoutSeconds Expected hostname
+ * @return ReCaptcha Current instance for fluent interface
+ */
+ public function setChallengeTimeout($timeoutSeconds)
+ {
+ $this->timeoutSeconds = $timeoutSeconds;
+ return $this;
+ }
+}
diff --git a/includes/recaptcha/src/ReCaptcha/RequestMethod.php b/includes/recaptcha/src/ReCaptcha/RequestMethod.php
new file mode 100755
index 0000000..bd2a949
--- /dev/null
+++ b/includes/recaptcha/src/ReCaptcha/RequestMethod.php
@@ -0,0 +1,49 @@
+<?php
+/**
+ * This is a PHP library that handles calling reCAPTCHA.
+ *
+ * BSD 3-Clause License
+ * @copyright (c) 2019, Google Inc.
+ * @link https://www.google.com/recaptcha
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace ReCaptcha;
+
+/**
+ * Method used to send the request to the service.
+ */
+interface RequestMethod
+{
+ /**
+ * Submit the request with the specified parameters.
+ *
+ * @param RequestParameters $params Request parameters
+ * @return string Body of the reCAPTCHA response
+ */
+ public function submit(RequestParameters $params);
+}
diff --git a/includes/recaptcha/src/ReCaptcha/RequestMethod/Curl.php b/includes/recaptcha/src/ReCaptcha/RequestMethod/Curl.php
new file mode 100755
index 0000000..2d3b389
--- /dev/null
+++ b/includes/recaptcha/src/ReCaptcha/RequestMethod/Curl.php
@@ -0,0 +1,81 @@
+<?php
+/**
+ * This is a PHP library that handles calling reCAPTCHA.
+ *
+ * BSD 3-Clause License
+ * @copyright (c) 2019, Google Inc.
+ * @link https://www.google.com/recaptcha
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace ReCaptcha\RequestMethod;
+
+/**
+ * Convenience wrapper around the cURL functions to allow mocking.
+ */
+class Curl
+{
+ /**
+ * @see http://php.net/curl_init
+ * @param string $url
+ * @return resource cURL handle
+ */
+ public function init($url = null)
+ {
+ return curl_init($url);
+ }
+
+ /**
+ * @see http://php.net/curl_setopt_array
+ * @param resource $ch
+ * @param array $options
+ * @return bool
+ */
+ public function setoptArray($ch, array $options)
+ {
+ return curl_setopt_array($ch, $options);
+ }
+
+ /**
+ * @see http://php.net/curl_exec
+ * @param resource $ch
+ * @return mixed
+ */
+ public function exec($ch)
+ {
+ return curl_exec($ch);
+ }
+
+ /**
+ * @see http://php.net/curl_close
+ * @param resource $ch
+ */
+ public function close($ch)
+ {
+ curl_close($ch);
+ }
+}
diff --git a/includes/recaptcha/src/ReCaptcha/RequestMethod/CurlPost.php b/includes/recaptcha/src/ReCaptcha/RequestMethod/CurlPost.php
new file mode 100755
index 0000000..1bd7cce
--- /dev/null
+++ b/includes/recaptcha/src/ReCaptcha/RequestMethod/CurlPost.php
@@ -0,0 +1,104 @@
+<?php
+/**
+ * This is a PHP library that handles calling reCAPTCHA.
+ *
+ * BSD 3-Clause License
+ * @copyright (c) 2019, Google Inc.
+ * @link https://www.google.com/recaptcha
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace ReCaptcha\RequestMethod;
+
+use ReCaptcha\ReCaptcha;
+use ReCaptcha\RequestMethod;
+use ReCaptcha\RequestParameters;
+
+/**
+ * Sends cURL request to the reCAPTCHA service.
+ * Note: this requires the cURL extension to be enabled in PHP
+ * @see http://php.net/manual/en/book.curl.php
+ */
+class CurlPost implements RequestMethod
+{
+ /**
+ * Curl connection to the reCAPTCHA service
+ * @var Curl
+ */
+ private $curl;
+
+ /**
+ * URL for reCAPTCHA siteverify API
+ * @var string
+ */
+ private $siteVerifyUrl;
+
+ /**
+ * Only needed if you want to override the defaults
+ *
+ * @param Curl $curl Curl resource
+ * @param string $siteVerifyUrl URL for reCAPTCHA siteverify API
+ */
+ public function __construct(Curl $curl = null, $siteVerifyUrl = null)
+ {
+ $this->curl = (is_null($curl)) ? new Curl() : $curl;
+ $this->siteVerifyUrl = (is_null($siteVerifyUrl)) ? ReCaptcha::SITE_VERIFY_URL : $siteVerifyUrl;
+ }
+
+ /**
+ * Submit the cURL request with the specified parameters.
+ *
+ * @param RequestParameters $params Request parameters
+ * @return string Body of the reCAPTCHA response
+ */
+ public function submit(RequestParameters $params)
+ {
+ $handle = $this->curl->init($this->siteVerifyUrl);
+
+ $options = array(
+ CURLOPT_POST => true,
+ CURLOPT_POSTFIELDS => $params->toQueryString(),
+ CURLOPT_HTTPHEADER => array(
+ 'Content-Type: application/x-www-form-urlencoded'
+ ),
+ CURLINFO_HEADER_OUT => false,
+ CURLOPT_HEADER => false,
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_SSL_VERIFYPEER => true
+ );
+ $this->curl->setoptArray($handle, $options);
+
+ $response = $this->curl->exec($handle);
+ $this->curl->close($handle);
+
+ if ($response !== false) {
+ return $response;
+ }
+
+ return '{"success": false, "error-codes": ["'.ReCaptcha::E_CONNECTION_FAILED.'"]}';
+ }
+}
diff --git a/includes/recaptcha/src/ReCaptcha/RequestMethod/Post.php b/includes/recaptcha/src/ReCaptcha/RequestMethod/Post.php
new file mode 100755
index 0000000..a4ff716
--- /dev/null
+++ b/includes/recaptcha/src/ReCaptcha/RequestMethod/Post.php
@@ -0,0 +1,88 @@
+<?php
+/**
+ * This is a PHP library that handles calling reCAPTCHA.
+ *
+ * BSD 3-Clause License
+ * @copyright (c) 2019, Google Inc.
+ * @link https://www.google.com/recaptcha
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace ReCaptcha\RequestMethod;
+
+use ReCaptcha\ReCaptcha;
+use ReCaptcha\RequestMethod;
+use ReCaptcha\RequestParameters;
+
+/**
+ * Sends POST requests to the reCAPTCHA service.
+ */
+class Post implements RequestMethod
+{
+ /**
+ * URL for reCAPTCHA siteverify API
+ * @var string
+ */
+ private $siteVerifyUrl;
+
+ /**
+ * Only needed if you want to override the defaults
+ *
+ * @param string $siteVerifyUrl URL for reCAPTCHA siteverify API
+ */
+ public function __construct($siteVerifyUrl = null)
+ {
+ $this->siteVerifyUrl = (is_null($siteVerifyUrl)) ? ReCaptcha::SITE_VERIFY_URL : $siteVerifyUrl;
+ }
+
+ /**
+ * Submit the POST request with the specified parameters.
+ *
+ * @param RequestParameters $params Request parameters
+ * @return string Body of the reCAPTCHA response
+ */
+ public function submit(RequestParameters $params)
+ {
+ $options = array(
+ 'http' => array(
+ 'header' => "Content-type: application/x-www-form-urlencoded\r\n",
+ 'method' => 'POST',
+ 'content' => $params->toQueryString(),
+ // Force the peer to validate (not needed in 5.6.0+, but still works)
+ 'verify_peer' => true,
+ ),
+ );
+ $context = stream_context_create($options);
+ $response = file_get_contents($this->siteVerifyUrl, false, $context);
+
+ if ($response !== false) {
+ return $response;
+ }
+
+ return '{"success": false, "error-codes": ["'.ReCaptcha::E_CONNECTION_FAILED.'"]}';
+ }
+}
diff --git a/includes/recaptcha/src/ReCaptcha/RequestMethod/Socket.php b/includes/recaptcha/src/ReCaptcha/RequestMethod/Socket.php
new file mode 100755
index 0000000..236bd5f
--- /dev/null
+++ b/includes/recaptcha/src/ReCaptcha/RequestMethod/Socket.php
@@ -0,0 +1,112 @@
+<?php
+/**
+ * This is a PHP library that handles calling reCAPTCHA.
+ *
+ * BSD 3-Clause License
+ * @copyright (c) 2019, Google Inc.
+ * @link https://www.google.com/recaptcha
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace ReCaptcha\RequestMethod;
+
+/**
+ * Convenience wrapper around native socket and file functions to allow for
+ * mocking.
+ */
+class Socket
+{
+ private $handle = null;
+
+ /**
+ * fsockopen
+ *
+ * @see http://php.net/fsockopen
+ * @param string $hostname
+ * @param int $port
+ * @param int $errno
+ * @param string $errstr
+ * @param float $timeout
+ * @return resource
+ */
+ public function fsockopen($hostname, $port = -1, &$errno = 0, &$errstr = '', $timeout = null)
+ {
+ $this->handle = fsockopen($hostname, $port, $errno, $errstr, (is_null($timeout) ? ini_get("default_socket_timeout") : $timeout));
+
+ if ($this->handle != false && $errno === 0 && $errstr === '') {
+ return $this->handle;
+ }
+ return false;
+ }
+
+ /**
+ * fwrite
+ *
+ * @see http://php.net/fwrite
+ * @param string $string
+ * @param int $length
+ * @return int | bool
+ */
+ public function fwrite($string, $length = null)
+ {
+ return fwrite($this->handle, $string, (is_null($length) ? strlen($string) : $length));
+ }
+
+ /**
+ * fgets
+ *
+ * @see http://php.net/fgets
+ * @param int $length
+ * @return string
+ */
+ public function fgets($length = null)
+ {
+ return fgets($this->handle, $length);
+ }
+
+ /**
+ * feof
+ *
+ * @see http://php.net/feof
+ * @return bool
+ */
+ public function feof()
+ {
+ return feof($this->handle);
+ }
+
+ /**
+ * fclose
+ *
+ * @see http://php.net/fclose
+ * @return bool
+ */
+ public function fclose()
+ {
+ return fclose($this->handle);
+ }
+}
diff --git a/includes/recaptcha/src/ReCaptcha/RequestMethod/SocketPost.php b/includes/recaptcha/src/ReCaptcha/RequestMethod/SocketPost.php
new file mode 100755
index 0000000..19d50ab
--- /dev/null
+++ b/includes/recaptcha/src/ReCaptcha/RequestMethod/SocketPost.php
@@ -0,0 +1,110 @@
+<?php
+/**
+ * This is a PHP library that handles calling reCAPTCHA.
+ *
+ * BSD 3-Clause License
+ * @copyright (c) 2019, Google Inc.
+ * @link https://www.google.com/recaptcha
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace ReCaptcha\RequestMethod;
+
+use ReCaptcha\ReCaptcha;
+use ReCaptcha\RequestMethod;
+use ReCaptcha\RequestParameters;
+
+/**
+ * Sends a POST request to the reCAPTCHA service, but makes use of fsockopen()
+ * instead of get_file_contents(). This is to account for people who may be on
+ * servers where allow_url_open is disabled.
+ */
+class SocketPost implements RequestMethod
+{
+ /**
+ * Socket to the reCAPTCHA service
+ * @var Socket
+ */
+ private $socket;
+
+ private $siteVerifyUrl;
+
+ /**
+ * Only needed if you want to override the defaults
+ *
+ * @param \ReCaptcha\RequestMethod\Socket $socket optional socket, injectable for testing
+ * @param string $siteVerifyUrl URL for reCAPTCHA siteverify API
+ */
+ public function __construct(Socket $socket = null, $siteVerifyUrl = null)
+ {
+ $this->socket = (is_null($socket)) ? new Socket() : $socket;
+ $this->siteVerifyUrl = (is_null($siteVerifyUrl)) ? ReCaptcha::SITE_VERIFY_URL : $siteVerifyUrl;
+ }
+
+ /**
+ * Submit the POST request with the specified parameters.
+ *
+ * @param RequestParameters $params Request parameters
+ * @return string Body of the reCAPTCHA response
+ */
+ public function submit(RequestParameters $params)
+ {
+ $errno = 0;
+ $errstr = '';
+ $urlParsed = parse_url($this->siteVerifyUrl);
+
+ if (false === $this->socket->fsockopen('ssl://' . $urlParsed['host'], 443, $errno, $errstr, 30)) {
+ return '{"success": false, "error-codes": ["'.ReCaptcha::E_CONNECTION_FAILED.'"]}';
+ }
+
+ $content = $params->toQueryString();
+
+ $request = "POST " . $urlParsed['path'] . " HTTP/1.0\r\n";
+ $request .= "Host: " . $urlParsed['host'] . "\r\n";
+ $request .= "Content-Type: application/x-www-form-urlencoded\r\n";
+ $request .= "Content-length: " . strlen($content) . "\r\n";
+ $request .= "Connection: close\r\n\r\n";
+ $request .= $content . "\r\n\r\n";
+
+ $this->socket->fwrite($request);
+ $response = '';
+
+ while (!$this->socket->feof()) {
+ $response .= $this->socket->fgets(4096);
+ }
+
+ $this->socket->fclose();
+
+ if (0 !== strpos($response, 'HTTP/1.0 200 OK')) {
+ return '{"success": false, "error-codes": ["'.ReCaptcha::E_BAD_RESPONSE.'"]}';
+ }
+
+ $parts = preg_split("#\n\s*\n#Uis", $response);
+
+ return $parts[1];
+ }
+}
diff --git a/includes/recaptcha/src/ReCaptcha/RequestParameters.php b/includes/recaptcha/src/ReCaptcha/RequestParameters.php
new file mode 100755
index 0000000..e9ba453
--- /dev/null
+++ b/includes/recaptcha/src/ReCaptcha/RequestParameters.php
@@ -0,0 +1,111 @@
+<?php
+/**
+ * This is a PHP library that handles calling reCAPTCHA.
+ *
+ * BSD 3-Clause License
+ * @copyright (c) 2019, Google Inc.
+ * @link https://www.google.com/recaptcha
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace ReCaptcha;
+
+/**
+ * Stores and formats the parameters for the request to the reCAPTCHA service.
+ */
+class RequestParameters
+{
+ /**
+ * The shared key between your site and reCAPTCHA.
+ * @var string
+ */
+ private $secret;
+
+ /**
+ * The user response token provided by reCAPTCHA, verifying the user on your site.
+ * @var string
+ */
+ private $response;
+
+ /**
+ * Remote user's IP address.
+ * @var string
+ */
+ private $remoteIp;
+
+ /**
+ * Client version.
+ * @var string
+ */
+ private $version;
+
+ /**
+ * Initialise parameters.
+ *
+ * @param string $secret Site secret.
+ * @param string $response Value from g-captcha-response form field.
+ * @param string $remoteIp User's IP address.
+ * @param string $version Version of this client library.
+ */
+ public function __construct($secret, $response, $remoteIp = null, $version = null)
+ {
+ $this->secret = $secret;
+ $this->response = $response;
+ $this->remoteIp = $remoteIp;
+ $this->version = $version;
+ }
+
+ /**
+ * Array representation.
+ *
+ * @return array Array formatted parameters.
+ */
+ public function toArray()
+ {
+ $params = array('secret' => $this->secret, 'response' => $this->response);
+
+ if (!is_null($this->remoteIp)) {
+ $params['remoteip'] = $this->remoteIp;
+ }
+
+ if (!is_null($this->version)) {
+ $params['version'] = $this->version;
+ }
+
+ return $params;
+ }
+
+ /**
+ * Query string representation for HTTP request.
+ *
+ * @return string Query string formatted parameters.
+ */
+ public function toQueryString()
+ {
+ return http_build_query($this->toArray(), '', '&');
+ }
+}
diff --git a/includes/recaptcha/src/ReCaptcha/Response.php b/includes/recaptcha/src/ReCaptcha/Response.php
new file mode 100755
index 0000000..8a5d3aa
--- /dev/null
+++ b/includes/recaptcha/src/ReCaptcha/Response.php
@@ -0,0 +1,218 @@
+<?php
+/**
+ * This is a PHP library that handles calling reCAPTCHA.
+ *
+ * BSD 3-Clause License
+ * @copyright (c) 2019, Google Inc.
+ * @link https://www.google.com/recaptcha
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace ReCaptcha;
+
+/**
+ * The response returned from the service.
+ */
+class Response
+{
+ /**
+ * Success or failure.
+ * @var boolean
+ */
+ private $success = false;
+
+ /**
+ * Error code strings.
+ * @var array
+ */
+ private $errorCodes = array();
+
+ /**
+ * The hostname of the site where the reCAPTCHA was solved.
+ * @var string
+ */
+ private $hostname;
+
+ /**
+ * Timestamp of the challenge load (ISO format yyyy-MM-dd'T'HH:mm:ssZZ)
+ * @var string
+ */
+ private $challengeTs;
+
+ /**
+ * APK package name
+ * @var string
+ */
+ private $apkPackageName;
+
+ /**
+ * Score assigned to the request
+ * @var float
+ */
+ private $score;
+
+ /**
+ * Action as specified by the page
+ * @var string
+ */
+ private $action;
+
+ /**
+ * Build the response from the expected JSON returned by the service.
+ *
+ * @param string $json
+ * @return \ReCaptcha\Response
+ */
+ public static function fromJson($json)
+ {
+ $responseData = json_decode($json, true);
+
+ if (!$responseData) {
+ return new Response(false, array(ReCaptcha::E_INVALID_JSON));
+ }
+
+ $hostname = isset($responseData['hostname']) ? $responseData['hostname'] : '';
+ $challengeTs = isset($responseData['challenge_ts']) ? $responseData['challenge_ts'] : '';
+ $apkPackageName = isset($responseData['apk_package_name']) ? $responseData['apk_package_name'] : '';
+ $score = isset($responseData['score']) ? floatval($responseData['score']) : null;
+ $action = isset($responseData['action']) ? $responseData['action'] : '';
+
+ if (isset($responseData['success']) && $responseData['success'] == true) {
+ return new Response(true, array(), $hostname, $challengeTs, $apkPackageName, $score, $action);
+ }
+
+ if (isset($responseData['error-codes']) && is_array($responseData['error-codes'])) {
+ return new Response(false, $responseData['error-codes'], $hostname, $challengeTs, $apkPackageName, $score, $action);
+ }
+
+ return new Response(false, array(ReCaptcha::E_UNKNOWN_ERROR), $hostname, $challengeTs, $apkPackageName, $score, $action);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param boolean $success
+ * @param string $hostname
+ * @param string $challengeTs
+ * @param string $apkPackageName
+ * @param float $score
+ * @param string $action
+ * @param array $errorCodes
+ */
+ public function __construct($success, array $errorCodes = array(), $hostname = '', $challengeTs = '', $apkPackageName = '', $score = null, $action = '')
+ {
+ $this->success = $success;
+ $this->hostname = $hostname;
+ $this->challengeTs = $challengeTs;
+ $this->apkPackageName = $apkPackageName;
+ $this->score = $score;
+ $this->action = $action;
+ $this->errorCodes = $errorCodes;
+ }
+
+ /**
+ * Is success?
+ *
+ * @return boolean
+ */
+ public function isSuccess()
+ {
+ return $this->success;
+ }
+
+ /**
+ * Get error codes.
+ *
+ * @return array
+ */
+ public function getErrorCodes()
+ {
+ return $this->errorCodes;
+ }
+
+ /**
+ * Get hostname.
+ *
+ * @return string
+ */
+ public function getHostname()
+ {
+ return $this->hostname;
+ }
+
+ /**
+ * Get challenge timestamp
+ *
+ * @return string
+ */
+ public function getChallengeTs()
+ {
+ return $this->challengeTs;
+ }
+
+ /**
+ * Get APK package name
+ *
+ * @return string
+ */
+ public function getApkPackageName()
+ {
+ return $this->apkPackageName;
+ }
+ /**
+ * Get score
+ *
+ * @return float
+ */
+ public function getScore()
+ {
+ return $this->score;
+ }
+
+ /**
+ * Get action
+ *
+ * @return string
+ */
+ public function getAction()
+ {
+ return $this->action;
+ }
+
+ public function toArray()
+ {
+ return array(
+ 'success' => $this->isSuccess(),
+ 'hostname' => $this->getHostname(),
+ 'challenge_ts' => $this->getChallengeTs(),
+ 'apk_package_name' => $this->getApkPackageName(),
+ 'score' => $this->getScore(),
+ 'action' => $this->getAction(),
+ 'error-codes' => $this->getErrorCodes(),
+ );
+ }
+}
diff --git a/includes/recaptcha/src/autoload.php b/includes/recaptcha/src/autoload.php
new file mode 100755
index 0000000..7947a10
--- /dev/null
+++ b/includes/recaptcha/src/autoload.php
@@ -0,0 +1,69 @@
+<?php
+
+/* An autoloader for ReCaptcha\Foo classes. This should be required()
+ * by the user before attempting to instantiate any of the ReCaptcha
+ * classes.
+ *
+ * BSD 3-Clause License
+ * @copyright (c) 2019, Google Inc.
+ * @link https://www.google.com/recaptcha
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+spl_autoload_register(function ($class) {
+ if (substr($class, 0, 10) !== 'ReCaptcha\\') {
+ /* If the class does not lie under the "ReCaptcha" namespace,
+ * then we can exit immediately.
+ */
+ return;
+ }
+
+ /* All of the classes have names like "ReCaptcha\Foo", so we need
+ * to replace the backslashes with frontslashes if we want the
+ * name to map directly to a location in the filesystem.
+ */
+ $class = str_replace('\\', '/', $class);
+
+ /* First, check under the current directory. It is important that
+ * we look here first, so that we don't waste time searching for
+ * test classes in the common case.
+ */
+ $path = dirname(__FILE__).'/'.$class.'.php';
+ if (is_readable($path)) {
+ require_once $path;
+
+ return;
+ }
+
+ /* If we didn't find what we're looking for already, maybe it's
+ * a test class?
+ */
+ $path = dirname(__FILE__).'/../tests/'.$class.'.php';
+ if (is_readable($path)) {
+ require_once $path;
+ }
+});
diff --git a/includes/rules.php b/includes/rules.php
new file mode 100644
index 0000000..921062c
--- /dev/null
+++ b/includes/rules.php
@@ -0,0 +1,74 @@
+<p><?= l("lang_rules_intro") ?></p>
+
+<h2><?= l("lang_rules_sections_0") ?></h2>
+<ul>
+ <li><?= l("lang_rules_lines_0_0") ?></li>
+ <li><?= l("lang_rules_lines_0_1") ?></li>
+ <li><?= l("lang_rules_lines_0_2") ?></li>
+ <li><?= l("lang_rules_lines_0_3") ?></li>
+ <li><?= l("lang_rules_lines_0_5") ?></li>
+ <li><?= l("lang_rules_lines_0_6") ?></li>
+ <li><?= l("lang_rules_lines_0_7") ?></li>
+ <li><?= l("lang_rules_lines_0_8") ?></li>
+</ul>
+
+<h2><?= l("lang_rules_sections_1") ?></h2>
+<ul>
+ <li><?= l("lang_rules_lines_1_0") ?></li>
+ <li><?= l("lang_rules_lines_1_1") ?></li>
+ <li><?= l("lang_rules_lines_1_2") ?></li>
+ <li><?= l("lang_rules_lines_1_3") ?></li>
+ <li><?= l("lang_rules_lines_1_4") ?></li>
+ <li><?= l("lang_rules_lines_1_5") ?></li>
+ <li><?= l("lang_rules_lines_1_6") ?></li>
+ <li><?= l("lang_rules_lines_1_7") ?></li>
+ <li><?= l("lang_rules_lines_1_8") ?></li>
+</ul>
+
+<h2><?= l("lang_rules_sections_2") ?></h2>
+<ul>
+ <li><?= l("lang_rules_lines_2_0") ?></li>
+ <li><?= l("lang_rules_lines_2_1") ?></li>
+ <li><?= l("lang_rules_lines_2_2") ?></li>
+ <li><?= l("lang_rules_lines_2_3") ?></li>
+ <li><?= l("lang_rules_lines_2_4") ?></li>
+ <li><?= l("lang_rules_lines_2_5") ?></li>
+ <li><?= l("lang_rules_lines_2_6") ?></li>
+</ul>
+
+<h2><?= l("lang_rules_sections_3") ?></h2>
+<ul>
+ <li><?= l("lang_rules_lines_3_0") ?></li>
+ <li><?= l("lang_rules_lines_3_1") ?></li>
+ <li><?= l("lang_rules_lines_3_2") ?></li>
+ <li><?= l("lang_rules_lines_3_3") ?></li>
+ <li><?= l("lang_rules_lines_3_4") ?></li>
+ <li><?= l("lang_rules_lines_3_5") ?></li>
+ <li><?= l("lang_rules_lines_3_6") ?></li>
+ <li><?= l("lang_rules_lines_3_7") ?></li>
+ <li><?= l("lang_rules_lines_3_8") ?></li>
+ <li><?= l("lang_rules_lines_3_9") ?></li>
+</ul>
+
+<h2><?= l("lang_rules_sections_4") ?></h2>
+<ul>
+ <li><?= l("lang_rules_lines_4_0") ?></li>
+ <li><?= l("lang_rules_lines_4_1") ?></li>
+ <li><?= l("lang_rules_lines_4_2") ?></li>
+ <li><?= l("lang_rules_lines_4_3") ?></li>
+ <li><?= l("lang_rules_lines_4_4") ?></li>
+ <li><?= l("lang_rules_lines_4_5") ?></li>
+ <li><?= l("lang_rules_lines_4_6") ?></li>
+</ul>
+
+<h2><?= l("lang_rules_sections_5") ?></h2>
+<ul>
+ <li><?= l("lang_rules_lines_5_0") ?></li>
+ <li><?= l("lang_rules_lines_5_1") ?></li>
+ <li><?= l("lang_rules_lines_5_2") ?></li>
+ <li><?= l("lang_rules_lines_5_3") ?></li>
+ <li><?= l("lang_rules_lines_5_4") ?></li>
+ <li><?= l("lang_rules_lines_5_5") ?></li>
+ <li><?= l("lang_rules_lines_5_6") ?></li>
+ <li><?= l("lang_rules_lines_5_7") ?></li>
+</ul> \ No newline at end of file
diff --git a/lang/en.json b/lang/en.json
index b3e863f..348bad7 100644
--- a/lang/en.json
+++ b/lang/en.json
@@ -1034,6 +1034,7 @@
"handoff": "Handoff cookie export",
"objects": "Database objects",
"requests": "Pending requests",
+ "registrations": "Account registrations",
"support": "Support codes"
},
"save": "Save",
@@ -1064,7 +1065,23 @@
"merge": "Approve and merge",
"reject": "Reject",
"manual": "Manual changes required",
- "notify": "Author will be notified of the decision taken"
+ "notify": "Author will be notified of the decision taken",
+ "full": "View full request object"
+ },
+ "registrations": {
+ "nothing": "There are no pending registrations for now.",
+ "start": "open template",
+ "view": [
+ "UUID:",
+ "Identifier:",
+ "Submit date:",
+ "Name:",
+ "Contact methods:",
+ "Birth date:",
+ "Parents/guardian contact:",
+ "Language:"
+ ],
+ "reject": "Enter a reason why this registration was rejected. The user speaks %1."
},
"search": "Search objects in the Delta database...",
"modified": {
@@ -1143,5 +1160,105 @@
"experimental": {
"title": "You are using an experimental version of Delta.",
"message": "Expect to encounter bugs and other major issues. In all cases, you can %1contact technical support%2 to report them."
+ },
+ "register": {
+ "title": "Registration",
+ "welcome": "Welcome to Delta!",
+ "intro": "We are going to guide you through registering for Delta. As Delta is a private application, you need approval from the administrators to enter, but we'll help you get this done in no time.",
+ "id": "During your registration process, the administrators may ask you for a document to prove your identity.",
+ "start": "Get started",
+ "continue": "Next",
+ "confirm": "Send registration",
+ "name": {
+ "title": "First, who are you?",
+ "description": "Delta is not anonymous, so we need to know who you are. Make sure to spell your name properly because you probably don't want to have to ask us to change it. If you are transgender, don't forget to indicate what we should call you.",
+ "last1": "Last name:",
+ "last2": "Enter your last name",
+ "first1": "First name:",
+ "first2": "Enter your first name",
+ "use1": "I am a transgender person and would like to be called",
+ "use2": "Enter your name",
+ "use3": "Leave this empty if you are not transgender"
+ },
+ "contact": {
+ "title": "Then, how can we reach you?",
+ "description": "It is important for the administrators to be able to reach you regarding your use of the application. Entering a phone number (make sure to include the country code, e.g. +33) is not required but can help when you need help with Delta.",
+ "email1": "Email address:",
+ "email2": "Enter your email address",
+ "phone1": "Phone number:",
+ "phone2": "Enter your phone number"
+ },
+ "birthday": {
+ "title": "How old are you?",
+ "description": "Some features of Delta are not accessible to people under a certain age. Make sure you enter your real age, as entering a fake one can get you permanently blocked off Delta. Note that you need to be at least 7 years old to access Delta.",
+ "day": "Day",
+ "month": "Month",
+ "year": "Year",
+ "underage1": "Since you are under 13, you need consent from your parent or guardian to access Delta. Enter their email address here:",
+ "underage2": "Enter the parent or guardian email address"
+ },
+ "rules": {
+ "title": "This is important",
+ "description": "Below are the Delta community rules. Make sure you read and understand them before continuing. If you have any questions about them, you can ask the administrators after you are registered."
+ },
+ "check": {
+ "title": "Just one more check!",
+ "description": "Make sure the information displayed here is correct, you can scroll back if you want to change it. Once you are sure it is correct, click on Next.",
+ "steps": [
+ "Your name is",
+ "Your email address is",
+ "Your phone number is",
+ "You are",
+ "years old",
+ "you were born on a",
+ "The email address of your parent or guardian is"
+ ]
+ },
+ "send": {
+ "title": "Time for takeoff!",
+ "description": "You have completed all the steps required to send your registration to Delta. You can double check that all the information you've entered is correct, and then send it.",
+ "email": "The administrators will contact you by email if they need additional information and to let you know about the decision taken.",
+ "email2": "The administrators will contact you at %1 if they need additional information about you (such as a way to prove your identity) and to let you know about the decision taken."
+ },
+ "complete": {
+ "title": "Now just you wait...",
+ "description": "You have successfully sent your registration to Delta, you should have received a confirmation email. We are now processing it and we will get back to you as soon as we can, please be patient!",
+ "thanks": "Thanks for using Delta."
+ },
+ "recaptcha": "This site is protected by reCAPTCHA, the Google %1privacy policy%2 and %3terms of service%4 apply.",
+ "errors": {
+ "captcha": "Our systems have detected you might be a robot and have prevented you from sending this registration form. If you believe this is an error, please try again.",
+ "invalid": "It appears some of the information sent to the server about your registration is either invalid or missing."
+ },
+ "email": {
+ "title": "[Delta] Registration confirmation #%1",
+ "content": [
+ "Hello %1,",
+ "Thank you for registering on Delta. We have received your registration and we will get back to you as soon as we can. In the meantime, feel free to talk about Delta with other family members, discover new features, and try to bring in new users on Delta to improve your experience.",
+ "If you have not requested to register on Delta, please reply to this email so we can unregister you. If you need help about your registration, you can reply to this email; you may be asked the following ID: %1. Please note that it may take up to 30 days for your registration to be reviewed.",
+ "See you soon on Delta!"
+ ],
+ "reason": "You are receiving this email because you have sent a registration to Delta."
+ },
+ "approved": {
+ "title": "[Delta] Your registration #%1 has been approved",
+ "content": [
+ "Hello %1,",
+ "Thank you for registering on Delta again. After reviewing your registration, we have decided you are all right and have completed your registration on Delta. You should now be able to log into Delta like any other user would and enjoy all the content that awaits you.",
+ "If you have not requested to register on Delta, or you do not want to use Delta anymore, please reply to this email so we can delete your account. If you are having trouble logging in, try again in a few hours.",
+ "See you soon on Delta!"
+ ]
+ },
+ "rejected": {
+ "title": "[Delta] Your registration #%1 has been rejected",
+ "content": [
+ "Hello %1,",
+ "Thank you for registering on Delta again. After reviewing your registration, we have decided you are not allowed on Delta and have reject your registration. %1",
+ "If you have not requested to register on Delta, simply ignore this email. If you believe this is an error and that you should be allowed on Delta, please reply to this email indicating why we are making a mistake.",
+ "Thank you.",
+ "We have decided not to provide a reason as to why your registration has been rejected.",
+ "Here is the reason we have provided:"
+ ]
+ }
}
} \ No newline at end of file
diff --git a/lang/fr.json b/lang/fr.json
index 69e0e29..ffd49f6 100644
--- a/lang/fr.json
+++ b/lang/fr.json
@@ -270,7 +270,7 @@
"confirm": {
"title": "Publier ces changements",
"description": "Voulez-vous publier les changements apportés à cette page ? Après les avoir publié, le texte que vous avez entré sera visible par tous les utilisateurs de Delta.",
- "followup": "Assurez-vous que le contenu de cette page n'est pas inappropriate pour certains utilisateurs, et respectez les règles. Votre compte pourrait être bloqué si vous ne respectez pas cela."
+ "followup": "Assurez-vous que le contenu de cette page n'est pas inapproprié pour certains utilisateurs, et respectez les règles. Votre compte pourrait être bloqué si vous ne respectez pas cela."
},
"birth": "né·e le",
"birth_male": "né le",
@@ -1034,6 +1034,7 @@
"handoff": "Exportation du témoin de remise",
"objects": "Objets de la base de données",
"requests": "Demandes en attente",
+ "registrations": "Enregistrements de compte",
"support": "Codes du support"
},
"save": "Enregistrer",
@@ -1064,9 +1065,29 @@
"merge": "Approuver et fusionner",
"reject": "Rejeter",
"manual": "Des changements manuels sont nécessaires",
- "notify": "L'auteur sera notifié de la décision price"
+ "notify": "L'auteur sera notifié de la décision prise",
+ "full": "Afficher l'objet de demande complet"
},
- "search": "Rechercher des objets dans la base de données Delta..."
+ "registrations": {
+ "nothing": "Il n'y a aucun enregistrement en attente pour l'instant.",
+ "start": "ouvrir le modèle",
+ "view": [
+ "UUID :",
+ "Identifiant :",
+ "Date d'envoi :",
+ "Nom :",
+ "Moyens de contact :",
+ "Date de naissance :",
+ "Contact du parent/responsable :",
+ "Langue :"
+ ],
+ "reject": "Entrez une raison sur pourquoi cet enregistrement a été rejeté. L'utilisateur parle %1."
+ },
+ "search": "Rechercher des objets dans la base de données Delta...",
+ "modified": {
+ "title": "Impossible de changer cette demande :",
+ "description": "Vous ne pouvez pas changer cette demande car elle a été modifiée pendant que vous la relisiez. Merci de la relire à nouveau."
+ }
},
"oobe": {
"next": "Suivant",
@@ -1139,5 +1160,105 @@
"experimental": {
"title": "Vous utilisez une version expérimentale de Delta.",
"message": "Attendez-vous à rencontrer des bugs et autres problèmes importants. Dans tous les cas, vous pouvez %1contacter le support technique%2 pour les signaler."
+ },
+ "register": {
+ "title": "Enregistrement",
+ "welcome": "Bienvenue sur Delta !",
+ "intro": "Nous allons vous guider dans votre enregistrement sur Delta. Comme Delta est une application privée, vous aurez besoin d'une autorisation des administrateur·ice·s pour entrer, mais nous vous aideront à compléter cela en un rien de temps.",
+ "id": "Durant votre processus d'enregistrement, les administrateur·ice·s peuvent vous demander un document permettant de prouver votre identité.",
+ "start": "Commencer",
+ "continue": "Suivant",
+ "confirm": "Envoyer votre enregistrement",
+ "name": {
+ "title": "Pour commencer, qui êtes-vous ?",
+ "description": "Delta n'est pas anonyme, nous devons donc savoir qui vous êtes. Assurez-vous d'écrire votre nom correctement car vous ne voulez sans doute pas nous demander de le changer plus tard. Si vous êtes transgenre, n'oubliez pas d'indiquer votre nom d'usage.",
+ "last1": "Nom de famille :",
+ "last2": "Entrez votre nom de famille",
+ "first1": "Prénom :",
+ "first2": "Entrez votre prénom",
+ "use1": "Je suis une personne transgenre et je souhaite être appelé·e",
+ "use2": "Entrez votre nom",
+ "use3": "Laissez vide si vous n'êtes pas transgenre"
+ },
+ "contact": {
+ "title": "Ensuite, comment vous contacter ?",
+ "description": "Il est important pour les administrateur·ice·s de pouvoir vous contacter concernant votre utilisation de l'application. Entrer un numéro de téléphone (avec le code de pays, par exemple +33) n'est pas obligatoire mais peut vous aider à obtenir de l'aide avec Delta.",
+ "email1": "Adresse courriel :",
+ "email2": "Entrez votre adresse courriel",
+ "phone1": "Numéro de téléphone :",
+ "phone2": "Entrez votre numéro de téléphone"
+ },
+ "birthday": {
+ "title": "Quel âge avez-vous ?",
+ "description": "Certaines fonctionnalités de Delta ne sont pas accessible aux personnes en dessous d'un certain age. Assurez-vous d'entrer votre vrai âge, car mentir pourrait mener à un blocage permanent de Delta. Notez que vous devez avoir au moins 7 ans pour accéder à Delta.",
+ "day": "Jour",
+ "month": "Mois",
+ "year": "Année",
+ "underage1": "Comme vous avez moins de 13 ans, vous avez besoin de l'accord d'un parent ou responsable légal pour accéder à Delta. Entrez son adresse courriel ici :",
+ "underage2": "Entrez l'adresse courriel du parent ou responsable légal"
+ },
+ "rules": {
+ "title": "Cela est important",
+ "description": "Ci-dessous sont les règles de la communauté Delta. Assurez-vous de les avoir lues et comprises avant de continuer. Si vous avez des questions les concernant, vous pouvez les poser aux administrateur·ice·s après votre enregistrement."
+ },
+ "check": {
+ "title": "Une dernière vérification !",
+ "description": "Assurez-vous que les informations affichées ici sont correctes, vous pouvez faire défiler vers le haut si vous avez besoin de les modifier. Quand vous êtes sur·e que c'est correct, cliquez sur Suivant.",
+ "steps": [
+ "Votre nom est",
+ "Votre adresse courriel est",
+ "Votre numéro de téléphone est",
+ "Vous avez",
+ "ans",
+ "vous êtes né·e un",
+ "L'adresse courriel de votre parent ou responsable légal est"
+ ]
+ },
+ "send": {
+ "title": "Attention au départ !",
+ "description": "Vous avez terminé toutes les étapes nécessaires pour envoyer votre enregistrement à Delta. Vous pouvez vérifier encore une fois que les informations entrées sont correctes, puis les envoyer.",
+ "email": "Les administrateur·ice·s vous contacteront par courriel si ils/elles ont besoin d'informations supplémentaires et pour vous informer de la décision prise.",
+ "email2": "Les administrateur·ice·s vous contacteront à %1 si ils/elles ont besoin d'informations supplémentaires et pour vous informer de la décision prise."
+ },
+ "complete": {
+ "title": "Reste plus qu'à attendre...",
+ "description": "Vous avez correctement envoyé votre enregistrement à Delta, vous devriez avoir reçu un courriel de confirmation. Nous sommes maintenant en train de le traiter et nous vous recontacterons aussi vite que possible, soyez patient·e !",
+ "thanks": "Merci d'utiliser Delta."
+ },
+ "recaptcha": "Ce site est protégé reCAPTCHA, la %1politique de confidentialité%2 et les %3conditions d'utilisation%4 de Google s'applique.",
+ "errors": {
+ "captcha": "Nos systèmes ont détecté que vous pourriez être un robot et vous a empêché d'envoyer ce formulaire d'enregistrement. Si vous pensez qu'il s'agit d'une erreur, merci de réessayer.",
+ "invalid": "Il semblerait que des informations envoyées au serveur au sujet de votre enregistrement soient invalides ou manquantes."
+ },
+ "email": {
+ "title": "[Delta] Confirmation d'enregistrement n°%1",
+ "content": [
+ "Bonjour %1,",
+ "Merci de vous être enregistré·e sur Delta. Nous avons reçu votre enregistrement et nous vous recontacterons aussi vite que possible. En attendant, n'hésitez pas à parler de Delta aux autres membres de la famille, découvrir de nouvelles fonctionnalités et essayer d'amener de nouveaux utilisateurs sur Delta pour améliorer votre expérience.",
+ "Si vous n'avez pas demandé à vous enregistrer sur Delta, merci de répondre à ce message afin que nous supprimions votre enregistrement. Si vous avez besoin d'aide concernant votre enregistrement, vous pouvez répondre à ce message ; l'identifiant suivant peut vous être demandé : %1. Veuillez noter que la relecture de votre enregistrement peut prendre jusqu'à 30 jours.",
+ "À bientôt sur Delta !"
+ ],
+ "reason": "Vous recevez ce courriel car vous avez envoyer une demande d'enregistrement à Delta."
+ },
+ "approved": {
+ "title": "[Delta] Votre enregistrement n°%1 a été approuvé",
+ "content": [
+ "Bonjour %1,",
+ "Merci encore de vous êtes enregistré·e sur Delta. Après avoir analysé votre enregistrement, nous avons décidé que vous êtes en règle et avons terminé votre enregistrement sur Delta. Vous devriez maintenant pouvoir vous connecter à Delta comme n'importe quel autre utilisateur le ferait et apprécier tout le contenu qui vous attend.",
+ "Si vous n'avez pas demandé à vous enregistrer sur Delta, ou que vous ne voulez plus utiliser Delta, merci de répondre à ce message afin que nous supprimions votre compte. Si vous rencontrez des problèmes pour vous connecter, réessayez dans quelques heures.",
+ "À bientôt sur Delta !"
+ ]
+ },
+ "rejected": {
+ "title": "[Delta] Votre enregistrement n°%1 a été rejeté",
+ "content": [
+ "Bonjour %1,",
+ "Merci encore de vous êtes enregistré·e sur Delta. Après avoir analysé votre enregistrement, nous avons décidé que vous n'êtes pas autorisé·e à accéder à Delta et avons pris la décision de rejeter votre enregistrement. %1",
+ "Si vous n'avez pas demander à vous enregistrer sur Delta, ignorez simplement ce message. Si vous pensez qu'il s'agit d'une erreur et que vous devriez avoir accès à Delta, merci de répondre à ce message en indiquant pourquoi nous commettons une erreur.",
+ "Merci.",
+ "Nous avons décidé de ne pas fournir de raison sur pourquoi votre enregistrement a été rejeté.",
+ "Voici la raison que nous avons fournie :"
+ ]
+ }
}
} \ No newline at end of file
diff --git a/login/index.php b/login/index.php
index 8bad962..672eabc 100644
--- a/login/index.php
+++ b/login/index.php
@@ -92,7 +92,7 @@ if ($step === 0 && $_GET["method"] === "hub") {
header("Location: https://auth.equestria.horse/hub/api/rest/oauth2/auth?client_id=" . $app["oauth2_id"] . "&response_type=code&redirect_uri=https://" . ($_SERVER["HTTP_HOST"] === "192.168.1.121:81" ? "delta-staging" : "delta") . ".equestria.dev/login/oauth&scope=Hub&request_credentials=default&access_type=offline");
die();
} else if ($step === 0 && $_GET["method"] === "apply") {
- header("Location: https://docs.google.com/forms/d/e/1FAIpQLScCrxLNSfoutlRw8-F5DFMMgWDleoJCEM0QU4rNZt0uy5HOcw/viewform");
+ header("Location: /register");
die();
}
diff --git a/logo.png b/logo.png
new file mode 100644
index 0000000..4a4ae69
--- /dev/null
+++ b/logo.png
Binary files differ
diff --git a/plus/index.php b/plus/index.php
index d10778d..cf046cf 100644
--- a/plus/index.php
+++ b/plus/index.php
@@ -54,10 +54,10 @@ $age = (int)(explode(" ", timeAgo($_PROFILE["birth"] ?? "1990-01-01", false, tru
<div style="text-align: center; margin-top: 20px;">
<?php if ($_PROFILE["plus"]): ?>
- <a href="/plus/subscribe" class="btn btn-outline-light <?= $age < 16 ? "disabled" : "" ?>"><?= l("lang_plus_change") ?></a><br>
+ <a href="/plus/subscribe" class="btn btn-outline-dark <?= $age < 16 ? "disabled" : "" ?>"><?= l("lang_plus_change") ?></a><br>
<small class="text-muted" style="margin-top: 10px;display: block;"><?= str_replace("%1", $_PROFILE['ultra'] ? "Delta Ultra" : "Delta Plus", l("lang_plus_notice")) ?></small>
<?php else: ?>
- <a href="/plus/subscribe" class="btn btn-outline-light <?= $age < 16 ? "disabled" : "" ?>"><?= str_replace("%1", "<sup>1</sup>", str_replace("%2", $price, l("lang_plus_buy"))) ?></a><br>
+ <a href="/plus/subscribe" class="btn btn-outline-dark <?= $age < 16 ? "disabled" : "" ?>"><?= str_replace("%1", "<sup>1</sup>", str_replace("%2", $price, l("lang_plus_buy"))) ?></a><br>
<small class="text-muted" style="margin-top: 10px;display: block;"><?= l("lang_plus_terms") ?></small>
<?php endif; ?>
</div>
diff --git a/register/complete/index.php b/register/complete/index.php
new file mode 100644
index 0000000..c817017
--- /dev/null
+++ b/register/complete/index.php
@@ -0,0 +1,57 @@
+<?php
+
+require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/functions.php";
+
+$title = "lang_register_title"; require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/header.php";
+
+?>
+
+<style>
+ html, body {
+ overflow: hidden;
+ scroll-behavior: smooth;
+ }
+
+ * {
+ scroll-behavior: smooth;
+ }
+
+ .full-page {
+ border-bottom: 1px solid var(--palette-5);
+ padding-top: 50px;
+ height: 100vh;
+ scroll-snap-align: start;
+ scroll-snap-stop: always;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ }
+
+ .full-page:nth-last-child(1) {
+ border-bottom: none;
+ }
+</style>
+
+<div style="scroll-snap-type: y mandatory;position: fixed;inset: 0;overflow: scroll;">
+ <div class="full-page" style="background-color: var(--palette-6);">
+ <div class="container" style="max-width: 960px; color: var(--bs-secondary-bg);">
+ <img src="/icons/magic-register.svg" style="width: 96px;">
+ <h1 style="margin-top: 10px; margin-bottom: 10px;"><?= l("lang_register_complete_title") ?></h1>
+ <p><?= l("lang_register_complete_description") ?></p>
+ <div class="alert alert-secondary" style="display: grid; grid-template-columns: max-content 1fr; grid-gap: 10px; max-width: max-content; margin-left: auto; margin-right: auto;">
+ <div style="display: flex; align-items: center;">
+ <img src="/icons/email.svg">
+ </div>
+ <div style="display: flex; align-items: center;">
+ <span><?= str_replace("%1", strip_tags($_GET["email"] ?? "-"), l("lang_register_send_email2")) ?></span>
+ </div>
+ </div>
+ <p><?= l("lang_register_complete_thanks") ?></p>
+ </div>
+ </div>
+</div>
+
+<br><br>
+
+<?php require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/footer.php"; ?> \ No newline at end of file
diff --git a/register/index.php b/register/index.php
new file mode 100644
index 0000000..db0c5ee
--- /dev/null
+++ b/register/index.php
@@ -0,0 +1,347 @@
+<?php
+
+require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/functions.php";
+
+$title = "lang_register_title"; require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/header.php";
+
+?>
+
+<script src="https://www.google.com/recaptcha/api.js"></script>
+
+<style>
+ html, body {
+ overflow: hidden;
+ scroll-behavior: smooth;
+ }
+
+ * {
+ scroll-behavior: smooth;
+ }
+
+ .full-page {
+ border-bottom: 1px solid var(--palette-5);
+ padding-top: 50px;
+ height: 100vh;
+ scroll-snap-align: start;
+ scroll-snap-stop: always;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ }
+
+ .full-page:nth-last-child(1) {
+ border-bottom: none;
+ }
+
+ .grecaptcha-badge { visibility: hidden; }
+</style>
+
+<form id="form" style="scroll-snap-type: y mandatory;position: fixed;inset: 0;overflow: scroll;" action="/register/submit/" method="post">
+ <div class="full-page" id="/0" style="background-color: var(--palette-0);">
+ <div class="container" style="max-width: 960px;">
+ <?php if (isset($_GET["error"])): ?>
+ <div class="alert alert-primary" style="max-width: max-content; margin-left: auto; margin-right: auto;">
+ <?= l("lang_register_errors_" . $_GET["error"]) ?>
+ </div>
+ <?php endif; ?>
+
+ <img src="/logo.svg" style="width: 96px;">
+ <h1 style="margin-top: 10px; margin-bottom: 10px;"><?= l("lang_register_welcome") ?></h1>
+ <p><?= l("lang_register_intro") ?></p>
+
+ <div class="alert alert-secondary" style="display: grid; grid-template-columns: max-content 1fr; grid-gap: 10px; max-width: max-content; margin-left: auto; margin-right: auto;">
+ <div style="display: flex; align-items: center;">
+ <img src="/icons/id.svg">
+ </div>
+ <div style="display: flex; align-items: center;">
+ <span><?= l("lang_register_id") ?></span>
+ </div>
+ </div>
+ <a href="#/1" class="btn btn-primary"><?= l("lang_register_start") ?> ↓</a>
+ </div>
+ </div>
+
+ <div class="full-page" id="/1" style="background-color: var(--palette-1);">
+ <div class="container" style="max-width: 960px;">
+ <img src="/icons/name.svg" style="width: 96px;">
+ <h1 style="margin-top: 10px; margin-bottom: 10px;"><?= l("lang_register_name_title") ?></h1>
+ <p><?= l("lang_register_name_description") ?></p>
+ <div style="display: grid; grid-template-columns: max-content 1fr; grid-gap: 10px; max-width: 480px; margin: 10px auto;">
+ <div style="display: flex; align-items: center;">
+ <label style="margin: 0;" for="first_name" class="form-label"><?= l("lang_register_name_first1") ?></label>
+ </div>
+ <input onkeydown="updatePage1();" onkeyup="updatePage1();" onchange="updatePage1();" type="text" class="form-control ignore-enter" maxlength="30" minlength="0" placeholder="<?= l("lang_register_name_first2") ?>" id="first_name" name="first_name" required>
+ </div>
+ <div style="display: grid; grid-template-columns: max-content 1fr; grid-gap: 10px; max-width: 480px; margin: 10px auto;">
+ <div style="display: flex; align-items: center;">
+ <label style="margin: 0;" for="last_name" class="form-label"><?= l("lang_register_name_last1") ?></label>
+ </div>
+ <input onkeydown="updatePage1();" onkeyup="updatePage1();" onchange="updatePage1();" type="text" class="form-control ignore-enter" maxlength="30" minlength="0" placeholder="<?= l("lang_register_name_last2") ?>" id="last_name" name="last_name" required>
+ </div>
+ <div class="alert alert-primary" style="max-width: max-content; margin-left: auto; margin-right: auto;">
+ <label style="margin: 0; display: inline;" for="use_name">
+ <?= l("lang_register_name_use1") ?>&nbsp;
+ </label>
+ <input onkeydown="updatePage1();" onkeyup="updatePage1();" onchange="updatePage1();" style="display: inline; width: 20ch;" type="text" class="form-control ignore-enter" maxlength="30" minlength="0" placeholder="<?= l("lang_register_name_use2") ?>" id="use_name" name="use_name"><br>
+ <small>(<?= l("lang_register_name_use3") ?>)</small>
+ </div>
+ <a href="#/2" class="btn btn-primary disabled" id="page1_btn"><?= l("lang_register_continue") ?> ↓</a>
+ </div>
+ </div>
+
+ <div class="full-page" id="/2" style="background-color: var(--palette-2);">
+ <div class="container" style="max-width: 960px;">
+ <img src="/icons/smartphone.svg" style="width: 96px;">
+ <h1 style="margin-top: 10px; margin-bottom: 10px;"><?= l("lang_register_contact_title") ?></h1>
+ <p><?= l("lang_register_contact_description") ?></p>
+ <div style="display: grid; grid-template-columns: max-content 1fr; grid-gap: 10px; max-width: 480px; margin: 10px auto;">
+ <div style="display: flex; align-items: center;">
+ <label style="margin: 0;" for="email" class="form-label"><?= l("lang_register_contact_email1") ?></label>
+ </div>
+ <input onkeydown="updatePage2();" onkeyup="updatePage2();" onchange="updatePage2();" type="email" class="form-control ignore-enter" maxlength="60" minlength="0" placeholder="<?= l("lang_register_contact_email2") ?>" id="email" name="email" required>
+ </div>
+ <div style="display: grid; grid-template-columns: max-content 1fr; grid-gap: 10px; max-width: 480px; margin: 10px auto;">
+ <div style="display: flex; align-items: center;">
+ <label style="margin: 0;" for="phone" class="form-label"><?= l("lang_register_contact_phone1") ?></label>
+ </div>
+ <input onkeydown="updatePage2();" onkeyup="updatePage2();" onchange="updatePage2();" type="text" class="form-control ignore-enter" maxlength="30" minlength="0" placeholder="<?= l("lang_register_contact_phone2") ?>" id="phone" name="phone">
+ </div>
+ <a href="#/3" class="btn btn-primary disabled" id="page2_btn"><?= l("lang_register_continue") ?> ↓</a>
+ </div>
+ </div>
+
+ <div class="full-page" id="/3" style="background-color: var(--palette-3);">
+ <div class="container" style="max-width: 960px;">
+ <img src="/icons/age.svg" style="width: 96px;">
+ <h1 style="margin-top: 10px; margin-bottom: 10px;"><?= l("lang_register_birthday_title") ?></h1>
+ <p><?= l("lang_register_birthday_description") ?></p>
+ <p>
+ <select style="display: inline;width: max-content;" onchange="updatePage3();" class="form-select" id="birth_day" name="birth_day" required>
+ <option value="99" selected disabled>(<?= l("lang_register_birthday_day") ?>)</option>
+ <?php for ($i = 1; $i < 32; $i++): ?>
+ <option><?= $i ?></option>
+ <?php endfor; ?>
+ </select>
+ <select style="display: inline;width: max-content;" onchange="updatePage3();" class="form-select" id="birth_month" name="birth_month" required>
+ <option value="99" selected disabled>(<?= l("lang_register_birthday_month") ?>)</option>
+ <?php foreach ([
+ [ 1, "jan" ],
+ [ 2, "feb" ],
+ [ 3, "mar" ],
+ [ 4, "apr" ],
+ [ 5, "may" ],
+ [ 6, "jun" ],
+ [ 7, "jul" ],
+ [ 8, "aug" ],
+ [ 9, "sep" ],
+ [ 10, "oct" ],
+ [ 11, "nov" ],
+ [ 12, "dec" ],
+ ] as $month): ?>
+ <option value="<?= $month[0] ?>"><?= l("lang_months_" . $month[1]) ?></option>
+ <?php endforeach; ?>
+ </select>
+ <select style="display: inline;width: max-content;" onchange="updatePage3();" class="form-select" id="birth_year" name="birth_year" required>
+ <option value="999999" selected disabled>(<?= l("lang_register_birthday_year") ?>)</option>
+ <?php for ($i = ((int)date('Y')) - 7; $i > ((int)date('Y')) - 107; $i--): ?>
+ <option><?= $i ?></option>
+ <?php endfor; ?>
+ </select>
+ <div class="alert alert-primary" style="max-width: max-content; margin-left: auto; margin-right: auto; display: none;" id="underage">
+ <label style="margin: 0; display: inline;" for="underage_email">
+ <?= l("lang_register_birthday_underage1") ?>&nbsp;
+ </label>
+ <input onkeydown="updatePage3();" onkeyup="updatePage3();" onchange="updatePage3();" type="email" class="form-control ignore-enter" maxlength="60" minlength="0" placeholder="<?= l("lang_register_birthday_underage2") ?>" id="underage_email" name="underage_email">
+ </div>
+ </p>
+ <a href="#/4" class="btn btn-primary disabled" id="page3_btn"><?= l("lang_register_continue") ?> ↓</a>
+ </div>
+ </div>
+
+ <div class="full-page" id="/4" style="background-color: var(--palette-4);">
+ <div class="container" style="max-width: 960px;">
+ <img src="/icons/rules.svg" style="width: 96px;">
+ <h1 style="margin-top: 10px; margin-bottom: 10px;"><?= l("lang_register_rules_title") ?></h1>
+ <p><?= l("lang_register_rules_description") ?></p>
+
+ <div id="rules" style="overflow: auto;height: 256px;background: var(--palette-5);border: 1px solid var(--palette-9);border-radius: 10px;padding: 10px;margin-bottom: 1rem;text-align: left;" onscroll="updatePage4();">
+ <?php require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/rules.php"; ?>
+ </div>
+
+ <a href="#/5" class="btn btn-primary disabled" id="page4_btn"><?= l("lang_register_continue") ?> ↓</a>
+ </div>
+ </div>
+
+ <div class="full-page" id="/5" style="background-color: var(--palette-5);">
+ <div class="container" style="max-width: 960px;">
+ <img src="/icons/save.svg" style="width: 96px;">
+ <h1 style="margin-top: 10px; margin-bottom: 10px;"><?= l("lang_register_check_title") ?></h1>
+ <p><?= l("lang_register_check_description") ?></p>
+
+ <ul style="text-align: left;">
+ <li><?= l("lang_register_check_steps_0") ?> <b id="check-first_name">-</b> <b id="check-last_name">-</b><span id="check-with_use_name"> (<span id="check-first_name2">-</span>)</span></li>
+ <li><?= l("lang_register_check_steps_1") ?> <b id="check-email">-</b></li>
+ <li id="check-with_phone"><?= l("lang_register_check_steps_2") ?> <b id="check-phone">-</b></li>
+ <li><?= l("lang_register_check_steps_3") ?> <b><span id="check-age">-</span> <?= l("lang_register_check_steps_4") ?></b>, <?= l("lang_register_check_steps_5") ?> <span id="check-birth">-</span></li>
+ <li id="check-with_underage"><?= l("lang_register_check_steps_6") ?> <b id="check-underage">-</b></li>
+ </ul>
+
+ <a href="#/6" class="btn btn-primary"><?= l("lang_register_continue") ?> ↓</a>
+ </div>
+ </div>
+
+ <div class="full-page" id="/6" style="background-color: var(--palette-6);">
+ <div class="container" style="max-width: 960px; color: var(--bs-secondary-bg);">
+ <img src="/icons/upload-register.svg" style="width: 96px;">
+ <h1 style="margin-top: 10px; margin-bottom: 10px;"><?= l("lang_register_send_title") ?></h1>
+ <p><?= l("lang_register_send_description") ?></p>
+ <div class="alert alert-secondary" style="display: grid; grid-template-columns: max-content 1fr; grid-gap: 10px; max-width: max-content; margin-left: auto; margin-right: auto;">
+ <div style="display: flex; align-items: center;">
+ <img src="/icons/email.svg">
+ </div>
+ <div style="display: flex; align-items: center;">
+ <span><?= l("lang_register_send_email") ?></span>
+ </div>
+ </div>
+ <button class="btn btn-outline-light g-recaptcha disabled" id="submit_btn" data-sitekey="<?= trim(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/recaptcha-site")) ?>" data-callback='onSubmit' data-action='submit'><?= l("lang_register_confirm") ?></button><br>
+ <small style="margin-top: 10px; display: inline-block;">
+ <?= str_replace("%4", '</a>', str_replace("%3", '<a style="color: white;" href="https://policies.google.com/terms" target="_blank">', str_replace("%2", '</a>', str_replace("%1", '<a style="color: white;" href="https://policies.google.com/privacy" target="_blank">', l("lang_register_recaptcha"))))) ?>
+ </small>
+ </div>
+ </div>
+</form>
+
+<br><br>
+
+<script>
+ function onSubmit(_) {
+ document.getElementById("form").submit();
+ }
+
+ location.hash = "";
+
+ Array.from(document.getElementsByClassName("ignore-enter")).forEach((i) => {
+ i.addEventListener('keypress', function (e) {
+ if (e.keyCode === 13 || e.which === 13) {
+ e.preventDefault();
+ return false;
+ }
+ });
+ });
+
+ function validateEmail(email) {
+ const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
+ return re.test(String(email).toLowerCase());
+ }
+
+ function validatePhone(phone) {
+ const re = /^(\+|)\d{1,3}(-| |)((\(|)\d(\)|)(-| |))+$/;
+ return re.test(String(phone).toLowerCase());
+ }
+
+ function updatePage1() {
+ if (document.getElementById("first_name").value.trim().length > 0 && document.getElementById("first_name").value.trim().length < 31
+ && document.getElementById("last_name").value.trim().length > 0 && document.getElementById("last_name").value.trim().length < 31
+ && ((document.getElementById("use_name").value.trim().length > 0 && document.getElementById("use_name").value.trim().length < 31) || document.getElementById("use_name").value.trim().length === 0)) {
+ document.getElementById("page1_btn").classList.remove("disabled");
+ } else {
+ document.getElementById("page1_btn").classList.add("disabled");
+ }
+
+ document.getElementById("check-first_name").innerText = document.getElementById("use_name").value.trim().length > 0 ? document.getElementById("use_name").value.trim() : document.getElementById("first_name").value.trim();
+ document.getElementById("check-last_name").innerText = document.getElementById("last_name").value.trim();
+
+ if (document.getElementById("use_name").value.trim().length > 0) {
+ document.getElementById("check-with_use_name").style.display = "";
+ document.getElementById("check-first_name2").innerText = document.getElementById("first_name").value.trim();
+ } else {
+ document.getElementById("check-with_use_name").style.display = "none";
+ }
+
+ updateAll();
+ }
+
+ function updatePage2() {
+ if (document.getElementById("email").value.trim().length > 0 && document.getElementById("email").value.trim().length < 61 && validateEmail(document.getElementById("email").value.trim())
+ && ((document.getElementById("phone").value.trim().length > 0 && document.getElementById("phone").value.trim().length < 31 && validatePhone(document.getElementById("phone").value)) || document.getElementById("phone").value.trim().length === 0)) {
+ document.getElementById("page2_btn").classList.remove("disabled");
+ } else {
+ document.getElementById("page2_btn").classList.add("disabled");
+ }
+
+ document.getElementById("check-email").innerText = document.getElementById("email").value.trim();
+ document.getElementById("check-phone").innerText = document.getElementById("phone").value.trim();
+
+ if (document.getElementById("phone").value.trim().length > 0) {
+ document.getElementById("check-with_phone").style.display = "";
+ } else {
+ document.getElementById("check-with_phone").style.display = "none";
+ }
+
+ updateAll();
+ }
+
+ function updatePage3() {
+ try {
+ let date = new Date(document.getElementById("birth_year").value + "-" + document.getElementById("birth_month").value + "-" + document.getElementById("birth_day").value);
+ date.toISOString();
+
+ if (new Date().getTime() - date.getTime() > 220903200000) {
+ if (new Date().getTime() - date.getTime() < 410248800000) {
+ document.getElementById("underage").style.display = "";
+
+ if (document.getElementById("underage_email").value.trim().length > 0 && document.getElementById("underage_email").value.trim().length < 61 && validateEmail(document.getElementById("underage_email").value.trim())) {
+ document.getElementById("page3_btn").classList.remove("disabled");
+ } else {
+ document.getElementById("page3_btn").classList.add("disabled");
+ }
+ } else {
+ document.getElementById("underage").style.display = "none";
+ document.getElementById("underage_email").value = "";
+ document.getElementById("page3_btn").classList.remove("disabled");
+ }
+ } else {
+ document.getElementById("underage").style.display = "none";
+ document.getElementById("underage_email").value = "";
+ document.getElementById("page3_btn").classList.add("disabled");
+ }
+
+ document.getElementById("check-birth").innerText = document.getElementById("birth_day").value + " " + document.getElementById("birth_month").options[document.getElementById("birth_month").selectedIndex].text;
+ document.getElementById("check-age").innerText = Math.floor((new Date().getTime() - date.getTime()) / 31557600000).toString();
+ document.getElementById("check-underage").innerText = document.getElementById("underage_email").value.trim();
+
+ if (document.getElementById("underage_email").value.trim().length > 0) {
+ document.getElementById("check-with_underage").style.display = "";
+ } else {
+ document.getElementById("check-with_underage").style.display = "none";
+ }
+ } catch (e) {
+ document.getElementById("underage").style.display = "none";
+ document.getElementById("underage_email").value = "";
+ document.getElementById("page3_btn").classList.add("disabled");
+ }
+
+ updateAll();
+ }
+
+ function updatePage4() {
+ if (document.getElementById("rules").scrollTop >= document.getElementById("rules").scrollHeight - 256) {
+ document.getElementById("page4_btn").classList.remove("disabled");
+ } else {
+ document.getElementById("page4_btn").classList.add("disabled");
+ }
+
+ updateAll();
+ }
+
+ function updateAll() {
+ if (document.getElementById("page4_btn").classList.contains("disabled") || document.getElementById("page3_btn").classList.contains("disabled") || document.getElementById("page2_btn").classList.contains("disabled") || document.getElementById("page1_btn").classList.contains("disabled")) {
+ document.getElementById("submit_btn").classList.add("disabled");
+ } else {
+ document.getElementById("submit_btn").classList.remove("disabled");
+ }
+ }
+</script>
+
+<?php require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/footer.php"; ?> \ No newline at end of file
diff --git a/register/submit/index.php b/register/submit/index.php
new file mode 100644
index 0000000..21c7966
--- /dev/null
+++ b/register/submit/index.php
@@ -0,0 +1,122 @@
+<?php
+
+require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/recaptcha/src/autoload.php";
+require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/functions.php";
+require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/email.php";
+
+$recaptcha = new \ReCaptcha\ReCaptcha(trim(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/recaptcha-secret")));
+header("Content-Type: text/plain");
+
+$resp = $recaptcha->setExpectedHostname(($_SERVER["HTTP_HOST"] === "192.168.1.121:81" ? "delta-staging" : "delta") . ".equestria.dev")
+ ->verify($_POST["g-recaptcha-response"], $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR']);
+if (!$resp->isSuccess()) {
+ header("Location: /register/?error=captcha");
+ die();
+}
+
+function generateID() {
+ $uuid = uuid();
+ return [
+ "uuid" => $uuid,
+ "id" => strtoupper(substr(str_replace("/", "", str_replace("=", "", str_replace(".", "", str_replace("+", "", base64_encode(hex2bin(md5($uuid))))))), 0, 12))
+ ];
+}
+
+function validateEmail($email) {
+ return (bool)preg_match('/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/', $email);
+}
+
+function validatePhone($phone) {
+ return (bool)preg_match('/^(\+|)\d{1,3}(-| |)((\(|)\d(\)|)(-| |))+$/', $phone);
+}
+
+function checkField($field, $profile) {
+ global $data;
+
+ if (isset($_POST[$field])) {
+ $_POST[$field] = trim($_POST[$field]);
+
+ if ($profile === "text30" && strlen($_POST[$field]) > 0 && strlen($_POST[$field]) < 31) {
+ $data[$field] = strip_tags($_POST[$field]);
+ } elseif ($profile === "text30o" && strlen($_POST[$field]) > 0 && strlen($_POST[$field]) < 31) {
+ $data[$field] = strip_tags($_POST[$field]);
+ } elseif ($profile === "text30o" && strlen($_POST[$field]) === 0) {
+ $data[$field] = null;
+ } elseif ($profile === "email60" && strlen($_POST[$field]) > 0 && strlen($_POST[$field]) < 61 && validateEmail($_POST[$field])) {
+ $data[$field] = strip_tags($_POST[$field]);
+ } elseif ($profile === "email60o" && strlen($_POST[$field]) > 0 && strlen($_POST[$field]) < 61 && validateEmail($_POST[$field])) {
+ $data[$field] = strip_tags($_POST[$field]);
+ } elseif ($profile === "email60o" && strlen($_POST[$field]) === 0) {
+ $data[$field] = null;
+ } elseif ($profile === "phone30" && strlen($_POST[$field]) > 0 && strlen($_POST[$field]) < 31 && validatePhone($_POST[$field])) {
+ $data[$field] = strip_tags($_POST[$field]);
+ } elseif ($profile === "phone30o" && strlen($_POST[$field]) > 0 && strlen($_POST[$field]) < 31 && validatePhone($_POST[$field])) {
+ $data[$field] = strip_tags($_POST[$field]);
+ } elseif ($profile === "phone30o" && strlen($_POST[$field]) === 0) {
+ $data[$field] = null;
+ } else {
+ header("Location: /register/?error=invalid&field=" . $field);
+ die();
+ }
+ } else {
+ header("Location: /register/?error=invalid&field=" . $field);
+ die();
+ }
+}
+
+$data = [];
+
+checkField("first_name", "text30");
+checkField("last_name", "text30");
+checkField("use_name", "text30o");
+checkField("email", "email60");
+checkField("phone", "phone30o");
+checkField("underage_email", "email60o");
+
+$date = "0000-00-00";
+
+if (!isset($_POST["birth_year"]) || !isset($_POST["birth_month"]) || !isset($_POST["birth_day"]) || !is_numeric($_POST["birth_year"]) || !is_numeric($_POST["birth_month"]) || !is_numeric($_POST["birth_day"]) || (int)$_POST["birth_year"] < 1 || (int)$_POST["birth_month"] < 1 || (int)$_POST["birth_day"] < 1 || (int)$_POST["birth_year"] > (int)date('Y') || (int)$_POST["birth_month"] > 12 || (int)$_POST["birth_day"] > 31) {
+ header("Location: /register/?error=invalid&field=birth");
+ die();
+} else if (strtotime($_POST["birth_year"] . "-" . $_POST["birth_month"] . "-" . $_POST["birth_day"]) === false) {
+ header("Location: /register/?error=invalid&field=birth");
+ die();
+} else if (time() - strtotime($_POST["birth_year"] . "-" . $_POST["birth_month"] . "-" . $_POST["birth_day"]) <= 220903200) {
+ header("Location: /register/?error=invalid&field=birth");
+ die();
+} else if (time() - strtotime($_POST["birth_year"] . "-" . $_POST["birth_month"] . "-" . $_POST["birth_day"]) < 410248800 && !isset($data["underage_email"])) {
+ header("Location: /register/?error=invalid&field=underage_email");
+ die();
+} else {
+ $date = date('Y-m-d', strtotime($_POST["birth_year"] . "-" . $_POST["birth_month"] . "-" . $_POST["birth_day"]));
+}
+
+$data["birth_date"] = $date;
+$id = generateID();
+$data["uuid"] = $id["uuid"];
+$data["id"] = $id["id"];
+$data["lang"] = l("lang__name");
+$data["date"] = date('c');
+
+if (!file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/registrations")) mkdir($_SERVER['DOCUMENT_ROOT'] . "/includes/data/registrations");
+file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/registrations/" . $id["uuid"] . ".json", json_encode($data, JSON_PRETTY_PRINT));
+sendRegistration($data["email"], $data["use_name"] ?? $data["first_name"], $id["id"]);
+
+$config = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/email.json"), true);
+
+file_get_contents('https://notifications.equestria.dev/delta', false, stream_context_create([
+ 'http' => [
+ 'method' => 'POST',
+ 'header' =>
+ "Content-Type: text/plain\r\n" .
+ "Title: " . formatPonypush("New registration request") . "\r\n" .
+ "Priority: default\r\n" .
+ "Tags: requests\r\n" .
+ "Actions: view, Open registrations, https://delta.equestria.dev/admin/registrations/, clear=true\r\n" .
+ "Authorization: Basic " . base64_encode($config["ntfyuser"] . ":" . $config["ntfypass"]),
+ 'content' => formatPonypush(($data['use_name'] ?? $data['first_name']) . " " . $data["last_name"] . " tried to register on Delta with ID #" . $id["id"])
+ ]
+]));
+
+header("Location: /register/complete/?email=" . $data["email"]);
+die(); \ No newline at end of file
diff --git a/support/rules/index.php b/support/rules/index.php
index a36a598..42dbec6 100644
--- a/support/rules/index.php
+++ b/support/rules/index.php
@@ -12,80 +12,7 @@ global $_PROFILE; global $_USER;
<br><br>
<h1><?= l("lang_support_rules") ?></h1>
- <p><?= l("lang_rules_intro") ?></p>
-
- <h2><?= l("lang_rules_sections_0") ?></h2>
- <ul>
- <li><?= l("lang_rules_lines_0_0") ?></li>
- <li><?= l("lang_rules_lines_0_1") ?></li>
- <li><?= l("lang_rules_lines_0_2") ?></li>
- <li><?= l("lang_rules_lines_0_3") ?></li>
- <li><?= l("lang_rules_lines_0_5") ?></li>
- <li><?= l("lang_rules_lines_0_6") ?></li>
- <li><?= l("lang_rules_lines_0_7") ?></li>
- <li><?= l("lang_rules_lines_0_8") ?></li>
- </ul>
-
- <h2><?= l("lang_rules_sections_1") ?></h2>
- <ul>
- <li><?= l("lang_rules_lines_1_0") ?></li>
- <li><?= l("lang_rules_lines_1_1") ?></li>
- <li><?= l("lang_rules_lines_1_2") ?></li>
- <li><?= l("lang_rules_lines_1_3") ?></li>
- <li><?= l("lang_rules_lines_1_4") ?></li>
- <li><?= l("lang_rules_lines_1_5") ?></li>
- <li><?= l("lang_rules_lines_1_6") ?></li>
- <li><?= l("lang_rules_lines_1_7") ?></li>
- <li><?= l("lang_rules_lines_1_8") ?></li>
- </ul>
-
- <h2><?= l("lang_rules_sections_2") ?></h2>
- <ul>
- <li><?= l("lang_rules_lines_2_0") ?></li>
- <li><?= l("lang_rules_lines_2_1") ?></li>
- <li><?= l("lang_rules_lines_2_2") ?></li>
- <li><?= l("lang_rules_lines_2_3") ?></li>
- <li><?= l("lang_rules_lines_2_4") ?></li>
- <li><?= l("lang_rules_lines_2_5") ?></li>
- <li><?= l("lang_rules_lines_2_6") ?></li>
- </ul>
-
- <h2><?= l("lang_rules_sections_3") ?></h2>
- <ul>
- <li><?= l("lang_rules_lines_3_0") ?></li>
- <li><?= l("lang_rules_lines_3_1") ?></li>
- <li><?= l("lang_rules_lines_3_2") ?></li>
- <li><?= l("lang_rules_lines_3_3") ?></li>
- <li><?= l("lang_rules_lines_3_4") ?></li>
- <li><?= l("lang_rules_lines_3_5") ?></li>
- <li><?= l("lang_rules_lines_3_6") ?></li>
- <li><?= l("lang_rules_lines_3_7") ?></li>
- <li><?= l("lang_rules_lines_3_8") ?></li>
- <li><?= l("lang_rules_lines_3_9") ?></li>
- </ul>
-
- <h2><?= l("lang_rules_sections_4") ?></h2>
- <ul>
- <li><?= l("lang_rules_lines_4_0") ?></li>
- <li><?= l("lang_rules_lines_4_1") ?></li>
- <li><?= l("lang_rules_lines_4_2") ?></li>
- <li><?= l("lang_rules_lines_4_3") ?></li>
- <li><?= l("lang_rules_lines_4_4") ?></li>
- <li><?= l("lang_rules_lines_4_5") ?></li>
- <li><?= l("lang_rules_lines_4_6") ?></li>
- </ul>
-
- <h2><?= l("lang_rules_sections_5") ?></h2>
- <ul>
- <li><?= l("lang_rules_lines_5_0") ?></li>
- <li><?= l("lang_rules_lines_5_1") ?></li>
- <li><?= l("lang_rules_lines_5_2") ?></li>
- <li><?= l("lang_rules_lines_5_3") ?></li>
- <li><?= l("lang_rules_lines_5_4") ?></li>
- <li><?= l("lang_rules_lines_5_5") ?></li>
- <li><?= l("lang_rules_lines_5_6") ?></li>
- <li><?= l("lang_rules_lines_5_7") ?></li>
- </ul>
+ <?php require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/rules.php"; ?>
<br><br>
</div>