From e72d0adbcd7e69928a1ef380c7a841c160284c68 Mon Sep 17 00:00:00 2001 From: Minteck Date: Mon, 2 Jan 2023 20:29:01 +0100 Subject: Update --- TODO.md | 19 + api/desktop-app.php | 76 ++ app.php | 6 + app/fronters/profiles/auvwc.png | Bin 504733 -> 272775 bytes app/fronters/profiles/bbrig.png | Bin 148262 -> 148246 bytes app/fronters/profiles/ckqsw.png | Bin 1559833 -> 1559817 bytes app/fronters/profiles/dogwu.png | Bin 2055 -> 2039 bytes app/fronters/profiles/eebmh.png | Bin 570974 -> 570958 bytes app/fronters/profiles/erefx.png | Bin 826530 -> 826514 bytes app/fronters/profiles/erknz.png | Bin 56915 -> 56915 bytes app/fronters/profiles/exudo.png | Bin 298183 -> 298167 bytes app/fronters/profiles/fdaay.png | Bin 181311 -> 181295 bytes app/fronters/profiles/gevde.png | Bin 218853 -> 218837 bytes app/fronters/profiles/gfhsr.png | Bin 2283131 -> 2283131 bytes app/fronters/profiles/ghrby.png | Bin 167497 -> 376305 bytes app/fronters/profiles/hpwyq.png | Bin 2770 -> 2754 bytes app/fronters/profiles/irxyh.png | Bin 26543 -> 26527 bytes app/fronters/profiles/jjzcb.png | Bin 0 -> 299715 bytes app/fronters/profiles/jnbae.png | Bin 60223 -> 60207 bytes app/fronters/profiles/khsbb.png | Bin 249236 -> 249220 bytes app/fronters/profiles/kkhbw.png | Bin 40300 -> 40284 bytes app/fronters/profiles/lllfw.png | Bin 324334 -> 324269 bytes app/fronters/profiles/lqolg.png | Bin 0 -> 13673 bytes app/fronters/profiles/lzlaq.png | Bin 26543 -> 26527 bytes app/fronters/profiles/mglyq.png | Bin 91476 -> 91460 bytes app/fronters/profiles/mhnqy.png | Bin 27452 -> 27436 bytes app/fronters/profiles/mvaws.png | Bin 473421 -> 473421 bytes app/fronters/profiles/pabmo.png | Bin 180390 -> 180374 bytes app/fronters/profiles/qbzxm.png | Bin 2843 -> 2827 bytes app/fronters/profiles/qcemf.png | Bin 370856 -> 370840 bytes app/fronters/profiles/qraku.png | Bin 45898 -> 45882 bytes app/fronters/profiles/rdstg.png | Bin 16881 -> 16881 bytes app/fronters/profiles/rirgf.png | Bin 156974 -> 156958 bytes app/fronters/profiles/rpjok.png | Bin 1542918 -> 1542902 bytes app/fronters/profiles/sbxze.png | Bin 50554 -> 50538 bytes app/fronters/profiles/sehke.png | Bin 1947 -> 1931 bytes app/fronters/profiles/sjuao.png | Bin 1045570 -> 1045570 bytes app/fronters/profiles/tfbob.png | Bin 14049 -> 14033 bytes app/fronters/profiles/ufadt.png | Bin 339704 -> 339688 bytes app/fronters/profiles/umbyl.png | Bin 0 -> 574344 bytes app/fronters/profiles/vncoa.png | Bin 46475 -> 46459 bytes app/fronters/profiles/vvsxf.png | Bin 61010 -> 60994 bytes app/fronters/profiles/xbvwt.png | Bin 70342 -> 70326 bytes app/fronters/profiles/xcjhj.png | Bin 107053 -> 107037 bytes app/fronters/profiles/yhbrc.png | Bin 494559 -> 494543 bytes app/fronters/profiles/zajrk.png | Bin 2097 -> 2081 bytes app/fronters/profiles/zdtsg.png | Bin 1628 -> 1612 bytes app/fronters/profiles/zhtzs.png | Bin 673763 -> 673763 bytes app/fronters/profiles/ztfjz.png | Bin 184686 -> 184670 bytes app/fronters/profiles/zzise.png | Bin 69145 -> 69129 bytes app/sw.js | 5 + assets/logo/custom.css | 56 +- assets/uploads/pt-cinnamonfire.png | Bin 0 -> 4749 bytes assets/uploads/pt-cozyglow.png | Bin 0 -> 6561 bytes assets/uploads/pt-creamychocolate.png | Bin 0 -> 5227 bytes includes/Parsedown.php | 1994 +++++++++++++++++++++++++++++++++ includes/banner.inc | 8 - includes/bitset.inc | 65 +- includes/details.inc | 112 +- includes/footer.inc | 3 +- includes/fullbanner.inc | 63 -- includes/functions.inc | 13 +- includes/maintenance/clearUnused.php | 34 + includes/member.inc | 66 +- includes/navigation.inc | 13 +- includes/pages.json | 13 +- includes/planner.inc | 132 ++- includes/refresh.php | 22 +- includes/sysbanner.inc | 2 +- includes/system/species.inc | 4 +- pages/bitset.inc | 440 ++++---- pages/page.inc | 4 + pages/relations.inc | 10 +- pages/rules-old.inc | 253 +---- pages/rules.inc | 251 +++-- pages/stats.inc | 226 ++++ spec.md | 253 ----- 77 files changed, 3114 insertions(+), 1029 deletions(-) create mode 100644 TODO.md create mode 100644 api/desktop-app.php create mode 100644 app/fronters/profiles/jjzcb.png create mode 100644 app/fronters/profiles/lqolg.png create mode 100644 app/fronters/profiles/umbyl.png create mode 100644 assets/uploads/pt-cinnamonfire.png create mode 100644 assets/uploads/pt-cozyglow.png create mode 100644 assets/uploads/pt-creamychocolate.png create mode 100644 includes/Parsedown.php create mode 100644 includes/maintenance/clearUnused.php delete mode 100644 spec.md diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..a9c7970 --- /dev/null +++ b/TODO.md @@ -0,0 +1,19 @@ +# TODO + +- [ ] Make all pony heads the same size (square) +- [ ] Add a "similar pages" section in the navigation bar + +---- + +- [x] Remove "Sensitivity" item +- [x] Rename "Sexually active" to "Preemptive sexual consent" +- [x] Add romantic/sexual alignments + - [x] Aromantic/asexual (default), straight, gay/lesbian (pick automatically depending on gender), bi, pan + - [x] Leave some bits for potentially more options +- [x] Toggles for sexual polyamory, romantic polyamory, or both +- [x] Actually remove deprecated values +- [x] Allow caretakers for younger ponies (not just littles) +- [x] Redesign the "systems rules" page +- [x] Add Markdown support +- [x] Redesign the bitset calculator +- [x] Redesign the "front planner" page \ No newline at end of file diff --git a/api/desktop-app.php b/api/desktop-app.php new file mode 100644 index 0000000..20b1da9 --- /dev/null +++ b/api/desktop-app.php @@ -0,0 +1,76 @@ + 0) { + $fronter = $fronters[0]; + $info = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/metadata/" . $fronter["id"] . ".json"), true); + $info = parseMetadata($info); + + if (isset($info["birth"]["age"]) && $info["birth"]["age"] < 16 && $info["birth"]["age"] > 0) { + $proceed = false; + } else if (isset($info["birth"]["year"]) && $info["birth"]["year"] > 1900) { + if (!isset($info["birth"]["date"])) $info["birth"]["date"] = "01-01"; + + $age = (int)date('Y') - $info["birth"]["year"] + (strtotime(date('Y') . "-" . $info["birth"]["date"]) <= time() ? 0 : -1); + + if ($age < 16) { + $proceed = false; + } + } + } + } + + if ($proceed) { + $data = $keys[$_GET["id"]]["config"] ?? []; + } +} else { + $data = [ + "success" => true, + "valid" => false, + "underage" => false, + "normal" => false + ]; + + $keys = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/permitted.json"), true); + + if (in_array($_GET["id"], array_keys($keys))) { + $data["valid"] = true; + + $fronters = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/" . $keys[$_GET["id"]]["system"] . "/fronters.json"), true)["members"]; + + if (count($fronters) > 0) { + $fronter = $fronters[0]; + $info = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/metadata/" . $fronter["id"] . ".json"), true); + $info = parseMetadata($info); + + $data["normal"] = $info["sexually_active"]; + + if (isset($info["birth"]["age"]) && $info["birth"]["age"] < 16 && $info["birth"]["age"] > 0) { + $data["underage"] = true; + } else if (isset($info["birth"]["year"]) && $info["birth"]["year"] > 1900) { + if (!isset($info["birth"]["date"])) $info["birth"]["date"] = "01-01"; + + $age = (int)date('Y') - $info["birth"]["year"] + (strtotime(date('Y') . "-" . $info["birth"]["date"]) <= time() ? 0 : -1); + + if ($age < 16) { + $data["underage"] = true; + } + } + } + } +} + +die(json_encode($data)); \ No newline at end of file diff --git a/app.php b/app.php index d4ed154..7bfbe29 100644 --- a/app.php +++ b/app.php @@ -18,6 +18,7 @@ if (in_array($toplevel, ["editor", "icons", "species", "uploads"])) { header("Location: /?error=" . $lang["app"]["file"]) and die(); } } elseif ($toplevel === "") { + $pageFile = $_SERVER['DOCUMENT_ROOT'] . "/pages/home.inc"; require_once $_SERVER['DOCUMENT_ROOT'] . "/pages/home.inc"; } else { if ($toplevel === "-") { @@ -25,12 +26,14 @@ if (in_array($toplevel, ["editor", "icons", "species", "uploads"])) { $toplevel = explode("/", $pagename)[0]; if (file_exists($_SERVER['DOCUMENT_ROOT'] . "/pages/" . $toplevel . ".inc")) { + $pageFile = $_SERVER['DOCUMENT_ROOT'] . "/pages/" . $toplevel . ".inc"; require_once $_SERVER['DOCUMENT_ROOT'] . "/pages/" . $toplevel . ".inc"; } else { header("Location: /?error=" . $lang["app"]["page"] . " " . strip_tags($pagename)) and die(); } } else if ($toplevel === "api") { if (file_exists($_SERVER['DOCUMENT_ROOT'] . "/pages/" . $toplevel . ".inc")) { + $pageFile = $_SERVER['DOCUMENT_ROOT'] . "/pages/" . $toplevel . ".inc"; require_once $_SERVER['DOCUMENT_ROOT'] . "/pages/" . $toplevel . ".inc"; } } else if ($toplevel === "cloudburst" || $toplevel === "raindrops") { @@ -50,6 +53,7 @@ if (in_array($toplevel, ["editor", "icons", "species", "uploads"])) { die(); } + $pageFile = $_SERVER['DOCUMENT_ROOT'] . "/pages/page.inc"; require_once $_SERVER['DOCUMENT_ROOT'] . "/pages/page.inc"; } else { if (file_exists($_SERVER['DOCUMENT_ROOT'] . "/pages/" . $toplevel . ".inc")) { @@ -63,9 +67,11 @@ if (in_array($toplevel, ["editor", "icons", "species", "uploads"])) { }, json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/gdapd/members.json"), true)), "unknown-rd"]; if ((in_array($toplevel, $namesCloudburst) || in_array($toplevel, $namesRaindrops)) && $toplevel !== "unknown") { + $pageFile = $_SERVER['DOCUMENT_ROOT'] . "/pages/page.inc"; require_once $_SERVER['DOCUMENT_ROOT'] . "/pages/page.inc"; } else { global $toplevel; + $pageFile = $_SERVER['DOCUMENT_ROOT'] . "/includes/short.inc"; require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/short.inc"; } } diff --git a/app/fronters/profiles/auvwc.png b/app/fronters/profiles/auvwc.png index ac404f7..8eeb044 100644 Binary files a/app/fronters/profiles/auvwc.png and b/app/fronters/profiles/auvwc.png differ diff --git a/app/fronters/profiles/bbrig.png b/app/fronters/profiles/bbrig.png index 8dc8c32..138d98f 100644 Binary files a/app/fronters/profiles/bbrig.png and b/app/fronters/profiles/bbrig.png differ diff --git a/app/fronters/profiles/ckqsw.png b/app/fronters/profiles/ckqsw.png index 83acb02..b6aec17 100644 Binary files a/app/fronters/profiles/ckqsw.png and b/app/fronters/profiles/ckqsw.png differ diff --git a/app/fronters/profiles/dogwu.png b/app/fronters/profiles/dogwu.png index 60302f5..d0ad4cc 100644 Binary files a/app/fronters/profiles/dogwu.png and b/app/fronters/profiles/dogwu.png differ diff --git a/app/fronters/profiles/eebmh.png b/app/fronters/profiles/eebmh.png index 07cb4fc..b09a6c8 100644 Binary files a/app/fronters/profiles/eebmh.png and b/app/fronters/profiles/eebmh.png differ diff --git a/app/fronters/profiles/erefx.png b/app/fronters/profiles/erefx.png index 3c9be5f..0b908c1 100644 Binary files a/app/fronters/profiles/erefx.png and b/app/fronters/profiles/erefx.png differ diff --git a/app/fronters/profiles/erknz.png b/app/fronters/profiles/erknz.png index 96e6dc8..6b08bd7 100644 Binary files a/app/fronters/profiles/erknz.png and b/app/fronters/profiles/erknz.png differ diff --git a/app/fronters/profiles/exudo.png b/app/fronters/profiles/exudo.png index 9118868..0a90596 100644 Binary files a/app/fronters/profiles/exudo.png and b/app/fronters/profiles/exudo.png differ diff --git a/app/fronters/profiles/fdaay.png b/app/fronters/profiles/fdaay.png index 602a400..6631d16 100644 Binary files a/app/fronters/profiles/fdaay.png and b/app/fronters/profiles/fdaay.png differ diff --git a/app/fronters/profiles/gevde.png b/app/fronters/profiles/gevde.png index 11837f5..677e598 100644 Binary files a/app/fronters/profiles/gevde.png and b/app/fronters/profiles/gevde.png differ diff --git a/app/fronters/profiles/gfhsr.png b/app/fronters/profiles/gfhsr.png index b8a7eec..7132071 100644 Binary files a/app/fronters/profiles/gfhsr.png and b/app/fronters/profiles/gfhsr.png differ diff --git a/app/fronters/profiles/ghrby.png b/app/fronters/profiles/ghrby.png index 2e7fd08..b176521 100644 Binary files a/app/fronters/profiles/ghrby.png and b/app/fronters/profiles/ghrby.png differ diff --git a/app/fronters/profiles/hpwyq.png b/app/fronters/profiles/hpwyq.png index 6ad6c22..87b3078 100644 Binary files a/app/fronters/profiles/hpwyq.png and b/app/fronters/profiles/hpwyq.png differ diff --git a/app/fronters/profiles/irxyh.png b/app/fronters/profiles/irxyh.png index cee8265..a77bec6 100644 Binary files a/app/fronters/profiles/irxyh.png and b/app/fronters/profiles/irxyh.png differ diff --git a/app/fronters/profiles/jjzcb.png b/app/fronters/profiles/jjzcb.png new file mode 100644 index 0000000..0800abb Binary files /dev/null and b/app/fronters/profiles/jjzcb.png differ diff --git a/app/fronters/profiles/jnbae.png b/app/fronters/profiles/jnbae.png index a7e8a6e..dc71f43 100644 Binary files a/app/fronters/profiles/jnbae.png and b/app/fronters/profiles/jnbae.png differ diff --git a/app/fronters/profiles/khsbb.png b/app/fronters/profiles/khsbb.png index d7a0595..34998de 100644 Binary files a/app/fronters/profiles/khsbb.png and b/app/fronters/profiles/khsbb.png differ diff --git a/app/fronters/profiles/kkhbw.png b/app/fronters/profiles/kkhbw.png index e943fa5..c461040 100644 Binary files a/app/fronters/profiles/kkhbw.png and b/app/fronters/profiles/kkhbw.png differ diff --git a/app/fronters/profiles/lllfw.png b/app/fronters/profiles/lllfw.png index 64fe05d..98526b2 100644 Binary files a/app/fronters/profiles/lllfw.png and b/app/fronters/profiles/lllfw.png differ diff --git a/app/fronters/profiles/lqolg.png b/app/fronters/profiles/lqolg.png new file mode 100644 index 0000000..beace78 Binary files /dev/null and b/app/fronters/profiles/lqolg.png differ diff --git a/app/fronters/profiles/lzlaq.png b/app/fronters/profiles/lzlaq.png index 98846f5..01ccf00 100644 Binary files a/app/fronters/profiles/lzlaq.png and b/app/fronters/profiles/lzlaq.png differ diff --git a/app/fronters/profiles/mglyq.png b/app/fronters/profiles/mglyq.png index 52c60f4..17b3156 100644 Binary files a/app/fronters/profiles/mglyq.png and b/app/fronters/profiles/mglyq.png differ diff --git a/app/fronters/profiles/mhnqy.png b/app/fronters/profiles/mhnqy.png index c3e1d43..7966132 100644 Binary files a/app/fronters/profiles/mhnqy.png and b/app/fronters/profiles/mhnqy.png differ diff --git a/app/fronters/profiles/mvaws.png b/app/fronters/profiles/mvaws.png index 3d152a9..ae45fef 100644 Binary files a/app/fronters/profiles/mvaws.png and b/app/fronters/profiles/mvaws.png differ diff --git a/app/fronters/profiles/pabmo.png b/app/fronters/profiles/pabmo.png index 7f8f020..117a75f 100644 Binary files a/app/fronters/profiles/pabmo.png and b/app/fronters/profiles/pabmo.png differ diff --git a/app/fronters/profiles/qbzxm.png b/app/fronters/profiles/qbzxm.png index 3679f54..e2848ad 100644 Binary files a/app/fronters/profiles/qbzxm.png and b/app/fronters/profiles/qbzxm.png differ diff --git a/app/fronters/profiles/qcemf.png b/app/fronters/profiles/qcemf.png index 609d535..debf1a7 100644 Binary files a/app/fronters/profiles/qcemf.png and b/app/fronters/profiles/qcemf.png differ diff --git a/app/fronters/profiles/qraku.png b/app/fronters/profiles/qraku.png index 10858a6..50d25b5 100644 Binary files a/app/fronters/profiles/qraku.png and b/app/fronters/profiles/qraku.png differ diff --git a/app/fronters/profiles/rdstg.png b/app/fronters/profiles/rdstg.png index 2fbc54c..e1f15af 100644 Binary files a/app/fronters/profiles/rdstg.png and b/app/fronters/profiles/rdstg.png differ diff --git a/app/fronters/profiles/rirgf.png b/app/fronters/profiles/rirgf.png index 152c8e2..36a45d6 100644 Binary files a/app/fronters/profiles/rirgf.png and b/app/fronters/profiles/rirgf.png differ diff --git a/app/fronters/profiles/rpjok.png b/app/fronters/profiles/rpjok.png index c38ba39..950a4fa 100644 Binary files a/app/fronters/profiles/rpjok.png and b/app/fronters/profiles/rpjok.png differ diff --git a/app/fronters/profiles/sbxze.png b/app/fronters/profiles/sbxze.png index 2744fff..6d7b24a 100644 Binary files a/app/fronters/profiles/sbxze.png and b/app/fronters/profiles/sbxze.png differ diff --git a/app/fronters/profiles/sehke.png b/app/fronters/profiles/sehke.png index 0e6f956..0d2c424 100644 Binary files a/app/fronters/profiles/sehke.png and b/app/fronters/profiles/sehke.png differ diff --git a/app/fronters/profiles/sjuao.png b/app/fronters/profiles/sjuao.png index 12f5b33..b19ce07 100644 Binary files a/app/fronters/profiles/sjuao.png and b/app/fronters/profiles/sjuao.png differ diff --git a/app/fronters/profiles/tfbob.png b/app/fronters/profiles/tfbob.png index 4cc390a..ea78bea 100644 Binary files a/app/fronters/profiles/tfbob.png and b/app/fronters/profiles/tfbob.png differ diff --git a/app/fronters/profiles/ufadt.png b/app/fronters/profiles/ufadt.png index 4af6c47..b0a09a8 100644 Binary files a/app/fronters/profiles/ufadt.png and b/app/fronters/profiles/ufadt.png differ diff --git a/app/fronters/profiles/umbyl.png b/app/fronters/profiles/umbyl.png new file mode 100644 index 0000000..119321c Binary files /dev/null and b/app/fronters/profiles/umbyl.png differ diff --git a/app/fronters/profiles/vncoa.png b/app/fronters/profiles/vncoa.png index 9636278..bdf4e80 100644 Binary files a/app/fronters/profiles/vncoa.png and b/app/fronters/profiles/vncoa.png differ diff --git a/app/fronters/profiles/vvsxf.png b/app/fronters/profiles/vvsxf.png index 74f542e..95a1b4b 100644 Binary files a/app/fronters/profiles/vvsxf.png and b/app/fronters/profiles/vvsxf.png differ diff --git a/app/fronters/profiles/xbvwt.png b/app/fronters/profiles/xbvwt.png index 4b6c8d3..f8fb01a 100644 Binary files a/app/fronters/profiles/xbvwt.png and b/app/fronters/profiles/xbvwt.png differ diff --git a/app/fronters/profiles/xcjhj.png b/app/fronters/profiles/xcjhj.png index 4ab828d..ada7641 100644 Binary files a/app/fronters/profiles/xcjhj.png and b/app/fronters/profiles/xcjhj.png differ diff --git a/app/fronters/profiles/yhbrc.png b/app/fronters/profiles/yhbrc.png index 8b07487..e7949d1 100644 Binary files a/app/fronters/profiles/yhbrc.png and b/app/fronters/profiles/yhbrc.png differ diff --git a/app/fronters/profiles/zajrk.png b/app/fronters/profiles/zajrk.png index e6cfa68..6ab4b18 100644 Binary files a/app/fronters/profiles/zajrk.png and b/app/fronters/profiles/zajrk.png differ diff --git a/app/fronters/profiles/zdtsg.png b/app/fronters/profiles/zdtsg.png index 6ec0b3c..2e62832 100644 Binary files a/app/fronters/profiles/zdtsg.png and b/app/fronters/profiles/zdtsg.png differ diff --git a/app/fronters/profiles/zhtzs.png b/app/fronters/profiles/zhtzs.png index b65fd37..a5aee05 100644 Binary files a/app/fronters/profiles/zhtzs.png and b/app/fronters/profiles/zhtzs.png differ diff --git a/app/fronters/profiles/ztfjz.png b/app/fronters/profiles/ztfjz.png index 7765304..0308b63 100644 Binary files a/app/fronters/profiles/ztfjz.png and b/app/fronters/profiles/ztfjz.png differ diff --git a/app/fronters/profiles/zzise.png b/app/fronters/profiles/zzise.png index 76b1996..46ad36e 100644 Binary files a/app/fronters/profiles/zzise.png and b/app/fronters/profiles/zzise.png differ diff --git a/app/sw.js b/app/sw.js index e002932..5a17da4 100644 --- a/app/sw.js +++ b/app/sw.js @@ -127,6 +127,7 @@ let filesToCache = [ "/app/fronters/profiles/hpwyq.png", "/app/fronters/profiles/irxyh.png", "/app/fronters/profiles/jhemc.png", + "/app/fronters/profiles/jjzcb.png", "/app/fronters/profiles/jlpjf.png", "/app/fronters/profiles/jmasq.png", "/app/fronters/profiles/jnbae.png", @@ -137,6 +138,7 @@ let filesToCache = [ "/app/fronters/profiles/kzfjn.png", "/app/fronters/profiles/lllfw.png", "/app/fronters/profiles/lqahp.png", + "/app/fronters/profiles/lqolg.png", "/app/fronters/profiles/lyotd.png", "/app/fronters/profiles/lzlaq.png", "/app/fronters/profiles/mglyq.png", @@ -179,6 +181,7 @@ let filesToCache = [ "/app/fronters/profiles/ughya.png", "/app/fronters/profiles/uhfic.png", "/app/fronters/profiles/uicxr.png", + "/app/fronters/profiles/umbyl.png", "/app/fronters/profiles/vahcl.png", "/app/fronters/profiles/vaxyy.png", "/app/fronters/profiles/vncoa.png", @@ -230,8 +233,10 @@ let filesToCache = [ "/assets/uploads/pt-applebloom.png", "/assets/uploads/pt-babsseed.png", "/assets/uploads/pt-blueberrycloud.png", + "/assets/uploads/pt-cinnamonfire.png", "/assets/uploads/pt-cloudydreams.png", "/assets/uploads/pt-coral.png", + "/assets/uploads/pt-cozyglow.png", "/assets/uploads/pt-dahlia.png", "/assets/uploads/pt-duskrainbow.png", "/assets/uploads/pt-floralorchid.png", diff --git a/assets/logo/custom.css b/assets/logo/custom.css index 76ad1d5..11f29f9 100644 --- a/assets/logo/custom.css +++ b/assets/logo/custom.css @@ -119,15 +119,15 @@ body { } .dropdown-menu { - background-color: #222; + background-color: #222 !important; } .dropdown-item:hover { - background-color: rgba(255, 255, 255, .1); + background-color: rgba(255, 255, 255, .1) !important; } .dropdown-item:active, .dropdown-item:focus { - background-color: rgba(255, 255, 255, .2); + background-color: rgba(255, 255, 255, .2) !important; } .dropdown-item { @@ -135,16 +135,16 @@ body { } .dropdown-icon { - filter: invert(1); + filter: invert(1) !important; } .dropdown-toggle .dropdown-icon { - opacity: .5; - transition: 200ms opacity; + opacity: .5; !important; + transition: 200ms opacity !important; } .dropdown-toggle:hover .dropdown-icon, .dropdown-toggle:active .dropdown-icon, .dropdown-toggle:focus .dropdown-icon { - opacity: .75; + opacity: .75 !important; } dd { @@ -596,7 +596,7 @@ peh-muted { } .dropdown-toggle::after { - margin-bottom: -3px; + margin-bottom: -3px !important; } .navbar-nav { @@ -621,10 +621,27 @@ peh-muted { border-bottom-left-radius: 10px; border-bottom-right-radius: 10px; display: grid; - grid-template-columns: repeat(6, 1fr); + grid-template-columns: repeat(5, 1fr); text-align: center; } +#member-details.member-details-loggedIn { + border-radius: 0; + padding-bottom: 0 !important; +} + +#member-details-2 { + border-bottom-left-radius: 10px; + border-bottom-right-radius: 10px; + padding: 10px 20px; + text-align: center; + display: grid; + grid-template-columns: repeat(3, 1fr); + background: rgba(255, 255, 255, .1); + border: 1px solid transparent; + border-top: none; +} + .navbar-collapse.show { z-index: 99999; } @@ -646,6 +663,27 @@ peh-muted { grid-template-columns: repeat(2, 1fr) !important; text-align: left; } + + #member-details-2 { + grid-template-columns: repeat(2, 1fr) !important; + text-align: left; + } + + .member-detail-desktop { + display: none !important; + } + + .member-detail-mobile { + display: initial !important; + } +} + +.member-detail-desktop { + display: initial; +} + +.member-detail-mobile { + display: none; } .linked-card { diff --git a/assets/uploads/pt-cinnamonfire.png b/assets/uploads/pt-cinnamonfire.png new file mode 100644 index 0000000..c23c1ad Binary files /dev/null and b/assets/uploads/pt-cinnamonfire.png differ diff --git a/assets/uploads/pt-cozyglow.png b/assets/uploads/pt-cozyglow.png new file mode 100644 index 0000000..4f65ee9 Binary files /dev/null and b/assets/uploads/pt-cozyglow.png differ diff --git a/assets/uploads/pt-creamychocolate.png b/assets/uploads/pt-creamychocolate.png new file mode 100644 index 0000000..1cb2699 Binary files /dev/null and b/assets/uploads/pt-creamychocolate.png differ diff --git a/includes/Parsedown.php b/includes/Parsedown.php new file mode 100644 index 0000000..3e29589 --- /dev/null +++ b/includes/Parsedown.php @@ -0,0 +1,1994 @@ +textElements($text); + + # convert to markup + $markup = $this->elements($Elements); + + # trim line breaks + $markup = trim($markup, "\n"); + + return $markup; + } + + protected function textElements($text) + { + # make sure no definitions are set + $this->DefinitionData = array(); + + # standardize line breaks + $text = str_replace(array("\r\n", "\r"), "\n", $text); + + # remove surrounding line breaks + $text = trim($text, "\n"); + + # split text into lines + $lines = explode("\n", $text); + + # iterate through lines to identify blocks + return $this->linesElements($lines); + } + + # + # Setters + # + + function setBreaksEnabled($breaksEnabled) + { + $this->breaksEnabled = $breaksEnabled; + + return $this; + } + + protected $breaksEnabled; + + function setMarkupEscaped($markupEscaped) + { + $this->markupEscaped = $markupEscaped; + + return $this; + } + + protected $markupEscaped; + + function setUrlsLinked($urlsLinked) + { + $this->urlsLinked = $urlsLinked; + + return $this; + } + + protected $urlsLinked = true; + + function setSafeMode($safeMode) + { + $this->safeMode = (bool) $safeMode; + + return $this; + } + + protected $safeMode; + + function setStrictMode($strictMode) + { + $this->strictMode = (bool) $strictMode; + + return $this; + } + + protected $strictMode; + + protected $safeLinksWhitelist = array( + 'http://', + 'https://', + 'ftp://', + 'ftps://', + 'mailto:', + 'tel:', + 'data:image/png;base64,', + 'data:image/gif;base64,', + 'data:image/jpeg;base64,', + 'irc:', + 'ircs:', + 'git:', + 'ssh:', + 'news:', + 'steam:', + ); + + # + # Lines + # + + protected $BlockTypes = array( + '#' => array('Header'), + '*' => array('Rule', 'List'), + '+' => array('List'), + '-' => array('SetextHeader', 'Table', 'Rule', 'List'), + '0' => array('List'), + '1' => array('List'), + '2' => array('List'), + '3' => array('List'), + '4' => array('List'), + '5' => array('List'), + '6' => array('List'), + '7' => array('List'), + '8' => array('List'), + '9' => array('List'), + ':' => array('Table'), + '<' => array('Comment', 'Markup'), + '=' => array('SetextHeader'), + '>' => array('Quote'), + '[' => array('Reference'), + '_' => array('Rule'), + '`' => array('FencedCode'), + '|' => array('Table'), + '~' => array('FencedCode'), + ); + + # ~ + + protected $unmarkedBlockTypes = array( + 'Code', + ); + + # + # Blocks + # + + protected function lines(array $lines) + { + return $this->elements($this->linesElements($lines)); + } + + protected function linesElements(array $lines) + { + $Elements = array(); + $CurrentBlock = null; + + foreach ($lines as $line) + { + if (chop($line) === '') + { + if (isset($CurrentBlock)) + { + $CurrentBlock['interrupted'] = (isset($CurrentBlock['interrupted']) + ? $CurrentBlock['interrupted'] + 1 : 1 + ); + } + + continue; + } + + while (($beforeTab = strstr($line, "\t", true)) !== false) + { + $shortage = 4 - mb_strlen($beforeTab, 'utf-8') % 4; + + $line = $beforeTab + . str_repeat(' ', $shortage) + . substr($line, strlen($beforeTab) + 1) + ; + } + + $indent = strspn($line, ' '); + + $text = $indent > 0 ? substr($line, $indent) : $line; + + # ~ + + $Line = array('body' => $line, 'indent' => $indent, 'text' => $text); + + # ~ + + if (isset($CurrentBlock['continuable'])) + { + $methodName = 'block' . $CurrentBlock['type'] . 'Continue'; + $Block = $this->$methodName($Line, $CurrentBlock); + + if (isset($Block)) + { + $CurrentBlock = $Block; + + continue; + } + else + { + if ($this->isBlockCompletable($CurrentBlock['type'])) + { + $methodName = 'block' . $CurrentBlock['type'] . 'Complete'; + $CurrentBlock = $this->$methodName($CurrentBlock); + } + } + } + + # ~ + + $marker = $text[0]; + + # ~ + + $blockTypes = $this->unmarkedBlockTypes; + + if (isset($this->BlockTypes[$marker])) + { + foreach ($this->BlockTypes[$marker] as $blockType) + { + $blockTypes []= $blockType; + } + } + + # + # ~ + + foreach ($blockTypes as $blockType) + { + $Block = $this->{"block$blockType"}($Line, $CurrentBlock); + + if (isset($Block)) + { + $Block['type'] = $blockType; + + if ( ! isset($Block['identified'])) + { + if (isset($CurrentBlock)) + { + $Elements[] = $this->extractElement($CurrentBlock); + } + + $Block['identified'] = true; + } + + if ($this->isBlockContinuable($blockType)) + { + $Block['continuable'] = true; + } + + $CurrentBlock = $Block; + + continue 2; + } + } + + # ~ + + if (isset($CurrentBlock) and $CurrentBlock['type'] === 'Paragraph') + { + $Block = $this->paragraphContinue($Line, $CurrentBlock); + } + + if (isset($Block)) + { + $CurrentBlock = $Block; + } + else + { + if (isset($CurrentBlock)) + { + $Elements[] = $this->extractElement($CurrentBlock); + } + + $CurrentBlock = $this->paragraph($Line); + + $CurrentBlock['identified'] = true; + } + } + + # ~ + + if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type'])) + { + $methodName = 'block' . $CurrentBlock['type'] . 'Complete'; + $CurrentBlock = $this->$methodName($CurrentBlock); + } + + # ~ + + if (isset($CurrentBlock)) + { + $Elements[] = $this->extractElement($CurrentBlock); + } + + # ~ + + return $Elements; + } + + protected function extractElement(array $Component) + { + if ( ! isset($Component['element'])) + { + if (isset($Component['markup'])) + { + $Component['element'] = array('rawHtml' => $Component['markup']); + } + elseif (isset($Component['hidden'])) + { + $Component['element'] = array(); + } + } + + return $Component['element']; + } + + protected function isBlockContinuable($Type) + { + return method_exists($this, 'block' . $Type . 'Continue'); + } + + protected function isBlockCompletable($Type) + { + return method_exists($this, 'block' . $Type . 'Complete'); + } + + # + # Code + + protected function blockCode($Line, $Block = null) + { + if (isset($Block) and $Block['type'] === 'Paragraph' and ! isset($Block['interrupted'])) + { + return; + } + + if ($Line['indent'] >= 4) + { + $text = substr($Line['body'], 4); + + $Block = array( + 'element' => array( + 'name' => 'pre', + 'element' => array( + 'name' => 'code', + 'text' => $text, + ), + ), + ); + + return $Block; + } + } + + protected function blockCodeContinue($Line, $Block) + { + if ($Line['indent'] >= 4) + { + if (isset($Block['interrupted'])) + { + $Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']); + + unset($Block['interrupted']); + } + + $Block['element']['element']['text'] .= "\n"; + + $text = substr($Line['body'], 4); + + $Block['element']['element']['text'] .= $text; + + return $Block; + } + } + + protected function blockCodeComplete($Block) + { + return $Block; + } + + # + # Comment + + protected function blockComment($Line) + { + if ($this->markupEscaped or $this->safeMode) + { + return; + } + + if (strpos($Line['text'], '') !== false) + { + $Block['closed'] = true; + } + + return $Block; + } + } + + protected function blockCommentContinue($Line, array $Block) + { + if (isset($Block['closed'])) + { + return; + } + + $Block['element']['rawHtml'] .= "\n" . $Line['body']; + + if (strpos($Line['text'], '-->') !== false) + { + $Block['closed'] = true; + } + + return $Block; + } + + # + # Fenced Code + + protected function blockFencedCode($Line) + { + $marker = $Line['text'][0]; + + $openerLength = strspn($Line['text'], $marker); + + if ($openerLength < 3) + { + return; + } + + $infostring = trim(substr($Line['text'], $openerLength), "\t "); + + if (strpos($infostring, '`') !== false) + { + return; + } + + $Element = array( + 'name' => 'code', + 'text' => '', + ); + + if ($infostring !== '') + { + /** + * https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes + * Every HTML element may have a class attribute specified. + * The attribute, if specified, must have a value that is a set + * of space-separated tokens representing the various classes + * that the element belongs to. + * [...] + * The space characters, for the purposes of this specification, + * are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab), + * U+000A LINE FEED (LF), U+000C FORM FEED (FF), and + * U+000D CARRIAGE RETURN (CR). + */ + $language = substr($infostring, 0, strcspn($infostring, " \t\n\f\r")); + + $Element['attributes'] = array('class' => "language-$language"); + } + + $Block = array( + 'char' => $marker, + 'openerLength' => $openerLength, + 'element' => array( + 'name' => 'pre', + 'element' => $Element, + ), + ); + + return $Block; + } + + protected function blockFencedCodeContinue($Line, $Block) + { + if (isset($Block['complete'])) + { + return; + } + + if (isset($Block['interrupted'])) + { + $Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']); + + unset($Block['interrupted']); + } + + if (($len = strspn($Line['text'], $Block['char'])) >= $Block['openerLength'] + and chop(substr($Line['text'], $len), ' ') === '' + ) { + $Block['element']['element']['text'] = substr($Block['element']['element']['text'], 1); + + $Block['complete'] = true; + + return $Block; + } + + $Block['element']['element']['text'] .= "\n" . $Line['body']; + + return $Block; + } + + protected function blockFencedCodeComplete($Block) + { + return $Block; + } + + # + # Header + + protected function blockHeader($Line) + { + $level = strspn($Line['text'], '#'); + + if ($level > 6) + { + return; + } + + $text = trim($Line['text'], '#'); + + if ($this->strictMode and isset($text[0]) and $text[0] !== ' ') + { + return; + } + + $text = trim($text, ' '); + + $Block = array( + 'element' => array( + 'name' => 'h' . $level, + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $text, + 'destination' => 'elements', + ) + ), + ); + + return $Block; + } + + # + # List + + protected function blockList($Line, array $CurrentBlock = null) + { + list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]{1,9}+[.\)]'); + + if (preg_match('/^('.$pattern.'([ ]++|$))(.*+)/', $Line['text'], $matches)) + { + $contentIndent = strlen($matches[2]); + + if ($contentIndent >= 5) + { + $contentIndent -= 1; + $matches[1] = substr($matches[1], 0, -$contentIndent); + $matches[3] = str_repeat(' ', $contentIndent) . $matches[3]; + } + elseif ($contentIndent === 0) + { + $matches[1] .= ' '; + } + + $markerWithoutWhitespace = strstr($matches[1], ' ', true); + + $Block = array( + 'indent' => $Line['indent'], + 'pattern' => $pattern, + 'data' => array( + 'type' => $name, + 'marker' => $matches[1], + 'markerType' => ($name === 'ul' ? $markerWithoutWhitespace : substr($markerWithoutWhitespace, -1)), + ), + 'element' => array( + 'name' => $name, + 'elements' => array(), + ), + ); + $Block['data']['markerTypeRegex'] = preg_quote($Block['data']['markerType'], '/'); + + if ($name === 'ol') + { + $listStart = ltrim(strstr($matches[1], $Block['data']['markerType'], true), '0') ?: '0'; + + if ($listStart !== '1') + { + if ( + isset($CurrentBlock) + and $CurrentBlock['type'] === 'Paragraph' + and ! isset($CurrentBlock['interrupted']) + ) { + return; + } + + $Block['element']['attributes'] = array('start' => $listStart); + } + } + + $Block['li'] = array( + 'name' => 'li', + 'handler' => array( + 'function' => 'li', + 'argument' => !empty($matches[3]) ? array($matches[3]) : array(), + 'destination' => 'elements' + ) + ); + + $Block['element']['elements'] []= & $Block['li']; + + return $Block; + } + } + + protected function blockListContinue($Line, array $Block) + { + if (isset($Block['interrupted']) and empty($Block['li']['handler']['argument'])) + { + return null; + } + + $requiredIndent = ($Block['indent'] + strlen($Block['data']['marker'])); + + if ($Line['indent'] < $requiredIndent + and ( + ( + $Block['data']['type'] === 'ol' + and preg_match('/^[0-9]++'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches) + ) or ( + $Block['data']['type'] === 'ul' + and preg_match('/^'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches) + ) + ) + ) { + if (isset($Block['interrupted'])) + { + $Block['li']['handler']['argument'] []= ''; + + $Block['loose'] = true; + + unset($Block['interrupted']); + } + + unset($Block['li']); + + $text = isset($matches[1]) ? $matches[1] : ''; + + $Block['indent'] = $Line['indent']; + + $Block['li'] = array( + 'name' => 'li', + 'handler' => array( + 'function' => 'li', + 'argument' => array($text), + 'destination' => 'elements' + ) + ); + + $Block['element']['elements'] []= & $Block['li']; + + return $Block; + } + elseif ($Line['indent'] < $requiredIndent and $this->blockList($Line)) + { + return null; + } + + if ($Line['text'][0] === '[' and $this->blockReference($Line)) + { + return $Block; + } + + if ($Line['indent'] >= $requiredIndent) + { + if (isset($Block['interrupted'])) + { + $Block['li']['handler']['argument'] []= ''; + + $Block['loose'] = true; + + unset($Block['interrupted']); + } + + $text = substr($Line['body'], $requiredIndent); + + $Block['li']['handler']['argument'] []= $text; + + return $Block; + } + + if ( ! isset($Block['interrupted'])) + { + $text = preg_replace('/^[ ]{0,'.$requiredIndent.'}+/', '', $Line['body']); + + $Block['li']['handler']['argument'] []= $text; + + return $Block; + } + } + + protected function blockListComplete(array $Block) + { + if (isset($Block['loose'])) + { + foreach ($Block['element']['elements'] as &$li) + { + if (end($li['handler']['argument']) !== '') + { + $li['handler']['argument'] []= ''; + } + } + } + + return $Block; + } + + # + # Quote + + protected function blockQuote($Line) + { + if (preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches)) + { + $Block = array( + 'element' => array( + 'name' => 'blockquote', + 'handler' => array( + 'function' => 'linesElements', + 'argument' => (array) $matches[1], + 'destination' => 'elements', + ) + ), + ); + + return $Block; + } + } + + protected function blockQuoteContinue($Line, array $Block) + { + if (isset($Block['interrupted'])) + { + return; + } + + if ($Line['text'][0] === '>' and preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches)) + { + $Block['element']['handler']['argument'] []= $matches[1]; + + return $Block; + } + + if ( ! isset($Block['interrupted'])) + { + $Block['element']['handler']['argument'] []= $Line['text']; + + return $Block; + } + } + + # + # Rule + + protected function blockRule($Line) + { + $marker = $Line['text'][0]; + + if (substr_count($Line['text'], $marker) >= 3 and chop($Line['text'], " $marker") === '') + { + $Block = array( + 'element' => array( + 'name' => 'hr', + ), + ); + + return $Block; + } + } + + # + # Setext + + protected function blockSetextHeader($Line, array $Block = null) + { + if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted'])) + { + return; + } + + if ($Line['indent'] < 4 and chop(chop($Line['text'], ' '), $Line['text'][0]) === '') + { + $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2'; + + return $Block; + } + } + + # + # Markup + + protected function blockMarkup($Line) + { + if ($this->markupEscaped or $this->safeMode) + { + return; + } + + if (preg_match('/^<[\/]?+(\w*)(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+(\/)?>/', $Line['text'], $matches)) + { + $element = strtolower($matches[1]); + + if (in_array($element, $this->textLevelElements)) + { + return; + } + + $Block = array( + 'name' => $matches[1], + 'element' => array( + 'rawHtml' => $Line['text'], + 'autobreak' => true, + ), + ); + + return $Block; + } + } + + protected function blockMarkupContinue($Line, array $Block) + { + if (isset($Block['closed']) or isset($Block['interrupted'])) + { + return; + } + + $Block['element']['rawHtml'] .= "\n" . $Line['body']; + + return $Block; + } + + # + # Reference + + protected function blockReference($Line) + { + if (strpos($Line['text'], ']') !== false + and preg_match('/^\[(.+?)\]:[ ]*+?(?:[ ]+["\'(](.+)["\')])?[ ]*+$/', $Line['text'], $matches) + ) { + $id = strtolower($matches[1]); + + $Data = array( + 'url' => $matches[2], + 'title' => isset($matches[3]) ? $matches[3] : null, + ); + + $this->DefinitionData['Reference'][$id] = $Data; + + $Block = array( + 'element' => array(), + ); + + return $Block; + } + } + + # + # Table + + protected function blockTable($Line, array $Block = null) + { + if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted'])) + { + return; + } + + if ( + strpos($Block['element']['handler']['argument'], '|') === false + and strpos($Line['text'], '|') === false + and strpos($Line['text'], ':') === false + or strpos($Block['element']['handler']['argument'], "\n") !== false + ) { + return; + } + + if (chop($Line['text'], ' -:|') !== '') + { + return; + } + + $alignments = array(); + + $divider = $Line['text']; + + $divider = trim($divider); + $divider = trim($divider, '|'); + + $dividerCells = explode('|', $divider); + + foreach ($dividerCells as $dividerCell) + { + $dividerCell = trim($dividerCell); + + if ($dividerCell === '') + { + return; + } + + $alignment = null; + + if ($dividerCell[0] === ':') + { + $alignment = 'left'; + } + + if (substr($dividerCell, - 1) === ':') + { + $alignment = $alignment === 'left' ? 'center' : 'right'; + } + + $alignments []= $alignment; + } + + # ~ + + $HeaderElements = array(); + + $header = $Block['element']['handler']['argument']; + + $header = trim($header); + $header = trim($header, '|'); + + $headerCells = explode('|', $header); + + if (count($headerCells) !== count($alignments)) + { + return; + } + + foreach ($headerCells as $index => $headerCell) + { + $headerCell = trim($headerCell); + + $HeaderElement = array( + 'name' => 'th', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $headerCell, + 'destination' => 'elements', + ) + ); + + if (isset($alignments[$index])) + { + $alignment = $alignments[$index]; + + $HeaderElement['attributes'] = array( + 'style' => "text-align: $alignment;", + ); + } + + $HeaderElements []= $HeaderElement; + } + + # ~ + + $Block = array( + 'alignments' => $alignments, + 'identified' => true, + 'element' => array( + 'name' => 'table', + 'elements' => array(), + ), + ); + + $Block['element']['elements'] []= array( + 'name' => 'thead', + ); + + $Block['element']['elements'] []= array( + 'name' => 'tbody', + 'elements' => array(), + ); + + $Block['element']['elements'][0]['elements'] []= array( + 'name' => 'tr', + 'elements' => $HeaderElements, + ); + + return $Block; + } + + protected function blockTableContinue($Line, array $Block) + { + if (isset($Block['interrupted'])) + { + return; + } + + if (count($Block['alignments']) === 1 or $Line['text'][0] === '|' or strpos($Line['text'], '|')) + { + $Elements = array(); + + $row = $Line['text']; + + $row = trim($row); + $row = trim($row, '|'); + + preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]++`|`)++/', $row, $matches); + + $cells = array_slice($matches[0], 0, count($Block['alignments'])); + + foreach ($cells as $index => $cell) + { + $cell = trim($cell); + + $Element = array( + 'name' => 'td', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $cell, + 'destination' => 'elements', + ) + ); + + if (isset($Block['alignments'][$index])) + { + $Element['attributes'] = array( + 'style' => 'text-align: ' . $Block['alignments'][$index] . ';', + ); + } + + $Elements []= $Element; + } + + $Element = array( + 'name' => 'tr', + 'elements' => $Elements, + ); + + $Block['element']['elements'][1]['elements'] []= $Element; + + return $Block; + } + } + + # + # ~ + # + + protected function paragraph($Line) + { + return array( + 'type' => 'Paragraph', + 'element' => array( + 'name' => 'p', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $Line['text'], + 'destination' => 'elements', + ), + ), + ); + } + + protected function paragraphContinue($Line, array $Block) + { + if (isset($Block['interrupted'])) + { + return; + } + + $Block['element']['handler']['argument'] .= "\n".$Line['text']; + + return $Block; + } + + # + # Inline Elements + # + + protected $InlineTypes = array( + '!' => array('Image'), + '&' => array('SpecialCharacter'), + '*' => array('Emphasis'), + ':' => array('Url'), + '<' => array('UrlTag', 'EmailTag', 'Markup'), + '[' => array('Link'), + '_' => array('Emphasis'), + '`' => array('Code'), + '~' => array('Strikethrough'), + '\\' => array('EscapeSequence'), + ); + + # ~ + + protected $inlineMarkerList = '!*_&[:<`~\\'; + + # + # ~ + # + + public function line($text, $nonNestables = array()) + { + return $this->elements($this->lineElements($text, $nonNestables)); + } + + protected function lineElements($text, $nonNestables = array()) + { + # standardize line breaks + $text = str_replace(array("\r\n", "\r"), "\n", $text); + + $Elements = array(); + + $nonNestables = (empty($nonNestables) + ? array() + : array_combine($nonNestables, $nonNestables) + ); + + # $excerpt is based on the first occurrence of a marker + + while ($excerpt = strpbrk($text, $this->inlineMarkerList)) + { + $marker = $excerpt[0]; + + $markerPosition = strlen($text) - strlen($excerpt); + + $Excerpt = array('text' => $excerpt, 'context' => $text); + + foreach ($this->InlineTypes[$marker] as $inlineType) + { + # check to see if the current inline type is nestable in the current context + + if (isset($nonNestables[$inlineType])) + { + continue; + } + + $Inline = $this->{"inline$inlineType"}($Excerpt); + + if ( ! isset($Inline)) + { + continue; + } + + # makes sure that the inline belongs to "our" marker + + if (isset($Inline['position']) and $Inline['position'] > $markerPosition) + { + continue; + } + + # sets a default inline position + + if ( ! isset($Inline['position'])) + { + $Inline['position'] = $markerPosition; + } + + # cause the new element to 'inherit' our non nestables + + + $Inline['element']['nonNestables'] = isset($Inline['element']['nonNestables']) + ? array_merge($Inline['element']['nonNestables'], $nonNestables) + : $nonNestables + ; + + # the text that comes before the inline + $unmarkedText = substr($text, 0, $Inline['position']); + + # compile the unmarked text + $InlineText = $this->inlineText($unmarkedText); + $Elements[] = $InlineText['element']; + + # compile the inline + $Elements[] = $this->extractElement($Inline); + + # remove the examined text + $text = substr($text, $Inline['position'] + $Inline['extent']); + + continue 2; + } + + # the marker does not belong to an inline + + $unmarkedText = substr($text, 0, $markerPosition + 1); + + $InlineText = $this->inlineText($unmarkedText); + $Elements[] = $InlineText['element']; + + $text = substr($text, $markerPosition + 1); + } + + $InlineText = $this->inlineText($text); + $Elements[] = $InlineText['element']; + + foreach ($Elements as &$Element) + { + if ( ! isset($Element['autobreak'])) + { + $Element['autobreak'] = false; + } + } + + return $Elements; + } + + # + # ~ + # + + protected function inlineText($text) + { + $Inline = array( + 'extent' => strlen($text), + 'element' => array(), + ); + + $Inline['element']['elements'] = self::pregReplaceElements( + $this->breaksEnabled ? '/[ ]*+\n/' : '/(?:[ ]*+\\\\|[ ]{2,}+)\n/', + array( + array('name' => 'br'), + array('text' => "\n"), + ), + $text + ); + + return $Inline; + } + + protected function inlineCode($Excerpt) + { + $marker = $Excerpt['text'][0]; + + if (preg_match('/^(['.$marker.']++)[ ]*+(.+?)[ ]*+(? strlen($matches[0]), + 'element' => array( + 'name' => 'code', + 'text' => $text, + ), + ); + } + } + + protected function inlineEmailTag($Excerpt) + { + $hostnameLabel = '[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?'; + + $commonMarkEmail = '[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]++@' + . $hostnameLabel . '(?:\.' . $hostnameLabel . ')*'; + + if (strpos($Excerpt['text'], '>') !== false + and preg_match("/^<((mailto:)?$commonMarkEmail)>/i", $Excerpt['text'], $matches) + ){ + $url = $matches[1]; + + if ( ! isset($matches[2])) + { + $url = "mailto:$url"; + } + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'a', + 'text' => $matches[1], + 'attributes' => array( + 'href' => $url, + ), + ), + ); + } + } + + protected function inlineEmphasis($Excerpt) + { + if ( ! isset($Excerpt['text'][1])) + { + return; + } + + $marker = $Excerpt['text'][0]; + + if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches)) + { + $emphasis = 'strong'; + } + elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches)) + { + $emphasis = 'em'; + } + else + { + return; + } + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => $emphasis, + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $matches[1], + 'destination' => 'elements', + ) + ), + ); + } + + protected function inlineEscapeSequence($Excerpt) + { + if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters)) + { + return array( + 'element' => array('rawHtml' => $Excerpt['text'][1]), + 'extent' => 2, + ); + } + } + + protected function inlineImage($Excerpt) + { + if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[') + { + return; + } + + $Excerpt['text']= substr($Excerpt['text'], 1); + + $Link = $this->inlineLink($Excerpt); + + if ($Link === null) + { + return; + } + + $Inline = array( + 'extent' => $Link['extent'] + 1, + 'element' => array( + 'name' => 'img', + 'attributes' => array( + 'src' => $Link['element']['attributes']['href'], + 'alt' => $Link['element']['handler']['argument'], + ), + 'autobreak' => true, + ), + ); + + $Inline['element']['attributes'] += $Link['element']['attributes']; + + unset($Inline['element']['attributes']['href']); + + return $Inline; + } + + protected function inlineLink($Excerpt) + { + $Element = array( + 'name' => 'a', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => null, + 'destination' => 'elements', + ), + 'nonNestables' => array('Url', 'Link'), + 'attributes' => array( + 'href' => null, + 'title' => null, + ), + ); + + $extent = 0; + + $remainder = $Excerpt['text']; + + if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches)) + { + $Element['handler']['argument'] = $matches[1]; + + $extent += strlen($matches[0]); + + $remainder = substr($remainder, $extent); + } + else + { + return; + } + + if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*+"|\'[^\']*+\'))?\s*+[)]/', $remainder, $matches)) + { + $Element['attributes']['href'] = $matches[1]; + + if (isset($matches[2])) + { + $Element['attributes']['title'] = substr($matches[2], 1, - 1); + } + + $extent += strlen($matches[0]); + } + else + { + if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches)) + { + $definition = strlen($matches[1]) ? $matches[1] : $Element['handler']['argument']; + $definition = strtolower($definition); + + $extent += strlen($matches[0]); + } + else + { + $definition = strtolower($Element['handler']['argument']); + } + + if ( ! isset($this->DefinitionData['Reference'][$definition])) + { + return; + } + + $Definition = $this->DefinitionData['Reference'][$definition]; + + $Element['attributes']['href'] = $Definition['url']; + $Element['attributes']['title'] = $Definition['title']; + } + + return array( + 'extent' => $extent, + 'element' => $Element, + ); + } + + protected function inlineMarkup($Excerpt) + { + if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false) + { + return; + } + + if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*+[ ]*+>/s', $Excerpt['text'], $matches)) + { + return array( + 'element' => array('rawHtml' => $matches[0]), + 'extent' => strlen($matches[0]), + ); + } + + if ($Excerpt['text'][1] === '!' and preg_match('/^/s', $Excerpt['text'], $matches)) + { + return array( + 'element' => array('rawHtml' => $matches[0]), + 'extent' => strlen($matches[0]), + ); + } + + if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*+(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+\/?>/s', $Excerpt['text'], $matches)) + { + return array( + 'element' => array('rawHtml' => $matches[0]), + 'extent' => strlen($matches[0]), + ); + } + } + + protected function inlineSpecialCharacter($Excerpt) + { + if (substr($Excerpt['text'], 1, 1) !== ' ' and strpos($Excerpt['text'], ';') !== false + and preg_match('/^&(#?+[0-9a-zA-Z]++);/', $Excerpt['text'], $matches) + ) { + return array( + 'element' => array('rawHtml' => '&' . $matches[1] . ';'), + 'extent' => strlen($matches[0]), + ); + } + + return; + } + + protected function inlineStrikethrough($Excerpt) + { + if ( ! isset($Excerpt['text'][1])) + { + return; + } + + if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches)) + { + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'del', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $matches[1], + 'destination' => 'elements', + ) + ), + ); + } + } + + protected function inlineUrl($Excerpt) + { + if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/') + { + return; + } + + if (strpos($Excerpt['context'], 'http') !== false + and preg_match('/\bhttps?+:[\/]{2}[^\s<]+\b\/*+/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE) + ) { + $url = $matches[0][0]; + + $Inline = array( + 'extent' => strlen($matches[0][0]), + 'position' => $matches[0][1], + 'element' => array( + 'name' => 'a', + 'text' => $url, + 'attributes' => array( + 'href' => $url, + ), + ), + ); + + return $Inline; + } + } + + protected function inlineUrlTag($Excerpt) + { + if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w++:\/{2}[^ >]++)>/i', $Excerpt['text'], $matches)) + { + $url = $matches[1]; + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'a', + 'text' => $url, + 'attributes' => array( + 'href' => $url, + ), + ), + ); + } + } + + # ~ + + protected function unmarkedText($text) + { + $Inline = $this->inlineText($text); + return $this->element($Inline['element']); + } + + # + # Handlers + # + + protected function handle(array $Element) + { + if (isset($Element['handler'])) + { + if (!isset($Element['nonNestables'])) + { + $Element['nonNestables'] = array(); + } + + if (is_string($Element['handler'])) + { + $function = $Element['handler']; + $argument = $Element['text']; + unset($Element['text']); + $destination = 'rawHtml'; + } + else + { + $function = $Element['handler']['function']; + $argument = $Element['handler']['argument']; + $destination = $Element['handler']['destination']; + } + + $Element[$destination] = $this->{$function}($argument, $Element['nonNestables']); + + if ($destination === 'handler') + { + $Element = $this->handle($Element); + } + + unset($Element['handler']); + } + + return $Element; + } + + protected function handleElementRecursive(array $Element) + { + return $this->elementApplyRecursive(array($this, 'handle'), $Element); + } + + protected function handleElementsRecursive(array $Elements) + { + return $this->elementsApplyRecursive(array($this, 'handle'), $Elements); + } + + protected function elementApplyRecursive($closure, array $Element) + { + $Element = call_user_func($closure, $Element); + + if (isset($Element['elements'])) + { + $Element['elements'] = $this->elementsApplyRecursive($closure, $Element['elements']); + } + elseif (isset($Element['element'])) + { + $Element['element'] = $this->elementApplyRecursive($closure, $Element['element']); + } + + return $Element; + } + + protected function elementApplyRecursiveDepthFirst($closure, array $Element) + { + if (isset($Element['elements'])) + { + $Element['elements'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['elements']); + } + elseif (isset($Element['element'])) + { + $Element['element'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['element']); + } + + $Element = call_user_func($closure, $Element); + + return $Element; + } + + protected function elementsApplyRecursive($closure, array $Elements) + { + foreach ($Elements as &$Element) + { + $Element = $this->elementApplyRecursive($closure, $Element); + } + + return $Elements; + } + + protected function elementsApplyRecursiveDepthFirst($closure, array $Elements) + { + foreach ($Elements as &$Element) + { + $Element = $this->elementApplyRecursiveDepthFirst($closure, $Element); + } + + return $Elements; + } + + protected function element(array $Element) + { + if ($this->safeMode) + { + $Element = $this->sanitiseElement($Element); + } + + # identity map if element has no handler + $Element = $this->handle($Element); + + $hasName = isset($Element['name']); + + $markup = ''; + + if ($hasName) + { + $markup .= '<' . $Element['name']; + + if (isset($Element['attributes'])) + { + foreach ($Element['attributes'] as $name => $value) + { + if ($value === null) + { + continue; + } + + $markup .= " $name=\"".self::escape($value).'"'; + } + } + } + + $permitRawHtml = false; + + if (isset($Element['text'])) + { + $text = $Element['text']; + } + // very strongly consider an alternative if you're writing an + // extension + elseif (isset($Element['rawHtml'])) + { + $text = $Element['rawHtml']; + + $allowRawHtmlInSafeMode = isset($Element['allowRawHtmlInSafeMode']) && $Element['allowRawHtmlInSafeMode']; + $permitRawHtml = !$this->safeMode || $allowRawHtmlInSafeMode; + } + + $hasContent = isset($text) || isset($Element['element']) || isset($Element['elements']); + + if ($hasContent) + { + $markup .= $hasName ? '>' : ''; + + if (isset($Element['elements'])) + { + $markup .= $this->elements($Element['elements']); + } + elseif (isset($Element['element'])) + { + $markup .= $this->element($Element['element']); + } + else + { + if (!$permitRawHtml) + { + $markup .= self::escape($text, true); + } + else + { + $markup .= $text; + } + } + + $markup .= $hasName ? '' : ''; + } + elseif ($hasName) + { + $markup .= ' />'; + } + + return $markup; + } + + protected function elements(array $Elements) + { + $markup = ''; + + $autoBreak = true; + + foreach ($Elements as $Element) + { + if (empty($Element)) + { + continue; + } + + $autoBreakNext = (isset($Element['autobreak']) + ? $Element['autobreak'] : isset($Element['name']) + ); + // (autobreak === false) covers both sides of an element + $autoBreak = !$autoBreak ? $autoBreak : $autoBreakNext; + + $markup .= ($autoBreak ? "\n" : '') . $this->element($Element); + $autoBreak = $autoBreakNext; + } + + $markup .= $autoBreak ? "\n" : ''; + + return $markup; + } + + # ~ + + protected function li($lines) + { + $Elements = $this->linesElements($lines); + + if ( ! in_array('', $lines) + and isset($Elements[0]) and isset($Elements[0]['name']) + and $Elements[0]['name'] === 'p' + ) { + unset($Elements[0]['name']); + } + + return $Elements; + } + + # + # AST Convenience + # + + /** + * Replace occurrences $regexp with $Elements in $text. Return an array of + * elements representing the replacement. + */ + protected static function pregReplaceElements($regexp, $Elements, $text) + { + $newElements = array(); + + while (preg_match($regexp, $text, $matches, PREG_OFFSET_CAPTURE)) + { + $offset = $matches[0][1]; + $before = substr($text, 0, $offset); + $after = substr($text, $offset + strlen($matches[0][0])); + + $newElements[] = array('text' => $before); + + foreach ($Elements as $Element) + { + $newElements[] = $Element; + } + + $text = $after; + } + + $newElements[] = array('text' => $text); + + return $newElements; + } + + # + # Deprecated Methods + # + + function parse($text) + { + $markup = $this->text($text); + + return $markup; + } + + protected function sanitiseElement(array $Element) + { + static $goodAttribute = '/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/'; + static $safeUrlNameToAtt = array( + 'a' => 'href', + 'img' => 'src', + ); + + if ( ! isset($Element['name'])) + { + unset($Element['attributes']); + return $Element; + } + + if (isset($safeUrlNameToAtt[$Element['name']])) + { + $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]); + } + + if ( ! empty($Element['attributes'])) + { + foreach ($Element['attributes'] as $att => $val) + { + # filter out badly parsed attribute + if ( ! preg_match($goodAttribute, $att)) + { + unset($Element['attributes'][$att]); + } + # dump onevent attribute + elseif (self::striAtStart($att, 'on')) + { + unset($Element['attributes'][$att]); + } + } + } + + return $Element; + } + + protected function filterUnsafeUrlInAttribute(array $Element, $attribute) + { + foreach ($this->safeLinksWhitelist as $scheme) + { + if (self::striAtStart($Element['attributes'][$attribute], $scheme)) + { + return $Element; + } + } + + $Element['attributes'][$attribute] = str_replace(':', '%3A', $Element['attributes'][$attribute]); + + return $Element; + } + + # + # Static Methods + # + + protected static function escape($text, $allowQuotes = false) + { + return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, 'UTF-8'); + } + + protected static function striAtStart($string, $needle) + { + $len = strlen($needle); + + if ($len > strlen($string)) + { + return false; + } + else + { + return strtolower(substr($string, 0, $len)) === strtolower($needle); + } + } + + static function instance($name = 'default') + { + if (isset(self::$instances[$name])) + { + return self::$instances[$name]; + } + + $instance = new static(); + + self::$instances[$name] = $instance; + + return $instance; + } + + private static $instances = array(); + + # + # Fields + # + + protected $DefinitionData; + + # + # Read-Only + + protected $specialCharacters = array( + '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|', '~' + ); + + protected $StrongRegex = array( + '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*+[*])+?)[*]{2}(?![*])/s', + '_' => '/^__((?:\\\\_|[^_]|_[^_]*+_)+?)__(?!_)/us', + ); + + protected $EmRegex = array( + '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s', + '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us', + ); + + protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*+(?:\s*+=\s*+(?:[^"\'=<>`\s]+|"[^"]*+"|\'[^\']*+\'))?+'; + + protected $voidElements = array( + 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', + ); + + protected $textLevelElements = array( + 'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont', + 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing', + 'i', 'rp', 'del', 'code', 'strike', 'marquee', + 'q', 'rt', 'ins', 'font', 'strong', + 's', 'tt', 'kbd', 'mark', + 'u', 'xm', 'sub', 'nobr', + 'sup', 'ruby', + 'var', 'span', + 'wbr', 'time', + ); +} \ No newline at end of file diff --git a/includes/banner.inc b/includes/banner.inc index 1d33b43..4ad0583 100644 --- a/includes/banner.inc +++ b/includes/banner.inc @@ -367,14 +367,6 @@ function getMemberBannerData(string $id, string $system, bool $french = false) { ]; } - if (($metadata["sexually_active"] ?? false) && !$french && $isLoggedIn) { - $badges[] = [ - "id" => "sexually_active", - "color" => "d6a833", - "html" => 'Sexually active' - ]; - } - if (($metadata["leader"] ?? false) && $isLoggedIn) { $badges[] = [ "id" => "leader", diff --git a/includes/bitset.inc b/includes/bitset.inc index 96c6bfd..f45beb1 100644 --- a/includes/bitset.inc +++ b/includes/bitset.inc @@ -4,27 +4,23 @@ function parseBitset ($bitset) { $bin = str_repeat("0", 48 - strlen(decbin($bitset))) . decbin($bitset); $sharedMemory = bindec(substr($bin, 24, 2)); - $median = substr($bin, 26, 1) !== "0"; $little = bindec(substr($bin, 27, 2)); $food = bindec(substr($bin, 16, 2)); $nonverbal = substr($bin, 15, 1) !== "0"; $lessFrequent = substr($bin, 14, 1) !== "0"; $sexuallyActive = substr($bin, 13, 1) !== "0"; - $ageRegressor = substr($bin, 12, 1) !== "0"; $leader = substr($bin, 11, 1) !== "0"; $persecutor = substr($bin, 10, 1) !== "0"; - $magic = bindec(substr($bin, 18, 3)); - $sensitivity = bindec(substr($bin, 21, 3)); $protector = substr($bin, 29, 1) !== "0"; $fictive = substr($bin, 30, 1) !== "0"; - $notTalking = substr($bin, 31, 1) !== "0"; - $host = substr($bin, 32, 1) !== "0"; $robot = substr($bin, 45, 1) !== "0"; $plush = substr($bin, 46, 1) !== "0"; - $age = substr($bin, 47, 1) !== "0"; + $polyamorous1 = substr($bin, 18, 1) !== "0"; + $polyamorous2 = substr($bin, 19, 1) !== "0"; $species1 = substr($bin, 33, 4); $species2 = substr($bin, 37, 4); - $species3 = substr($bin, 41, 4); + $alignment1 = substr($bin, 41, 4); + $alignment2 = substr($bin, 20, 4); $species1 = match ($species1) { "0001" => "earth", @@ -48,46 +44,57 @@ function parseBitset ($bitset) { default => null, }; - $species3 = match ($species3) { - "0001" => "earth", - "0010" => "unicorn", - "0011" => "pegasus", - "0100" => "alicorn", - "0101" => "batpony", - "0110" => "crystal", - "0111" => "changeling", + $alignment1 = match ($alignment1) { + "0000" => "aroace", + "0001" => "hetero", + "0010" => "homo", + "0011" => "bi", + "0100" => "pan", default => null, }; - if ($little === 1) { - $ageRegressor = true; - $little = 0; - } + $alignment2 = match ($alignment2) { + "0000" => "aroace", + "0001" => "hetero", + "0010" => "homo", + "0011" => "bi", + "0100" => "pan", + default => null, + }; + + if ($little === 1) $little = 0; return [ 'shared_memory' => $sharedMemory, - 'median' => $median, + 'median' => false, 'protector' => $protector, 'fictive' => $fictive, 'little' => $little, - 'not_talking' => $notTalking, - 'host' => $host, + 'not_talking' => false, + 'host' => false, 'robot' => $robot, - 'magic' => $magic, - 'sensitivity' => $sensitivity, + 'magic' => 0, + 'sensitivity' => 0, 'food' => $food, 'plush' => $plush, 'nonverbal' => $nonverbal, 'less_frequent' => $lessFrequent, - 'age_spells' => $age, - 'age_regressor' => $ageRegressor, + 'age_spells' => false, + 'age_regressor' => false, 'leader' => $leader, 'persecutor' => $persecutor, 'sexually_active' => $sexuallyActive, + 'polyamorous' => [ + 'romantic' => $polyamorous1, + 'sexual' => $polyamorous2 + ], + 'alignment' => [ + 'romantic' => $alignment1, + 'sexual' => $alignment2 + ], 'species' => array_filter([ $species1, - $species2, - $species3 + $species2 ], function ($i) { return isset($i); }) diff --git a/includes/details.inc b/includes/details.inc index 8fee082..08ba52a 100644 --- a/includes/details.inc +++ b/includes/details.inc @@ -1,40 +1,32 @@ - +
; margin-left: -20px; margin-right: -20px;">

