summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app.php19
-rw-r--r--auth/callback/index.php2
-rw-r--r--auth/init/index.php2
-rw-r--r--includes/components/footer.inc13
-rw-r--r--includes/components/header.inc16
-rw-r--r--includes/components/navigation.inc148
-rw-r--r--includes/components/pane.inc5
-rw-r--r--includes/components/search.inc534
-rw-r--r--includes/components/wakeup.inc2
-rw-r--r--includes/external/chvfs/index.js104
-rw-r--r--includes/external/chvfs/node_modules/.package-lock.json15
-rw-r--r--includes/external/chvfs/node_modules/node-watch/LICENSE22
-rw-r--r--includes/external/chvfs/node_modules/node-watch/README.md233
-rw-r--r--includes/external/chvfs/node_modules/node-watch/lib/has-native-recursive.js115
-rw-r--r--includes/external/chvfs/node_modules/node-watch/lib/is.js78
-rw-r--r--includes/external/chvfs/node_modules/node-watch/lib/watch.d.ts75
-rw-r--r--includes/external/chvfs/node_modules/node-watch/lib/watch.js530
-rw-r--r--includes/external/chvfs/node_modules/node-watch/package.json36
-rw-r--r--includes/external/chvfs/package-lock.json27
-rw-r--r--includes/external/chvfs/package.json5
-rw-r--r--includes/jobs/FrontersNotification.php18
-rw-r--r--includes/jobs/PKMembers.php9
-rw-r--r--includes/jobs/PKSystem.php2
-rw-r--r--includes/jobs/UpdateAssets.php4
-rw-r--r--includes/pages.json350
-rw-r--r--includes/util/agewarning.inc10
-rw-r--r--includes/util/banner.inc13
-rw-r--r--includes/util/functions.inc16
-rw-r--r--includes/util/score.inc2
-rw-r--r--includes/util/session.inc6
-rw-r--r--includes/util/travelling.inc2
-rw-r--r--pages/api/emergency-real.php10
-rw-r--r--pages/api/emergency.php10
-rw-r--r--pages/api/me.php2
-rw-r--r--pages/api/pleasure-real.php6
-rw-r--r--pages/api/pleasure.php6
-rw-r--r--pages/api/plex-thumb.php2
-rw-r--r--pages/api/plex.php2
-rw-r--r--pages/api/pluralkit-integration.php8
-rw-r--r--pages/api/wakeup-real.php6
-rw-r--r--pages/api/wakeup.php6
-rw-r--r--pages/home.inc133
-rw-r--r--pages/jobs.inc30
-rw-r--r--pages/metadata.inc2
-rw-r--r--pages/money.inc12
-rw-r--r--pages/page.inc2
-rw-r--r--pages/travelling.inc2
47 files changed, 1621 insertions, 1031 deletions
diff --git a/app.php b/app.php
index b6651c9..ec85fbe 100644
--- a/app.php
+++ b/app.php
@@ -1,12 +1,21 @@
<?php
-ob_start();
+$start = microtime(true);
+$GLOBALS["ColdHazeStart"] = microtime(true);
+$GLOBALS["ColdHazePerformance"] = [];
+
+if (!function_exists("formatPonypush")) {
+ function formatPonypush($message) {
+ return "Update to Ponypush 3.1.0 or later — (\$PA1$\$" . base64_encode($message) . "\$\$)";
+ }
+}
require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/language.inc"; global $lang; global $pages;
require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/session.inc"; global $isLoggedIn; global $isLowerLoggedIn;
require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/functions.inc";
-$app = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true);
+global $app;
+$app = $GLOBALS["ColdHazeApp"] = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true);
if (str_ends_with($_GET['_'], "/")) {
$pagename = substr($_GET['_'], 0, strlen($_GET['_']) - 1);
@@ -25,6 +34,7 @@ if (in_array($toplevel, ["editor", "icons", "species", "uploads"])) {
}
} elseif ($toplevel === "") {
$pageFile = $_SERVER['DOCUMENT_ROOT'] . "/pages/home.inc";
+ $GLOBALS["ColdHazePerformance"]["router"] = (microtime(true) - $start) * 1000;
require_once $_SERVER['DOCUMENT_ROOT'] . "/pages/home.inc";
} else {
if ($toplevel === "-") {
@@ -33,6 +43,7 @@ if (in_array($toplevel, ["editor", "icons", "species", "uploads"])) {
if (file_exists($_SERVER['DOCUMENT_ROOT'] . "/pages/" . $toplevel . ".inc")) {
$pageFile = $_SERVER['DOCUMENT_ROOT'] . "/pages/" . $toplevel . ".inc";
+ $GLOBALS["ColdHazePerformance"]["router"] = (microtime(true) - $start) * 1000;
require_once $_SERVER['DOCUMENT_ROOT'] . "/pages/" . $toplevel . ".inc";
} else {
peh_error("Page not found: " . strip_tags($pagename), 404);
@@ -40,6 +51,7 @@ if (in_array($toplevel, ["editor", "icons", "species", "uploads"])) {
} else if ($toplevel === "api") {
if (file_exists($_SERVER['DOCUMENT_ROOT'] . "/pages/" . $toplevel . ".inc")) {
$pageFile = $_SERVER['DOCUMENT_ROOT'] . "/pages/" . $toplevel . ".inc";
+ $GLOBALS["ColdHazePerformance"]["router"] = (microtime(true) - $start) * 1000;
require_once $_SERVER['DOCUMENT_ROOT'] . "/pages/" . $toplevel . ".inc";
}
} else if ($toplevel === "cloudburst" || $toplevel === "raindrops" || (($isLoggedIn || $isLowerLoggedIn) && $toplevel === $app["other"]["slug"])) {
@@ -60,6 +72,7 @@ if (in_array($toplevel, ["editor", "icons", "species", "uploads"])) {
}
$pageFile = $_SERVER['DOCUMENT_ROOT'] . "/pages/page.inc";
+ $GLOBALS["ColdHazePerformance"]["router"] = (microtime(true) - $start) * 1000;
require_once $_SERVER['DOCUMENT_ROOT'] . "/pages/page.inc";
} else {
if (file_exists($_SERVER['DOCUMENT_ROOT'] . "/pages/" . $toplevel . ".inc")) {
@@ -81,10 +94,12 @@ if (in_array($toplevel, ["editor", "icons", "species", "uploads"])) {
if ((in_array($toplevel, $namesCloudburst) || in_array($toplevel, $namesRaindrops) || in_array($toplevel, $namesOther)) && $toplevel !== "unknown") {
$pageFile = $_SERVER['DOCUMENT_ROOT'] . "/pages/page.inc";
+ $GLOBALS["ColdHazePerformance"]["router"] = (microtime(true) - $start) * 1000;
require_once $_SERVER['DOCUMENT_ROOT'] . "/pages/page.inc";
} else {
global $toplevel;
$pageFile = $_SERVER['DOCUMENT_ROOT'] . "/includes/util/short.inc";
+ $GLOBALS["ColdHazePerformance"]["router"] = (microtime(true) - $start) * 1000;
require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/short.inc";
}
}
diff --git a/auth/callback/index.php b/auth/callback/index.php
index 8d040e9..3b9d6eb 100644
--- a/auth/callback/index.php
+++ b/auth/callback/index.php
@@ -8,7 +8,7 @@ if (!isset($_GET['code'])) {
die();
}
-$appdata = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true);
+$appdata = $GLOBALS["ColdHazeApp"];
$crl = curl_init('https://' . $server . '/hub/api/rest/oauth2/token');
curl_setopt($crl, CURLOPT_RETURNTRANSFER, true);
diff --git a/auth/init/index.php b/auth/init/index.php
index 3a4e7b7..9428825 100644
--- a/auth/init/index.php
+++ b/auth/init/index.php
@@ -2,5 +2,5 @@
$server = "auth.equestria.horse";
-header("Location: https://$server/hub/api/rest/oauth2/auth?client_id=" . json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true)["oauth"]["id"] . "&response_type=code&redirect_uri=https://ponies.equestria.horse/auth/callback&scope=Hub&request_credentials=default&access_type=offline");
+header("Location: https://$server/hub/api/rest/oauth2/auth?client_id=" . $GLOBALS["ColdHazeApp"]["oauth"]["id"] . "&response_type=code&redirect_uri=https://ponies.equestria.horse/auth/callback&scope=Hub&request_credentials=default&access_type=offline");
die();
diff --git a/includes/components/footer.inc b/includes/components/footer.inc
index d75b480..059d956 100644
--- a/includes/components/footer.inc
+++ b/includes/components/footer.inc
@@ -1,7 +1,8 @@
<?php
-
+global $start;
+$GLOBALS["ColdHazePerformance"]["page"] = (microtime(true) - $start) * 1000;
+$start = microtime(true);
global $pageFile;
-require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/functions.inc";
?>
@@ -16,8 +17,10 @@ require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/functions.inc";
global $lang; global $pages;
+ $time = microtime(true) - $GLOBALS["ColdHazeStart"];
+
?>
- © <?= date("Y") ?> <a href="https://equestria.horse" target="_blank" class="text-muted"><?= $lang["footer"]["copyright"] ?></a> · build <?= $version["build"] ?>.<?= $version["revision"] ?>
+ © <?= date("Y") ?> <a href="https://equestria.horse" target="_blank" class="text-muted"><?= $lang["footer"]["copyright"] ?></a> · build <?= $version["build"] ?>.<?= $version["revision"] ?>, took <?= round($time * 1000, 2) ?> ms
<br><br><br><br><br>
</div>
</div>
@@ -35,7 +38,9 @@ require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/functions.inc";
})
</script>
-<?php require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/components/search.inc"; ?>
+<?php if (isset($_GET["performance"])): ?>
+<pre><?php $GLOBALS["ColdHazePerformance"]["footer"] = (microtime(true) - $start) * 1000; var_dump($GLOBALS["ColdHazePerformance"]); ?></pre>
+<?php endif; ?>
</body>
</html> \ No newline at end of file
diff --git a/includes/components/header.inc b/includes/components/header.inc
index 06b9bdb..0542314 100644
--- a/includes/components/header.inc
+++ b/includes/components/header.inc
@@ -1,14 +1,6 @@
-<?php global $title; global $pages;
-
+<?php global $title; global $pages; global $readOnly;
+$start = microtime(true);
$useNewUI = !isset($_GET['old']);
-$readOnly = false;
-file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/.test", "hello");
-
-if (file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/.test")) {
- unlink($_SERVER['DOCUMENT_ROOT'] . "/includes/data/.test");
-} else {
- $readOnly = true;
-}
$isNormallyLoggedIn = false;
@@ -69,7 +61,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/functions.inc";
<title><?= $title && $title !== "-" ? $title . " · " : "" ?>Cold Haze</title>
<link rel="shortcut icon" href="/assets/logo/newlogo<?= $isLoggedIn || $isLowerLoggedIn ? "3" : "" ?>.png" type="image/png">
</head>
-<body<?php if ($page["rail"] && $isLoggedIn && !$useNewUI): ?> id="admin-page"<?php endif; ?>>
+<body>
<?php require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/components/navigation.inc"; global $navigation; ?>
<?php if (!$useNewUI): ?>
@@ -246,4 +238,4 @@ require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/functions.inc";
display: none;
}
</style>
-<?php } ?>
+<?php } $GLOBALS["ColdHazePerformance"]["header"] = (microtime(true) - $start) * 1000; $start = microtime(true); ?>
diff --git a/includes/components/navigation.inc b/includes/components/navigation.inc
index bd4ea4b..29168ec 100644
--- a/includes/components/navigation.inc
+++ b/includes/components/navigation.inc
@@ -4,7 +4,78 @@ $pages = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/pa
global $navigation;
global $toplevel;
-global $lang; global $pages; global $app; global $isLowerLoggedIn;
+global $lang; global $pages; global $app; global $isLowerLoggedIn; global $isLoggedIn;
+
+$cache = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/cache/navigation.json"), true);
+
+if (!isset($cache["raindrops"])) $cache["raindrops"] = [];
+if (!isset($cache["cloudburst"])) $cache["cloudburst"] = [];
+if (!isset($cache["other"])) $cache["other"] = [];
+
+foreach ([
+ [
+ "name" => "raindrops",
+ "id" => "gdapd"
+ ],
+ [
+ "name" => "cloudburst",
+ "id" => "ynmuc"
+ ],
+ [
+ "name" => "other",
+ "id" => $app["other"]["id"]
+ ]
+] as $cacheSystem) {
+ if (!isset($cache[$cacheSystem["name"]]["public"])) {
+ $isLoggedInOldState = $isLoggedIn;
+ $isLowerLoggedInOldState = $isLowerLoggedIn;
+
+ $isLoggedIn = false;
+ $isLowerLoggedIn = false;
+
+ $cache[$cacheSystem["name"]]["public"] = array_map(function ($member) {
+ return [
+ "name" => $member['display_name'] ?? $member['name'],
+ "icon" => getAsset($member["system"], $member["id"], "heads"),
+ "invert" => false,
+ "link" => "/$member[name]",
+ "stepped" => null,
+ "private" => false
+ ];
+ }, array_filter(scoreOrder(withTravelers(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$cacheSystem[id]/members.json"), true), "$cacheSystem[id]"), "$cacheSystem[id]"), function ($member) {
+ return $member['name'] !== "unknown" && $member['name'] !== "fusion" && $member['name'] !== "new";
+ }));
+
+ $isLoggedIn = $isLoggedInOldState;
+ $isLowerLoggedIn = $isLowerLoggedInOldState;
+ }
+
+ if (!isset($cache[$cacheSystem["name"]]["private"])) {
+ $isLoggedInOldState = $isLoggedIn;
+ $isLowerLoggedInOldState = $isLowerLoggedIn;
+
+ $isLoggedIn = true;
+ $isLowerLoggedIn = false;
+
+ $cache[$cacheSystem["name"]]["private"] = array_map(function ($member) {
+ return [
+ "name" => $member['display_name'] ?? $member['name'],
+ "icon" => getAsset($member["system"], $member["id"], "heads"),
+ "invert" => false,
+ "link" => "/$member[name]",
+ "stepped" => null,
+ "private" => false
+ ];
+ }, array_filter(scoreOrder(withTravelers(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$cacheSystem[id]/members.json"), true), "$cacheSystem[id]"), "$cacheSystem[id]"), function ($member) {
+ return $member['name'] !== "unknown" && $member['name'] !== "fusion" && $member['name'] !== "new";
+ }));
+
+ $isLoggedIn = $isLoggedInOldState;
+ $isLowerLoggedIn = $isLowerLoggedInOldState;
+ }
+}
+
+file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/cache/navigation.json", json_encode($cache));
$navigation_admin = [
"admin" => !$isLowerLoggedIn,
@@ -173,41 +244,7 @@ $navigation_admin = [
"private" => false
],
]
- ],
- /*"debug" => [
- "name" => $lang["navigation"]["debug"],
- "minimal" => false,
- "items" => [
- [
- "name" => $pages["debug"]["name"][$lang["_name"]],
- "icon" => "/assets/icons/debug.svg",
- "invert" => true,
- "link" => "/-/debug",
- "stepped" => null
- ],
- [
- "name" => $pages["bitset"]["name"][$lang["_name"]],
- "icon" => "/assets/icons/bitset.svg",
- "invert" => true,
- "link" => "/-/bitset",
- "stepped" => null
- ],
- [
- "name" => $pages["score"]["name"][$lang["_name"]],
- "icon" => "/assets/icons/score.svg",
- "invert" => true,
- "link" => "/-/score",
- "stepped" => null
- ],
- [
- "name" => $pages["logout"]["name"][$lang["_name"]],
- "icon" => "/assets/icons/logout.svg",
- "invert" => true,
- "link" => "/-/logout",
- "stepped" => null
- ]
- ]
- ]*/
+ ]
]
];
$navigation_global = [
@@ -287,18 +324,7 @@ $navigation_cloudburst = [
"members" => [
"name" => $lang["navigation"]["members"],
"minimal" => false,
- "items" => array_map(function ($member) {
- return [
- "name" => $member['display_name'] ?? $member['name'],
- "icon" => getAsset($member["system"], $member["id"], "heads"),
- "invert" => false,
- "link" => "/$member[name]",
- "stepped" => null,
- "private" => false
- ];
- }, array_filter(scoreOrder(withTravelers(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/ynmuc/members.json"), true), "ynmuc"), "ynmuc"), function ($member) {
- return $member['name'] !== "unknown" && $member['name'] !== "fusion" && $member['name'] !== "new";
- }))
+ "items" => $cache["cloudburst"][$isLoggedIn || $isLowerLoggedIn ? "private" : "public"]
]
]
];
@@ -341,18 +367,7 @@ $navigation_other = [
"members" => [
"name" => $lang["navigation"]["members"],
"minimal" => false,
- "items" => array_map(function ($member) {
- return [
- "name" => $member['display_name'] ?? $member['name'],
- "icon" => getAsset($member["system"], $member["id"], "heads"),
- "invert" => false,
- "link" => "/$member[name]",
- "stepped" => null,
- "private" => false
- ];
- }, array_filter(scoreOrder(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/other/members.json"), true), $app["other"]["id"]), function ($member) {
- return $member['name'] !== "unknown" && $member['name'] !== "fusion" && $member['name'] !== "new";
- }))
+ "items" => $cache["other"][$isLoggedIn || $isLowerLoggedIn ? "private" : "public"]
]
]
];
@@ -395,18 +410,7 @@ $navigation_raindrops = [
"members" => [
"name" => $lang["navigation"]["members"],
"minimal" => false,
- "items" => array_map(function ($member) {
- return [
- "name" => $member['display_name'] ?? $member['name'],
- "icon" => getAsset($member["system"], $member["id"], "heads"),
- "invert" => false,
- "link" => "/$member[name]",
- "stepped" => null,
- "private" => false
- ];
- }, array_filter(scoreOrder(withTravelers(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/gdapd/members.json"), true), "gdapd"), "gdapd"), function ($member) {
- return $member['name'] !== "unknown" && $member['name'] !== "fusion" && $member['name'] !== "new";
- }))
+ "items" => $cache["raindrops"][$isLoggedIn || $isLowerLoggedIn ? "private" : "public"]
]
]
];
diff --git a/includes/components/pane.inc b/includes/components/pane.inc
index eb735f5..1ecbcdf 100644
--- a/includes/components/pane.inc
+++ b/includes/components/pane.inc
@@ -22,8 +22,9 @@ foreach ($list as $color) {
</div>
<?php if ($isLoggedIn || $isLowerLoggedIn): ?>
- <a onclick="toggleGlobalSearch();" id="login-link" class="login-link-clickable">
- <?= $lang["navigation"]["search"] ?>
+ <a href="/-/logout" id="login-link" class="login-link-clickable">
+ <img alt="" src="/assets/icons/logout.svg" style="filter:invert(1);width:24px;vertical-align: middle;">
+ <span style="vertical-align: middle;"><?= $pages["logout"]["name"]["en"] ?></span>
</a>
<?php else: ?>
<a href="/-/login" id="login-link" class="login-link-clickable">
diff --git a/includes/components/search.inc b/includes/components/search.inc
deleted file mode 100644
index 278ec41..0000000
--- a/includes/components/search.inc
+++ /dev/null
@@ -1,534 +0,0 @@
-<?php global $lang; global $pages; global $isLowerLoggedIn; global $isLoggedIn; ?>
-
-<div id="global-search-container" style="display: none; position: fixed; z-index: 99999999;background-color: rgba(0, 0, 0, .75); inset: 0; backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); align-items: center; justify-content: center;">
- <div id="global-search-box" style="background: rgba(50, 50, 50, .5); color: white; backdrop-filter: blur(30px); -webkit-backdrop-filter: blur(30px); border-radius: 15px; width: 768px; max-width: 90vw;">
- <div id="global-search-input-container" onclick="document.getElementById('global-search-input').focus();" style="cursor:text;padding: 10px 50px; font-size: 22px; border: 1px solid rgba(100, 100, 100, .5); border-top-left-radius: 15px; border-top-right-radius: 15px; width: 768px; max-width: 90vw;">
- <span onchange="globalSearchPlaceholder(); globalSearch();" onkeyup="globalSearchPlaceholder(); globalSearch();" onkeydown="globalSearchPlaceholder(); globalSearch();" type="text" id="global-search-input" style="vertical-align: middle; display:inline-block; background: transparent; color: white; border: none;" spellcheck="false" contenteditable="true"></span><span id="global-search-placeholder" style="opacity:.5;"><?= $lang["search"]["title"] ?></span><span id="global-search-autocomplete" style="vertical-align: middle; opacity:.5;"></span><span id="global-search-action" style="vertical-align: middle; opacity:.5;font-size:16px;"></span>
- </div>
- <div id="global-search-results" style="border-bottom-left-radius: 15px; border-bottom-right-radius: 15px; border-top-style: none; border-right: 1px solid rgba(100, 100, 100, .5); border-bottom: 1px solid rgba(100, 100, 100, .5); border-left: 1px solid rgba(100, 100, 100, .5); width: 768px; max-width: 90vw; height: 400px; max-height: calc(90vh - 60px);">
- <div id="global-search-intro" style="display: flex; align-items: center; justify-content: center; height: 100%; text-align: center;">
- <div style="opacity: .5;">
- <img src="/assets/logo/newlogo<?= $isLoggedIn || $isLowerLoggedIn ? "3" : "" ?>.png" style="width: 64px; height: 64px; margin-bottom: 10px;">
- <p><?= $lang["search"]["placeholder"] ?></p>
- </div>
- </div>
- <div id="global-search-list" style="overflow: auto; display: flex; align-items: center; justify-content: center; height: 100%; text-align: center;"></div>
- </div>
- </div>
-</div>
-
-<script src="/assets/editor/fuse.js"></script>
-
-<!--suppress JSUnresolvedFunction -->
-<script>
- const pages_list = JSON.parse(atob(`<?php
-
- $base = array_values(array_filter(scandir($_SERVER['DOCUMENT_ROOT'] . "/pages"), function ($i) {
- return !str_starts_with($i, ".") && $i !== "page.inc" && $i !== "api.inc" && $i !== "demo.inc" && $i !== "logout.inc" && $i !== "edit.inc" && $i !== "metadata.inc" && $i !== "edit-private.inc" && $i !== "app.inc" && !str_ends_with($i, ".bak.php") && !str_ends_with($i, ".old.php") && !str_ends_with($i, "-dev.php") && !str_ends_with($i, ".bak.inc") && !str_ends_with($i, ".old.inc") && !str_ends_with($i, "-dev.inc");
- }));
- $list = array_values(array_filter(array_map(function ($i) use ($lang) {
- global $pages;
- global $isLoggedIn;
- global $isLowerLoggedIn;
-
- if (in_array(substr($i, 0, strlen($i) - 4), array_keys($pages)) && $pages[substr($i, 0, strlen($i) - 4)]["admin"] && !((!$pages[substr($i, 0, strlen($i) - 4)]["limited"] && $isLoggedIn) || ($pages[substr($i, 0, strlen($i) - 4)]["limited"] && $isLowerLoggedIn))) {
- return null;
- } else if (in_array(substr($i, 0, strlen($i) - 4), array_keys($pages))) {
- return [
- 'name' => in_array(substr($i, 0, strlen($i) - 4), array_keys($pages)) ? $pages[substr($i, 0, strlen($i) - 4)]["name"][$lang["_name"]] : substr($i, 0, strlen($i) - 4),
- 'description' => in_array(substr($i, 0, strlen($i) - 4), array_keys($pages)) ? ($pages[substr($i, 0, strlen($i) - 4)]["short"] ?? $pages[substr($i, 0, strlen($i) - 4)]["name"][$lang["_name"]]) : substr($i, 0, strlen($i) - 4),
- 'url' => "/-/" . substr($i, 0, strlen($i) - 4),
- 'icon' => file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/icons/uncolored/" . substr($i, 0, strlen($i) - 4) . ".svg") ? ("/assets/icons/uncolored/" . (file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/icons/uncolored/" . substr($i, 0, strlen($i) - 4) . ".svg") ? substr($i, 0, strlen($i) - 4) : "") . ".svg") : ("/assets/icons/" . (file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/icons/" . substr($i, 0, strlen($i) - 4) . ".svg") ? substr($i, 0, strlen($i) - 4) : "") . ".svg"),
- 'invert' => true
- ];
- } else {
- return null;
- }
- }, $base), function ($i) {
- return isset($i);
- }));
-
- echo(base64_encode(json_encode($list)));
-
- ?>`));
- const pages = new Fuse(pages_list, {
- includeScore: true,
- keys: [
- {
- name: 'name',
- weight: 0.7
- },
- {
- name: 'description',
- weight: 0.5
- },
- {
- name: 'url',
- weight: 0.2
- }
- ]
- })
-
- const ponies_list = JSON.parse(atob(`<?php
-
- $base = array_values(scoreOrderGlobal());
- $list = array_map(function ($i) {
- return [
- 'name' => $i["display_name"] ?? $i["name"],
- 'description' => file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/content/$i[id].html") ? strip_tags(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/content/$i[id].html")) : "",
- 'url' => '/' . $i["name"],
- 'icon' => getAsset($i["system"], $i["id"], "heads"),
- 'invert' => false
- ];
- }, $base);
-
- echo(base64_encode(json_encode($list)));
-
- ?>`));
- const ponies = new Fuse(ponies_list, {
- includeScore: true,
- keys: [
- {
- name: 'name',
- weight: 1
- },
- {
- name: 'description',
- weight: 0.7
- },
- {
- name: 'url',
- weight: 0.5
- }
- ]
- })
-
- const toys_list = JSON.parse(atob(`<?php
-
- global $isLoggedIn;
-
- if (false && $isLoggedIn) {
- $base = array_values(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/toys/toys.json"), true));
- $list = array_map(function ($i) {
- return [
- 'name' => $i["name"],
- 'description' => $i["description"],
- 'icon' => "/assets/icons/toys.svg",
- 'url' => "/-/toys/$i[id]",
- 'invert' => true
- ];
- }, $base);
-
- echo(base64_encode(json_encode($list)));
- } else {
- echo(base64_encode(json_encode([])));
- }
-
- ?>`));
- const toys = new Fuse(toys_list, {
- includeScore: true,
- keys: [
- {
- name: 'name',
- weight: 1
- },
- {
- name: 'description',
- weight: 0.7
- },
- {
- name: 'url',
- weight: 0.5
- }
- ]
- })
-
- const actions_list = JSON.parse(atob(`<?php
-
- global $isLoggedIn;
-
- if (false && $isLoggedIn) {
- $base = array_values(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/actions/actions.json"), true));
- $list = array_map(function ($i) {
- return [
- 'name' => $i["name"],
- 'description' => $i["description"],
- 'icon' => "/assets/icons/actions.svg",
- 'url' => "/-/actions/$i[id]",
- 'invert' => true
- ];
- }, $base);
-
- echo(base64_encode(json_encode($list)));
- } else {
- echo(base64_encode(json_encode([])));
- }
-
- ?>`));
- const actions = new Fuse(actions_list, {
- includeScore: true,
- keys: [
- {
- name: 'name',
- weight: 1
- },
- {
- name: 'description',
- weight: 0.7
- },
- {
- name: 'url',
- weight: 0.5
- }
- ]
- })
-
- const documents_list = JSON.parse(atob(`<?php
-
- global $isLoggedIn;
-
- if ($isLoggedIn) {
- $base = array_values(array_map(function ($i) {
- return [
- "id" => substr($i, 0, -5),
- ...(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/docs/" . $i), true) ?? [])
- ];
- }, array_filter(scandir($_SERVER['DOCUMENT_ROOT'] . "/includes/data/docs"), function ($i) {
- return !str_starts_with($i, ".") && str_ends_with($i, ".json");
- })));
- $list = array_map(function ($i) {
- return [
- 'name' => $i["name"],
- 'content' => strip_tags($i["contents"]),
- 'category' => $i["category"],
- 'icon' => "/assets/icons/docs.svg",
- 'url' => "/-/docs/$i[id]",
- 'invert' => true
- ];
- }, $base);
-
- echo(base64_encode(json_encode($list)));
- } else {
- echo(base64_encode(json_encode([])));
- }
-
- ?>`));
- const documents = new Fuse(documents_list, {
- includeScore: true,
- keys: [
- {
- name: 'name',
- weight: 1
- },
- {
- name: 'category',
- weight: 0.7
- },
- {
- name: 'content',
- weight: 0.4
- },
- {
- name: 'url',
- weight: 0.5
- }
- ]
- })
-
- window.shiftPresses = 0;
- window.searchBoxOpen = false;
- window.lastKnownSearchQuery = null;
-
- function toggleGlobalSearch() {
- window.shiftPresses = 0;
- console.log("Search!");
-
- if (document.getElementById("global-search-container").style.display === "none") {
- document.getElementById("global-search-container").style.display = "flex";
- document.getElementById("global-search-list").style.display = "none";
- document.getElementById("global-search-intro").style.display = "flex";
- document.getElementById("global-search-input").focus();
- document.getElementById("global-search-input").value = "";
- window.searchBoxOpen = true;
- } else if (document.getElementById("global-search-container").style.display === "flex") {
- document.getElementById("global-search-container").style.display = "none";
- window.searchBoxOpen = false;
- }
- }
-
- function globalSearchPlaceholder() {
- if (document.getElementById("global-search-input").innerText === "") {
- document.getElementById("global-search-placeholder").style.display = "";
- document.getElementById("global-search-autocomplete").innerText = "";
- document.getElementById("global-search-action").innerText = "";
- } else {
- document.getElementById("global-search-placeholder").style.display = "none";
- }
- }
-
- function globalSearchAutocomplete(firstResult) {
- let querySearch = document.getElementById("global-search-input").innerText.toLowerCase();
- let firstResultSearch = firstResult.name.toLowerCase();
- let isAutocomplete = true;
-
- if (firstResultSearch.length > querySearch.length) {
- let count = 0;
-
- for (let i = 0; i < querySearch.length; i++) {
- if (querySearch.substring(i, i + 1) === firstResultSearch.substring(i, i + 1)) {
- count++;
- } else {
- isAutocomplete = false;
- }
- }
-
- if (isAutocomplete) {
- document.getElementById("global-search-autocomplete").innerText = firstResult.name.substring(count);
- document.getElementById("global-search-action").innerText = " — <?= $lang["search"]["view"] ?>";
- } else {
- document.getElementById("global-search-autocomplete").innerText = "";
- document.getElementById("global-search-action").innerText = " — " + firstResult.name;
- }
- } else {
- document.getElementById("global-search-autocomplete").innerText = "";
- document.getElementById("global-search-action").innerText = "";
- }
- }
-
- function globalSearch() {
- let query = document.getElementById("global-search-input").innerText.trim();
-
- if (query !== lastKnownSearchQuery) {
- window.lastKnownSearchQuery = query;
- } else {
- return;
- }
-
- let results, scores;
- let categories = [];
- console.log("Query:", query);
-
- if (query.trim() === "") {
- document.getElementById("global-search-list").style.display = "none";
- document.getElementById("global-search-intro").style.display = "flex";
- return;
- } else {
- document.getElementById("global-search-list").style.display = "block";
- document.getElementById("global-search-intro").style.display = "none";
- }
-
- results = ponies.search(query).map((i) => {
- i.item.score = i.score;
- return i.item;
- });
- scores = results.map((i) => { return i.score; });
- let results_ponies = {
- category: {
- name: "<?= $lang["search"]["categories"][0] ?>",
- id: "ponies"
- },
- average: scores.length > 0 ? scores.reduce((a, b) => { return a + b; }) / results.length : 1,
- results
- };
-
- results = pages.search(query).map((i) => {
- i.item.score = i.score;
- return i.item;
- });
- scores = results.map((i) => { return i.score; });
- let results_pages = {
- category: {
- name: "<?= $lang["search"]["categories"][1] ?>",
- id: "pages"
- },
- average: scores.length > 0 ? scores.reduce((a, b) => { return a + b; }) / results.length : 1,
- results
- };
-
- results = actions.search(query).map((i) => {
- i.item.score = i.score;
- return i.item;
- });
- scores = results.map((i) => { return i.score; });
- let results_actions = {
- category: {
- name: "<?= $lang["search"]["categories"][2] ?>",
- id: "actions"
- },
- average: scores.length > 0 ? scores.reduce((a, b) => { return a + b; }) / results.length : 1,
- results
- };
-
- results = documents.search(query).map((i) => {
- i.item.score = i.score;
- return i.item;
- });
- scores = results.map((i) => { return i.score; });
- let results_documents = {
- category: {
- name: "<?= $lang["search"]["categories"][3] ?>",
- id: "docs"
- },
- average: scores.length > 0 ? scores.reduce((a, b) => { return a + b; }) / results.length : 1,
- results
- };
-
- results = toys.search(query).map((i) => {
- i.item.score = i.score;
- return i.item;
- });
- scores = results.map((i) => { return i.score; });
- let results_toys = {
- category: {
- name: "<?= $lang["search"]["categories"][4] ?>",
- id: "toys"
- },
- average: scores.length > 0 ? scores.reduce((a, b) => { return a + b; }) / results.length : 1,
- results
- };
-
- window.allSearchResults = [];
- categories.push(results_ponies, results_pages, results_documents, results_actions, results_toys);
- categories = categories.sort((a, b) => {
- return a.average - b.average;
- }).filter((i) => i.results.length > 0);
-
- console.log(categories);
-
- index = -1;
- document.getElementById("global-search-list").innerHTML = categories.map((i) => `
- <div class="global-search-category">
- <div class="global-search-category-header">${i.category.name}</div>
- <div class="global-search-category-items">
- ${i.results.map((j) => { index++; allSearchResults.push(j); return `
- <a href="${j.url}" class="global-search-item ${index === 0 ? 'selected' : ''}" id="global-search-item-${index}">
- <img src="${j.icon}" class="global-search-item-icon" ${j['invert'] ? 'style="filter:invert(1) brightness(5);"' : ""}>
- <span class="global-search-item-title">${j.name}</span>
- </a>
- ` }).join("")}
- </div>
- </div>
- `).join("");
-
- globalSearchAutocomplete(allSearchResults[0]);
-
- console.log("=================================");
- }
-
- document.onkeydown = (event) => {
- if (window.searchBoxOpen) {
- console.log(event);
-
- if (event.key === "Escape") {
- if (document.getElementById("global-search-input").innerText.trim() === "") {
- toggleGlobalSearch();
- } else {
- document.getElementById("global-search-input").innerText = "";
- globalSearch();
- }
- }
-
- if (event.key === "ArrowDown") {
- let id = parseInt(document.querySelector(".global-search-item.selected").id.split("-")[3]);
-
- if (document.getElementById("global-search-item-" + (id + 1)) !== null) {
- document.getElementById("global-search-item-" + id).classList.remove("selected");
- document.getElementById("global-search-item-" + (id + 1)).classList.add("selected");
- document.getElementById("global-search-item-" + (id + 1)).scrollIntoView({
- block: "center",
- inline: "center"
- });
-
- globalSearchAutocomplete(allSearchResults[id + 1]);
- }
-
- event.preventDefault();
- }
-
- if (event.key === "ArrowUp") {
- let id = parseInt(document.querySelector(".global-search-item.selected").id.split("-")[3]);
-
- if (document.getElementById("global-search-item-" + (id - 1)) !== null) {
- document.getElementById("global-search-item-" + id).classList.remove("selected");
- document.getElementById("global-search-item-" + (id - 1)).classList.add("selected");
- document.getElementById("global-search-item-" + (id - 1)).scrollIntoView({
- block: "center",
- inline: "center"
- });
-
- globalSearchAutocomplete(allSearchResults[id - 1]);
- }
-
- event.preventDefault();
- }
-
- if (event.key === "Enter") {
- document.querySelector(".global-search-item.selected").click();
-
- event.preventDefault();
- }
- }
-
- if (event.key === "Shift") {
- window.shiftPresses++;
- }
-
- if ((event.code === "Space" && event.ctrlKey) || window.shiftPresses === 2 && document.getElementsByClassName("ck-focused").length === 0) {
- toggleGlobalSearch();
- }
- }
-
- document.getElementById("global-search-container").onclick = (event) => {
- if (event.target === document.getElementById("global-search-container")) toggleGlobalSearch();
- }
-
- setInterval(() => {
- window.shiftPresses = 0;
- }, 500);
-</script>
-
-<style>
- .global-search-category {
- text-align: left;
- padding: 10px 20px;
- }
-
- .global-search-category-header {
- font-size: 12px;
- opacity: .5;
- margin-bottom: 5px;
- margin-top: 10px;
- }
-
- .global-search-item-icon {
- vertical-align: middle;
- width: 24px;
- height: 24px;
- margin-right: 5px;
- }
-
- .global-search-item-title {
- vertical-align: middle;
- }
-
- .global-search-item {
- text-decoration: none;
- display: block;
- color: white !important;
- border-radius: 10px;
- padding: 3px 5px;
- height: 32px;
- }
-
- .global-search-item:hover, .global-search-item:focus, .global-search-item.selected {
- background-color: rgba(255, 255, 255, .1);
- }
-
- .global-search-item:active {
- background-color: rgba(255, 255, 255, .2);
- }
-
- #global-search-container * {
- outline: none !important;
- }
-</style> \ No newline at end of file
diff --git a/includes/components/wakeup.inc b/includes/components/wakeup.inc
index 818378a..cc54821 100644
--- a/includes/components/wakeup.inc
+++ b/includes/components/wakeup.inc
@@ -104,7 +104,7 @@
if (window.alertIntervalCounter === 0) {
sendNotification();
} else if (window.alertIntervalCounter > -1) {
- document.getElementById("next-notification").innerText = window.alertIntervalCounter + " second" + (window.alertIntervalCounter > 1 ? "s" : "");
+ document.getElementById("next-notification").innerText = "in " + window.alertIntervalCounter + " second" + (window.alertIntervalCounter > 1 ? "s" : "");
}
}, 1000);
}
diff --git a/includes/external/chvfs/index.js b/includes/external/chvfs/index.js
new file mode 100644
index 0000000..0dfd958
--- /dev/null
+++ b/includes/external/chvfs/index.js
@@ -0,0 +1,104 @@
+let active = true;
+let queue = [];
+
+const watch = require('node-watch');
+const child_process = require('child_process');
+const fs = require('fs').promises;
+const fss = require('fs');
+
+process.on('uncaughtException', (e) => {
+ console.error(e);
+});
+
+function start() {
+ console.log("Mounting chvfs...");
+ child_process.execSync("mount -t tmpfs -o size=1G chvfs /_ch");
+ console.log("Mounted chvfs to /_ch");
+
+ console.log("Preparing filesystem...");
+ child_process.execSync("cp -r /opt/peh_save/* /_ch");
+ child_process.execSync("chmod -Rf 777 /_ch");
+ console.log("Filesystem is ready");
+
+ console.log("Watching for changes...");
+ watch("/_ch", { recursive: true }, (event, filename) => {
+ try {
+ if (!active || !filename) return;
+
+ if (event === "update") {
+ console.log(filename + " was created or affected");
+
+ queue.push({
+ file: filename,
+ remove: false
+ });
+ } else if (event === "remove") {
+ console.log(filename + " was dropped");
+
+ queue.push({
+ file: filename,
+ remove: true
+ });
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ })
+}
+
+function stop() {
+ active = false;
+
+ console.log("Unmounting chvfs...");
+ child_process.execSync("umount -l /_ch");
+ console.log("Unmounted chvfs");
+}
+
+start();
+setInterval(async () => {
+ for (let item of queue) {
+ try {
+ if (item.remove) {
+ console.log("Dropping " + item.file);
+ await fs.unlink("/opt/peh_save/" + item.file.substring(5));
+ } else {
+ console.log("Copying " + item.file);
+ await fs.copyFile(item.file, "/opt/peh_save/" + item.file.substring(5));
+ }
+ } catch (e) {
+ console.error(e);
+ }
+
+ queue.splice(0, 1);
+ }
+});
+
+process.on('exit', () => {
+ process.stdout.write("\n");
+
+ for (let item of queue) {
+ try {
+ if (item.remove) {
+ console.log("Dropping " + item.file);
+ fss.unlinkSync("/opt/peh_save/" + item.file.substring(5));
+ } else {
+ console.log("Copying " + item.file);
+ fss.copyFileSync(item.file, "/opt/peh_save/" + item.file.substring(5));
+ }
+ } catch (e) {
+ console.error(e);
+ }
+
+ queue.splice(0, 1);
+ }
+
+ stop();
+});
+
+process.on('SIGINT', () => {
+ process.exit();
+});
+
+process.on('SIGTERM', () => {
+ process.exit();
+}); \ No newline at end of file
diff --git a/includes/external/chvfs/node_modules/.package-lock.json b/includes/external/chvfs/node_modules/.package-lock.json
new file mode 100644
index 0000000..6f51c8f
--- /dev/null
+++ b/includes/external/chvfs/node_modules/.package-lock.json
@@ -0,0 +1,15 @@
+{
+ "name": "chvfs",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "node_modules/node-watch": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/node-watch/-/node-watch-0.7.3.tgz",
+ "integrity": "sha512-3l4E8uMPY1HdMMryPRUAl+oIHtXtyiTlIiESNSVSNxcPfzAFzeTbXFQkZfAwBbo0B1qMSG8nUABx+Gd+YrbKrQ==",
+ "engines": {
+ "node": ">=6"
+ }
+ }
+ }
+}
diff --git a/includes/external/chvfs/node_modules/node-watch/LICENSE b/includes/external/chvfs/node_modules/node-watch/LICENSE
new file mode 100644
index 0000000..3718eaa
--- /dev/null
+++ b/includes/external/chvfs/node_modules/node-watch/LICENSE
@@ -0,0 +1,22 @@
+(The MIT License)
+
+Copyright (c) 2012-2021 Yuan Chuan <yuanchuan23@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/includes/external/chvfs/node_modules/node-watch/README.md b/includes/external/chvfs/node_modules/node-watch/README.md
new file mode 100644
index 0000000..c0f8bd2
--- /dev/null
+++ b/includes/external/chvfs/node_modules/node-watch/README.md
@@ -0,0 +1,233 @@
+# node-watch [![Status](https://github.com/yuanchuan/node-watch/actions/workflows/ci.yml/badge.svg)](https://github.com/yuanchuan/node-watch/actions/workflows/ci.yml/badge.svg)
+
+A wrapper and enhancements for [fs.watch](http://nodejs.org/api/fs.html#fs_fs_watch_filename_options_listener).
+
+[![NPM](https://nodei.co/npm/node-watch.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/node-watch.png/)
+
+
+## Installation
+
+```bash
+npm install node-watch
+```
+
+## Example
+
+```js
+var watch = require('node-watch');
+
+watch('file_or_dir', { recursive: true }, function(evt, name) {
+ console.log('%s changed.', name);
+});
+```
+
+Now it's fast to watch **deep** directories on macOS and Windows, since the `recursive` option is natively supported except on Linux.
+
+```js
+// watch the whole disk
+watch('/', { recursive: true }, console.log);
+```
+
+
+## Why?
+
+* Some editors will generate temporary files which will cause the callback function to be triggered multiple times.
+* The callback function will only be triggered once on watching a single file.
+* <del>Missing an option to watch a directory recursively.</del>
+* Recursive watch is not supported on Linux or in older versions of nodejs.
+* Keep it simple, stupid.
+
+
+## Options
+
+The usage and options of `node-watch` are compatible with [fs.watch](https://nodejs.org/dist/latest-v7.x/docs/api/fs.html#fs_fs_watch_filename_options_listener).
+* `persistent: Boolean` (default **true**)
+* `recursive: Boolean` (default **false**)
+* `encoding: String` (default **'utf8'**)
+
+**Extra options**
+
+* `filter: RegExp | Function`
+
+ Return that matches the filter expression.
+
+ ```js
+ // filter with regular expression
+ watch('./', { filter: /\.json$/ });
+
+ // filter with custom function
+ watch('./', { filter: f => !/node_modules/.test(f) });
+
+ ```
+
+ Each file and directory will be passed to the filter to determine whether
+ it will then be passed to the callback function. Like `Array.filter` does in `JavaScript`.
+ There are three kinds of return values for filter function:
+
+ * **`true`**: Will be passed to callback.
+ * **`false`**: Will not be passed to callback.
+ * **`skip`**: Same with `false`, and skip to watch all its subdirectories.
+
+ On Linux, where the `recursive` option is not natively supported,
+ it is more efficient to skip ignored directories by returning the `skip` flag:
+
+ ```js
+ watch('./', {
+ recursive: true,
+ filter(f, skip) {
+ // skip node_modules
+ if (/\/node_modules/.test(f)) return skip;
+ // skip .git folder
+ if (/\.git/.test(f)) return skip;
+ // only watch for js files
+ return /\.js$/.test(f);
+ }
+ });
+
+ ```
+
+ If you prefer glob patterns you can use [minimatch](https://www.npmjs.com/package/minimatch) or [picomatch](https://www.npmjs.com/package/picomatch)
+ together with filter:
+
+ ```js
+ const pm = require('picomatch');
+ let isMatch = pm('*.js');
+
+ watch('./', {
+ filter: f => isMatch(f)
+ });
+ ```
+
+* `delay: Number` (in ms, default **200**)
+
+ Delay time of the callback function.
+
+ ```js
+ // log after 5 seconds
+ watch('./', { delay: 5000 }, console.log);
+ ```
+
+## Events
+
+The events provided by the callback function is either `update` or `remove`, which is less confusing to `fs.watch`'s `rename` or `change`.
+
+```js
+watch('./', function(evt, name) {
+
+ if (evt == 'update') {
+ // on create or modify
+ }
+
+ if (evt == 'remove') {
+ // on delete
+ }
+
+});
+```
+
+
+## Watcher object
+
+The watch function returns a [fs.FSWatcher](https://nodejs.org/api/fs.html#fs_class_fs_fswatcher) like object as the same as `fs.watch` (>= v0.4.0).
+
+#### Watcher events
+
+```js
+let watcher = watch('./', { recursive: true });
+
+watcher.on('change', function(evt, name) {
+ // callback
+});
+
+watcher.on('error', function(err) {
+ // handle error
+});
+
+watcher.on('ready', function() {
+ // the watcher is ready to respond to changes
+});
+```
+
+#### Close
+
+```js
+// close
+watcher.close();
+
+// is closed?
+watcher.isClosed()
+```
+
+#### List of methods
+
+* `.on`
+* `.once`
+* `.emit`
+* `.close`
+* `.listeners`
+* `.setMaxListeners`
+* `.getMaxListeners`
+
+##### Extra methods
+* `.isClosed` detect if the watcher is closed
+* `.getWatchedPaths` get all the watched paths
+
+
+## Known issues
+
+**Windows, node < v4.2.5**
+
+ * Failed to detect `remove` event
+ * Failed to get deleted filename or directory name
+
+**MacOS, node 0.10.x**
+ * Will emit double event if the directory name is of one single character.
+
+
+## Misc
+
+#### 1. Watch multiple files or directories in one place
+```js
+watch(['file1', 'file2'], console.log);
+```
+
+#### 2. Customize watch command line tool
+```js
+#!/usr/bin/env node
+
+// https://github.com/nodejs/node-v0.x-archive/issues/3211
+require('epipebomb')();
+
+let watcher = require('node-watch')(
+ process.argv[2] || './', { recursive: true }, console.log
+);
+
+process.on('SIGINT', watcher.close);
+```
+Monitoring chrome from disk:
+```bash
+$ watch / | grep -i chrome
+```
+
+#### 3. Got ENOSPC error?
+
+If you get ENOSPC error, but you actually have free disk space - it means that your OS watcher limit is too low and you probably want to recursively watch a big tree of files.
+
+Follow this description to increase the limit:
+[https://confluence.jetbrains.com/display/IDEADEV/Inotify+Watches+Limit](https://confluence.jetbrains.com/display/IDEADEV/Inotify+Watches+Limit)
+
+
+## Alternatives
+
+* [chokidar](https://github.com/paulmillr/chokidar)
+* [gaze](https://github.com/shama/gaze)
+* [mikeal/watch](https://github.com/mikeal/watch)
+
+## Contributors
+
+Thanks goes to [all wonderful people](https://github.com/yuanchuan/node-watch/graphs/contributors) who have helped this project.
+
+## License
+MIT
+
+Copyright (c) 2012-2021 [yuanchuan](https://github.com/yuanchuan)
diff --git a/includes/external/chvfs/node_modules/node-watch/lib/has-native-recursive.js b/includes/external/chvfs/node_modules/node-watch/lib/has-native-recursive.js
new file mode 100644
index 0000000..19c1b88
--- /dev/null
+++ b/includes/external/chvfs/node_modules/node-watch/lib/has-native-recursive.js
@@ -0,0 +1,115 @@
+var fs = require('fs');
+var os = require('os');
+var path = require('path');
+var is = require('./is');
+
+var IS_SUPPORT;
+var TEMP_DIR = os.tmpdir && os.tmpdir()
+ || process.env.TMPDIR
+ || process.env.TEMP
+ || process.cwd();
+
+function TempStack() {
+ this.stack = [];
+}
+
+TempStack.prototype = {
+ create: function(type, base) {
+ var name = path.join(base,
+ 'node-watch-' + Math.random().toString(16).substr(2)
+ );
+ this.stack.push({ name: name, type: type });
+ return name;
+ },
+ write: function(/* file */) {
+ for (var i = 0; i < arguments.length; ++i) {
+ fs.writeFileSync(arguments[i], ' ');
+ }
+ },
+ mkdir: function(/* dirs */) {
+ for (var i = 0; i < arguments.length; ++i) {
+ fs.mkdirSync(arguments[i]);
+ }
+ },
+ cleanup: function(fn) {
+ try {
+ var temp;
+ while ((temp = this.stack.pop())) {
+ var type = temp.type;
+ var name = temp.name;
+ if (type === 'file' && is.file(name)) {
+ fs.unlinkSync(name);
+ }
+ else if (type === 'dir' && is.directory(name)) {
+ fs.rmdirSync(name);
+ }
+ }
+ }
+ finally {
+ if (is.func(fn)) fn();
+ }
+ }
+};
+
+var pending = false;
+
+module.exports = function hasNativeRecursive(fn) {
+ if (!is.func(fn)) {
+ return false;
+ }
+ if (IS_SUPPORT !== undefined) {
+ return fn(IS_SUPPORT);
+ }
+
+ if (!pending) {
+ pending = true;
+ }
+ // check again later
+ else {
+ return setTimeout(function() {
+ hasNativeRecursive(fn);
+ }, 300);
+ }
+
+ var stack = new TempStack();
+ var parent = stack.create('dir', TEMP_DIR);
+ var child = stack.create('dir', parent);
+ var file = stack.create('file', child);
+
+ stack.mkdir(parent, child);
+
+ var options = { recursive: true };
+ var watcher;
+
+ try {
+ watcher = fs.watch(parent, options);
+ } catch (e) {
+ if (e.code == 'ERR_FEATURE_UNAVAILABLE_ON_PLATFORM') {
+ return fn(IS_SUPPORT = false);
+ } else {
+ throw e;
+ }
+ }
+
+ if (!watcher) {
+ return false;
+ }
+
+ var timer = setTimeout(function() {
+ watcher.close();
+ stack.cleanup(function() {
+ fn(IS_SUPPORT = false);
+ });
+ }, 200);
+
+ watcher.on('change', function(evt, name) {
+ if (path.basename(file) === path.basename(name)) {
+ watcher.close();
+ clearTimeout(timer);
+ stack.cleanup(function() {
+ fn(IS_SUPPORT = true);
+ });
+ }
+ });
+ stack.write(file);
+}
diff --git a/includes/external/chvfs/node_modules/node-watch/lib/is.js b/includes/external/chvfs/node_modules/node-watch/lib/is.js
new file mode 100644
index 0000000..ebe0600
--- /dev/null
+++ b/includes/external/chvfs/node_modules/node-watch/lib/is.js
@@ -0,0 +1,78 @@
+var fs = require('fs');
+var path = require('path');
+var os = require('os');
+
+function matchObject(item, str) {
+ return Object.prototype.toString.call(item)
+ === '[object ' + str + ']';
+}
+
+function checkStat(name, fn) {
+ try {
+ return fn(name);
+ } catch (err) {
+ if (/^(ENOENT|EPERM|EACCES)$/.test(err.code)) {
+ if (err.code !== 'ENOENT') {
+ console.warn('Warning: Cannot access %s', name);
+ }
+ return false;
+ }
+ throw err;
+ }
+}
+
+var is = {
+ nil: function(item) {
+ return item == null;
+ },
+ array: function(item) {
+ return Array.isArray(item);
+ },
+ emptyObject: function(item) {
+ for (var key in item) {
+ return false;
+ }
+ return true;
+ },
+ buffer: function(item) {
+ return Buffer.isBuffer(item);
+ },
+ regExp: function(item) {
+ return matchObject(item, 'RegExp');
+ },
+ string: function(item) {
+ return matchObject(item, 'String');
+ },
+ func: function(item) {
+ return typeof item === 'function';
+ },
+ number: function(item) {
+ return matchObject(item, 'Number');
+ },
+ exists: function(name) {
+ return fs.existsSync(name);
+ },
+ file: function(name) {
+ return checkStat(name, function(n) {
+ return fs.statSync(n).isFile()
+ });
+ },
+ samePath: function(a, b) {
+ return path.resolve(a) === path.resolve(b);
+ },
+ directory: function(name) {
+ return checkStat(name, function(n) {
+ return fs.statSync(n).isDirectory()
+ });
+ },
+ symbolicLink: function(name) {
+ return checkStat(name, function(n) {
+ return fs.lstatSync(n).isSymbolicLink();
+ });
+ },
+ windows: function() {
+ return os.platform() === 'win32';
+ }
+};
+
+module.exports = is;
diff --git a/includes/external/chvfs/node_modules/node-watch/lib/watch.d.ts b/includes/external/chvfs/node_modules/node-watch/lib/watch.d.ts
new file mode 100644
index 0000000..9eca5d4
--- /dev/null
+++ b/includes/external/chvfs/node_modules/node-watch/lib/watch.d.ts
@@ -0,0 +1,75 @@
+import { FSWatcher } from 'fs';
+
+/**
+ * Watch for changes on `filename`, where filename is either a file or a directory.
+ * The second argument is optional.
+ *
+ * If `options` is provided as a string, it specifies the encoding.
+ * Otherwise `options` should be passed as an object.
+ *
+ * The listener callback gets two arguments, `(eventType, filePath)`,
+ * which is the same with `fs.watch`.
+ * `eventType` is either `update` or `remove`,
+ * `filePath` is the name of the file which triggered the event.
+ *
+ * @param {Filename} filename File or directory to watch.
+ * @param {Options|string} options
+ * @param {Function} callback
+ */
+declare function watch(pathName: PathName): Watcher;
+declare function watch(pathName: PathName, options: Options) : Watcher;
+declare function watch(pathName: PathName, callback: Callback): Watcher;
+declare function watch(pathName: PathName, options: Options, callback: Callback): Watcher;
+
+type EventType = 'update' | 'remove';
+type Callback = (eventType: EventType, filePath: string) => any;
+type PathName = string | Array<string>;
+type FilterReturn = boolean | symbol;
+
+type Options = {
+ /**
+ * Indicates whether the process should continue to run
+ * as long as files are being watched.
+ * @default true
+ */
+ persistent ?: boolean;
+
+ /**
+ * Indicates whether all subdirectories should be watched.
+ * @default false
+ */
+ recursive ?: boolean;
+
+ /**
+ * Specifies the character encoding to be used for the filename
+ * passed to the listener.
+ * @default 'utf8'
+ */
+ encoding ?: string;
+
+ /**
+ * Only files which pass this filter (when it returns `true`)
+ * will be sent to the listener.
+ */
+ filter ?: RegExp | ((file: string, skip: symbol) => FilterReturn);
+
+ /**
+ * Delay time of the callback function.
+ * @default 200
+ */
+ delay ?: number;
+};
+
+declare interface Watcher extends FSWatcher {
+ /**
+ * Returns `true` if the watcher has been closed.
+ */
+ isClosed(): boolean;
+
+ /**
+ * Returns all watched paths.
+ */
+ getWatchedPaths(): Array<string>;
+}
+
+export default watch;
diff --git a/includes/external/chvfs/node_modules/node-watch/lib/watch.js b/includes/external/chvfs/node_modules/node-watch/lib/watch.js
new file mode 100644
index 0000000..b3d6889
--- /dev/null
+++ b/includes/external/chvfs/node_modules/node-watch/lib/watch.js
@@ -0,0 +1,530 @@
+var fs = require('fs');
+var path = require('path');
+var util = require('util');
+var events = require('events');
+
+var hasNativeRecursive = require('./has-native-recursive');
+var is = require('./is');
+
+var EVENT_UPDATE = 'update';
+var EVENT_REMOVE = 'remove';
+
+var SKIP_FLAG = Symbol('skip');
+
+function hasDup(arr) {
+ return arr.some(function(v, i, self) {
+ return self.indexOf(v) !== i;
+ });
+}
+
+function unique(arr) {
+ return arr.filter(function(v, i, self) {
+ return self.indexOf(v) === i;
+ });
+}
+
+// One level flat
+function flat1(arr) {
+ return arr.reduce(function(acc, v) {
+ return acc.concat(v);
+ }, []);
+}
+
+function assertEncoding(encoding) {
+ if (encoding && encoding !== 'buffer' && !Buffer.isEncoding(encoding)) {
+ throw new Error('Unknown encoding: ' + encoding);
+ }
+}
+
+function guard(fn) {
+ if (is.func(fn)) {
+ return function(arg, action) {
+ if (fn(arg, false)) action();
+ }
+ }
+ if (is.regExp(fn)) {
+ return function(arg, action) {
+ if (fn.test(arg)) action();
+ }
+ }
+ return function(arg, action) {
+ action();
+ }
+}
+
+function composeMessage(names) {
+ return names.map(function(n) {
+ return is.exists(n)
+ ? [EVENT_UPDATE, n]
+ : [EVENT_REMOVE, n];
+ });
+}
+
+function getMessages(cache) {
+ var filtered = unique(cache);
+
+ // Saving file from an editor? If so, assuming the
+ // non-existed files in the cache are temporary files
+ // generated by an editor and thus be filtered.
+ var reg = /~$|^\.#|^##$/g;
+ var hasSpecialChar = cache.some(function(c) {
+ return reg.test(c);
+ });
+
+ if (hasSpecialChar) {
+ var dup = hasDup(cache.map(function(c) {
+ return c.replace(reg, '');
+ }));
+ if (dup) {
+ filtered = filtered.filter(function(m) {
+ return is.exists(m);
+ });
+ }
+ }
+
+ return composeMessage(filtered);
+}
+
+function debounce(info, fn) {
+ var timer, cache = [];
+ var encoding = info.options.encoding;
+ var delay = info.options.delay;
+ if (!is.number(delay)) {
+ delay = 200;
+ }
+ function handle() {
+ getMessages(cache).forEach(function(msg) {
+ msg[1] = Buffer.from(msg[1]);
+ if (encoding !== 'buffer') {
+ msg[1] = msg[1].toString(encoding);
+ }
+ fn.apply(null, msg);
+ });
+ timer = null;
+ cache = [];
+ }
+ return function(rawEvt, name) {
+ cache.push(name);
+ if (!timer) {
+ timer = setTimeout(handle, delay);
+ }
+ }
+}
+
+function createDupsFilter() {
+ var memo = {};
+ return function(fn) {
+ return function(evt, name) {
+ memo[evt + name] = [evt, name];
+ setTimeout(function() {
+ Object.keys(memo).forEach(function(n) {
+ fn.apply(null, memo[n]);
+ });
+ memo = {};
+ });
+ }
+ }
+}
+
+function getSubDirectories(dir, fn, done = function() {}) {
+ if (is.directory(dir)) {
+ fs.readdir(dir, function(err, all) {
+ if (err) {
+ // don't throw permission errors.
+ if (/^(EPERM|EACCES)$/.test(err.code)) {
+ console.warn('Warning: Cannot access %s.', dir);
+ } else {
+ throw err;
+ }
+ }
+ else {
+ all.forEach(function(f) {
+ var sdir = path.join(dir, f);
+ if (is.directory(sdir)) fn(sdir);
+ });
+ done();
+ }
+ });
+ } else {
+ done();
+ }
+}
+
+function semaphore(final) {
+ var counter = 0;
+ return function start() {
+ counter++;
+ return function stop() {
+ counter--;
+ if (counter === 0) final();
+ };
+ };
+}
+
+function nullCounter() {
+ return function nullStop() {};
+}
+
+function shouldNotSkip(filePath, filter) {
+ // watch it only if the filter is not function
+ // or not being skipped explicitly.
+ return !is.func(filter) || filter(filePath, SKIP_FLAG) !== SKIP_FLAG;
+}
+
+var deprecationWarning = util.deprecate(
+ function() {},
+ '(node-watch) First param in callback function\
+ is replaced with event name since 0.5.0, use\
+ `(evt, filename) => {}` if you want to get the filename'
+);
+
+function Watcher() {
+ events.EventEmitter.call(this);
+ this.watchers = {};
+ this._isReady = false;
+ this._isClosed = false;
+}
+
+util.inherits(Watcher, events.EventEmitter);
+
+Watcher.prototype.expose = function() {
+ var expose = {};
+ var self = this;
+ var methods = [
+ 'on', 'emit', 'once',
+ 'close', 'isClosed',
+ 'listeners', 'setMaxListeners', 'getMaxListeners',
+ 'getWatchedPaths'
+ ];
+ methods.forEach(function(name) {
+ expose[name] = function() {
+ return self[name].apply(self, arguments);
+ }
+ });
+ return expose;
+}
+
+Watcher.prototype.isClosed = function() {
+ return this._isClosed;
+}
+
+Watcher.prototype.close = function(fullPath) {
+ var self = this;
+ if (fullPath) {
+ var watcher = this.watchers[fullPath];
+ if (watcher && watcher.close) {
+ watcher.close();
+ delete self.watchers[fullPath];
+ }
+ getSubDirectories(fullPath, function(fpath) {
+ self.close(fpath);
+ });
+ }
+ else {
+ Object.keys(self.watchers).forEach(function(fpath) {
+ var watcher = self.watchers[fpath];
+ if (watcher && watcher.close) {
+ watcher.close();
+ }
+ });
+ this.watchers = {};
+ }
+ // Do not close the Watcher unless all child watchers are closed.
+ // https://github.com/yuanchuan/node-watch/issues/75
+ if (is.emptyObject(self.watchers)) {
+ // should emit once
+ if (!this._isClosed) {
+ this._isClosed = true;
+ process.nextTick(emitClose, this);
+ }
+ }
+}
+
+Watcher.prototype.getWatchedPaths = function(fn) {
+ if (is.func(fn)) {
+ var self = this;
+ if (self._isReady) {
+ fn(Object.keys(self.watchers));
+ } else {
+ self.on('ready', function() {
+ fn(Object.keys(self.watchers));
+ });
+ }
+ }
+}
+
+function emitReady(self) {
+ if (!self._isReady) {
+ self._isReady = true;
+ // do not call emit for 'ready' until after watch() has returned,
+ // so that consumer can call on().
+ process.nextTick(function () {
+ self.emit('ready');
+ });
+ }
+}
+
+function emitClose(self) {
+ self.emit('close');
+}
+
+Watcher.prototype.add = function(watcher, info) {
+ var self = this;
+ info = info || { fpath: '' };
+ var watcherPath = path.resolve(info.fpath);
+ this.watchers[watcherPath] = watcher;
+
+ // Internal callback for handling fs.FSWatcher 'change' events
+ var internalOnChange = function(rawEvt, rawName) {
+ if (self.isClosed()) {
+ return;
+ }
+
+ // normalise lack of name and convert to full path
+ var name = rawName;
+ if (is.nil(name)) {
+ name = '';
+ }
+ name = path.join(info.fpath, name);
+
+ if (info.options.recursive) {
+ hasNativeRecursive(function(has) {
+ if (!has) {
+ var fullPath = path.resolve(name);
+ // remove watcher on removal
+ if (!is.exists(name)) {
+ self.close(fullPath);
+ }
+ // watch new created directory
+ else {
+ var shouldWatch = is.directory(name)
+ && !self.watchers[fullPath]
+ && shouldNotSkip(name, info.options.filter);
+
+ if (shouldWatch) {
+ self.watchDirectory(name, info.options);
+ }
+ }
+ }
+ });
+ }
+
+ handlePublicEvents(rawEvt, name);
+ };
+
+ // Debounced based on the 'delay' option
+ var handlePublicEvents = debounce(info, function (evt, name) {
+ // watch single file
+ if (info.compareName) {
+ if (info.compareName(name)) {
+ self.emit('change', evt, name);
+ }
+ }
+ // watch directory
+ else {
+ var filterGuard = guard(info.options.filter);
+ filterGuard(name, function() {
+ if (self.flag) self.flag = '';
+ else self.emit('change', evt, name);
+ });
+ }
+ });
+
+ watcher.on('error', function(err) {
+ if (self.isClosed()) {
+ return;
+ }
+ if (is.windows() && err.code === 'EPERM') {
+ watcher.emit('change', EVENT_REMOVE, info.fpath && '');
+ self.flag = 'windows-error';
+ self.close(watcherPath);
+ } else {
+ self.emit('error', err);
+ }
+ });
+
+ watcher.on('change', internalOnChange);
+}
+
+Watcher.prototype.watchFile = function(file, options, fn) {
+ var parent = path.join(file, '../');
+ var opts = Object.assign({}, options, {
+ // no filter for single file
+ filter: null,
+ encoding: 'utf8'
+ });
+
+ // no need to watch recursively
+ delete opts.recursive;
+
+ var watcher = fs.watch(parent, opts);
+ this.add(watcher, {
+ type: 'file',
+ fpath: parent,
+ options: Object.assign({}, opts, {
+ encoding: options.encoding
+ }),
+ compareName: function(n) {
+ return is.samePath(n, file);
+ }
+ });
+
+ if (is.func(fn)) {
+ if (fn.length === 1) deprecationWarning();
+ this.on('change', fn);
+ }
+}
+
+Watcher.prototype.watchDirectory = function(dir, options, fn, counter = nullCounter) {
+ var self = this;
+ var done = counter();
+ hasNativeRecursive(function(has) {
+ // always specify recursive
+ options.recursive = !!options.recursive;
+ // using utf8 internally
+ var opts = Object.assign({}, options, {
+ encoding: 'utf8'
+ });
+ if (!has) {
+ delete opts.recursive;
+ }
+
+ // check if it's closed before calling watch.
+ if (self._isClosed) {
+ done();
+ return self.close();
+ }
+
+ var watcher = fs.watch(dir, opts);
+
+ self.add(watcher, {
+ type: 'dir',
+ fpath: dir,
+ options: options
+ });
+
+ if (is.func(fn)) {
+ if (fn.length === 1) deprecationWarning();
+ self.on('change', fn);
+ }
+
+ if (options.recursive && !has) {
+ getSubDirectories(dir, function(d) {
+ if (shouldNotSkip(d, options.filter)) {
+ self.watchDirectory(d, options, null, counter);
+ }
+ }, counter());
+ }
+
+ done();
+ });
+}
+
+function composeWatcher(watchers) {
+ var watcher = new Watcher();
+ var filterDups = createDupsFilter();
+ var counter = watchers.length;
+
+ watchers.forEach(function(w) {
+ w.on('change', filterDups(function(evt, name) {
+ watcher.emit('change', evt, name);
+ }));
+ w.on('error', function(err) {
+ watcher.emit('error', err);
+ });
+ w.on('ready', function() {
+ if (!(--counter)) {
+ emitReady(watcher);
+ }
+ });
+ });
+
+ watcher.close = function() {
+ watchers.forEach(function(w) {
+ w.close();
+ });
+ process.nextTick(emitClose, watcher);
+ }
+
+ watcher.getWatchedPaths = function(fn) {
+ if (is.func(fn)) {
+ var promises = watchers.map(function(w) {
+ return new Promise(function(resolve) {
+ w.getWatchedPaths(resolve);
+ });
+ });
+ Promise.all(promises).then(function(result) {
+ var ret = unique(flat1(result));
+ fn(ret);
+ });
+ }
+ }
+
+ return watcher.expose();
+}
+
+function watch(fpath, options, fn) {
+ var watcher = new Watcher();
+
+ if (is.buffer(fpath)) {
+ fpath = fpath.toString();
+ }
+
+ if (!is.array(fpath) && !is.exists(fpath)) {
+ watcher.emit('error',
+ new Error(fpath + ' does not exist.')
+ );
+ }
+
+ if (is.string(options)) {
+ options = {
+ encoding: options
+ }
+ }
+
+ if (is.func(options)) {
+ fn = options;
+ options = {};
+ }
+
+ if (arguments.length < 2) {
+ options = {};
+ }
+
+ if (options.encoding) {
+ assertEncoding(options.encoding);
+ } else {
+ options.encoding = 'utf8';
+ }
+
+ if (is.array(fpath)) {
+ if (fpath.length === 1) {
+ return watch(fpath[0], options, fn);
+ }
+ var filterDups = createDupsFilter();
+ return composeWatcher(unique(fpath).map(function(f) {
+ var w = watch(f, options);
+ if (is.func(fn)) {
+ w.on('change', filterDups(fn));
+ }
+ return w;
+ }));
+ }
+
+ if (is.file(fpath)) {
+ watcher.watchFile(fpath, options, fn);
+ emitReady(watcher);
+ }
+
+ else if (is.directory(fpath)) {
+ var counter = semaphore(function () {
+ emitReady(watcher);
+ });
+ watcher.watchDirectory(fpath, options, fn, counter);
+ }
+
+ return watcher.expose();
+}
+
+module.exports = watch;
+module.exports.default = watch;
diff --git a/includes/external/chvfs/node_modules/node-watch/package.json b/includes/external/chvfs/node_modules/node-watch/package.json
new file mode 100644
index 0000000..241dc81
--- /dev/null
+++ b/includes/external/chvfs/node_modules/node-watch/package.json
@@ -0,0 +1,36 @@
+{
+ "description": "A wrapper and enhancements for fs.watch",
+ "license": "MIT",
+ "name": "node-watch",
+ "repository": {
+ "url": "git://github.com/yuanchuan/node-watch.git",
+ "type": "git"
+ },
+ "keywords": [
+ "fs.watch",
+ "watch",
+ "watchfile"
+ ],
+ "version": "0.7.3",
+ "bugs": {
+ "url": "https://github.com/yuanchuan/node-watch/issues"
+ },
+ "url": "https://github.com/yuanchuan/node-watch",
+ "author": "yuanchuan <yuanchuan23@gmail.com> (http://yuanchuan.name)",
+ "main": "./lib/watch",
+ "types": "./lib/watch.d.ts",
+ "files": [
+ "lib/"
+ ],
+ "homepage": "https://github.com/yuanchuan/node-watch#readme",
+ "scripts": {
+ "test": "mocha test/test.js --exit --slow 500"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "devDependencies": {
+ "fs-extra": "^7.0.1",
+ "mocha": "^5.2.0"
+ }
+}
diff --git a/includes/external/chvfs/package-lock.json b/includes/external/chvfs/package-lock.json
new file mode 100644
index 0000000..e2d2ba4
--- /dev/null
+++ b/includes/external/chvfs/package-lock.json
@@ -0,0 +1,27 @@
+{
+ "name": "chvfs",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "dependencies": {
+ "node-watch": "^0.7.3"
+ }
+ },
+ "node_modules/node-watch": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/node-watch/-/node-watch-0.7.3.tgz",
+ "integrity": "sha512-3l4E8uMPY1HdMMryPRUAl+oIHtXtyiTlIiESNSVSNxcPfzAFzeTbXFQkZfAwBbo0B1qMSG8nUABx+Gd+YrbKrQ==",
+ "engines": {
+ "node": ">=6"
+ }
+ }
+ },
+ "dependencies": {
+ "node-watch": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/node-watch/-/node-watch-0.7.3.tgz",
+ "integrity": "sha512-3l4E8uMPY1HdMMryPRUAl+oIHtXtyiTlIiESNSVSNxcPfzAFzeTbXFQkZfAwBbo0B1qMSG8nUABx+Gd+YrbKrQ=="
+ }
+ }
+}
diff --git a/includes/external/chvfs/package.json b/includes/external/chvfs/package.json
new file mode 100644
index 0000000..8a3cbee
--- /dev/null
+++ b/includes/external/chvfs/package.json
@@ -0,0 +1,5 @@
+{
+ "dependencies": {
+ "node-watch": "^0.7.3"
+ }
+}
diff --git a/includes/jobs/FrontersNotification.php b/includes/jobs/FrontersNotification.php
index d95f273..f29a1e5 100644
--- a/includes/jobs/FrontersNotification.php
+++ b/includes/jobs/FrontersNotification.php
@@ -5,6 +5,12 @@ $_SERVER['DOCUMENT_ROOT'] = "../..";
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/composer/vendor/autoload.php';
use ColorThief\ColorThief;
+if (!function_exists("formatPonypush")) {
+ function formatPonypush($message) {
+ return "Update to Ponypush 3.1.0 or later — (\$PA1$\$" . base64_encode($message) . "\$\$)";
+ }
+}
+
$app = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true);
$system = $options["system"];
@@ -48,12 +54,12 @@ if (count($fronters["members"]) > 1) {
'method' => 'POST',
'header' =>
"Content-Type: text/plain\r\n" .
- "Title: 🐴 Switch occurred in $name\r\n" .
+ "Title: " . formatPonypush("🐴 Switch occurred in $name") . "\r\n" .
"Priority: default\r\n" .
"Tags: switch\r\n" .
"Actions: view, Open " . $fronters["members"][0]["display_name"] . " on Cold Haze, https://ponies.equestria.horse/" . $fronters["members"][0]["name"] . "/, clear=true;view, Open " . $fronters["members"][1]["display_name"] . " on Cold Haze, https://ponies.equestria.horse/" . $fronters["members"][1]["name"] . "/, clear=true\r\n" .
"Authorization: Basic " . base64_encode($ntfy["user"] . ":" . $ntfy["password"]),
- 'content' => ($fronters["members"][0]["display_name"] ?? $fronters["members"][0]["name"]) . " and " . ($fronters["members"][1]["display_name"] ?? $fronters["members"][1]["name"]) . " switched in just now"
+ 'content' => formatPonypush(($fronters["members"][0]["display_name"] ?? $fronters["members"][0]["name"]) . " and " . ($fronters["members"][1]["display_name"] ?? $fronters["members"][1]["name"]) . " switched in just now")
]
]);
} else if (count($fronters["members"]) > 0) {
@@ -62,12 +68,12 @@ if (count($fronters["members"]) > 1) {
'method' => 'POST',
'header' =>
"Content-Type: text/plain\r\n" .
- "Title: 🐴 Switch occurred in $name\r\n" .
+ "Title: " . formatPonypush("🐴 Switch occurred in $name") . "\r\n" .
"Priority: default\r\n" .
"Tags: switch\r\n" .
"Actions: view, Open on Cold Haze, https://ponies.equestria.horse/" . $fronters["members"][0]["name"] . "/, clear=true\r\n" .
"Authorization: Basic " . base64_encode($ntfy["user"] . ":" . $ntfy["password"]),
- 'content' => ($fronters["members"][0]["display_name"] ?? $fronters["members"][0]["name"]) . " switched in just now"
+ 'content' => formatPonypush(($fronters["members"][0]["display_name"] ?? $fronters["members"][0]["name"]) . " switched in just now")
]
]);
} else {
@@ -76,12 +82,12 @@ if (count($fronters["members"]) > 1) {
'method' => 'POST',
'header' =>
"Content-Type: text/plain\r\n" .
- "Title: 🐴 Switch occurred in $name\r\n" .
+ "Title: " . formatPonypush("🐴 Switch occurred in $name") . "\r\n" .
"Priority: default\r\n" .
"Tags: switch\r\n" .
"Actions: view, Open on Cold Haze, https://ponies.equestria.horse/, clear=true\r\n" .
"Authorization: Basic " . base64_encode($ntfy["user"] . ":" . $ntfy["password"]),
- 'content' => "The fallback pony switched in just now"
+ 'content' => formatPonypush("The fallback pony switched in just now")
]
]);
}
diff --git a/includes/jobs/PKMembers.php b/includes/jobs/PKMembers.php
index 529a820..536853d 100644
--- a/includes/jobs/PKMembers.php
+++ b/includes/jobs/PKMembers.php
@@ -30,6 +30,8 @@ if (trim($data) !== "" && $data !== false) {
$parsed = json_decode($data, true);
foreach ($parsed as $index => $member) {
+ echo(($member["display_name"] ?? $member["name"]) . "\n");
+
if (isset($member["avatar_url"])) {
$dominantColor = substr(ColorThief::getColor($member["avatar_url"], outputFormat: "hex"), 1);
} else {
@@ -44,5 +46,8 @@ if (trim($data) !== "" && $data !== false) {
}
$data = json_encode($parsed, JSON_PRETTY_PRINT);
- file_put_contents("./data/$system/members.json", $data);
-} \ No newline at end of file
+ file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$system/members.json", $data);
+}
+
+file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/cache/navigation.json", "{}");
+file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/cache/home.json", "{}"); \ No newline at end of file
diff --git a/includes/jobs/PKSystem.php b/includes/jobs/PKSystem.php
index c3fce1b..045eb67 100644
--- a/includes/jobs/PKSystem.php
+++ b/includes/jobs/PKSystem.php
@@ -27,5 +27,5 @@ if ($app["other"]["id"] === $system) {
$data = file_get_contents("https://pluralkit.equestria.dev/v2/systems/$system", false, $ctx);
if (trim($data) !== "" && $data !== false) {
- file_put_contents("./data/$system/general.json", $data);
+ file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$system/general.json", $data);
} \ No newline at end of file
diff --git a/includes/jobs/UpdateAssets.php b/includes/jobs/UpdateAssets.php
index 8898b94..e52a6fa 100644
--- a/includes/jobs/UpdateAssets.php
+++ b/includes/jobs/UpdateAssets.php
@@ -19,7 +19,7 @@ function downloadAssets($system, $path = null) {
$path = $system;
}
- $general = json_decode(file_get_contents("./data/$path/general.json"), true);
+ $general = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$path/general.json"), true);
if ($options["type"] === "system") {
if (isset($general["avatar_url"])) {
@@ -39,7 +39,7 @@ function downloadAssets($system, $path = null) {
}
}
- $members = json_decode(file_get_contents("./data/$path/members.json"), true);
+ $members = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$path/members.json"), true);
foreach ($members as $member) {
$id = preg_replace("/^([\da-f]{8})-([\da-f]{4})-([\da-f]{4})-([\da-f]{4})-([\da-f]{12})$/", "$1$2$3$4$5", $general["uuid"]) . preg_replace("/^([\da-f]{8})-([\da-f]{4})-([\da-f]{4})-([\da-f]{4})-([\da-f]{12})$/", "$1$2$3$4$5", $member["uuid"]);
diff --git a/includes/pages.json b/includes/pages.json
index 69aa00d..748234f 100644
--- a/includes/pages.json
+++ b/includes/pages.json
@@ -1,442 +1,212 @@
{
- "about": {
- "name": {
- "en": "About Cold Haze",
- "fr": "About Cold Haze"
- },
- "short": "About",
- "admin": true,
- "limited": true,
- "rail": true
- },
- "actions": {
- "name": {
- "en": "Actions database",
- "fr": "Actions database"
- },
- "short": "Actions",
- "admin": true,
- "limited": false,
- "rail": true
- },
"alphabet": {
"name": {
- "en": "Members by prefix letters",
- "fr": "Par lettre du préfixe"
- },
- "short": null,
- "admin": false,
- "limited": false,
- "rail": false
- },
- "api": {
- "name": {
- "en": "API",
- "fr": "API"
- },
- "short": null,
- "admin": false,
- "limited": false,
- "rail": false
- },
- "app": {
- "name": {
- "en": "Mobile application",
- "fr": "Application mobile"
+ "en": "Members by prefix letters"
},
- "short": null,
"admin": false,
- "limited": false,
- "rail": false
+ "limited": false
},
"byfront": {
"name": {
- "en": "Members by last fronted",
- "fr": "Members by last fronted"
+ "en": "Members by last fronted"
},
- "short": "Last fronts",
"admin": true,
- "limited": true,
- "rail": true
+ "limited": true
},
"computers": {
"name": {
- "en": "Devices",
- "fr": "Devices"
- },
- "short": "PCs",
- "admin": true,
- "limited": false,
- "rail": true
- },
- "dashboard": {
- "name": {
- "en": "Dashboard",
- "fr": "Dashboard"
- },
- "short": null,
- "admin": true,
- "limited": false,
- "rail": true
- },
- "debug": {
- "name": {
- "en": "Data updater debugging",
- "fr": "Débogage des mises à jour"
+ "en": "Devices"
},
- "short": null,
"admin": true,
- "limited": true,
- "rail": true
+ "limited": false
},
"docs": {
"name": {
- "en": "Documents",
- "fr": "Documents"
+ "en": "Documents"
},
- "short": "Docs",
"admin": true,
- "limited": false,
- "rail": true
- },
- "disclaimers": {
- "name": {
- "en": "Disclaimers",
- "fr": "Avertissements"
- },
- "short": null,
- "admin": false,
- "limited": false,
- "rail": false
+ "limited": false
},
"edit": {
"name": {
- "en": "Editor",
- "fr": "Editor"
+ "en": "Editor"
},
- "short": null,
"admin": true,
- "limited": true,
- "rail": false
+ "limited": true
},
"edit-private": {
"name": {
- "en": "Editor (private)",
- "fr": "Editor (private)"
+ "en": "Editor (private)"
},
- "short": null,
"admin": true,
- "limited": false,
- "rail": false
+ "limited": false
},
"emergency": {
"name": {
- "en": "Emergency alert",
- "fr": "Emergency alert"
+ "en": "Emergency alert"
},
- "short": null,
"admin": true,
- "limited": true,
- "rail": true
+ "limited": true
},
"fronting": {
"name": {
- "en": "Front planner",
- "fr": "Front planner"
+ "en": "Front planner"
},
- "short": null,
"admin": true,
- "limited": true,
- "rail": true
+ "limited": true
},
"games": {
"name": {
- "en": "Equestria Games",
- "fr": "Equestria Games"
+ "en": "Equestria Games"
},
- "short": null,
"admin": true,
- "limited": true,
- "rail": false
- },
- "government": {
- "name": {
- "en": "Government",
- "fr": "Gouvernement"
- },
- "short": null,
- "admin": false,
- "limited": false,
- "rail": false
+ "limited": true
},
"home": {
"name": {
- "en": "Home",
- "fr": "Accueil"
+ "en": "Home"
},
- "short": null,
"admin": false,
- "limited": false,
- "rail": false
+ "limited": false
},
"jobs": {
"name": {
- "en": "Jobs history",
- "fr": "Jobs history"
+ "en": "Jobs history"
},
- "short": null,
"admin": true,
- "limited": true,
- "rail": true
+ "limited": true
},
"login": {
"name": {
- "en": "Login",
- "fr": "Connexion"
+ "en": "Login"
},
- "short": null,
"admin": false,
- "limited": false,
- "rail": false
+ "limited": false
},
"logout": {
"name": {
- "en": "Logout",
- "fr": "Déconnexion"
+ "en": "Logout"
},
- "short": null,
"admin": false,
- "limited": false,
- "rail": false
+ "limited": false
},
"metadata": {
"name": {
- "en": "Metadata editor",
- "fr": "Metadata editor"
+ "en": "Metadata editor"
},
- "short": "Metadata",
"admin": true,
- "limited": true,
- "rail": false
+ "limited": true
},
"money": {
"name": {
- "en": "Money tracker",
- "fr": "Money tracker"
- },
- "short": "Money",
- "admin": true,
- "limited": false,
- "rail": false
- },
- "nicknames": {
- "name": {
- "en": "Relations nicknames",
- "fr": "Relations nicknames"
+ "en": "Money tracker"
},
- "short": "Nicknames",
"admin": true,
- "limited": false,
- "rail": true
- },
- "page": {
- "name": {
- "en": "Member/system page",
- "fr": "Page de membre/système"
- },
- "short": null,
- "admin": false,
- "limited": false,
- "rail": false
+ "limited": false
},
"pleasure": {
"name": {
- "en": "Sex alert",
- "fr": "Sex alert"
+ "en": "Sex alert"
},
- "short": null,
"admin": true,
- "limited": true,
- "rail": true
+ "limited": true
},
"ponytown": {
"name": {
- "en": "Pony Town uploader",
- "fr": "Pony Town uploader"
+ "en": "Pony Town uploader"
},
- "short": null,
"admin": true,
- "limited": true,
- "rail": true
+ "limited": true
},
"profiles": {
"name": {
- "en": "Profile scores",
- "fr": "Profile scores"
+ "en": "Profile scores"
},
- "short": null,
"admin": true,
- "limited": true,
- "rail": true
+ "limited": true
},
"relations": {
"name": {
- "en": "Relations",
- "fr": "Relations"
+ "en": "Relations"
},
- "short": null,
"admin": false,
- "limited": false,
- "rail": false
+ "limited": false
},
"rules": {
"name": {
- "en": "Rules",
- "fr": "Rules"
+ "en": "Rules"
},
- "short": "Rules",
"admin": true,
- "limited": false,
- "rail": true
+ "limited": false
},
"s:compare": {
"name": {
- "en": "Compare members",
- "fr": "Comparer les membres"
+ "en": "Compare members"
},
- "short": null,
"admin": false,
- "limited": false,
- "rail": false
+ "limited": false
},
"s:history": {
"name": {
- "en": "Front history",
- "fr": "Historique de front"
+ "en": "Front history"
},
- "short": null,
"admin": false,
- "limited": false,
- "rail": false
+ "limited": false
},
"s:species": {
"name": {
- "en": "Members by species",
- "fr": "Membres par espèce"
- },
- "short": null,
- "admin": false,
- "limited": false,
- "rail": false
- },
- "s:tree": {
- "name": {
- "en": "System tree",
- "fr": "Arbre du système"
+ "en": "Members by species"
},
- "short": null,
"admin": false,
- "limited": false,
- "rail": false
+ "limited": false
},
"schedules": {
"name": {
- "en": "Schedules",
- "fr": "Schedules"
+ "en": "Schedules"
},
- "short": null,
"admin": true,
- "limited": true,
- "rail": true
- },
- "score": {
- "name": {
- "en": "Score system testing",
- "fr": "Score system testing"
- },
- "short": null,
- "admin": true,
- "limited": true,
- "rail": true
+ "limited": true
},
"splitting": {
"name": {
- "en": "Members by splitting date",
- "fr": "Members by splitting date"
+ "en": "Members by splitting date"
},
- "short": "Splits",
"admin": true,
- "limited": true,
- "rail": true
+ "limited": true
},
"stats": {
"name": {
- "en": "Statistics",
- "fr": "Statistics"
+ "en": "Statistics"
},
- "short": "Stats",
"admin": true,
- "limited": false,
- "rail": true
+ "limited": false
},
"terminology": {
"name": {
- "en": "Terminology",
- "fr": "Terminologie"
+ "en": "Terminology"
},
- "short": null,
"admin": false,
- "limited": false,
- "rail": true
- },
- "together": {
- "name": {
- "en": "Watch Together",
- "fr": "Watch Together"
- },
- "short": "Together",
- "admin": true,
- "limited": true,
- "rail": false
- },
- "together-dev": {
- "name": {
- "en": "Watch Together (dev)",
- "fr": "Watch Together (dev)"
- },
- "short": null,
- "admin": true,
- "limited": true,
- "rail": false
+ "limited": false
},
"toys": {
"name": {
- "en": "Toys database",
- "fr": "Toys database"
+ "en": "Toys database"
},
- "short": "Toys",
"admin": true,
- "limited": false,
- "rail": true
+ "limited": false
},
"travelling": {
"name": {
- "en": "System travels manager",
- "fr": "System travels manager"
+ "en": "System travels manager"
},
- "short": "Travels",
"admin": true,
- "limited": true,
- "rail": true
+ "limited": true
},
"wakeup": {
"name": {
- "en": "Wake-up alert",
- "fr": "Wake-up alert"
+ "en": "Wake-up alert"
},
- "short": null,
"admin": true,
- "limited": false,
- "rail": true
+ "limited": false
}
} \ No newline at end of file
diff --git a/includes/util/agewarning.inc b/includes/util/agewarning.inc
index 1f51647..f8bc650 100644
--- a/includes/util/agewarning.inc
+++ b/includes/util/agewarning.inc
@@ -1,7 +1,7 @@
<?php
function showWarning($name, $id, $system) {
- $ntfy = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true)["ntfy"];
+ $ntfy = $GLOBALS["ColdHazeApp"]["ntfy"];
if (time() > 1677628800) {
file_get_contents('https://' . $ntfy["server"] . '/' . $ntfy['topic'], false, stream_context_create([
@@ -9,12 +9,12 @@ function showWarning($name, $id, $system) {
'method' => 'POST',
'header' =>
"Content-Type: text/plain\r\n" .
- "Title: ⚠️ $name does not have an age or birth year set\r\n" .
+ "Title: " . formatPonypush("⚠️ $name does not have an age or birth year set") . "\r\n" .
"Priority: max\r\n" .
"Tags: switch\r\n" .
"Actions: view, Edit on Cold Haze, https://ponies.equestria.horse/-/metadata/" . ($system === "gdapd" ? "raindrops" : "cloudburst") . "/" . $id . "/, clear=true\r\n" .
"Authorization: Basic " . base64_encode($ntfy["user"] . ":" . $ntfy["password"]),
- 'content' => "To make sure they appear on the fronting schedule (and to make sure they can front again), they need to set an age or birth year now."
+ 'content' => formatPonypush("To make sure they appear on the fronting schedule (and to make sure they can front again), they need to set an age or birth year now.")
]
]));
} else {
@@ -23,12 +23,12 @@ function showWarning($name, $id, $system) {
'method' => 'POST',
'header' =>
"Content-Type: text/plain\r\n" .
- "Title: ⚠️ $name does not have an age or birth year set\r\n" .
+ "Title: " . formatPonypush("⚠️ $name does not have an age or birth year set") . "\r\n" .
"Priority: max\r\n" .
"Tags: switch\r\n" .
"Actions: view, Edit on Cold Haze, https://ponies.equestria.horse/-/metadata/" . ($system === "gdapd" ? "raindrops" : "cloudburst") . "/" . $id . "/, clear=true\r\n" .
"Authorization: Basic " . base64_encode($ntfy["user"] . ":" . $ntfy["password"]),
- 'content' => "To make sure they still appear on the fronting schedule after March 1st, they need to set an age or birth year now."
+ 'content' => formatPonypush("To make sure they still appear on the fronting schedule after March 1st, they need to set an age or birth year now.")
]
]));
}
diff --git a/includes/util/banner.inc b/includes/util/banner.inc
index 1657233..947e5c6 100644
--- a/includes/util/banner.inc
+++ b/includes/util/banner.inc
@@ -1,6 +1,6 @@
<?php
-require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/session.inc"; global $isLoggedIn; global $lang; global $pages;
+require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/session.inc"; global $isLoggedIn; global $lang; global $pages; global $isLowerLoggedIn;
require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/pronouns.inc";
require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/functions.inc";
require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/Parsedown.php"; $Parsedown = new Parsedown();
@@ -21,6 +21,7 @@ function _header_getMember(string $id, $system) {
function getMemberBannerData(string $id, string $system, bool $french = false) {
global $travelling;
global $isLoggedIn;
+ global $isLowerLoggedIn;
global $lang;
global $Parsedown;
@@ -155,7 +156,7 @@ function getMemberBannerData(string $id, string $system, bool $french = false) {
];
}
- $app = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true);
+ $app = $GLOBALS["ColdHazeApp"];
$systemData = [];
$systemData['page'] = "/" . ($system === "gdapd" ? "raindrops" : ($system === $app["other"]["id"] ? $app["other"]["slug"] : "cloudburst"));
@@ -190,7 +191,7 @@ function getMemberBannerData(string $id, string $system, bool $french = false) {
sort($mfMember);
$mfMember = $mfMember[0];
- $marefriends[] = [
+ if (!($mfSystem === $app["other"]["id"] && !$isLoggedIn && !$isLowerLoggedIn)) $marefriends[] = [
"id" => $marefriend,
"link" => "/" . ($mfMember["name"]),
"icon" => getAsset($mfSystem, $mfMemberID, "heads"),
@@ -212,7 +213,7 @@ function getMemberBannerData(string $id, string $system, bool $french = false) {
sort($mfMember);
$mfMember = $mfMember[0];
- $sexfriends[] = [
+ if (!($mfSystem === $app["other"]["id"] && !$isLoggedIn && !$isLowerLoggedIn)) $sexfriends[] = [
"id" => $marefriend,
"link" => "/" . ($mfMember["name"]),
"icon" => getAsset($mfSystem, $mfMemberID, "heads"),
@@ -232,7 +233,7 @@ function getMemberBannerData(string $id, string $system, bool $french = false) {
sort($mfMember);
$mfMember = $mfMember[0];
- $sisters[] = [
+ if (!($mfSystem === $app["other"]["id"] && !$isLoggedIn && !$isLowerLoggedIn)) $sisters[] = [
"id" => $marefriend,
"link" => "/" . ($mfMember["name"]),
"icon" => getAsset($mfSystem, $mfMemberID, "heads"),
@@ -254,7 +255,7 @@ function getMemberBannerData(string $id, string $system, bool $french = false) {
sort($mfMember);
$mfMember = $mfMember[0];
- $caretakers[] = [
+ if (!($mfSystem === $app["other"]["id"] && !$isLoggedIn && !$isLowerLoggedIn)) $caretakers[] = [
"id" => $marefriend,
"link" => "/" . ($mfMember["name"]),
"icon" => getAsset($mfSystem, $mfMemberID, "heads"),
diff --git a/includes/util/functions.inc b/includes/util/functions.inc
index 5b5b6b5..79a47c6 100644
--- a/includes/util/functions.inc
+++ b/includes/util/functions.inc
@@ -17,6 +17,12 @@ if (!function_exists("createJob")) {
}
}
+if (!function_exists("formatPonypush")) {
+ function formatPonypush($message) {
+ return "Update to Ponypush 3.1.0 or later — (\$PA1$\$" . base64_encode($message) . "\$\$)";
+ }
+}
+
if (!function_exists("peh_error")) {
function peh_error($message, $code = 500): void {
header("Location: /?em=" . urlencode(base64_encode($message)) . "&ec=" . $code);
@@ -26,7 +32,7 @@ if (!function_exists("peh_error")) {
if (!function_exists("getAsset")) {
function getAsset($systemID, $memberID = null, $type = "avatars") {
- $app = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true);
+ $app = $GLOBALS["ColdHazeApp"] ?? json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true);
$systemFile = (isset($app["other"]) && $systemID === $app["other"]["id"]) ? "other" : $systemID;
if (file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemFile/general.json")) {
@@ -234,7 +240,7 @@ if (!function_exists("withCaretakersDown")) {
if (!function_exists("getSystemMember")) {
function getSystemMember(string $system, string $id) {
- $app = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true);
+ $app = $GLOBALS["ColdHazeApp"] ?? json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true);
$systemID = $system;
$members = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/" . ($systemID === $app["other"]["id"] ? "other" : $systemID) . "/members.json"), true);
@@ -270,7 +276,7 @@ if (!function_exists("getMemberWithoutSystem")) {
}
if ($isLowerLoggedIn || $isLoggedIn) {
- $app = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true);
+ $app = $GLOBALS["ColdHazeApp"] ?? json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true);
$members3 = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/other/members.json"), true);
foreach ($members3 as $m) {
@@ -316,7 +322,7 @@ if (!function_exists("prettySize")) {
if (!function_exists("showSystem")) {
function showSystem(string $id, string $name, string $color, bool $hideTitle) {
- $app = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true);
+ $app = $GLOBALS["ColdHazeApp"] ?? json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true);
global $travelling;
$global = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/" . (isset($app["other"]) && $id === $app["other"]["id"] ? "other" : $id) . "/general.json"), true);
@@ -381,7 +387,7 @@ if (!function_exists("raindrops")) {
if (!function_exists("other")) {
function other(bool $hideTitle): void {
- $app = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true);
+ $app = $GLOBALS["ColdHazeApp"] ?? json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true);
showSystem($app["other"]["id"], $app["other"]["name"], "#" . $app["other"]["color"] . "a6", $hideTitle);
}
}
diff --git a/includes/util/score.inc b/includes/util/score.inc
index ad3c8e4..9deacca 100644
--- a/includes/util/score.inc
+++ b/includes/util/score.inc
@@ -113,7 +113,7 @@ function scoreOrderGlobal() {
}
if ($isLowerLoggedIn || $isLoggedIn) {
- $app = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true);
+ $app = $GLOBALS["ColdHazeApp"] ?? json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true);
foreach (json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/other/members.json"), true) as $member) {
if ($member["name"] !== "unknown" && $member["name"] !== "fusion" && $member["name"] !== "new" && !str_starts_with($member["name"], "smol") && !str_ends_with($member["name"], "-travelling") && file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/metadata/$member[id].json")) {
if (isset($member["color"])) {
diff --git a/includes/util/session.inc b/includes/util/session.inc
index 7a6b931..72dd34d 100644
--- a/includes/util/session.inc
+++ b/includes/util/session.inc
@@ -9,6 +9,12 @@ global $_PROFILE;
$isLoggedIn = false;
$isLowerLoggedIn = false;
+if (!function_exists("formatPonypush")) {
+ function formatPonypush($message) {
+ return "Update to Ponypush 3.1.0 or later — (\$PA1$\$" . base64_encode($message) . "\$\$)";
+ }
+}
+
if (isset($_COOKIE['PEH2_SESSION_TOKEN'])) {
if (!(str_contains($_COOKIE['PEH2_SESSION_TOKEN'], ".") || str_contains($_COOKIE['PEH2_SESSION_TOKEN'], "/") || trim($_COOKIE["PEH2_SESSION_TOKEN"]) === "")) {
if (file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . str_replace(".", "", str_replace("/", "", $_COOKIE['PEH2_SESSION_TOKEN'])))) {
diff --git a/includes/util/travelling.inc b/includes/util/travelling.inc
index 0d1696a..eced4f2 100644
--- a/includes/util/travelling.inc
+++ b/includes/util/travelling.inc
@@ -1,6 +1,6 @@
<?php
-$app = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true);
+$app = $GLOBALS["ColdHazeApp"] ?? json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true);
$travelling = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/travelling/travelling.json"), true);
$json_cloudburst = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/ynmuc/members.json"), true);
diff --git a/pages/api/emergency-real.php b/pages/api/emergency-real.php
index d0412c8..a67f549 100644
--- a/pages/api/emergency-real.php
+++ b/pages/api/emergency-real.php
@@ -9,18 +9,18 @@ if (!$isLoggedIn && !$isLowerLoggedIn) {
global $_PROFILE;
-$ntfy = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true)["ntfy"];
+$ntfy = $GLOBALS["ColdHazeApp"]["ntfy"];
file_get_contents('https://' . $ntfy["server"] . '/' . $ntfy["topic"], false, stream_context_create([
'http' => [
'method' => 'POST',
'header' =>
"Content-Type: text/plain\r\n" .
- "Title: ⚠️🆘 EMERGENCY ⚠️🆘\r\n" .
+ "Title: " . formatPonypush("⚠️🆘 EMERGENCY ⚠️🆘") . "\r\n" .
"Priority: urgent\r\n" .
"Tags: emergency\r\n" .
"Authorization: Basic " . base64_encode($ntfy["user"] . ":" . $ntfy["password"]),
- 'content' => "This is an emergency, " . $_PROFILE['name'] . " is in need of IMMEDIATE help. Please act now!"
+ 'content' => formatPonypush("This is an emergency, " . $_PROFILE['name'] . " is in need of IMMEDIATE help. Please act now!")
]
]));
@@ -29,11 +29,11 @@ file_get_contents('https://' . $ntfy["server"] . '/emergency', false, stream_con
'method' => 'POST',
'header' =>
"Content-Type: text/plain\r\n" .
- "Title: ⚠️🆘 EMERGENCY ⚠️🆘\r\n" .
+ "Title: " . formatPonypush("⚠️🆘 EMERGENCY ⚠️🆘") . "\r\n" .
"Priority: urgent\r\n" .
"Tags: emergency\r\n" .
"Authorization: Basic " . base64_encode($ntfy["user"] . ":" . $ntfy["password"]),
- 'content' => "This is an emergency, " . $_PROFILE['name'] . " is in need of IMMEDIATE help. Please act now!"
+ 'content' => formatPonypush("This is an emergency, " . $_PROFILE['name'] . " is in need of IMMEDIATE help. Please act now!")
]
]));
diff --git a/pages/api/emergency.php b/pages/api/emergency.php
index 2f764b1..24c975a 100644
--- a/pages/api/emergency.php
+++ b/pages/api/emergency.php
@@ -9,18 +9,18 @@ if (!$isLoggedIn && !$isLowerLoggedIn) {
global $_PROFILE;
-$ntfy = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true)["ntfy"];
+$ntfy = $GLOBALS["ColdHazeApp"]["ntfy"];
file_get_contents('https://' . $ntfy["server"] . '/' . $ntfy["topic"], false, stream_context_create([
'http' => [
'method' => 'POST',
'header' =>
"Content-Type: text/plain\r\n" .
- "Title: [Test] ⚠️🆘 EMERGENCY ⚠️🆘\r\n" .
+ "Title: " . formatPonypush("[Test] ⚠️🆘 EMERGENCY ⚠️🆘") . "\r\n" .
"Priority: urgent\r\n" .
"Tags: emergency\r\n" .
"Authorization: Basic " . base64_encode($ntfy["user"] . ":" . $ntfy["password"]),
- 'content' => "[This notification is test] This is an emergency, " . $_PROFILE['name'] . " is in need of IMMEDIATE help. Please act now! [This notification is test]"
+ 'content' => formatPonypush("[This notification is test] This is an emergency, " . $_PROFILE['name'] . " is in need of IMMEDIATE help. Please act now! [This notification is test]")
]
]));
@@ -29,11 +29,11 @@ file_get_contents('https://' . $ntfy["server"] . '/emergency', false, stream_con
'method' => 'POST',
'header' =>
"Content-Type: text/plain\r\n" .
- "Title: [Test] ⚠️🆘 EMERGENCY ⚠️🆘\r\n" .
+ "Title: " . formatPonypush("[Test] ⚠️🆘 EMERGENCY ⚠️🆘") . "\r\n" .
"Priority: urgent\r\n" .
"Tags: emergency\r\n" .
"Authorization: Basic " . base64_encode($ntfy["user"] . ":" . $ntfy["password"]),
- 'content' => "[This notification is test] This is an emergency, " . $_PROFILE['name'] . " is in need of IMMEDIATE help. Please act now! [This notification is test]"
+ 'content' => formatPonypush("[This notification is test] This is an emergency, " . $_PROFILE['name'] . " is in need of IMMEDIATE help. Please act now! [This notification is test]")
]
]));
diff --git a/pages/api/me.php b/pages/api/me.php
index 4f86549..5b13208 100644
--- a/pages/api/me.php
+++ b/pages/api/me.php
@@ -1,6 +1,6 @@
<?php
-$app = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true);
+$app = $GLOBALS["ColdHazeApp"];
require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/session.inc"; global $isLoggedIn; global $isLowerLoggedIn;
diff --git a/pages/api/pleasure-real.php b/pages/api/pleasure-real.php
index 5ac0039..122d994 100644
--- a/pages/api/pleasure-real.php
+++ b/pages/api/pleasure-real.php
@@ -22,18 +22,18 @@ if ($_PROFILE["login"] === "raindrops" && isset($frontRaindrops[0])) {
$pony = "somepony";
}
-$ntfy = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true)["ntfy"];
+$ntfy = $GLOBALS["ColdHazeApp"]["ntfy"];
file_get_contents('https://' . $ntfy["server"] . '/pleasure', false, stream_context_create([
'http' => [
'method' => 'POST',
'header' =>
"Content-Type: text/plain\r\n" .
- "Title: 🏩 $pony wants to play for a bit\r\n" .
+ "Title: " . formatPonypush("🏩 $pony wants to play for a bit") . "\r\n" .
"Priority: high\r\n" .
"Tags: pleasure\r\n" .
"Authorization: Basic " . base64_encode($ntfy["user"] . ":" . $ntfy["password"]),
- 'content' => "Hey, $pony wants to play and have fun for a bit, get up!"
+ 'content' => formatPonypush("Hey, $pony wants to play and have fun for a bit, get up!")
]
]));
diff --git a/pages/api/pleasure.php b/pages/api/pleasure.php
index 705adfe..14abada 100644
--- a/pages/api/pleasure.php
+++ b/pages/api/pleasure.php
@@ -22,18 +22,18 @@ if ($_PROFILE["login"] === "raindrops" && isset($frontRaindrops[0])) {
$pony = "somepony";
}
-$ntfy = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true)["ntfy"];
+$ntfy = $GLOBALS["ColdHazeApp"]["ntfy"];
file_get_contents('https://' . $ntfy["server"] . '/pleasure', false, stream_context_create([
'http' => [
'method' => 'POST',
'header' =>
"Content-Type: text/plain\r\n" .
- "Title: [Test] 🏩 $pony wants to play for a bit\r\n" .
+ "Title: " . formatPonypush("[Test] 🏩 $pony wants to play for a bit") . "\r\n" .
"Priority: high\r\n" .
"Tags: pleasure\r\n" .
"Authorization: Basic " . base64_encode($ntfy["user"] . ":" . $ntfy["password"]),
- 'content' => "[This is a test] Hey, $pony wants to play and have fun for a bit, get up!"
+ 'content' => formatPonypush("[This is a test] Hey, $pony wants to play and have fun for a bit, get up!")
]
]));
diff --git a/pages/api/plex-thumb.php b/pages/api/plex-thumb.php
index 4a20159..4009871 100644
--- a/pages/api/plex-thumb.php
+++ b/pages/api/plex-thumb.php
@@ -1,6 +1,6 @@
<?php
-$app = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true);
+$app = $GLOBALS["ColdHazeApp"];
if (isset($_GET["library"]) && isset($_GET["id"]) && is_numeric($_GET["library"]) && is_numeric($_GET["id"])) {
header("Content-Type: image/jpg");
diff --git a/pages/api/plex.php b/pages/api/plex.php
index a2fa739..00164c7 100644
--- a/pages/api/plex.php
+++ b/pages/api/plex.php
@@ -1,7 +1,7 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/random.inc";
-$app = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true);
+$app = $GLOBALS["ColdHazeApp"];
function formatTitle($metadata) {
if ($metadata['grandparentTitle']) {
diff --git a/pages/api/pluralkit-integration.php b/pages/api/pluralkit-integration.php
index 0efa8cb..69d99b1 100644
--- a/pages/api/pluralkit-integration.php
+++ b/pages/api/pluralkit-integration.php
@@ -2,14 +2,14 @@
require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/agewarning.inc";
-$app = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true);
+$app = $GLOBALS["ColdHazeApp"];
$user = $_GET['user'] ?? null;
$inputJSON = file_get_contents('php://input');
$input = json_decode($inputJSON, true);
-$data = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true)["webhook"];
+$data = $GLOBALS["ColdHazeApp"]["webhook"];
-if (isset(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true)["ponytown"][$user])) {
- $ponytown = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true)["ponytown"][$user];
+if (isset($GLOBALS["ColdHazeApp"]["ponytown"][$user])) {
+ $ponytown = $GLOBALS["ColdHazeApp"]["ponytown"][$user];
} else {
header("HTTP/1.1 404 Not Found") and die();
}
diff --git a/pages/api/wakeup-real.php b/pages/api/wakeup-real.php
index fd113ba..f00d5ea 100644
--- a/pages/api/wakeup-real.php
+++ b/pages/api/wakeup-real.php
@@ -15,18 +15,18 @@ $frontRaindrops = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/in
$pony = "somepony";
if ($_PROFILE['login'] === "raindrops") $pony = $frontRaindrops[0]["display_name"]; else $pony = $frontCloudburst[0]["display_name"];
-$ntfy = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true)["ntfy"];
+$ntfy = $GLOBALS["ColdHazeApp"]["ntfy"];
file_get_contents('https://' . $ntfy["server"] . '/' . $ntfy["topic"], false, stream_context_create([
'http' => [
'method' => 'POST',
'header' =>
"Content-Type: text/plain\r\n" .
- "Title: 🥱 Wake up!\r\n" .
+ "Title: " . formatPonypush("🥱 Wake up!") . "\r\n" .
"Priority: high\r\n" .
"Tags: wakeup\r\n" .
"Authorization: Basic " . base64_encode($ntfy["user"] . ":" . $ntfy["password"]),
- 'content' => "Hey, $pony wants you to wake up!"
+ 'content' => formatPonypush("Hey, $pony wants you to wake up!")
]
]));
diff --git a/pages/api/wakeup.php b/pages/api/wakeup.php
index e4603ef..a5d2b9f 100644
--- a/pages/api/wakeup.php
+++ b/pages/api/wakeup.php
@@ -15,18 +15,18 @@ $frontRaindrops = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/in
$pony = "somepony";
if ($_PROFILE['login'] === "raindrops") $pony = $frontRaindrops[0]["display_name"]; else $pony = $frontCloudburst[0]["display_name"];
-$ntfy = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true)["ntfy"];
+$ntfy = $GLOBALS["ColdHazeApp"]["ntfy"];
file_get_contents('https://' . $ntfy["server"] . '/' . $ntfy["topic"], false, stream_context_create([
'http' => [
'method' => 'POST',
'header' =>
"Content-Type: text/plain\r\n" .
- "Title: [Test] 🥱 Wake up!\r\n" .
+ "Title: " . formatPonypush("[Test] 🥱 Wake up!") . "\r\n" .
"Priority: high\r\n" .
"Tags: wakeup\r\n" .
"Authorization: Basic " . base64_encode($ntfy["user"] . ":" . $ntfy["password"]),
- 'content' => "[This notification is test] Hey, $pony wants you to wake up! [This notification is test]"
+ 'content' => formatPonypush("[This notification is test] Hey, $pony wants you to wake up! [This notification is test]")
]
]));
diff --git a/pages/home.inc b/pages/home.inc
index 86bf513..cc04e42 100644
--- a/pages/home.inc
+++ b/pages/home.inc
@@ -4,24 +4,9 @@ if (isset($_GET["ec"])) {
header("HTTP/1.1 " . $_GET["ec"] . " Error");
}
-require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/init.inc"; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/components/header.inc'; global $readOnly; global $isNormallyLoggedIn; global $_PROFILE; global $lang; global $pages; global $isLowerLoggedIn; global $app; global $isLoggedIn; ?>
+require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/init.inc"; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/components/header.inc'; global $readOnly; global $isNormallyLoggedIn; global $_PROFILE; global $lang; global $pages; global $isLowerLoggedIn; global $app; global $isLoggedIn;
-<br>
-<div class="container">
- <?php if (isset($_GET['em'])): ?>
- <div class="alert alert-danger alert-dismissible">
- <button onclick='window.history.pushState({"html":null,"pageTitle":document.title},"", "/");' type="button" class="btn-close" data-bs-dismiss="alert"></button>
- <b><?= $lang["home"]["error"] ?> </b><?= strip_tags(base64_decode($_GET['em'])) ?>
- </div>
- <?php endif; ?>
-
- <?php if ($readOnly && $isNormallyLoggedIn || $readOnly && $isLowerLoggedIn): ?>
- <div class="alert alert-warning">
- <b>Notice: </b>This website is temporarily under maintenance and the administrators have locked the database. Although you are logged in as <?= $_PROFILE['name'] ?>, you cannot access any of the logged-in features while the website is under maintenance. <a href="/-/emergency">Alerts dispatching</a> remains possible in case of an emergency.
- </div>
- <?php endif; ?>
-
- <?php global $travelling; $byColor = getMembersByColor(); ?>
+function banner() { global $isLoggedIn; global $isLowerLoggedIn; $byColor = getMembersByColor(); global $lang; ?>
<div style="text-align: center;">
<img alt="" src="/assets/logo/newlogo<?= $isLoggedIn || $isLowerLoggedIn ? "3" : "" ?>.png" style="width:128px;">
<p style="z-index:999;position:relative;background:transparent;margin: 20px -10px 0 -20px;padding-right:30px;height:32px;text-align: center;display:grid;grid-template-columns: repeat(<?= count($byColor) ?>, 1fr);">
@@ -30,18 +15,20 @@ require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/init.inc"; require_once $_SE
<div style="margin-top:-33px;margin-bottom:0;margin-left:-20px;margin-right:20px;height:32px;text-align: center;display:grid;grid-template-columns: repeat(<?= count($byColor) ?>, 1fr);position:relative;left:10px;">
<?php foreach ($byColor as $member): ?><div>
<span style="display: inline-block;background: transparent;position:absolute;width: 0;height: 0;margin-top: 17px;box-shadow: 0 6px 20px 20px #<?= $member["color"] ?>;z-index: 9;margin-left: 8px;opacity: .75;"></span>
- </div><?php endforeach; ?>
+ </div><?php endforeach; ?>
</div>
<div style="padding:5px 10px;background:#3332328a;border-bottom-left-radius: 10px;border-bottom-right-radius: 10px;position:relative;z-index: 999;backdrop-filter: blur(30px);">
<h2 style="margin-top: 20px;">Cold Haze</h2>
<?php if ($isLoggedIn || $isLowerLoggedIn): ?>
- <p><?= count($byColor) ?> ponies in 3 plural systems</p>
+ <p><?= count($byColor) ?> ponies in 3 plural systems</p>
<?php else: ?>
- <p><?= count($byColor) ?> <?= $lang["home"]["intro"] ?></p>
+ <p><?= count($byColor) ?> <?= $lang["home"]["intro"] ?></p>
<?php endif; ?>
</div>
</div>
+<?php }
+function members() { global $isLoggedIn; global $isLowerLoggedIn; global $app; ?>
<div id="new-homepage" style="margin-top:20px;">
<div id="new-homepage-systems" <?php if ($isLoggedIn || $isLowerLoggedIn): ?>style="grid-template-columns: repeat(3, 1fr);"<?php endif; ?>>
@@ -73,6 +60,110 @@ require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/init.inc"; require_once $_SE
endforeach; ?>
</div>
</div>
+<?php } ?>
+
+<br>
+<div class="container">
+ <?php if (isset($_GET['em'])): ?>
+ <div class="alert alert-danger alert-dismissible">
+ <button onclick='window.history.pushState({"html":null,"pageTitle":document.title},"", "/");' type="button" class="btn-close" data-bs-dismiss="alert"></button>
+ <b><?= $lang["home"]["error"] ?> </b><?= strip_tags(base64_decode($_GET['em'])) ?>
+ </div>
+ <?php endif; ?>
+
+ <?php if ($readOnly && $isNormallyLoggedIn || $readOnly && $isLowerLoggedIn): ?>
+ <div class="alert alert-warning">
+ <b>Notice: </b>This website is temporarily under maintenance and the administrators have locked the database. Although you are logged in as <?= $_PROFILE['name'] ?>, you cannot access any of the logged-in features while the website is under maintenance. <a href="/-/emergency">Alerts dispatching</a> remains possible in case of an emergency.
+ </div>
+ <?php endif; ?>
+
+ <?php
+
+ $cache = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/cache/home.json"), true);
+ if (!isset($cache["banner"])) $cache["banner"] = [];
+ if (!isset($cache["members"])) $cache["members"] = [];
+
+ if (!isset($cache["banner"]["public"])) {
+ ob_start();
+
+ $isLoggedInOldState = $isLoggedIn;
+ $isLowerLoggedInOldState = $isLowerLoggedIn;
+ $isLoggedIn = false;
+ $isLowerLoggedIn = false;
+ banner();
+ $isLoggedIn = $isLoggedInOldState;
+ $isLowerLoggedIn = $isLowerLoggedInOldState;
+
+ $cache["banner"]["public"] = ob_get_contents();
+ ob_end_clean();
+ }
+
+ if (!isset($cache["banner"]["private"])) {
+ ob_start();
+
+ $isLoggedInOldState = $isLoggedIn;
+ $isLowerLoggedInOldState = $isLowerLoggedIn;
+ $isLoggedIn = true;
+ $isLowerLoggedIn = true;
+ banner();
+ $isLoggedIn = $isLoggedInOldState;
+ $isLowerLoggedIn = $isLowerLoggedInOldState;
+
+ $cache["banner"]["private"] = ob_get_contents();
+ ob_end_clean();
+ }
+
+ if (!isset($cache["members"]["public"])) {
+ ob_start();
+
+ $isLoggedInOldState = $isLoggedIn;
+ $isLowerLoggedInOldState = $isLowerLoggedIn;
+ $isLoggedIn = false;
+ $isLowerLoggedIn = false;
+ members();
+ $isLoggedIn = $isLoggedInOldState;
+ $isLowerLoggedIn = $isLowerLoggedInOldState;
+
+ $cache["members"]["public"] = ob_get_contents();
+ ob_end_clean();
+ }
+
+ if (!isset($cache["members"]["private"])) {
+ ob_start();
+
+ $isLoggedInOldState = $isLoggedIn;
+ $isLowerLoggedInOldState = $isLowerLoggedIn;
+ $isLoggedIn = true;
+ $isLowerLoggedIn = true;
+ members();
+ $isLoggedIn = $isLoggedInOldState;
+ $isLowerLoggedIn = $isLowerLoggedInOldState;
+
+ $cache["members"]["private"] = ob_get_contents();
+ ob_end_clean();
+ }
+
+ if ($isLowerLoggedIn || $isLoggedIn) {
+ echo($cache["banner"]["private"]);
+ } else {
+ echo($cache["banner"]["public"]);
+ }
+
+ ?>
+
+ <div class="alert alert-warning" style="margin-top:20px;">
+ <b>Notice:</b> The administrators are currently trying a new optimisation technique based on a virtual file system (chvfs). Data loss, corruption or inconsistency may happen and should be reported on <a href="https://bugs.equestria.dev/issues/CH" target="_blank">bugs.equestria.dev</a>.
+ </div>
+
+ <?php
+
+ if ($isLowerLoggedIn || $isLoggedIn) {
+ echo($cache["members"]["private"]);
+ } else {
+ echo($cache["members"]["public"]);
+ }
+
+ ?>
</div>
-<?php require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/components/footer.inc'; ?> \ No newline at end of file
+<?php file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/cache/home.json", json_encode($cache)); require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/components/footer.inc'; ?> \ No newline at end of file
diff --git a/pages/jobs.inc b/pages/jobs.inc
index ae1405d..109b484 100644
--- a/pages/jobs.inc
+++ b/pages/jobs.inc
@@ -86,31 +86,11 @@ $history = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/
<b>Job lifetime:</b>
<ul class="tracking">
- <li><b>Queued by PHP</b><br><?php
- $d = explode("|", substr(date('M jS Y, G:i|s.u', strtotime($item["tracking"]["queue"])), 0, -3));
- $d[1] = (string)((float)$d[1]);
- echo($d[0] . ":" . $d[1]);
- ?></li>
- <?php if (isset($item["tracking"]["pickup"])): ?><li><b>Picked up by the runner</b><br><?php
- $d = explode("|", substr(date('M jS Y, G:i|s.u', strtotime($item["tracking"]["pickup"])), 0, -3));
- $d[1] = (string)((float)$d[1]);
- echo($d[0] . ":" . $d[1]);
- ?></li><?php endif; ?>
- <?php if (isset($item["tracking"]["start"])): ?><li><b>Started</b><br><?php
- $d = explode("|", substr(date('M jS Y, G:i|s.u', strtotime($item["tracking"]["start"])), 0, -3));
- $d[1] = (string)((float)$d[1]);
- echo($d[0] . ":" . $d[1]);
- ?></li><?php endif; ?>
- <?php if (isset($item["tracking"]["end"])): ?><li><b><?= $item["completed"] ? "Finished" : "Failed" ?></b><br><?php
- $d = explode("|", substr(date('M jS Y, G:i|s.u', strtotime($item["tracking"]["end"])), 0, -3));
- $d[1] = (string)((float)$d[1]);
- echo($d[0] . ":" . $d[1]);
- ?></li><?php endif; ?>
- <?php if (isset($item["tracking"]["logged"])): ?><li><b>Tracking logged</b><br><?php
- $d = explode("|", substr(date('M jS Y, G:i|s.u', strtotime($item["tracking"]["logged"])), 0, -3));
- $d[1] = (string)((float)$d[1]);
- echo($d[0] . ":" . $d[1]);
- ?></li><?php endif; ?>
+ <li><b>Queued by PHP</b><br><?= date('M jS Y, G:i:s', strtotime($item["tracking"]["queue"])) ?></li>
+ <?php if (isset($item["tracking"]["pickup"])): ?><li><b>Picked up by the runner</b><br><?= date('M jS Y, G:i:s', strtotime($item["tracking"]["pickup"])) ?></li><?php endif; ?>
+ <?php if (isset($item["tracking"]["start"])): ?><li><b>Started</b><br><?= date('M jS Y, G:i:s', strtotime($item["tracking"]["start"])) ?></li><?php endif; ?>
+ <?php if (isset($item["tracking"]["end"])): ?><li><b><?= $item["completed"] ? "Finished" : "Failed" ?></b><br><?= date('M jS Y, G:i:s', strtotime($item["tracking"]["end"])) ?></li><?php endif; ?>
+ <?php if (isset($item["tracking"]["logged"])): ?><li><b>Tracking logged</b><br><?= date('M jS Y, G:i:s', strtotime($item["tracking"]["logged"])) ?></li><?php endif; ?>
</ul>
</div>
</div>
diff --git a/pages/metadata.inc b/pages/metadata.inc
index adde5e7..04579a8 100644
--- a/pages/metadata.inc
+++ b/pages/metadata.inc
@@ -178,6 +178,8 @@ if ($member === null) {
if (trim($metadata["species"][1]) === "") unset($metadata["species"][1]);
file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/metadata/" . $memberID . ".json", json_encode($metadata));
+ file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/cache/navigation.json", "{}");
+ file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/cache/home.json", "{}");
header("Location: /" . $_GET['_']);
} else {
diff --git a/pages/money.inc b/pages/money.inc
index 15a7a83..387f5c0 100644
--- a/pages/money.inc
+++ b/pages/money.inc
@@ -64,16 +64,16 @@ if ((isset($_GET["create"]) || isset($_GET["delete"])) && isset($parts[2])) {
if (!isset($_GET["description"])) $_GET["description"] = "";
$_GET["description"] = substr($_GET["description"], 0, 150);
- $ntfy = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true)["ntfy"];
+ $ntfy = $GLOBALS["ColdHazeApp"]["ntfy"];
file_get_contents('https://' . $ntfy["server"] . '/' . $ntfy["topic"], false, stream_context_create([
'http' => [
'method' => 'POST',
'header' =>
"Content-Type: text/plain\r\n" .
- "Title: " . (getMember($myId)["display_name"] ?? getMember($myId)["name"]) . " created a transaction to " . $account["name"] . " (" . ucfirst($account["owner"]) . ")\r\n" .
+ "Title: " . formatPonypush((getMember($myId)["display_name"] ?? getMember($myId)["name"]) . " created a transaction to " . $account["name"] . " (" . ucfirst($account["owner"]) . ")") . "\r\n" .
"Tags: bits\r\n" .
"Authorization: Basic " . base64_encode($ntfy["user"] . ":" . $ntfy["password"]),
- 'content' => ($account["currency"] === "gbp" ? "£" : "€") . abs((float)$_GET["amount"]) . " were " . ((float)$_GET["amount"] >= 0 ? "added" : "removed") . " just now" . (trim($_GET["description"]) !== "" ? ": " . $_GET["description"] : "")
+ 'content' => formatPonypush(($account["currency"] === "gbp" ? "£" : "€") . abs((float)$_GET["amount"]) . " were " . ((float)$_GET["amount"] >= 0 ? "added" : "removed") . " just now" . (trim($_GET["description"]) !== "" ? ": " . $_GET["description"] : ""))
]
]));
@@ -90,16 +90,16 @@ if ((isset($_GET["create"]) || isset($_GET["delete"])) && isset($parts[2])) {
file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/money/" . $name . ".json", json_encode($account, JSON_PRETTY_PRINT));
} else {
if (isset($_GET["id"]) && is_numeric($_GET["id"]) && isset($account["transactions"][(int)$_GET["id"]])) {
- $ntfy = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true)["ntfy"];
+ $ntfy = $GLOBALS["ColdHazeApp"]["ntfy"];
file_get_contents('https://' . $ntfy["server"] . '/' . $ntfy["topic"], false, stream_context_create([
'http' => [
'method' => 'POST',
'header' =>
"Content-Type: text/plain\r\n" .
- "Title: " . (getMember($myId)["display_name"] ?? getMember($myId)["name"]) . " deleted a transaction from " . $account["name"] . " (" . ucfirst($account["owner"]) . ")\r\n" .
+ "Title: " . formatPonypush((getMember($myId)["display_name"] ?? getMember($myId)["name"]) . " deleted a transaction from " . $account["name"] . " (" . ucfirst($account["owner"]) . ")") . "\r\n" .
"Tags: bits\r\n" .
"Authorization: Basic " . base64_encode($ntfy["user"] . ":" . $ntfy["password"]),
- 'content' => ($account["currency"] === "gbp" ? "£" : "€") . abs((float)$account["transactions"][(int)$_GET["id"]]["amount"]) . " " . ((float)$account["transactions"][(int)$_GET["id"]]["amount"] >= 0 ? "advance" : "withdrawal") . " created by " . (getMemberWithoutSystem($account["transactions"][(int)$_GET["id"]]["author"])["display_name"] ?? getMemberWithoutSystem($account["transactions"][(int)$_GET["id"]]["author"])["name"]) . " " . timeAgo($account["transactions"][(int)$_GET["id"]]["date"])
+ 'content' => formatPonypush(($account["currency"] === "gbp" ? "£" : "€") . abs((float)$account["transactions"][(int)$_GET["id"]]["amount"]) . " " . ((float)$account["transactions"][(int)$_GET["id"]]["amount"] >= 0 ? "advance" : "withdrawal") . " created by " . (getMemberWithoutSystem($account["transactions"][(int)$_GET["id"]]["author"])["display_name"] ?? getMemberWithoutSystem($account["transactions"][(int)$_GET["id"]]["author"])["name"]) . " " . timeAgo($account["transactions"][(int)$_GET["id"]]["date"]))
]
]));
diff --git a/pages/page.inc b/pages/page.inc
index 8446062..931e6e5 100644
--- a/pages/page.inc
+++ b/pages/page.inc
@@ -2,7 +2,7 @@
global $lang; global $pages; global $isLoggedIn; global $isLowerLoggedIn;
-$app = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true);
+$app = $GLOBALS["ColdHazeApp"];
$travelling = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/travelling/travelling.json"), true);
if (!isset($_GET['_']) || trim($_GET['_']) === "") peh_error("Invalid request", 400);
diff --git a/pages/travelling.inc b/pages/travelling.inc
index fdcfa99..cfbd4ac 100644
--- a/pages/travelling.inc
+++ b/pages/travelling.inc
@@ -2,7 +2,7 @@
require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/init.inc"; global $title; global $isLoggedIn; global $lang; global $pages; global $isLowerLoggedIn;
$travelling = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/travelling/travelling.json"), true);
-$app = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true);
+$app = $GLOBALS["ColdHazeApp"];
require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/functions.inc";