"" . $lang["details"]["food_states"][0] . "", - 1 => "" . $lang["details"]["food_states"][1] . "", - 2 => "" . $lang["details"]["food_states"][2] . "", - 3 => "" . $lang["details"]["food_states"][3] . "", + 0 => "" . $lang["details"]["food_states"][0] . "", + 1 => "" . $lang["details"]["food_states"][1] . "", + 2 => "" . $lang["details"]["food_states"][2] . "", + 3 => "" . $lang["details"]["food_states"][3] . "", } ?>

"" . $lang["details"]["memory_states"][0] . "", - 1 => "" . $lang["details"]["memory_states"][1] . "", - 2 => "" . $lang["details"]["memory_states"][2] . "", + 0 => "" . $lang["details"]["memory_states"][0] . "", + 1 => "" . $lang["details"]["memory_states"][1] . "", + 2 => "" . $lang["details"]["memory_states"][2] . "", } ?>
-
- Sensitivity:
- "None", - 1 => "Maybe", - 2 => "Affectionate", - 3 => "Sexual", - 4 => "Affectionate and sexual", - } ?> -
Age:
- + INF ?> + Eternal" ?> + - - - " . $metadata["birth"]["age"] . "* years old" ?> + + " . $metadata["birth"]["age"] . "* years old" ?> @@ -47,7 +39,8 @@ $birthdate = $metadata["birth"]["year"] . "-" . $metadata["birth"]["date"]; } - $age = floor((time() - strtotime($birthdate)) / 31557600); + $age = (int)date('Y') - $metadata["birth"]["year"] + (strtotime(date('Y') . "-" . $metadata["birth"]["date"]) <= time() ? 0 : -1); + $age2 = floor((time() - strtotime($birthdate)) / 31557600); $birthday = "as time passes"; if (isset($metadata["birth"]["date"]) && trim($metadata["birth"]["date"]) !== "" && $metadata["birth"]["date"] !== "01-01") { @@ -55,7 +48,7 @@ } ?> - " . $age . " years old" ?> + " . $age . " years old" ?>
@@ -76,4 +69,75 @@ -
- \ No newline at end of file + + +
; margin-left: -20px; margin-right: -20px;"> + = 16 && $metadata["little"] === 0) || (!isset($age) && $metadata["little"] === 0)): ?> +
+ Sexual consent:
+ + , however may ask to not have sex at the moment and such request must be honored." data-bs-toggle="tooltip">Preemptive + + ." data-bs-toggle="tooltip">Required + +
+
+ Sexual alignmentSex. algn.:
+ (poly) +
+ +
+ +
This member is too young to have a sexual relationship.
+
+ +
+ Romantic alignmentRom. algn.:
+ (poly) +
+ +
+ diff --git a/includes/footer.inc b/includes/footer.inc index b95ea78..cd1bb89 100644 --- a/includes/footer.inc +++ b/includes/footer.inc @@ -1,5 +1,6 @@ @@ -16,7 +17,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/functions.inc"; global $lang; global $pages; ?> - © · version 2...
+ © · version 2.. · node
( 360): ?>; ; ms, 0 ? (count($refresh["restored"]) > 1 ? $lang["footer"]["failures"][0] . count($refresh["restored"]) . $lang["footer"]["failures"][1] : $lang["footer"]["failure"]) : $lang["footer"]["no_failure"] ?>)




diff --git a/includes/fullbanner.inc b/includes/fullbanner.inc index 24e0295..c50ad5f 100644 --- a/includes/fullbanner.inc +++ b/includes/fullbanner.inc @@ -20,69 +20,6 @@ This member's metadata needs an update. It still uses the old JSON metadata system instead of the new 24bit integer-based system. Contact a developer to request an update. (only administrators can see this; file: /includes/data/metadata/.json) -
-
- Private administrator information -
    -
  • ID: (, )
  • -
  • Files: -
      -
    • ()
    • -
    • ()
    • -
    • ()
    • -
    -
  • -
  • Date added: (, )
  • -
  • Pronouns:
  • -
  • Pronouns usage:
      $usage) { - if (is_string($usage) && $type !== "color") { - echo("
    • " . $type . ": " . $usage . "
    • "); - } - } - - ?>
  • -
  • Color: ;display:inline-block;width:16px;height:16px;border-radius:5px;vertical-align: middle;filter: invert(1) hue-rotate(180deg);"> # -
  • Bitset: (0x, )
  • Not using bitset; please update. -
  • Reduced name:
  • -
  • Shared memory access: ()
  • -
  • Protector: ()
  • -
  • Little: ()
  • -
  • Relations count:
  • - -
  • - Score breakdown: -
      $usage) { - if (is_string($usage)) { - echo("
    • " . $type . ": " . $usage . "
    • "); - } else if (is_array($usage)) { - if (count($usage) === 0) { - echo("
    • " . $type . ": []
    • "); - } else { - echo("
    • " . $type . ":
        "); - - foreach ($usage as $key => $item) { - if (is_string($item)) { - echo("
      • " . $key . ": " . $item . "
      • "); - } else { - echo("
      • " . $key . ": " . json_encode($item, JSON_UNESCAPED_SLASHES) . "
      • "); - } - } - - echo("
    • "); - } - } else { - echo("
    • " . $type . ": " . json_encode($usage, JSON_UNESCAPED_SLASHES) . "
    • "); - } - } - - ?>
  • -
-
-
diff --git a/includes/functions.inc b/includes/functions.inc index 915d41b..bdc961b 100644 --- a/includes/functions.inc +++ b/includes/functions.inc @@ -196,18 +196,7 @@ if (!function_exists("getMiniName")) { if (!function_exists("withCaretakersDown")) { function withCaretakersDown(array $ordered): array { - $caretakersNo = []; - $caretakersYes = []; - - foreach ($ordered as $item) { - if ($item["_metadata"]["little"] === 2) { - $caretakersYes[] = $item; - } else { - $caretakersNo[] = $item; - } - } - - return [...$caretakersNo, ...$caretakersYes]; + return $ordered; } } diff --git a/includes/maintenance/clearUnused.php b/includes/maintenance/clearUnused.php new file mode 100644 index 0000000..99b6ca4 --- /dev/null +++ b/includes/maintenance/clearUnused.php @@ -0,0 +1,34 @@ + - (edit: metadata, public, private) +
+ + (edit: metadata, public, private) + +
+
    +
  • ID: (, )
  • +
  • Files: +
      +
    • ()
    • +
    • ()
    • +
    • ()
    • +
    +
  • +
  • Date added: (, )
  • +
  • Pronouns:
  • +
  • Pronouns usage:
      $usage) { + if (is_string($usage) && $type !== "color") { + echo("
    • " . $type . ": " . $usage . "
    • "); + } + } + + ?>
  • +
  • Color: ;display:inline-block;width:16px;height:16px;border-radius:5px;vertical-align: middle;filter: invert(1) hue-rotate(180deg);"> # +
  • Bitset: (0x, )
  • Not using bitset; please update. +
  • Reduced name:
  • +
  • Shared memory access: ()
  • +
  • Protector: ()
  • +
  • Little: ()
  • +
  • Relations count:
  • + +
  • + Score breakdown: +
      $usage) { + if (is_string($usage)) { + echo("
    • " . $type . ": " . $usage . "
    • "); + } else if (is_array($usage)) { + if (count($usage) === 0) { + echo("
    • " . $type . ": []
    • "); + } else { + echo("
    • " . $type . ":
        "); + + foreach ($usage as $key => $item) { + if (is_string($item)) { + echo("
      • " . $key . ": " . $item . "
      • "); + } else { + echo("
      • " . $key . ": " . json_encode($item, JSON_UNESCAPED_SLASHES) . "
      • "); + } + } + + echo("
    • "); + } + } else { + echo("
    • " . $type . ": " . json_encode($usage, JSON_UNESCAPED_SLASHES) . "
    • "); + } + } + + ?>
  • +
+
+
diff --git a/includes/navigation.inc b/includes/navigation.inc index bdafe99..fe1c9ee 100644 --- a/includes/navigation.inc +++ b/includes/navigation.inc @@ -53,13 +53,6 @@ $navigation_admin = [ "name" => $lang["navigation"]["apps"], "minimal" => false, "items" => [ - [ - "name" => $pages["dashboard"]["name"][$lang["_name"]], - "icon" => "/assets/icons/dashboard.svg", - "invert" => true, - "link" => "/-/dashboard", - "stepped" => null - ], [ "name" => $pages["about"]["name"][$lang["_name"]], "icon" => "/assets/icons/about.svg", @@ -89,10 +82,10 @@ $navigation_admin = [ "stepped" => null ], [ - "name" => $pages["rules-old"]["name"][$lang["_name"]], - "icon" => "/assets/icons/rules-old.svg", + "name" => $pages["rules"]["name"][$lang["_name"]], + "icon" => "/assets/icons/rules.svg", "invert" => true, - "link" => "/-/rules-old", + "link" => "/-/rules", "stepped" => null ], [ diff --git a/includes/pages.json b/includes/pages.json index 4c6fcf5..a55c748 100644 --- a/includes/pages.json +++ b/includes/pages.json @@ -235,22 +235,13 @@ }, "rules": { "name": { - "en": "General rules", - "fr": "General rules" + "en": "Rules", + "fr": "Rules" }, "short": "Rules", "admin": true, "rail": true }, - "rules-old": { - "name": { - "en": "Systems rules (legacy)", - "fr": "Systems rules (legacy)" - }, - "short": "Rules (old)", - "admin": true, - "rail": true - }, "s:compare": { "name": { "en": "Compare members", diff --git a/includes/planner.inc b/includes/planner.inc index 225fca4..a5782a5 100644 --- a/includes/planner.inc +++ b/includes/planner.inc @@ -84,7 +84,9 @@ function day($display, $diff): void { if ($diff < 0) $disabled = true; else $dis ?> style="opacity: .75; pointer-events: none;"> - + +
+ style="opacity: .75; pointer-events: none;"> - Cloudburst System - Raindrops System + Cloudburst System + Raindrops System style="opacity: .75; pointer-events: none;"> - + - colspan="3" colspan="2"> + colspan="3" colspan="2"> - Multiple merged membersMerge + Multiple merged members + + Other/unknown/fallback pony - " style="width:24px;"> + " style="width:24px;"> - - - + + + - + - Multiple merged membersMerge + Multiple merged members + + Other/unknown/fallback pony - " style="width:24px;"> + " style="width:24px;"> - + - - Add new fronter + - + - + - colspan="3" colspan="2"> + colspan="3" colspan="2"> - Multiple merged membersMerge + Multiple merged members + + Other/unknown/fallback pony - " style="width:24px;"> + " style="width:24px;"> - - - + + + - Multiple merged membersMerge + Multiple merged members + + Other/unknown/fallback pony - " style="width:24px;"> + " style="width:24px;"> - + - - Add new fronter + - + style="opacity: .75; pointer-events: none;"> - - 0 && count($dayRaindrops) > 0): ?> - will sleep with - - Unable to calculate who will sleep with who - + +
+ 0 && count($dayRaindrops) > 0): ?> + will sleep with + + Unable to calculate who will sleep with who + +
@@ -358,6 +374,33 @@ function day($display, $diff): void { if ($diff < 0) $disabled = true; else $dis } } + .member-link, .planner-link { + background-color: transparent !important; + } + + .member-link-inner, .planner-add-link-inner, .planner-add-link-cofronter-inner { + display: block; + padding: 3px 7px; + border-radius: 5px; + } + + .planner-add-link-cofronter-inner { + border-radius: 100%; + } + + .planner-add-link-cofronter-inner .planner-add-icon { + margin-top: -2px; + } + + .member-link, .planner-add-link-inner, .planner-add-link-cofronter-inner { + padding: 2px 3px !important; + } + + .member-link:hover .member-link-inner, .planner-add-link:hover .planner-add-link-inner, .planner-add-link-cofronter:hover .planner-add-link-cofronter-inner { + background-color: rgba(255, 255, 255, .125); + /*background-color: rgba(255, 255, 255, .25);*/ + } + @@ -394,14 +437,16 @@ function day($display, $diff): void { if ($diff < 0) $disabled = true; else $dis let otherPony = window.fronting[window.addSystem === "gdapd" ? "cloudburst" : "raindrops"][window.currentWorkingDate][window.addIndex][0]; let availablePonies = window.relations[window.fronting[window.addSystem === "gdapd" ? "cloudburst" : "raindrops"][window.currentWorkingDate][window.addIndex][0]]; - document.getElementById("associated-results").innerHTML = ""; + if (availablePonies) { + document.getElementById("associated-results").innerHTML = ""; - for (let pony of availablePonies) { - document.getElementById("associated-results").innerHTML += document.getElementById("list-pony-" + pony).outerHTML; - } + for (let pony of availablePonies) { + document.getElementById("associated-results").innerHTML += document.getElementById("list-pony-" + pony).outerHTML; + } - document.getElementById("list").style.display = "none"; - document.getElementById("associated-results").style.display = ""; + document.getElementById("list").style.display = "none"; + document.getElementById("associated-results").style.display = ""; + } } } @@ -687,7 +732,8 @@ function day($display, $diff): void { if ($diff < 0) $disabled = true; else $dis " style="width:24px;"> - Multiple merged members + Multiple merged members + Other/unknown/fallback pony
diff --git a/includes/refresh.php b/includes/refresh.php index 18c01ed..fe01414 100644 --- a/includes/refresh.php +++ b/includes/refresh.php @@ -161,19 +161,31 @@ function getSystem(string $id) { echo(" Switches\n"); $currentOpStart = microtime(true); - echo(" Part 1/3\n"); + echo(" Part 1/6\n"); $switches1 = json_decode(file_get_contents("https://pluralkit.equestria.dev/v2/systems/$id/switches"), true); $oldest = $switches1[count($switches1) - 1]["timestamp"]; - echo(" Part 2/3\n"); + echo(" Part 2/6\n"); $switches2 = json_decode(file_get_contents("https://pluralkit.equestria.dev/v2/systems/$id/switches?before=$oldest"), true); $oldest = $switches2[count($switches2) - 1]["timestamp"]; - echo(" Part 3/3\n"); + echo(" Part 3/6\n"); $switches3 = json_decode(file_get_contents("https://pluralkit.equestria.dev/v2/systems/$id/switches?before=$oldest"), true); + $oldest = $switches3[count($switches3) - 1]["timestamp"]; - if ($switches1 !== null && $switches2 !== null && $switches3 !== null) { - file_put_contents("./data/$id/switches.json", json_encode([...$switches1, ...$switches2, ...$switches3], JSON_PRETTY_PRINT)); + echo(" Part 4/6\n"); + $switches4 = json_decode(file_get_contents("https://pluralkit.equestria.dev/v2/systems/$id/switches?before=$oldest"), true); + $oldest = $switches4[count($switches4) - 1]["timestamp"]; + + echo(" Part 5/6\n"); + $switches5 = json_decode(file_get_contents("https://pluralkit.equestria.dev/v2/systems/$id/switches?before=$oldest"), true); + $oldest = $switches5[count($switches5) - 1]["timestamp"]; + + echo(" Part 6/6\n"); + $switches6 = json_decode(file_get_contents("https://pluralkit.equestria.dev/v2/systems/$id/switches?before=$oldest"), true); + + if ($switches1 !== null && $switches2 !== null && $switches3 !== null && $switches4 !== null && $switches5 !== null && $switches6 !== null) { + file_put_contents("./data/$id/switches.json", json_encode([...$switches1, ...$switches2, ...$switches3, ...$switches4, ...$switches5, ...$switches6], JSON_PRETTY_PRINT)); $times["system-switches-$id"] = microtime(true) - $currentOpStart; } } diff --git a/includes/sysbanner.inc b/includes/sysbanner.inc index bafb10d..f9feb5c 100644 --- a/includes/sysbanner.inc +++ b/includes/sysbanner.inc @@ -82,7 +82,7 @@ $pages = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/pa diff --git a/includes/system/species.inc b/includes/system/species.inc index 41b06fe..8f4368e 100644 --- a/includes/system/species.inc +++ b/includes/system/species.inc @@ -9,11 +9,11 @@ function species(array $members, string $id, string $name) { global $systemID; g $members = [ ...array_map(function ($i) use ($systemID) { $i["_system"] = $systemID; return $i; },array_filter($members, function ($i) use ($systemID) { global $travelling; - return !$travelling[$i['id']]['travelling']; + return !($travelling[$i['id']]['travelling'] || (isset($travelling[$i['id']]['equestria']) && $travelling[$i['id']]['equestria'])); })), ...array_map(function ($i) use ($systemID) { $i["_system"] = $systemID === "gdapd" ? "ynmuc" : "gdapd"; return $i; }, array_filter(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/" . ($systemID === "gdapd" ? "ynmuc" : "gdapd") . "/members.json"), true), function ($i) use ($id, $systemID) { global $travelling; - return $travelling[$i['id']]['travelling'] && in_array($id, parseMetadata(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/metadata/" . $i['id'] . ".json"), true))["species"]); + return $travelling[$i['id']]['travelling'] && !$travelling[$i['id']]['equestria'] && in_array($id, parseMetadata(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/metadata/" . $i['id'] . ".json"), true))["species"]); })) ]; diff --git a/pages/bitset.inc b/pages/bitset.inc index d4239a7..c74fe64 100644 --- a/pages/bitset.inc +++ b/pages/bitset.inc @@ -31,7 +31,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.inc';
-

Bitset calculator

+

Bitset calculator

0
@@ -46,27 +46,27 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.inc';
0
0
0
-
0
-
0
+
0
+
0
0
0
0
0
-
0
-
0
-
0
-
0
-
0
-
0
+
0
+
0
+
0
+
0
+
0
+
0
0
0
-
0
+
0
0
0
0
0
-
0
-
0
+
0
+
0
0
0
0
@@ -75,18 +75,18 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.inc';
0
0
0
-
0
-
0
-
0
-
0
+
0
+
0
+
0
+
0
0
0
-
0
+
0

- Input:
- Output: 0b000000000000000000000000000000000000100000000000, 0x000008000000, 2048 +
+ 0b000000000000000000000000000000000000100000000000, 0x000008000000, 2048

+ + + + + + + + + + + + + + + + + + + +
+ Shared memory access: + + +
+ Food: + + +
+ Little/younger: + + +
+ Species: + + + + +
+ Alignment: + + + +

- Shared memory access: -
- Food: -
- Little/younger: -
- Species: - - -
-

- Magic: -
-
- Sensitivity: -

-

-


-

+
+

-
-
- Show deprecated options - -
-

-
-
-
-

-
+
diff --git a/pages/page.inc b/pages/page.inc index d13dd2f..f3859d3 100644 --- a/pages/page.inc +++ b/pages/page.inc @@ -38,11 +38,13 @@ $systemID = $system === "cloudburst" ? "ynmuc" : "gdapd"; if ($member === null) { global $_SystemName; $_SystemName = $system; + $pageFile = $_SERVER['DOCUMENT_ROOT'] . '/includes/system.inc'; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/system.inc'; } else if ($member === "-" && isset($parts[2])) { if (file_exists($_SERVER['DOCUMENT_ROOT'] . '/includes/system/' . $parts[2] . '.inc')) { global $_SystemPage; $_SystemPage = $parts[2]; + $pageFile = $_SERVER['DOCUMENT_ROOT'] . '/includes/system/' . $parts[2] . '.inc'; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/system/' . $parts[2] . '.inc'; } else { header("Location: /?error=" . $lang["page"]["system"] . " " . $parts[2]) and die(); @@ -89,6 +91,7 @@ if ($member === null) { if (file_exists($_SERVER['DOCUMENT_ROOT'] . '/includes/member/' . $parts[3] . '.inc')) { global $_MemberPage; $_MemberPage = $parts[3]; + $pageFile = $_SERVER['DOCUMENT_ROOT'] . '/includes/member/' . $parts[3] . '.inc'; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/member/' . $parts[3] . '.inc'; } else { header("Location: /?error=" . $lang["page"]["system"] . " " . $parts[3]) and die(); @@ -98,6 +101,7 @@ if ($member === null) { global $_MemberName; $_MemberName = $member; + $pageFile = $_SERVER['DOCUMENT_ROOT'] . '/includes/member.inc'; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/member.inc'; } diff --git a/pages/relations.inc b/pages/relations.inc index ce0d65c..8d14fe7 100644 --- a/pages/relations.inc +++ b/pages/relations.inc @@ -15,8 +15,8 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.inc'; } else { return true; } - })) as $member): if (count($member["_metadata"]["marefriends"]) > 0 || count($member["_metadata"]["sisters"]) > 0 || count($member["_metadata"]["caretakers"]) > 0): ?> -
;"> + })) as $member): ?> +
"> " style="width:24px;"> @@ -55,8 +55,8 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.inc'; - - + = 2): ?> +

@@ -74,7 +74,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.inc';
- +
- - +header("Location: /-/rules"); +die(); \ No newline at end of file diff --git a/pages/rules.inc b/pages/rules.inc index cf88eb1..2268e75 100644 --- a/pages/rules.inc +++ b/pages/rules.inc @@ -1,6 +1,40 @@ $rule) { + if (!isset($rule["name"]) || !isset($rule["content"]) && !is_numeric($index)) { + header("Location: /-/rules"); + die(); + } + + if (trim($rule["name"]) === "") { + unset($_POST["payload"][$index]); + continue; + } + + if (!isset($rule["approved"])) $rule["approved"] = []; + if (isset($rule["approved"][0])) $rule["approved"][0] = true; else $rule["approved"][0] = false; + if (isset($rule["approved"][1])) $rule["approved"][1] = true; else $rule["approved"][1] = false; + + $_POST["payload"][$index] = $rule; + } + + file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/rules/rules.json", utf8_encode(json_encode($_POST["payload"]))); + + header("Location: /-/rules"); + die(); +} + require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.inc'; ?> @@ -8,95 +42,159 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.inc';
-

General rules

-

Click on a rule in the list to view the history of that rule, including all amendments. Note that older versions of a rule cannot be enforced anymore and are provided only for informational purposes.

+

Rules

+

Edit rules

- - + $rules = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/rules/rules.json"), true); -
    -
  • -
    - - 0): ?>-: : - + $protectorCloudburst = array_values(array_filter(scoreOrderGlobal(), function ($i) { + return $i["_system"] === "ynmuc" && $i["_metadata"]["protector"]; + }))[0]; + $protectorRaindrops = array_values(array_filter(scoreOrderGlobal(), function ($i) { + return $i["_system"] === "gdapd" && $i["_metadata"]["protector"]; + }))[0]; -
      - 0): ?> - + $pcName = getMiniName($protectorCloudburst["display_name"] ?? $protectorCloudburst["name"]); + $prName = getMiniName($protectorRaindrops["display_name"] ?? $protectorRaindrops["name"]); - -
    • Curent version:
    • - -
    • Amendment Preamble-:
    • - + ?> - -
    • Original rule:
    • - -
    • - This rule was never amended. -
    • - -
    -
    -
  • -
- - - -

Articles .1 to .:

- -
    - -
  • -
    - - Article . 0): ?>-: : - - -
      - 0): ?> - - - -
    • Curent version:
    • - -
    • Amendment Preamble-:
    • - - - -
    • Original rule:
    • - -
    • - This rule was never amended. -
    • + +

      + Rule : + + Unapproved -

    -
    + + +
    + This rule has not yet been approved. All rules need to be approved by the leaders from both systems. This rule is still missing approval from . +
    + +
    +

    text(strip_tags($rule["content"])) ?>

    +
  • - -
- - + +
+
- + + +