From b5f589c323f415bb42ea7069cb4d1a8a2233dd69 Mon Sep 17 00:00:00 2001 From: Minteck Date: Wed, 31 Aug 2022 22:03:07 +0200 Subject: Update I guess - Stuffie --- .gitignore | 3 +- .idea/deployment.xml | 2 +- .idea/sshConfigs.xml | 8 - .idea/webServers.xml | 14 - README.md | 23 +- api/app-images.php | 2 +- api/cloudburst-banners.php | 2 +- api/cloudburst-data.php | 2 +- api/cloudburst-scored.php | 9 + api/data.php | 2 +- api/emergency-real.php | 2 +- api/emergency.php | 2 +- api/eval.php | 12 + api/fronter.php | 2 +- api/me-picture.php | 2 +- api/me.php | 2 +- api/pleasure-real.php | 2 +- api/pleasure.php | 2 +- api/pluralkit-integration.php | 27 +- api/raindrops-banners.php | 2 +- api/raindrops-data.php | 2 +- api/raindrops-scored.php | 9 + api/save.php | 2 +- api/token.php | 2 +- api/video.php | 2 +- app/banner.js | 17 +- app/emergency/index.php | 2 +- app/fronters/style.css | 4 - app/index.html | 107 ++- app/load.js | 58 +- app/planner/index.php | 2 +- assets/editor/fuse.js | 9 + assets/icons/actions.svg | 1 + assets/icons/dashboard.svg | 1 + assets/icons/debug.svg | 1 + assets/icons/favicon/about.png | Bin 594631 -> 594631 bytes assets/icons/favicon/actions.png | Bin 0 -> 438632 bytes assets/icons/favicon/add.png | Bin 589454 -> 589454 bytes assets/icons/favicon/admin.png | Bin 652302 -> 652302 bytes assets/icons/favicon/bitset.png | Bin 441800 -> 441800 bytes assets/icons/favicon/compare.png | Bin 521946 -> 521946 bytes assets/icons/favicon/complete.png | Bin 936095 -> 936095 bytes assets/icons/favicon/dashboard.png | Bin 0 -> 410357 bytes assets/icons/favicon/debug.png | Bin 0 -> 535936 bytes assets/icons/favicon/delete.png | Bin 426083 -> 426083 bytes assets/icons/favicon/disclaimers.png | Bin 593494 -> 593494 bytes assets/icons/favicon/down.png | Bin 466268 -> 466268 bytes assets/icons/favicon/emergency.png | Bin 641671 -> 641671 bytes assets/icons/favicon/form.png | Bin 430598 -> 430598 bytes assets/icons/favicon/fronting.png | Bin 457289 -> 457289 bytes assets/icons/favicon/global.png | Bin 514979 -> 514979 bytes assets/icons/favicon/history.png | Bin 578528 -> 578528 bytes assets/icons/favicon/home.png | Bin 485901 -> 485901 bytes assets/icons/favicon/login.png | Bin 470485 -> 470485 bytes assets/icons/favicon/logout.png | Bin 466795 -> 466795 bytes assets/icons/favicon/nicknames.png | Bin 0 -> 545539 bytes assets/icons/favicon/none.png | Bin 737615 -> 737615 bytes assets/icons/favicon/parser.png | Bin 417426 -> 417426 bytes assets/icons/favicon/partial.png | Bin 623179 -> 623179 bytes assets/icons/favicon/pleasure.png | Bin 0 -> 566012 bytes assets/icons/favicon/prefix.png | Bin 497226 -> 497226 bytes assets/icons/favicon/relations.png | Bin 500946 -> 500946 bytes assets/icons/favicon/right.png | Bin 509122 -> 509122 bytes assets/icons/favicon/rules.png | Bin 0 -> 561709 bytes assets/icons/favicon/score.png | Bin 511410 -> 511410 bytes assets/icons/favicon/shield.png | Bin 549825 -> 549825 bytes assets/icons/favicon/species.png | Bin 628455 -> 628455 bytes assets/icons/favicon/splitting.png | Bin 430598 -> 430598 bytes assets/icons/favicon/terminology.png | Bin 429904 -> 429904 bytes assets/icons/favicon/together.png | Bin 439198 -> 439198 bytes assets/icons/favicon/toys.png | Bin 0 -> 512450 bytes assets/icons/favicon/travel.png | Bin 637874 -> 637874 bytes assets/icons/favicon/travelling.png | Bin 637874 -> 637874 bytes assets/icons/favicon/tree.png | Bin 403820 -> 403820 bytes assets/icons/favicon/up.png | Bin 475957 -> 475957 bytes assets/icons/favicon/user.png | Bin 522161 -> 522161 bytes assets/icons/favicon/visibility-depends.png | Bin 506315 -> 506315 bytes assets/icons/favicon/visibility-private.png | Bin 485196 -> 485196 bytes assets/icons/favicon/visibility-public.png | Bin 514979 -> 514979 bytes assets/icons/nicknames.svg | 1 + assets/icons/rules.svg | 1 + assets/icons/toys.svg | 1 + includes/backup.php | 81 ++ includes/banner.php | 24 +- includes/bitset.php | 4 + includes/emergency.php | 2 +- includes/footer.php | 7 +- includes/functions.php | 100 +++ includes/header.php | 73 +- includes/ical/LICENSE | 24 + includes/ical/bin/timezones.php | 20 + includes/ical/main.php | 7 + includes/ical/src/EventsList.php | 54 ++ includes/ical/src/Freq.php | 633 +++++++++++++++ includes/ical/src/IcalParser.php | 466 +++++++++++ includes/ical/src/Recurrence.php | 234 ++++++ includes/ical/src/WindowsTimezones.php | 214 +++++ includes/keywords.php | 100 +++ includes/member.php | 2 +- includes/planner.php | 11 +- includes/pleasure.php | 2 +- includes/random.php | 15 + includes/refresh.php | 16 +- includes/restore.php | 130 +++ includes/session.php | 6 + includes/sysbanner.php | 2 +- includes/system/compare.php | 38 +- pages/actions.php | 1132 +++++++++++++++++++++++++++ pages/bitset.php | 32 +- pages/dashboard.php | 299 +++++++ pages/debug.php | 91 +++ pages/edit.php | 2 +- pages/emergency.php | 2 +- pages/fronting.php | 2 +- pages/nicknames.php | 117 +++ pages/parser.php | 2 +- pages/pleasure.php | 2 +- pages/prefix.php | 2 +- pages/rules.php | 254 ++++++ pages/score.php | 4 +- pages/splitting.php | 2 +- pages/together.php | 2 +- pages/toys.php | 915 ++++++++++++++++++++++ pages/travelling.php | 4 +- 124 files changed, 5311 insertions(+), 162 deletions(-) delete mode 100644 .idea/sshConfigs.xml delete mode 100644 .idea/webServers.xml create mode 100644 api/cloudburst-scored.php create mode 100644 api/eval.php create mode 100644 api/raindrops-scored.php create mode 100644 assets/editor/fuse.js create mode 100644 assets/icons/actions.svg create mode 100644 assets/icons/dashboard.svg create mode 100644 assets/icons/debug.svg create mode 100644 assets/icons/favicon/actions.png create mode 100644 assets/icons/favicon/dashboard.png create mode 100644 assets/icons/favicon/debug.png create mode 100644 assets/icons/favicon/nicknames.png create mode 100644 assets/icons/favicon/pleasure.png create mode 100644 assets/icons/favicon/rules.png create mode 100644 assets/icons/favicon/toys.png create mode 100644 assets/icons/nicknames.svg create mode 100644 assets/icons/rules.svg create mode 100644 assets/icons/toys.svg create mode 100644 includes/backup.php create mode 100644 includes/ical/LICENSE create mode 100644 includes/ical/bin/timezones.php create mode 100644 includes/ical/main.php create mode 100644 includes/ical/src/EventsList.php create mode 100644 includes/ical/src/Freq.php create mode 100644 includes/ical/src/IcalParser.php create mode 100644 includes/ical/src/Recurrence.php create mode 100644 includes/ical/src/WindowsTimezones.php create mode 100644 includes/keywords.php create mode 100644 includes/random.php create mode 100644 includes/restore.php create mode 100644 pages/actions.php create mode 100644 pages/dashboard.php create mode 100644 pages/debug.php create mode 100644 pages/nicknames.php create mode 100644 pages/rules.php create mode 100644 pages/toys.php diff --git a/.gitignore b/.gitignore index 5f36e35..197f2f2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ includes/data.backup2 includes/app.json includes/tokens together/build -assets/editor/thing.mp4 \ No newline at end of file +assets/editor/thing.mp4 +includes/_restored \ No newline at end of file diff --git a/.idea/deployment.xml b/.idea/deployment.xml index d10c7a3..c2f202c 100644 --- a/.idea/deployment.xml +++ b/.idea/deployment.xml @@ -5,7 +5,7 @@ - + diff --git a/.idea/sshConfigs.xml b/.idea/sshConfigs.xml deleted file mode 100644 index dfef745..0000000 --- a/.idea/sshConfigs.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/webServers.xml b/.idea/webServers.xml deleted file mode 100644 index 57d9319..0000000 --- a/.idea/webServers.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index f067871..f37f24e 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,18 @@ 48bit bitsets are now used to define metadata that doesn't require a string input, they were 24bit in the past but were changed to 48bit after adding additional metadata. A lot of reserved values remain for future use. ``` +...............0................................ + => Is mostly verbal in real life. + +...............1................................ + => Is mostly non verbal in real life. + +..............0................................. + => Fronts frequently. + +..............1................................. + => Fronts less frequently. + ................00.............................. => Doesn't need to eat food. @@ -184,16 +196,7 @@ The following bits are reserved for future use or for technical reasons: ``` -.....................110........................ <(1)> -.....................111........................ <(1)> -.....................000........................ <(1)> -.....................001........................ <(1)> -.....................010........................ <(1)> -.....................010........................ <(1)> -.....................011........................ <(1)> -.....................100........................ <(1)> -.....................101........................ <(1)> -.....................111........................ <(1)> +xxxxxxxxxxxxxxxx................................ <(1)> ..................110........................... <(2)> ..................111........................... <(2)> .....................101........................ <(2)> diff --git a/api/app-images.php b/api/app-images.php index edab3c9..76b11a0 100644 --- a/api/app-images.php +++ b/api/app-images.php @@ -1,7 +1,7 @@ [ + 'method' => 'POST', + 'header' => + "Content-Type: text/plain\r\n" . + "Title: ๐Ÿด Switch occurred in the $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" + ] + ])); + file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/data.json-res", "affected: " . $input["type"] . " (" . gettype($input["type"]) . ")") and die(); } else { file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/data.json-res", "invalid method: " . $input["type"] . " (" . gettype($input["type"]) . ")") and die(); diff --git a/api/raindrops-banners.php b/api/raindrops-banners.php index 715d7c4..f42426a 100644 --- a/api/raindrops-banners.php +++ b/api/raindrops-banners.php @@ -3,7 +3,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; global $isLoggedIn; require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/bitset.php"; require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/banner.php"; -if (!$isLoggedIn) header("Location: /login") and die(); +if (!$isLoggedIn) header("Location: /-/login") and die(); $data = []; diff --git a/api/raindrops-data.php b/api/raindrops-data.php index 6af9233..958fb8f 100644 --- a/api/raindrops-data.php +++ b/api/raindrops-data.php @@ -2,7 +2,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; global $isLoggedIn; require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/bitset.php"; -if (!$isLoggedIn) header("Location: /login") and die(); +if (!$isLoggedIn) header("Location: /-/login") and die(); $data = []; $data["members"] = []; diff --git a/api/raindrops-scored.php b/api/raindrops-scored.php new file mode 100644 index 0000000..929cdc8 --- /dev/null +++ b/api/raindrops-scored.php @@ -0,0 +1,9 @@ + -
+

- ${data['id'] === "unknown" ? ` - Unknown member (${data['system']['name']} System) - ` : data['id'] === "fusion" ? (data['name'] === "fusion" ? ` - Multiple merged members - ` : data['name']) : data['name']} + + + ${data['id'] === "unknown" ? ` + Unknown member (${data['system']['name']} System) + ` : data['id'] === "fusion" ? (data['name'] === "fusion" ? ` + Multiple merged members + ` : data['name']) : data['name']} +

@@ -176,7 +179,7 @@ async function refreshBanner(offline, french) { ` : ''}
${data['id'] !== "unknown" && data['id'] !== "fusion" ? ` -
+
${french ? (data['relations']['marefriends'].length > 1 ? 'Partenaires ' : 'Partenaire ') : `Marefriend${data['relations']['marefriends'].length > 1 ? 's' : ''}`}: ${data['relations']['marefriends'].length > 1 ? '
' : ''} ${data['relations']['marefriends'].map(relation => ` diff --git a/app/emergency/index.php b/app/emergency/index.php index 395f0d9..d8cfedf 100644 --- a/app/emergency/index.php +++ b/app/emergency/index.php @@ -1,4 +1,4 @@ - + diff --git a/app/fronters/style.css b/app/fronters/style.css index 135500a..cc3a8cb 100644 --- a/app/fronters/style.css +++ b/app/fronters/style.css @@ -8,10 +8,6 @@ grid-template-columns: repeat(6, 1fr) !important; } -@media (max-width: 1399px) { - -} - @media (max-width: 1199px) { .wiaf-grid { grid-template-columns: repeat(4, 1fr) !important; diff --git a/app/index.html b/app/index.html index decc6b7..22ee9a9 100644 --- a/app/index.html +++ b/app/index.html @@ -201,6 +201,29 @@ } } + .list-group-item { + color: #fff; + background-color: #222; + border: 1px solid rgba(255, 255, 255, .125); + } + + .list-group-item.disabled { + color: #fff; + background-color: #222; + border-color: rgba(255, 255, 255, .125); + opacity: .75; + } + + .list-group-item-action:hover { + background-color: #252525; + color: #ddd; + } + + .list-group-item-action:active, .list-group-item-action:focus { + background-color: #272727; + color: #bbb; + } + #member-banner a { color: white; } @@ -220,6 +243,23 @@ grid-template-columns: 1fr !important; text-align: left; } + + #member-icon-mobile { + display: inline-block !important; + } + + #system-info { + grid-template-columns: 1fr !important; + } + + #member-icon, #member-icon-outer { + display: none !important; + } + + #member-relations { + grid-template-columns: 1fr !important; + text-align: left; + } } .tooltip.show { @@ -230,6 +270,10 @@ background: #151515; box-shadow: 3px 4px 10px #ffffff26; } + + html, body, #app { + height: 100%; + } @@ -242,43 +286,38 @@
-
-
-
-

-

 Greetings Name!

- -
-
- - - Fronters - - - - Bits - - - - Planner - - - - Emergency - +
+
+
+ +

In the other system: Some member

-

- -

-
- Demo mode (for Raindrops' therapist) -

- -
-
- +
+

ยฉ Equestria.dev Developers ยท Data updated

diff --git a/app/load.js b/app/load.js index 9e4d3b9..1be0b57 100644 --- a/app/load.js +++ b/app/load.js @@ -46,6 +46,21 @@ let valuesToGet = { limited: false, name: "Switches at Raindrops...", }, + "actions": { + url: "/api/data?f=actions.json", + limited: false, + name: "Actions...", + }, + "rules": { + url: "/api/data?f=rules.json", + limited: false, + name: "Systems rules...", + }, + "nicknames": { + url: "/api/data?f=nicknames.json", + limited: false, + name: "Relations nicknames...", + }, "peh-cloudburst-data": { url: "/api/cloudburst-data", limited: false, @@ -56,6 +71,16 @@ let valuesToGet = { limited: false, name: "Raindrops data...", }, + "peh-cloudburst-scored": { + url: "/api/cloudburst-scored", + limited: false, + name: "Cloudburst ordered members...", + }, + "peh-raindrops-scored": { + url: "/api/raindrops-scored", + limited: false, + name: "Raindrops ordered members...", + }, "peh-cloudburst-banners": { url: "/api/cloudburst-banners", limited: false, @@ -138,13 +163,27 @@ async function getNewValue() { } try { - if (valuesToGet[keys[0]].condition && !(await valuesToGet[keys[0]].condition())) throw new Error(); + if (valuesToGet[keys[0]].condition && !(await valuesToGet[keys[0]].condition())) { + keys.shift(); + + if (!keys[0]) { + await localforage.setItem("refresh", new Date().toISOString()); + await postLoad(); + return; + } + + document.getElementById("progress-inner").style.width = ((index / Object.keys(valuesToGet).length) * 100) + "%"; + index++; + await getNewValue(); + return; + } document.getElementById("loader-message").innerText = valuesToGet[keys[0]].name; await localforage.setItem(keys[0], (await (await fetchPlus(valuesToGet[keys[0]]["url"], { timeout: 3000 })).text())); keys.shift(); if (!keys[0]) { + await localforage.setItem("refresh", new Date().toISOString()); await postLoad(); return; } @@ -155,6 +194,9 @@ async function getNewValue() { await getNewValue(); }, valuesToGet[keys[0]]["limited"] ? 550 : 0); } catch (e) { + console.log("Error while fetching", keys[0]); + console.error(e); + for (let key of Object.keys(valuesToGet)) { if (await localforage.getItem(key) === null) { throw new Error("App requested key '" + key + "' but it can't be retrieved at the moment"); @@ -181,6 +223,8 @@ async function postLoad() { if (key !== "images") localStorage.setItem(key, await localforage.getItem(key)) } + window.images = JSON.parse(await localforage.getItem("images")); + localStorage.setItem("pluralkit-0", localStorage.getItem("pluralkit-raindrops-members")); localStorage.setItem("pluralkit-1", localStorage.getItem("pluralkit-cloudburst-members")); @@ -242,6 +286,13 @@ async function postLoad() { break; } + window.otherSystem = window.data.identity.id === "raindrops" ? "cloudburst" : "raindrops"; + window.otherFronters = window.data["pluralkit-" + otherSystem + "-fronters"]; + + document.getElementById("home-other-system").innerText = otherSystem === "raindrops" ? "Raindrops System" : "Cloudburst System"; + document.getElementById("home-other-name").innerText = window.otherFronters.members[0].display_name ?? window.otherFronters.members[0].name; + document.getElementById("home-other-img").src = window.images.profile[otherSystem === "raindrops" ? "gdapd" : "ynmuc"][window.otherFronters.members[0].id]; + if (!window.connected) { document.getElementById("home-app-planner").classList.add("disabled"); document.getElementById("home-app-emergency").classList.add("disabled"); @@ -261,9 +312,8 @@ async function postLoad() { } } - window.currentMemberData = JSON.parse(localStorage.getItem("peh-" + JSON.parse(localStorage.getItem("identity")).id + "-banners"))[JSON.parse(localStorage.getItem("pluralkit-" + JSON.parse(localStorage.getItem("identity")).id + "-fronters")).members[0].name]; - - //await refreshBanner(true); refreshTooltips(); + document.getElementById("copyright-year").innerText = new Date().getFullYear().toString(); + document.getElementById("update-date").innerText = timeAgo(await localforage.getItem("refresh")); setInterval(async () => { Array.from(document.getElementsByClassName("relative-time")).forEach((el) => { diff --git a/app/planner/index.php b/app/planner/index.php index 30f09dd..b2070da 100644 --- a/app/planner/index.php +++ b/app/planner/index.php @@ -1,4 +1,4 @@ - + diff --git a/assets/editor/fuse.js b/assets/editor/fuse.js new file mode 100644 index 0000000..74894ce --- /dev/null +++ b/assets/editor/fuse.js @@ -0,0 +1,9 @@ +/** + * Fuse.js v6.6.2 - Lightweight fuzzy-search (http://fusejs.io) + * + * Copyright (c) 2022 Kiro Risk (http://kiro.me) + * All Rights Reserved. Apache Software License 2.0 + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +var e,t;e=this,t=function(){"use strict";function e(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function t(t){for(var n=1;ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n0&&void 0!==arguments[0]?arguments[0]:1,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:3,n=new Map,r=Math.pow(10,t);return{get:function(t){var i=t.match(C).length;if(n.has(i))return n.get(i);var o=1/Math.pow(i,.5*e),c=parseFloat(Math.round(o*r)/r);return n.set(i,c),c},clear:function(){n.clear()}}}var $=function(){function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=t.getFn,i=void 0===n?I.getFn:n,o=t.fieldNormWeight,c=void 0===o?I.fieldNormWeight:o;r(this,e),this.norm=E(c,3),this.getFn=i,this.isCreated=!1,this.setIndexRecords()}return o(e,[{key:"setSources",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.docs=e}},{key:"setIndexRecords",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.records=e}},{key:"setKeys",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.keys=t,this._keysMap={},t.forEach((function(t,n){e._keysMap[t.id]=n}))}},{key:"create",value:function(){var e=this;!this.isCreated&&this.docs.length&&(this.isCreated=!0,g(this.docs[0])?this.docs.forEach((function(t,n){e._addString(t,n)})):this.docs.forEach((function(t,n){e._addObject(t,n)})),this.norm.clear())}},{key:"add",value:function(e){var t=this.size();g(e)?this._addString(e,t):this._addObject(e,t)}},{key:"removeAt",value:function(e){this.records.splice(e,1);for(var t=e,n=this.size();t2&&void 0!==arguments[2]?arguments[2]:{},r=n.getFn,i=void 0===r?I.getFn:r,o=n.fieldNormWeight,c=void 0===o?I.fieldNormWeight:o,a=new $({getFn:i,fieldNormWeight:c});return a.setKeys(e.map(_)),a.setSources(t),a.create(),a}function R(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.errors,r=void 0===n?0:n,i=t.currentLocation,o=void 0===i?0:i,c=t.expectedLocation,a=void 0===c?0:c,s=t.distance,u=void 0===s?I.distance:s,h=t.ignoreLocation,l=void 0===h?I.ignoreLocation:h,f=r/e.length;if(l)return f;var d=Math.abs(a-o);return u?f+d/u:d?1:f}function N(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:I.minMatchCharLength,n=[],r=-1,i=-1,o=0,c=e.length;o=t&&n.push([r,i]),r=-1)}return e[o-1]&&o-r>=t&&n.push([r,o-1]),n}var P=32;function W(e){for(var t={},n=0,r=e.length;n1&&void 0!==arguments[1]?arguments[1]:{},o=i.location,c=void 0===o?I.location:o,a=i.threshold,s=void 0===a?I.threshold:a,u=i.distance,h=void 0===u?I.distance:u,l=i.includeMatches,f=void 0===l?I.includeMatches:l,d=i.findAllMatches,v=void 0===d?I.findAllMatches:d,g=i.minMatchCharLength,y=void 0===g?I.minMatchCharLength:g,p=i.isCaseSensitive,m=void 0===p?I.isCaseSensitive:p,k=i.ignoreLocation,M=void 0===k?I.ignoreLocation:k;if(r(this,e),this.options={location:c,threshold:s,distance:h,includeMatches:f,findAllMatches:v,minMatchCharLength:y,isCaseSensitive:m,ignoreLocation:M},this.pattern=m?t:t.toLowerCase(),this.chunks=[],this.pattern.length){var b=function(e,t){n.chunks.push({pattern:e,alphabet:W(e),startIndex:t})},x=this.pattern.length;if(x>P){for(var w=0,L=x%P,S=x-L;w3&&void 0!==arguments[3]?arguments[3]:{},i=r.location,o=void 0===i?I.location:i,c=r.distance,a=void 0===c?I.distance:c,s=r.threshold,u=void 0===s?I.threshold:s,h=r.findAllMatches,l=void 0===h?I.findAllMatches:h,f=r.minMatchCharLength,d=void 0===f?I.minMatchCharLength:f,v=r.includeMatches,g=void 0===v?I.includeMatches:v,y=r.ignoreLocation,p=void 0===y?I.ignoreLocation:y;if(t.length>P)throw new Error(w(P));for(var m,k=t.length,M=e.length,b=Math.max(0,Math.min(o,M)),x=u,L=b,S=d>1||g,_=S?Array(M):[];(m=e.indexOf(t,L))>-1;){var O=R(t,{currentLocation:m,expectedLocation:b,distance:a,ignoreLocation:p});if(x=Math.min(O,x),L=m+k,S)for(var j=0;j=z;q-=1){var B=q-1,J=n[e.charAt(B)];if(S&&(_[B]=+!!J),K[q]=(K[q+1]<<1|1)&J,F&&(K[q]|=(A[q+1]|A[q])<<1|1|A[q+1]),K[q]&$&&(C=R(t,{errors:F,currentLocation:B,expectedLocation:b,distance:a,ignoreLocation:p}))<=x){if(x=C,(L=B)<=b)break;z=Math.max(1,2*b-L)}}if(R(t,{errors:F+1,currentLocation:b,expectedLocation:b,distance:a,ignoreLocation:p})>x)break;A=K}var U={isMatch:L>=0,score:Math.max(.001,C)};if(S){var V=N(_,d);V.length?g&&(U.indices=V):U.isMatch=!1}return U}(e,n,i,{location:c+o,distance:a,threshold:s,findAllMatches:u,minMatchCharLength:h,includeMatches:r,ignoreLocation:l}),p=y.isMatch,m=y.score,k=y.indices;p&&(g=!0),v+=m,p&&k&&(d=[].concat(f(d),f(k)))}));var y={isMatch:g,score:g?v/this.chunks.length:1};return g&&r&&(y.indices=d),y}}]),e}(),z=function(){function e(t){r(this,e),this.pattern=t}return o(e,[{key:"search",value:function(){}}],[{key:"isMultiMatch",value:function(e){return D(e,this.multiRegex)}},{key:"isSingleMatch",value:function(e){return D(e,this.singleRegex)}}]),e}();function D(e,t){var n=e.match(t);return n?n[1]:null}var K=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=e===this.pattern;return{isMatch:t,score:t?0:1,indices:[0,this.pattern.length-1]}}}],[{key:"type",get:function(){return"exact"}},{key:"multiRegex",get:function(){return/^="(.*)"$/}},{key:"singleRegex",get:function(){return/^=(.*)$/}}]),n}(z),q=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=-1===e.indexOf(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}}],[{key:"type",get:function(){return"inverse-exact"}},{key:"multiRegex",get:function(){return/^!"(.*)"$/}},{key:"singleRegex",get:function(){return/^!(.*)$/}}]),n}(z),B=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=e.startsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,this.pattern.length-1]}}}],[{key:"type",get:function(){return"prefix-exact"}},{key:"multiRegex",get:function(){return/^\^"(.*)"$/}},{key:"singleRegex",get:function(){return/^\^(.*)$/}}]),n}(z),J=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=!e.startsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}}],[{key:"type",get:function(){return"inverse-prefix-exact"}},{key:"multiRegex",get:function(){return/^!\^"(.*)"$/}},{key:"singleRegex",get:function(){return/^!\^(.*)$/}}]),n}(z),U=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=e.endsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[e.length-this.pattern.length,e.length-1]}}}],[{key:"type",get:function(){return"suffix-exact"}},{key:"multiRegex",get:function(){return/^"(.*)"\$$/}},{key:"singleRegex",get:function(){return/^(.*)\$$/}}]),n}(z),V=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=!e.endsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}}],[{key:"type",get:function(){return"inverse-suffix-exact"}},{key:"multiRegex",get:function(){return/^!"(.*)"\$$/}},{key:"singleRegex",get:function(){return/^!(.*)\$$/}}]),n}(z),G=function(e){a(n,e);var t=l(n);function n(e){var i,o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},c=o.location,a=void 0===c?I.location:c,s=o.threshold,u=void 0===s?I.threshold:s,h=o.distance,l=void 0===h?I.distance:h,f=o.includeMatches,d=void 0===f?I.includeMatches:f,v=o.findAllMatches,g=void 0===v?I.findAllMatches:v,y=o.minMatchCharLength,p=void 0===y?I.minMatchCharLength:y,m=o.isCaseSensitive,k=void 0===m?I.isCaseSensitive:m,M=o.ignoreLocation,b=void 0===M?I.ignoreLocation:M;return r(this,n),(i=t.call(this,e))._bitapSearch=new T(e,{location:a,threshold:u,distance:l,includeMatches:d,findAllMatches:g,minMatchCharLength:p,isCaseSensitive:k,ignoreLocation:b}),i}return o(n,[{key:"search",value:function(e){return this._bitapSearch.searchIn(e)}}],[{key:"type",get:function(){return"fuzzy"}},{key:"multiRegex",get:function(){return/^"(.*)"$/}},{key:"singleRegex",get:function(){return/^(.*)$/}}]),n}(z),H=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){for(var t,n=0,r=[],i=this.pattern.length;(t=e.indexOf(this.pattern,n))>-1;)n=t+i,r.push([t,n-1]);var o=!!r.length;return{isMatch:o,score:o?0:1,indices:r}}}],[{key:"type",get:function(){return"include"}},{key:"multiRegex",get:function(){return/^'"(.*)"$/}},{key:"singleRegex",get:function(){return/^'(.*)$/}}]),n}(z),Q=[K,H,B,J,V,U,q,G],X=Q.length,Y=/ +(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/;function Z(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e.split("|").map((function(e){for(var n=e.trim().split(Y).filter((function(e){return e&&!!e.trim()})),r=[],i=0,o=n.length;i1&&void 0!==arguments[1]?arguments[1]:{},i=n.isCaseSensitive,o=void 0===i?I.isCaseSensitive:i,c=n.includeMatches,a=void 0===c?I.includeMatches:c,s=n.minMatchCharLength,u=void 0===s?I.minMatchCharLength:s,h=n.ignoreLocation,l=void 0===h?I.ignoreLocation:h,f=n.findAllMatches,d=void 0===f?I.findAllMatches:f,v=n.location,g=void 0===v?I.location:v,y=n.threshold,p=void 0===y?I.threshold:y,m=n.distance,k=void 0===m?I.distance:m;r(this,e),this.query=null,this.options={isCaseSensitive:o,includeMatches:a,minMatchCharLength:u,findAllMatches:d,ignoreLocation:l,location:g,threshold:p,distance:k},this.pattern=o?t:t.toLowerCase(),this.query=Z(this.pattern,this.options)}return o(e,[{key:"searchIn",value:function(e){var t=this.query;if(!t)return{isMatch:!1,score:1};var n=this.options,r=n.includeMatches;e=n.isCaseSensitive?e:e.toLowerCase();for(var i=0,o=[],c=0,a=0,s=t.length;a-1&&(n.refIndex=e.idx),t.matches.push(n)}}))}function ve(e,t){t.score=e.score}function ge(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=n.includeMatches,i=void 0===r?I.includeMatches:r,o=n.includeScore,c=void 0===o?I.includeScore:o,a=[];return i&&a.push(de),c&&a.push(ve),e.map((function(e){var n=e.idx,r={item:t[n],refIndex:n};return a.length&&a.forEach((function(t){t(e,r)})),r}))}var ye=function(){function e(n){var i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},o=arguments.length>2?arguments[2]:void 0;r(this,e),this.options=t(t({},I),i),this.options.useExtendedSearch,this._keyStore=new S(this.options.keys),this.setCollection(n,o)}return o(e,[{key:"setCollection",value:function(e,t){if(this._docs=e,t&&!(t instanceof $))throw new Error("Incorrect 'index' type");this._myIndex=t||F(this.options.keys,this._docs,{getFn:this.options.getFn,fieldNormWeight:this.options.fieldNormWeight})}},{key:"add",value:function(e){k(e)&&(this._docs.push(e),this._myIndex.add(e))}},{key:"remove",value:function(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:function(){return!1},t=[],n=0,r=this._docs.length;n1&&void 0!==arguments[1]?arguments[1]:{},n=t.limit,r=void 0===n?-1:n,i=this.options,o=i.includeMatches,c=i.includeScore,a=i.shouldSort,s=i.sortFn,u=i.ignoreFieldNorm,h=g(e)?g(this._docs[0])?this._searchStringList(e):this._searchObjectList(e):this._searchLogical(e);return fe(h,{ignoreFieldNorm:u}),a&&h.sort(s),y(r)&&r>-1&&(h=h.slice(0,r)),ge(h,this._docs,{includeMatches:o,includeScore:c})}},{key:"_searchStringList",value:function(e){var t=re(e,this.options),n=this._myIndex.records,r=[];return n.forEach((function(e){var n=e.v,i=e.i,o=e.n;if(k(n)){var c=t.searchIn(n),a=c.isMatch,s=c.score,u=c.indices;a&&r.push({item:n,idx:i,matches:[{score:s,value:n,norm:o,indices:u}]})}})),r}},{key:"_searchLogical",value:function(e){var t=this,n=function(e,t){var n=(arguments.length>2&&void 0!==arguments[2]?arguments[2]:{}).auto,r=void 0===n||n,i=function e(n){var i=Object.keys(n),o=ue(n);if(!o&&i.length>1&&!se(n))return e(le(n));if(he(n)){var c=o?n[ce]:i[0],a=o?n[ae]:n[c];if(!g(a))throw new Error(x(c));var s={keyId:j(c),pattern:a};return r&&(s.searcher=re(a,t)),s}var u={foals:[],operator:i[0]};return i.forEach((function(t){var r=n[t];v(r)&&r.forEach((function(t){u.foals.push(e(t))}))})),u};return se(e)||(e=le(e)),i(e)}(e,this.options),r=function e(n,r,i){if(!n.foals){var o=n.keyId,c=n.searcher,a=t._findMatches({key:t._keyStore.get(o),value:t._myIndex.getValueForItemAtKeyId(r,o),searcher:c});return a&&a.length?[{idx:i,item:r,matches:a}]:[]}for(var s=[],u=0,h=n.foals.length;u1&&void 0!==arguments[1]?arguments[1]:{},n=t.getFn,r=void 0===n?I.getFn:n,i=t.fieldNormWeight,o=void 0===i?I.fieldNormWeight:i,c=e.keys,a=e.records,s=new $({getFn:r,fieldNormWeight:o});return s.setKeys(c),s.setIndexRecords(a),s},ye.config=I,function(){ne.push.apply(ne,arguments)}(te),ye},"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).Fuse=t(); \ No newline at end of file diff --git a/assets/icons/actions.svg b/assets/icons/actions.svg new file mode 100644 index 0000000..7b36d3d --- /dev/null +++ b/assets/icons/actions.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/dashboard.svg b/assets/icons/dashboard.svg new file mode 100644 index 0000000..984a8b7 --- /dev/null +++ b/assets/icons/dashboard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/debug.svg b/assets/icons/debug.svg new file mode 100644 index 0000000..e147a29 --- /dev/null +++ b/assets/icons/debug.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/favicon/about.png b/assets/icons/favicon/about.png index e17841c..a0fccd7 100644 Binary files a/assets/icons/favicon/about.png and b/assets/icons/favicon/about.png differ diff --git a/assets/icons/favicon/actions.png b/assets/icons/favicon/actions.png new file mode 100644 index 0000000..9638375 Binary files /dev/null and b/assets/icons/favicon/actions.png differ diff --git a/assets/icons/favicon/add.png b/assets/icons/favicon/add.png index 607ad2d..c7a0553 100644 Binary files a/assets/icons/favicon/add.png and b/assets/icons/favicon/add.png differ diff --git a/assets/icons/favicon/admin.png b/assets/icons/favicon/admin.png index 7762f56..720bb43 100644 Binary files a/assets/icons/favicon/admin.png and b/assets/icons/favicon/admin.png differ diff --git a/assets/icons/favicon/bitset.png b/assets/icons/favicon/bitset.png index 5999238..2fb3b08 100644 Binary files a/assets/icons/favicon/bitset.png and b/assets/icons/favicon/bitset.png differ diff --git a/assets/icons/favicon/compare.png b/assets/icons/favicon/compare.png index b41d42d..b27d92f 100644 Binary files a/assets/icons/favicon/compare.png and b/assets/icons/favicon/compare.png differ diff --git a/assets/icons/favicon/complete.png b/assets/icons/favicon/complete.png index b548081..c4c3c36 100644 Binary files a/assets/icons/favicon/complete.png and b/assets/icons/favicon/complete.png differ diff --git a/assets/icons/favicon/dashboard.png b/assets/icons/favicon/dashboard.png new file mode 100644 index 0000000..8725a74 Binary files /dev/null and b/assets/icons/favicon/dashboard.png differ diff --git a/assets/icons/favicon/debug.png b/assets/icons/favicon/debug.png new file mode 100644 index 0000000..1cc85b0 Binary files /dev/null and b/assets/icons/favicon/debug.png differ diff --git a/assets/icons/favicon/delete.png b/assets/icons/favicon/delete.png index 8194dd9..d4ccd46 100644 Binary files a/assets/icons/favicon/delete.png and b/assets/icons/favicon/delete.png differ diff --git a/assets/icons/favicon/disclaimers.png b/assets/icons/favicon/disclaimers.png index ab18631..31a44ff 100644 Binary files a/assets/icons/favicon/disclaimers.png and b/assets/icons/favicon/disclaimers.png differ diff --git a/assets/icons/favicon/down.png b/assets/icons/favicon/down.png index 305c94a..a90d632 100644 Binary files a/assets/icons/favicon/down.png and b/assets/icons/favicon/down.png differ diff --git a/assets/icons/favicon/emergency.png b/assets/icons/favicon/emergency.png index 255bdd6..0754554 100644 Binary files a/assets/icons/favicon/emergency.png and b/assets/icons/favicon/emergency.png differ diff --git a/assets/icons/favicon/form.png b/assets/icons/favicon/form.png index d194644..09a3324 100644 Binary files a/assets/icons/favicon/form.png and b/assets/icons/favicon/form.png differ diff --git a/assets/icons/favicon/fronting.png b/assets/icons/favicon/fronting.png index b5dd2f1..e4060be 100644 Binary files a/assets/icons/favicon/fronting.png and b/assets/icons/favicon/fronting.png differ diff --git a/assets/icons/favicon/global.png b/assets/icons/favicon/global.png index f3a2b9a..1953e5f 100644 Binary files a/assets/icons/favicon/global.png and b/assets/icons/favicon/global.png differ diff --git a/assets/icons/favicon/history.png b/assets/icons/favicon/history.png index 3a6581e..6ffc90a 100644 Binary files a/assets/icons/favicon/history.png and b/assets/icons/favicon/history.png differ diff --git a/assets/icons/favicon/home.png b/assets/icons/favicon/home.png index d449bda..39eb6f0 100644 Binary files a/assets/icons/favicon/home.png and b/assets/icons/favicon/home.png differ diff --git a/assets/icons/favicon/login.png b/assets/icons/favicon/login.png index 6cb2e6e..0006e32 100644 Binary files a/assets/icons/favicon/login.png and b/assets/icons/favicon/login.png differ diff --git a/assets/icons/favicon/logout.png b/assets/icons/favicon/logout.png index 2f4dd99..344788c 100644 Binary files a/assets/icons/favicon/logout.png and b/assets/icons/favicon/logout.png differ diff --git a/assets/icons/favicon/nicknames.png b/assets/icons/favicon/nicknames.png new file mode 100644 index 0000000..647bbd0 Binary files /dev/null and b/assets/icons/favicon/nicknames.png differ diff --git a/assets/icons/favicon/none.png b/assets/icons/favicon/none.png index 6c7f2d1..efe2b05 100644 Binary files a/assets/icons/favicon/none.png and b/assets/icons/favicon/none.png differ diff --git a/assets/icons/favicon/parser.png b/assets/icons/favicon/parser.png index 1c86e71..00a26cf 100644 Binary files a/assets/icons/favicon/parser.png and b/assets/icons/favicon/parser.png differ diff --git a/assets/icons/favicon/partial.png b/assets/icons/favicon/partial.png index bd95908..58e2f14 100644 Binary files a/assets/icons/favicon/partial.png and b/assets/icons/favicon/partial.png differ diff --git a/assets/icons/favicon/pleasure.png b/assets/icons/favicon/pleasure.png new file mode 100644 index 0000000..980b1f9 Binary files /dev/null and b/assets/icons/favicon/pleasure.png differ diff --git a/assets/icons/favicon/prefix.png b/assets/icons/favicon/prefix.png index 4860cf3..a96ee7f 100644 Binary files a/assets/icons/favicon/prefix.png and b/assets/icons/favicon/prefix.png differ diff --git a/assets/icons/favicon/relations.png b/assets/icons/favicon/relations.png index 01b6cde..9042590 100644 Binary files a/assets/icons/favicon/relations.png and b/assets/icons/favicon/relations.png differ diff --git a/assets/icons/favicon/right.png b/assets/icons/favicon/right.png index fd8c56d..c9f6b09 100644 Binary files a/assets/icons/favicon/right.png and b/assets/icons/favicon/right.png differ diff --git a/assets/icons/favicon/rules.png b/assets/icons/favicon/rules.png new file mode 100644 index 0000000..de4491a Binary files /dev/null and b/assets/icons/favicon/rules.png differ diff --git a/assets/icons/favicon/score.png b/assets/icons/favicon/score.png index ef82f07..6480b7f 100644 Binary files a/assets/icons/favicon/score.png and b/assets/icons/favicon/score.png differ diff --git a/assets/icons/favicon/shield.png b/assets/icons/favicon/shield.png index f8b5056..ed4e995 100644 Binary files a/assets/icons/favicon/shield.png and b/assets/icons/favicon/shield.png differ diff --git a/assets/icons/favicon/species.png b/assets/icons/favicon/species.png index f65b1b6..a7ee51f 100644 Binary files a/assets/icons/favicon/species.png and b/assets/icons/favicon/species.png differ diff --git a/assets/icons/favicon/splitting.png b/assets/icons/favicon/splitting.png index 4756275..50c4e24 100644 Binary files a/assets/icons/favicon/splitting.png and b/assets/icons/favicon/splitting.png differ diff --git a/assets/icons/favicon/terminology.png b/assets/icons/favicon/terminology.png index 3545fed..4a2ab56 100644 Binary files a/assets/icons/favicon/terminology.png and b/assets/icons/favicon/terminology.png differ diff --git a/assets/icons/favicon/together.png b/assets/icons/favicon/together.png index 7022be0..e60e4a6 100644 Binary files a/assets/icons/favicon/together.png and b/assets/icons/favicon/together.png differ diff --git a/assets/icons/favicon/toys.png b/assets/icons/favicon/toys.png new file mode 100644 index 0000000..72fa2b3 Binary files /dev/null and b/assets/icons/favicon/toys.png differ diff --git a/assets/icons/favicon/travel.png b/assets/icons/favicon/travel.png index fad716a..f320430 100644 Binary files a/assets/icons/favicon/travel.png and b/assets/icons/favicon/travel.png differ diff --git a/assets/icons/favicon/travelling.png b/assets/icons/favicon/travelling.png index 4c26122..b28b20a 100644 Binary files a/assets/icons/favicon/travelling.png and b/assets/icons/favicon/travelling.png differ diff --git a/assets/icons/favicon/tree.png b/assets/icons/favicon/tree.png index c530f48..01fdb17 100644 Binary files a/assets/icons/favicon/tree.png and b/assets/icons/favicon/tree.png differ diff --git a/assets/icons/favicon/up.png b/assets/icons/favicon/up.png index ce1a47b..96ceccc 100644 Binary files a/assets/icons/favicon/up.png and b/assets/icons/favicon/up.png differ diff --git a/assets/icons/favicon/user.png b/assets/icons/favicon/user.png index c98f156..44f3b0d 100644 Binary files a/assets/icons/favicon/user.png and b/assets/icons/favicon/user.png differ diff --git a/assets/icons/favicon/visibility-depends.png b/assets/icons/favicon/visibility-depends.png index 521e991..c401f23 100644 Binary files a/assets/icons/favicon/visibility-depends.png and b/assets/icons/favicon/visibility-depends.png differ diff --git a/assets/icons/favicon/visibility-private.png b/assets/icons/favicon/visibility-private.png index 000bc3e..ba18eeb 100644 Binary files a/assets/icons/favicon/visibility-private.png and b/assets/icons/favicon/visibility-private.png differ diff --git a/assets/icons/favicon/visibility-public.png b/assets/icons/favicon/visibility-public.png index b15ce73..cd14a0e 100644 Binary files a/assets/icons/favicon/visibility-public.png and b/assets/icons/favicon/visibility-public.png differ diff --git a/assets/icons/nicknames.svg b/assets/icons/nicknames.svg new file mode 100644 index 0000000..f1eef81 --- /dev/null +++ b/assets/icons/nicknames.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/rules.svg b/assets/icons/rules.svg new file mode 100644 index 0000000..633f0b2 --- /dev/null +++ b/assets/icons/rules.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/toys.svg b/assets/icons/toys.svg new file mode 100644 index 0000000..3fcc608 --- /dev/null +++ b/assets/icons/toys.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/includes/backup.php b/includes/backup.php new file mode 100644 index 0000000..18ed6a4 --- /dev/null +++ b/includes/backup.php @@ -0,0 +1,81 @@ + date('c'), + "files" => [] +]; + +foreach ($root as $file) { + if ($file === "backup.poniesbackup" || $file === "backup.ponieskey" || $file === "encrypted" || str_ends_with($file, ".poniesbackup")) continue; + + if (is_dir("data/$file")) { + foreach (array_filter(scandir("data/$file"), function ($i) { + return !str_starts_with($i, "."); + }) as $dirfile) { + if ($dirfile === "backup.poniesbackup" || $dirfile === "backup.ponieskey" || $dirfile === "encrypted" || str_ends_with($dirfile, ".poniesbackup")) continue; + + $files[] = [ + "dir" => $file, + "file" => $dirfile + ]; + } + } else { + $files[] = [ + "dir" => "", + "file" => $file + ]; + } +} + +foreach ($files as $file) { + $file["mime"] = mime_content_type("data/$file[dir]/$file[file]"); + $file["checksum"] = [ + sha1_file("data/$file[dir]/$file[file]"), + md5_file("data/$file[dir]/$file[file]") + ]; + $file["content"] = base64_encode(file_get_contents("data/$file[dir]/$file[file]")); + + $data["files"][] = $file; +} + +function pkcs7_pad($data, $size) { + $length = $size - strlen($data) % $size; + return $data . str_repeat(chr($length), $length); +} + +if (!file_exists("./data/backup.ponieskey")) { + $key = openssl_random_pseudo_bytes(512); + $iv = openssl_random_pseudo_bytes(16); + file_put_contents("./data/backup.ponieskey", base64_encode(json_encode([ + "iv" => bin2hex($iv), + "key" => bin2hex($key) + ]))); +} else { + $key_raw = json_decode(base64_decode(file_get_contents("./data/backup.ponieskey")), true); + $key = hex2bin($key_raw["key"]); + $iv = hex2bin($key_raw["iv"]); +} + +$payload = json_encode($data); +$encrypted = openssl_encrypt(pkcs7_pad($payload, 16), 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv); + +file_put_contents("./data/backup.poniesbackup", $encrypted); +@mkdir("./data/encrypted"); + +$id = str_replace(":", "-", date('c')); +copy("./data/backup.poniesbackup", "./data/encrypted/" . $id . ".poniesbackup"); + +exec("scp ./data/encrypted/" . $id . ".poniesbackup fedora@bridlewood.equestria.dev:/opt/ponies"); +exec('ssh fedora@bridlewood.equestria.dev bash -c "cd /opt/ponies; ls -tp | grep -v \'/$\' | tail -n +20 | xargs -I {} rm -- {}"'); + +exec("scp ./data/encrypted/" . $id . ".poniesbackup root@canterlot.equestria.dev:/opt/ponies"); +exec('ssh root@canterlot.equestria.dev bash -c "cd /opt/ponies; ls -tp | grep -v \'/$\' | tail -n +20 | xargs -I {} rm -- {}"'); + +copy("./data/encrypted/" . $id . ".poniesbackup", "/opt/ponies/" . $id . ".poniesbackup"); +exec('bash -c "cd /opt/ponies; ls -tp | grep -v \'/$\' | tail -n +20 | xargs -I {} rm -- {}"'); + +unlink("./data/encrypted/" . $id . ".poniesbackup"); \ No newline at end of file diff --git a/includes/banner.php b/includes/banner.php index 6305857..2582b63 100644 --- a/includes/banner.php +++ b/includes/banner.php @@ -322,14 +322,14 @@ function getMemberBannerData(string $id, string $system, bool $french = false) { if ($metadata["host"] ?? false) { if (!$travelling[$member['id']]["travelling"]) { $badges[] = [ - "id" => "host", + "id" => "mcf", "color" => "primary", "html" => ( $french ? - 'Hรดte' + 'Fronteuse la plus prรฉsente' : - 'Host' + 'Most common fronter' ) ]; } @@ -338,7 +338,7 @@ function getMemberBannerData(string $id, string $system, bool $french = false) { if (($metadata["age_spells"] ?? false) && !$french) { $badges[] = [ "id" => "age_spells", - "color" => "primary", + "color" => "#6f42c1", "html" => 'Affected by age spells' ]; } @@ -357,6 +357,22 @@ function getMemberBannerData(string $id, string $system, bool $french = false) { ]; } + if ($metadata["less_frequent"] ?? false) { + $badges[] = [ + "id" => "nonverbal", + "color" => "#fd7e14", + "html" => 'Fronts less often' + ]; + } + + if ($metadata["nonverbal"] ?? false) { + $badges[] = [ + "id" => "nonverbal", + "color" => "#20c997", + "html" => 'Non verbal IRL' + ]; + } + if ($member["name"] === "fusion") { $badges[] = [ "id" => "fusion", diff --git a/includes/bitset.php b/includes/bitset.php index 6483669..2e5a645 100644 --- a/includes/bitset.php +++ b/includes/bitset.php @@ -7,6 +7,8 @@ function parseBitset ($bitset) { $median = substr($bin, 10 + 16, 1) !== "0"; $little = bindec(substr($bin, 11 + 16, 2)); $food = bindec(substr($bin, 16, 2)); + $nonverbal = substr($bin, 15, 1) !== "0"; + $lessFrequent = substr($bin, 14, 1) !== "0"; $magic = bindec(substr($bin, 2 + 16, 3)); $sensitivity = bindec(substr($bin, 5 + 16, 3)); $protector = substr($bin, 13 + 16, 1) !== "0"; @@ -63,6 +65,8 @@ function parseBitset ($bitset) { 'sensitivity' => $sensitivity, 'food' => $food, 'plush' => $plush, + 'nonverbal' => $nonverbal, + 'less_frequent' => $lessFrequent, 'age_spells' => $age, 'species' => array_filter([ $species1, diff --git a/includes/emergency.php b/includes/emergency.php index a86e090..5490985 100644 --- a/includes/emergency.php +++ b/includes/emergency.php @@ -1,4 +1,4 @@ -

Emergency Alert +

Emergency alert
ยท diff --git a/includes/footer.php b/includes/footer.php index 4bf7877..4592b8e 100644 --- a/includes/footer.php +++ b/includes/footer.php @@ -35,8 +35,13 @@ if (!function_exists("timeAgo")) {
diff --git a/includes/functions.php b/includes/functions.php index 51317ca..db4a8df 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -34,6 +34,26 @@ if (!function_exists("getSystemMember")) { } } +if (!function_exists("getMemberWithoutSystem")) { + function getMemberWithoutSystem(string $id) { + $member = null; + + $members1 = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/ynmuc-members.json"), true); + foreach ($members1 as $m) { + $m["_system"] = "ynmuc"; + if ($m["id"] === $id) $member = $m; + } + + $members2 = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/gdapd-members.json"), true); + foreach ($members2 as $m) { + $m["_system"] = "gdapd"; + if ($m["id"] === $id) $member = $m; + } + + return $member; + } +} + if (!function_exists("showMembersFromList")) { function showMembersFromList(array $list) { foreach ($list as $member) { if ($member['name'] !== "unknown" && $member['name'] !== "fusion") { @@ -167,6 +187,86 @@ if (!function_exists("timeAgo")) { } } +if (!function_exists("timeIn")) { + function timeIn($time): string { + if (!is_numeric($time)) { + $time = strtotime($time); + } + + $periods = ["second", "minute", "hour", "day", "week", "month", "year", "age"]; + $lengths = array("60", "60", "24", "7", "4.35", "12", "100"); + + $now = time(); + + $difference = $time - $now; + if ($difference <= 10 && $difference >= 0) { + return $tense = "now"; + } elseif ($difference > 0) { + $tense = "in"; + } else { + $tense = "ago"; + } + + for ($j = 0; $difference >= $lengths[$j] && $j < count($lengths)-1; $j++) { + $difference /= $lengths[$j]; + } + + $difference = round($difference); + + $period = $periods[$j] . ($difference >1 ? "s" :''); + return "{$tense} {$difference} {$period}"; + } +} + +if (!function_exists("duration")) { + function duration($seconds) { + if ($seconds >= 60) { + if (floor($seconds / 60) >= 60) { + if (floor($seconds / 3600) >= 24) { + $days = floor($seconds / 86400); + return $days . " day" . ($days > 1 ? "s" : ""); + } else { + $hours = floor($seconds / 3600); + return $hours . " hour" . ($hours > 1 ? "s" : ""); + } + } else { + $minutes = floor($seconds / 60); + return $minutes . " minute" . ($minutes > 1 ? "s" : ""); + } + } else { + return $seconds . " seconds"; + } + } +} + +if (!function_exists("relativeDate")) { + function relativeDate($date, $showTime = true) { + if (!is_numeric($date)) $date = strtotime($date); + + if (!$showTime) { + if (date('Y-m-d', $date) === date('Y-m-d')) { + return "today"; + } elseif (date('Y-m-d', $date) === date('Y-m-d', time() + 86400)) { + return "tomorrow"; + } elseif ($date < time() + 518400) { + return date('l', $date); + } else { + return date('D j M', $date); + } + } else { + if (date('Y-m-d', $date) === date('Y-m-d')) { + return "today, " . date('H:i', $date) . ""; + } elseif (date('Y-m-d', $date) === date('Y-m-d', time() + 86400)) { + return "tomorrow, " . date('H:i', $date) . ""; + } elseif ($date < time() + 518400) { + return date('l', $date) . ", " . date('H:i', $date) . ""; + } else { + return date('D j M', $date) . ", " . date('H:i', $date) . ""; + } + } + } +} + if (!function_exists("getMemberSystem")) { function getMemberSystem(string $id) { $list = scoreOrderGlobal(); diff --git a/includes/header.php b/includes/header.php index 8ce8389..1f71aa5 100644 --- a/includes/header.php +++ b/includes/header.php @@ -29,6 +29,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; global $isLogg require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/banner.php"; require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/rainbow.php"; require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/functions.php"; +require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/ical/main.php"; ?> @@ -230,12 +231,33 @@ require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/functions.php"; grid-template-columns: 1fr !important; text-align: left; } + + #member-icon-mobile { + display: inline-block !important; + } + + #system-info { + grid-template-columns: 1fr !important; + } + + #member-icon, #member-icon-outer { + display: none !important; + } + + #member-relations { + grid-template-columns: 1fr !important; + text-align: left; + } } #page-content a { color: #afd0ff; } + #page-content .btn-outline-light:hover { + color: black !important; + } + #page-content a:hover { opacity: .75; } @@ -616,7 +638,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/functions.php"; } .navbar-collapse.show { - z-index: 999; + z-index: 99999; } @media (max-width: 991px) { @@ -637,6 +659,25 @@ require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/functions.php"; text-align: left; } } + + .linked-card { + opacity: 1 !important; + color: white !important; + text-decoration: none !important; + } + + .linked-card:hover { + opacity: .75 !important; + } + + .linked-card:active { + opacity: .5 !important; + } + + .navbar-brand { + position: relative; + z-index: 9999; + } @@ -712,28 +753,48 @@ require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/functions.php";
  • +
  • + + Dashboard +
  • Front planner
  • +
  • System travels manager
  • -
  • - -
  • -
  • + +
  • + +
  • +
  • + + Data updater debugging
  • diff --git a/includes/ical/LICENSE b/includes/ical/LICENSE new file mode 100644 index 0000000..ce7d41d --- /dev/null +++ b/includes/ical/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2014-2022, Roman Oลพana +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/includes/ical/bin/timezones.php b/includes/ical/bin/timezones.php new file mode 100644 index 0000000..3bf3708 --- /dev/null +++ b/includes/ical/bin/timezones.php @@ -0,0 +1,20 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +$windows_timezones = []; +$windowstimezonexml = new DOMDocument(); +$windowstimezonexml->load('https://raw.githubusercontent.com/unicode-org/cldr/master/common/supplemental/windowsZones.xml'); +$zones = $windowstimezonexml->getElementsByTagName('mapZone'); +foreach ($zones as $zone) { + if ($zone->getAttribute('territory') === '001') { + $windows_timezones[$zone->getAttribute('other')] = $zone->getAttribute('type'); + } +} + +file_put_contents(__DIR__ . '/../src/WindowsTimezones.php', " + */ +class EventsList extends \ArrayObject { + + /** + * Return array of Events + * + * @return array + */ + public function getArrayCopy(): array { + return array_values(parent::getArrayCopy()); + } + + /** + * Return sorted EventList (the newest dates are first) + * + * @return $this + */ + public function sorted(): EventsList { + $this->uasort(static function ($a, $b): int { + if ($a['DTSTART'] === $b['DTSTART']) { + return 0; + } + return ($a['DTSTART'] < $b['DTSTART']) ? -1 : 1; + }); + + return $this; + } + + /** + * Return reversed sorted EventList (the oldest dates are first) + * + * @return $this + */ + public function reversed(): EventsList { + $this->uasort(static function ($a, $b): int { + if ($a['DTSTART'] === $b['DTSTART']) { + return 0; + } + return ($a['DTSTART'] > $b['DTSTART']) ? -1 : 1; + }); + + return $this; + } + +} \ No newline at end of file diff --git a/includes/ical/src/Freq.php b/includes/ical/src/Freq.php new file mode 100644 index 0000000..8557683 --- /dev/null +++ b/includes/ical/src/Freq.php @@ -0,0 +1,633 @@ + + */ + +/** + * A class to store Frequency-rules in. Will allow a easy way to find the + * last and next occurrence of the rule. + * + * No - this is so not pretty. But.. ehh.. You do it better, and I will + * gladly accept patches. + * + * Created by trail-and-error on the examples given in the RFC. + * + * TODO: Update to a better way of doing calculating the different options. + * Instead of only keeping track of the best of the current dates found + * it should instead keep a array of all the calculated dates within the + * period. + * This should fix the issues with multi-rule + multi-rule interference, + * and make it possible to implement the SETPOS rule. + * By pushing the next period onto the stack as the last option will + * (hopefully) remove the need for the awful simpleMode + * + * @author Morten Fangel (C) 2008 + * @author Michael Kahn (C) 2013 + * @license http://creativecommons.org/licenses/by-sa/2.5/dk/deed.en_GB CC-BY-SA-DK + */ +class Freq { + + protected array $weekdays = [ + 'MO' => 'monday', + 'TU' => 'tuesday', + 'WE' => 'wednesday', + 'TH' => 'thursday', + 'FR' => 'friday', + 'SA' => 'saturday', + 'SU' => 'sunday', + ]; + protected array $knownRules = [ + 'month', + 'weekno', + 'day', + 'monthday', + 'yearday', + 'hour', + 'minute', + ]; //others : 'setpos', 'second' + + protected array $ruleModifiers = ['wkst']; + protected bool $simpleMode = true; + + protected array $rules = ['freq' => 'yearly', 'interval' => 1]; + protected int $start = 0; + protected string $freq = ''; + + protected array $excluded; //EXDATE + protected array $added; //RDATE + + protected $cache; // getAllOccurrences() + + /** + * Constructs a new Frequency-rule + * + * @param string|array $rule + * @param int $start Unix-timestamp (important : Need to be the start of Event) + * @param array $excluded of int (timestamps), see EXDATE documentation + * @param array $added of int (timestamps), see RDATE documentation + * @throws Exception + */ + public function __construct($rule, int $start, array $excluded = [], array $added = []) { + $this->start = $start; + $this->excluded = []; + + $rules = []; + foreach ($rule as $k => $v) { + $this->rules[strtolower($k)] = $v; + } + + if (isset($this->rules['until']) && is_string($this->rules['until'])) { + $this->rules['until'] = strtotime($this->rules['until']); + } elseif ($this->rules['until'] instanceof DateTime) { + $this->rules['until'] = $this->rules['until']->getTimestamp(); + } + $this->freq = strtolower($this->rules['freq']); + + foreach ($this->knownRules as $rule) { + if (isset($this->rules['by' . $rule])) { + if ($this->isPrerule($rule, $this->freq)) { + $this->simpleMode = false; + } + } + } + + if (!$this->simpleMode) { + if (!(isset($this->rules['byday']) || isset($this->rules['bymonthday']) || isset($this->rules['byyearday']))) { + $this->rules['bymonthday'] = date('d', $this->start); + } + } + + //set until, and cache + if (isset($this->rules['count'])) { + + $cache[$ts] = $ts = $this->start; + for ($n = 1; $n < $this->rules['count']; $n++) { + $ts = $this->findNext($ts); + $cache[$ts] = $ts; + } + $this->rules['until'] = $ts; + + //EXDATE + if (!empty($excluded)) { + foreach ($excluded as $ts) { + unset($cache[$ts]); + } + } + //RDATE + if (!empty($added)) { + $cache = array_unique(array_merge(array_values($cache), $added)); + asort($cache); + } + + $this->cache = array_values($cache); + } + + $this->excluded = $excluded; + $this->added = $added; + } + + private function isPrerule(string $rule, string $freq): bool { + if ($rule === 'year') { + return false; + } + if ($rule === 'month' && $freq === 'yearly') { + return true; + } + if ($rule === 'monthday' && in_array($freq, ['yearly', 'monthly']) && !isset($this->rules['byday'])) { + return true; + } + // TODO: is it faster to do monthday first, and ignore day if monthday exists? - prolly by a factor of 4.. + if ($rule === 'yearday' && $freq === 'yearly') { + return true; + } + if ($rule === 'weekno' && $freq === 'yearly') { + return true; + } + if ($rule === 'day' && in_array($freq, ['yearly', 'monthly', 'weekly'])) { + return true; + } + if ($rule === 'hour' && in_array($freq, ['yearly', 'monthly', 'weekly', 'daily'])) { + return true; + } + if ($rule === 'minute') { + return true; + } + + return false; + } + + /** + * Calculates the next time after the given offset that the rule + * will apply. + * + * The approach to finding the next is as follows: + * First we establish a timeframe to find timestamps in. This is + * between $offset and the end of the period that $offset is in. + * + * We then loop though all the rules (that is a Prerule in the + * current freq.), and finds the smallest timestamp inside the + * timeframe. + * + * If we find something, we check if the date is a valid recurrence + * (with validDate). If it is, we return it. Otherwise we try to + * find a new date inside the same timeframe (but using the new- + * found date as offset) + * + * If no new timestamps were found in the period, we try in the + * next period + * + * @param int $offset + * @return int|bool + * @throws Exception + */ + public function findNext(int $offset) { + if (!empty($this->cache)) { + foreach ($this->cache as $ts) { + if ($ts > $offset) { + return $ts; + } + } + } + + $debug = false; + + //make sure the offset is valid + if ($offset === false || (isset($this->rules['until']) && $offset > $this->rules['until'])) { + if ($debug) printf("STOP: %s\n", date('r', $offset)); + return false; + } + + $found = true; + + //set the timestamp of the offset (ignoring hours and minutes unless we want them to be + //part of the calculations. + if ($debug) printf("O: %s\n", date('r', $offset)); + $hour = (in_array($this->freq, ['hourly', 'minutely']) && $offset > $this->start) ? date('H', $offset) : date( + 'H', $this->start + ); + $minute = (($this->freq === 'minutely' || isset($this->rules['byminute'])) && $offset > $this->start) ? date( + 'i', $offset + ) : date('i', $this->start); + $t = mktime($hour, $minute, date('s', $this->start), date('m', $offset), date('d', $offset), date('Y', $offset)); + if ($debug) printf("START: %s\n", date('r', $t)); + + if ($this->simpleMode) { + if ($offset < $t) { + $ts = $t; + if ($ts && in_array($ts, $this->excluded, true)) { + $ts = $this->findNext($ts); + } + } else { + $ts = $this->findStartingPoint($t, $this->rules['interval'], false); + if (!$this->validDate($ts)) { + $ts = $this->findNext($ts); + } + } + + return $ts; + } + + //EOP needs to have the same TIME as START ($t) + $tO = new DateTime('@' . $t, new DateTimeZone('UTC')); + + $eop = $this->findEndOfPeriod($offset); + $eopO = new DateTime('@' . $eop, new DateTimeZone('UTC')); + $eopO->setTime($tO->format('H'), $tO->format('i'), $tO->format('s')); + $eop = $eopO->getTimestamp(); + unset($eopO, $tO); + + if ($debug) { + echo 'EOP: ' . date('r', $eop) . "\n"; + } + foreach ($this->knownRules as $rule) { + if ($found && isset($this->rules['by' . $rule])) { + if ($this->isPrerule($rule, $this->freq)) { + $subRules = explode(',', $this->rules['by' . $rule]); + $_t = null; + foreach ($subRules as $subRule) { + $imm = call_user_func_array([$this, "ruleBy$rule"], [$subRule, $t]); + if ($imm === false) { + break; + } + if ($debug) { + printf("%s: %s A: %d\n", strtoupper($rule), date('r', $imm), intval($imm > $offset && $imm < $eop)); + } + if ($imm > $offset && $imm <= $eop && ($_t == null || $imm < $_t)) { + $_t = $imm; + } + } + if ($_t !== null) { + $t = $_t; + } else { + $found = $this->validDate($t); + } + } + } + } + + if ($offset < $this->start && $this->start < $t) { + $ts = $this->start; + } elseif ($found && ($t != $offset)) { + if ($this->validDate($t)) { + if ($debug) echo 'OK' . "\n"; + $ts = $t; + } else { + if ($debug) echo 'Invalid' . "\n"; + $ts = $this->findNext($t); + } + } else { + if ($debug) echo 'Not found' . "\n"; + $ts = $this->findNext($this->findStartingPoint($offset, $this->rules['interval'])); + } + if ($ts && in_array($ts, $this->excluded, true)) { + return $this->findNext($ts); + } + + return $ts; + } + + /** + * Finds the starting point for the next rule. It goes $interval + * 'freq' forward in time since the given offset + * + * @param int $offset + * @param int $interval + * @param boolean $truncate + * @return int + */ + private function findStartingPoint(int $offset, int $interval, $truncate = true): int { + $_freq = ($this->freq === 'daily') ? 'day__' : $this->freq; + $t = '+' . $interval . ' ' . substr($_freq, 0, -2) . 's'; + if ($_freq === 'monthly' && $truncate) { + if ($interval > 1) { + $offset = strtotime('+' . ($interval - 1) . ' months ', $offset); // FIXME return type int|false + } + $t = '+' . (date('t', $offset) - date('d', $offset) + 1) . ' days'; + } + + $sp = strtotime($t, $offset); + + if ($truncate) { + $sp = $this->truncateToPeriod($sp, $this->freq); + } + + return $sp; + } + + /** + * Resets the timestamp to the beginning of the + * period specified by freq + * + * Yes - the fall-through is on purpose! + * + * @param int $time + * @param string $freq + * @return int + */ + private function truncateToPeriod(int $time, string $freq): int { + $date = getdate($time); + switch ($freq) { + case 'yearly': + $date['mon'] = 1; + case 'monthly': + $date['mday'] = 1; + case 'daily': + $date['hours'] = 0; + case 'hourly': + $date['minutes'] = 0; + case 'minutely': + $date['seconds'] = 0; + break; + case 'weekly': + if (date('N', $time) == 1) { // FIXME wrong compare, date return string|false + $date['hours'] = 0; + $date['minutes'] = 0; + $date['seconds'] = 0; + } else { + $date = getdate(strtotime('last monday 0:00', $time)); + } + break; + } + return mktime($date['hours'], $date['minutes'], $date['seconds'], $date['mon'], $date['mday'], $date['year']); + } + + private function validDate($t): bool { + if (isset($this->rules['until']) && $t > $this->rules['until']) { + return false; + } + + if (in_array($t, $this->excluded, true)) { + return false; + } + + if (isset($this->rules['bymonth'])) { + $months = explode(',', $this->rules['bymonth']); + if (!in_array(date('m', $t), $months, true)) { + return false; + } + } + if (isset($this->rules['byday'])) { + $days = explode(',', $this->rules['byday']); + foreach ($days as $i => $k) { + $days[$i] = $this->weekdays[preg_replace('/[^A-Z]/', '', $k)]; + } + if (!in_array(strtolower(date('l', $t)), $days, true)) { + return false; + } + } + if (isset($this->rules['byweekno'])) { + $weeks = explode(',', $this->rules['byweekno']); + if (!in_array(date('W', $t), $weeks, true)) { + return false; + } + } + if (isset($this->rules['bymonthday'])) { + $weekdays = explode(',', $this->rules['bymonthday']); + foreach ($weekdays as $i => $k) { + if ($k < 0) { + $weekdays[$i] = date('t', $t) + $k + 1; + } + } + if (!in_array(date('d', $t), $weekdays, true)) { + return false; + } + } + if (isset($this->rules['byhour'])) { + $hours = explode(',', $this->rules['byhour']); + if (!in_array(date('H', $t), $hours, true)) { + return false; + } + } + + return true; + } + + /** + * Finds the earliest timestamp possible outside this period. + * + * @param int $offset + * @return int + */ + public function findEndOfPeriod($offset = 0) { + return $this->findStartingPoint($offset, 1, false); + } + + /** + * Returns the previous (most recent) occurrence of the rule from the + * given offset + * + * @param int $offset + * @return int + * @throws Exception + */ + public function previousOccurrence(int $offset) { + if (!empty($this->cache)) { + $t2 = $this->start; + foreach ($this->cache as $ts) { + if ($ts >= $offset) { + return $t2; + } + $t2 = $ts; + } + } else { + $ts = $this->start; + while (($t2 = $this->findNext($ts)) < $offset) { + if ($t2 == false) { + break; + } + $ts = $t2; + } + } + + return $ts; + } + + /** + * Returns the next occurrence of this rule after the given offset + * + * @param int $offset + * @return int + * @throws Exception + */ + public function nextOccurrence(int $offset) { + if ($offset < $this->start) { + return $this->firstOccurrence(); + } + return $this->findNext($offset); + } + + /** + * Finds the first occurrence of the rule. + * + * @return int timestamp + * @throws Exception + */ + public function firstOccurrence() { + $t = $this->start; + if (in_array($t, $this->excluded)) { + $t = $this->findNext($t); + } + + return $t; + } + + /** + * Finds the absolute last occurrence of the rule from the given offset. + * Builds also the cache, if not set before... + * + * @return int timestamp + * @throws Exception + */ + public function lastOccurrence() { + //build cache if not done + $this->getAllOccurrences(); + //return last timestamp in cache + return end($this->cache); + } + + /** + * Returns all timestamps array(), build the cache if not made before + * + * @return array + * @throws Exception + */ + public function getAllOccurrences() { + if (empty($this->cache)) { + $cache = []; + + //build cache + $next = $this->firstOccurrence(); + while ($next) { + $cache[] = $next; + $next = $this->findNext($next); + } + if (!empty($this->added)) { + $cache = array_unique(array_merge($cache, $this->added)); + asort($cache); + } + $this->cache = $cache; + } + + return $this->cache; + } + + /** + * Applies the BYDAY rule to the given timestamp + * + * @param string $rule + * @param int $t + * @return int + */ + private function ruleByDay(string $rule, int $t): int { + $dir = ($rule[0] === '-') ? -1 : 1; + $dir_t = ($dir === 1) ? 'next' : 'last'; + + $d = $this->weekdays[substr($rule, -2)]; + $s = $dir_t . ' ' . $d . ' ' . date('H:i:s', $t); + + if ($rule == substr($rule, -2)) { + if (date('l', $t) == ucfirst($d)) { + $s = 'today ' . date('H:i:s', $t); + } + + $_t = strtotime($s, $t); + + if ($_t == $t && in_array($this->freq, ['weekly', 'monthly', 'yearly'])) { + // Yes. This is not a great idea.. but hey, it works.. for now + $s = 'next ' . $d . ' ' . date('H:i:s', $t); + $_t = strtotime($s, $_t); + } + + return $_t; + } else { + $_f = $this->freq; + if (isset($this->rules['bymonth']) && $this->freq === 'yearly') { + $this->freq = 'monthly'; + } + if ($dir === -1) { + $_t = $this->findEndOfPeriod($t); + } else { + $_t = $this->truncateToPeriod($t, $this->freq); + } + $this->freq = $_f; + + $c = preg_replace('/[^0-9]/', '', $rule); + $c = ($c == '') ? 1 : $c; + + $n = $_t; + while ($c > 0) { + if ($dir === 1 && $c == 1 && date('l', $t) == ucfirst($d)) { + $s = 'today ' . date('H:i:s', $t); + } + $n = strtotime($s, $n); + $c--; + } + + return $n; + } + } + + private function ruleByMonth($rule, int $t) { + $_t = mktime(date('H', $t), date('i', $t), date('s', $t), $rule, date('d', $t), date('Y', $t)); + if ($t == $_t && isset($this->rules['byday'])) { + // TODO: this should check if one of the by*day's exists, and have a multi-day value + return false; + } else { + return $_t; + } + } + + private function ruleByMonthday($rule, int $t) { + if ($rule < 0) { + $rule = date('t', $t) + $rule + 1; + } + + return mktime(date('H', $t), date('i', $t), date('s', $t), date('m', $t), $rule, date('Y', $t)); + } + + private function ruleByYearday($rule, int $t) { + if ($rule < 0) { + $_t = $this->findEndOfPeriod(); + $d = '-'; + } else { + $_t = $this->truncateToPeriod($t, $this->freq); + $d = '+'; + } + $s = $d . abs($rule - 1) . ' days ' . date('H:i:s', $t); + + return strtotime($s, $_t); + } + + private function ruleByWeekno($rule, int $t) { + if ($rule < 0) { + $_t = $this->findEndOfPeriod(); + $d = '-'; + } else { + $_t = $this->truncateToPeriod($t, $this->freq); + $d = '+'; + } + + $sub = (date('W', $_t) == 1) ? 2 : 1; + $s = $d . abs($rule - $sub) . ' weeks ' . date('H:i:s', $t); + $_t = strtotime($s, $_t); + + return $_t; + } + + private function ruleByHour($rule, int $t) { + return mktime($rule, date('i', $t), date('s', $t), date('m', $t), date('d', $t), date('Y', $t)); + } + + private function ruleByMinute($rule, int $t) { + return mktime(date('h', $t), $rule, date('s', $t), date('m', $t), date('d', $t), date('Y', $t)); + } +} diff --git a/includes/ical/src/IcalParser.php b/includes/ical/src/IcalParser.php new file mode 100644 index 0000000..b4996e4 --- /dev/null +++ b/includes/ical/src/IcalParser.php @@ -0,0 +1,466 @@ + + */ +class IcalParser { + + /** @var ?DateTimeZone */ + public ?DateTimeZone $timezone = null; + + /** @var array|null */ + public ?array $data = null; + + /** @var array */ + protected array $counters = []; + + /** @var array */ + private $windowsTimezones; + + public function __construct() { + $this->windowsTimezones = require __DIR__ . '/WindowsTimezones.php'; // load Windows timezones from separate file + } + + /** + * @param string $file + * @param callable|null $callback + * @return array|null + * @throws Exception + */ + public function parseFile(string $file, callable $callback = null): array { + if (!$handle = fopen($file, 'rb')) { + throw new RuntimeException('Can\'t open file' . $file . ' for reading'); + } + fclose($handle); + + return $this->parseString(file_get_contents($file), $callback); + } + + /** + * @param string $string + * @param callable|null $callback + * @param boolean $add if true the parsed string is added to existing data + * @return array|null + * @throws Exception + */ + public function parseString(string $string, callable $callback = null, bool $add = false): ?array { + if ($add === false) { + // delete old data + $this->data = []; + $this->counters = []; + } + + if (!preg_match('/BEGIN:VCALENDAR/', $string)) { + throw new InvalidArgumentException('Invalid ICAL data format'); + } + + $section = 'VCALENDAR'; + + // Replace \r\n with \n + $string = str_replace("\r\n", "\n", $string); + + // Unfold multi-line strings + $string = str_replace("\n ", '', $string); + + foreach (explode("\n", $string) as $row) { + + switch ($row) { + case 'BEGIN:DAYLIGHT': + case 'BEGIN:VALARM': + case 'BEGIN:VTIMEZONE': + case 'BEGIN:VFREEBUSY': + case 'BEGIN:VJOURNAL': + case 'BEGIN:STANDARD': + case 'BEGIN:VTODO': + case 'BEGIN:VEVENT': + $section = substr($row, 6); + $this->counters[$section] = isset($this->counters[$section]) ? $this->counters[$section] + 1 : 0; + continue 2; // while + case 'END:VEVENT': + $section = substr($row, 4); + $currCounter = $this->counters[$section]; + $event = $this->data[$section][$currCounter]; + if (!empty($event['RECURRENCE-ID'])) { + $this->data['_RECURRENCE_IDS'][$event['RECURRENCE-ID']] = $event; + } + + continue 2; // while + case 'END:DAYLIGHT': + case 'END:VALARM': + case 'END:VTIMEZONE': + case 'END:VFREEBUSY': + case 'END:VJOURNAL': + case 'END:STANDARD': + case 'END:VTODO': + continue 2; // while + + case 'END:VCALENDAR': + $veventSection = 'VEVENT'; + if (!empty($this->data[$veventSection])) { + foreach ($this->data[$veventSection] as $currCounter => $event) { + if (!empty($event['RRULE']) || !empty($event['RDATE'])) { + $recurrences = $this->parseRecurrences($event); + if (!empty($recurrences)) { + $this->data[$veventSection][$currCounter]['RECURRENCES'] = $recurrences; + } + + if (!empty($event['UID'])) { + $this->data["_RECURRENCE_COUNTERS_BY_UID"][$event['UID']] = $currCounter; + } + } + } + } + continue 2; // while + } + + [$key, $middle, $value] = $this->parseRow($row); + + if ($callback) { + // call user function for processing line + call_user_func($callback, $row, $key, $middle, $value, $section, $this->counters[$section]); + } else { + if ($section === 'VCALENDAR') { + $this->data[$key] = $value; + } else { + + // use an array since there can be multiple entries for this key. This does not + // break the current implementation--it leaves the original key alone and adds + // a new one specifically for the array of values. + + if ($newKey = $this->isMultipleKey($key)) { + $this->data[$section][$this->counters[$section]][$newKey][] = $value; + } + + // CATEGORIES can be multiple also but there is special case that there are comma separated categories + + if ($this->isMultipleKeyWithCommaSeparation($key)) { + + if (strpos($value, ',') !== false) { + $values = array_map('trim', preg_split('/(?data[$section][$this->counters[$section]][$key][] = $value; + } + + } else { + $this->data[$section][$this->counters[$section]][$key] = $value; + } + + } + + } + } + + return ($callback) ? null : $this->data; + } + + /** + * @param $event + * @return array + * @throws Exception + */ + public function parseRecurrences($event): array { + $recurring = new Recurrence($event['RRULE']); + $exclusions = []; + $additions = []; + + if (!empty($event['EXDATES'])) { + foreach ($event['EXDATES'] as $exDate) { + if (is_array($exDate)) { + foreach ($exDate as $singleExDate) { + $exclusions[] = $singleExDate->getTimestamp(); + } + } else { + $exclusions[] = $exDate->getTimestamp(); + } + } + } + + if (!empty($event['RDATES'])) { + foreach ($event['RDATES'] as $rDate) { + if (is_array($rDate)) { + foreach ($rDate as $singleRDate) { + $additions[] = $singleRDate->getTimestamp(); + } + } else { + $additions[] = $rDate->getTimestamp(); + } + } + } + + $until = $recurring->getUntil(); + if ($until === false) { + //forever... limit to 3 years from now + $end = new DateTime('now'); + $end->add(new DateInterval('P3Y')); // + 3 years + $recurring->setUntil($end); + $until = $recurring->getUntil(); + } + + date_default_timezone_set($event['DTSTART']->getTimezone()->getName()); + $frequency = new Freq($recurring->rrule, $event['DTSTART']->getTimestamp(), $exclusions, $additions); + $recurrenceTimestamps = $frequency->getAllOccurrences(); + $recurrences = []; + foreach ($recurrenceTimestamps as $recurrenceTimestamp) { + $tmp = new DateTime('now', $event['DTSTART']->getTimezone()); + $tmp->setTimestamp($recurrenceTimestamp); + + $recurrenceIDDate = $tmp->format('Ymd'); + $recurrenceIDDateTime = $tmp->format('Ymd\THis'); + if (empty($this->data['_RECURRENCE_IDS'][$recurrenceIDDate]) && + empty($this->data['_RECURRENCE_IDS'][$recurrenceIDDateTime])) { + $gmtCheck = new DateTime('now', new DateTimeZone('UTC')); + $gmtCheck->setTimestamp($recurrenceTimestamp); + $recurrenceIDDateTimeZ = $gmtCheck->format('Ymd\THis\Z'); + if (empty($this->data['_RECURRENCE_IDS'][$recurrenceIDDateTimeZ])) { + $recurrences[] = $tmp; + } + } + } + + return $recurrences; + } + + private function parseRow($row): array { + preg_match('#^([\w-]+);?([\w-]+="[^"]*"|.*?):(.*)$#i', $row, $matches); + + $key = false; + $middle = null; + $value = null; + + if ($matches) { + $key = $matches[1]; + $middle = $matches[2]; + $value = $matches[3]; + $timezone = null; + + if ($key === 'X-WR-TIMEZONE' || $key === 'TZID') { + if (preg_match('#(\w+/\w+)$#i', $value, $matches)) { + $value = $matches[1]; + } + $value = $this->toTimezone($value); + $this->timezone = new DateTimeZone($value); + } + + // have some middle part ? + if ($middle && preg_match_all('#(?[^=;]+)=(?[^;]+)#', $middle, $matches, PREG_SET_ORDER)) { + $middle = []; + foreach ($matches as $match) { + if ($match['key'] === 'TZID') { + $match['value'] = trim($match['value'], "'\""); + $match['value'] = $this->toTimezone($match['value']); + try { + $middle[$match['key']] = $timezone = new DateTimeZone($match['value']); + } catch (Exception $e) { + $middle[$match['key']] = $match['value']; + } + } elseif ($match['key'] === 'ENCODING') { + if ($match['value'] === 'QUOTED-PRINTABLE') { + $value = quoted_printable_decode($value); + } + } + } + } + } + + // process simple dates with timezone + if (in_array($key, ['DTSTAMP', 'LAST-MODIFIED', 'CREATED', 'DTSTART', 'DTEND'], true)) { + try { + $value = new DateTime($value, ($timezone ?: $this->timezone)); + } catch (Exception $e) { + $value = null; + } + } elseif (in_array($key, ['EXDATE', 'RDATE'])) { + $values = []; + foreach (explode(',', $value) as $singleValue) { + try { + $values[] = new DateTime($singleValue, ($timezone ?: $this->timezone)); + } catch (Exception $e) { + // pass + } + } + if (count($values) === 1) { + $value = $values[0]; + } else { + $value = $values; + } + } + + if ($key === 'RRULE' && preg_match_all('#(?[^=;]+)=(?[^;]+)#', $value, $matches, PREG_SET_ORDER)) { + $middle = null; + $value = []; + foreach ($matches as $match) { + if (in_array($match['key'], ['UNTIL'])) { + try { + $value[$match['key']] = new DateTime($match['value'], ($timezone ?: $this->timezone)); + } catch (Exception $e) { + $value[$match['key']] = $match['value']; + } + } else { + $value[$match['key']] = $match['value']; + } + } + } + + //implement 4.3.11 Text ESCAPED-CHAR + $text_properties = [ + 'CALSCALE', 'METHOD', 'PRODID', 'VERSION', 'CATEGORIES', 'CLASS', 'COMMENT', 'DESCRIPTION', + 'LOCATION', 'RESOURCES', 'STATUS', 'SUMMARY', 'TRANSP', 'TZID', 'TZNAME', 'CONTACT', + 'RELATED-TO', 'UID', 'ACTION', 'REQUEST-STATUS', 'URL', + ]; + if (in_array($key, $text_properties, true) || strpos($key, 'X-') === 0) { + if (is_array($value)) { + foreach ($value as &$var) { + $var = strtr($var, ['\\\\' => '\\', '\\N' => "\n", '\\n' => "\n", '\\;' => ';', '\\,' => ',']); + } + } else { + $value = strtr($value, ['\\\\' => '\\', '\\N' => "\n", '\\n' => "\n", '\\;' => ';', '\\,' => ',']); + } + } + + return [$key, $middle, $value]; + } + + /** + * Process timezone and return correct one... + * + * @param string $zone + * @return mixed|null + */ + private function toTimezone(string $zone) { + return $this->windowsTimezones[$zone] ?? $zone; + } + + public function isMultipleKey(string $key): ?string { + return (['ATTACH' => 'ATTACHMENTS', 'EXDATE' => 'EXDATES', 'RDATE' => 'RDATES'])[$key] ?? null; + } + + /** + * @param $key + * @return string|null + */ + public function isMultipleKeyWithCommaSeparation($key): ?string { + return (['X-CATEGORIES' => 'X-CATEGORIES', 'CATEGORIES' => 'CATEGORIES'])[$key] ?? null; + } + + public function getAlarms(): array { + return $this->data['VALARM'] ?? []; + } + + public function getTimezone(): array { + return $this->getTimezones(); + } + + public function getTimezones(): array { + return $this->data['VTIMEZONE'] ?? []; + } + + /** + * Return sorted event list as ArrayObject + * + * @deprecated use IcalParser::getEvents()->sorted() instead + */ + public function getSortedEvents(): \ArrayObject { + return $this->getEvents()->sorted(); + } + + public function getEvents(): EventsList { + $events = new EventsList(); + if (isset($this->data['VEVENT'])) { + foreach ($this->data['VEVENT'] as $iValue) { + $event = $iValue; + + if (empty($event['RECURRENCES'])) { + if (!empty($event['RECURRENCE-ID']) && !empty($event['UID']) && isset($event['SEQUENCE'])) { + $modifiedEventUID = $event['UID']; + $modifiedEventRecurID = $event['RECURRENCE-ID']; + $modifiedEventSeq = (int)$event['SEQUENCE']; + + if (isset($this->data['_RECURRENCE_COUNTERS_BY_UID'][$modifiedEventUID])) { + $counter = $this->data['_RECURRENCE_COUNTERS_BY_UID'][$modifiedEventUID]; + + $originalEvent = $this->data['VEVENT'][$counter]; + if (isset($originalEvent['SEQUENCE'])) { + $originalEventSeq = (int)$originalEvent['SEQUENCE']; + $originalEventFormattedStartDate = $originalEvent['DTSTART']->format('Ymd\THis'); + if ($modifiedEventRecurID === $originalEventFormattedStartDate && $modifiedEventSeq > $originalEventSeq) { + // this modifies the original event + $modifiedEvent = array_replace_recursive($originalEvent, $event); + $this->data['VEVENT'][$counter] = $modifiedEvent; + foreach ($events as $z => $event) { + if ($events[$z]['UID'] === $originalEvent['UID'] && + $events[$z]['SEQUENCE'] === $originalEvent['SEQUENCE']) { + // replace the original event with the modified event + $events[$z] = $modifiedEvent; + break; + } + } + $event = null; // don't add this to the $events[] array again + } elseif (!empty($originalEvent['RECURRENCES'])) { + for ($j = 0; $j < count($originalEvent['RECURRENCES']); $j++) { + $recurDate = $originalEvent['RECURRENCES'][$j]; + $formattedStartDate = $recurDate->format('Ymd\THis'); + if ($formattedStartDate === $modifiedEventRecurID) { + unset($this->data['VEVENT'][$counter]['RECURRENCES'][$j]); + $this->data['VEVENT'][$counter]['RECURRENCES'] = array_values($this->data['VEVENT'][$counter]['RECURRENCES']); + break; + } + } + } + } + } + } + + if (!empty($event)) { + $events->append($event); + } + } else { + $recurrences = $event['RECURRENCES']; + $event['RECURRING'] = true; + $event['DTEND'] = !empty($event['DTEND']) ? $event['DTEND'] : $event['DTSTART']; + $eventInterval = $event['DTSTART']->diff($event['DTEND']); + + $firstEvent = true; + foreach ($recurrences as $j => $recurDate) { + $newEvent = $event; + if (!$firstEvent) { + unset($newEvent['RECURRENCES']); + $newEvent['DTSTART'] = $recurDate; + $newEvent['DTEND'] = clone($recurDate); + $newEvent['DTEND']->add($eventInterval); + } + + $newEvent['RECURRENCE_INSTANCE'] = $j; + $events->append($newEvent); + $firstEvent = false; + } + } + } + } + return $events; + } + + /** + * @return \ArrayObject + * @deprecated use IcalParser::getEvents->reversed(); + */ + public function getReverseSortedEvents(): \ArrayObject { + return $this->getEvents()->reversed(); + } + +} diff --git a/includes/ical/src/Recurrence.php b/includes/ical/src/Recurrence.php new file mode 100644 index 0000000..15f39cd --- /dev/null +++ b/includes/ical/src/Recurrence.php @@ -0,0 +1,234 @@ +parseRrule($rrule); + } + + /** + * Parses an 'RRULE' array and sets the member variables of this object. + * Expects a string that looks like this: 'FREQ=WEEKLY;INTERVAL=2;BYDAY=SU,TU,WE' + * + * @param $rrule + */ + protected function parseRrule($rrule): void { + $this->rrule = $rrule; + //loop through the properties in the line and set their associated + //member variables + foreach ($this->rrule as $propertyName => $propertyValue) { + //need the lower-case name for setting the member variable + $propertyName = strtolower($propertyName); + //split up the list of values into an array (if it's a list) + if (in_array($propertyName, $this->listProperties, true)) { + $propertyValue = explode(',', $propertyValue); + } + $this->$propertyName = $propertyValue; + } + } + + /** + * Returns the frequency - corresponds to FREQ in RFC 2445. + * + * @return mixed string if the member has been set, false otherwise + */ + public function getFreq() { + return $this->getMember('freq'); + } + + /** + * Retrieves the desired member variable and returns it (if it's set) + * + * @param string $member name of the member variable + * @return mixed the variable value (if set), false otherwise + */ + protected function getMember(string $member) { + return $this->$member ?? false; + } + + /** + * Returns when the event will go until - corresponds to UNTIL in RFC 2445. + * + * @return mixed string if the member has been set, false otherwise + */ + public function getUntil() { + return $this->getMember('until'); + } + + /** + * Set the $until member + * + * @param mixed $ts timestamp (int) / Valid DateTime format (string) + * @throws Exception + */ + public function setUntil($ts): void { + if ($ts instanceof DateTime) { + $dt = $ts; + } elseif (is_int($ts)) { + $dt = new DateTime('@' . $ts); + } else { + $dt = new DateTime($ts); + } + $this->until = $dt->format('Ymd\THisO'); + $this->rrule['until'] = $this->until; + } + + /** + * Returns the count of the times the event will occur (should only appear if 'until' + * does not appear) - corresponds to COUNT in RFC 2445. + * + * @return mixed string if the member has been set, false otherwise + */ + public function getCount() { + return $this->getMember('count'); + } + + /** + * Returns the interval - corresponds to INTERVAL in RFC 2445. + * + * @return mixed string if the member has been set, false otherwise + */ + public function getInterval() { + return $this->getMember('interval'); + } + + /** + * Returns the bysecond part of the event - corresponds to BYSECOND in RFC 2445. + * + * @return mixed string if the member has been set, false otherwise + */ + public function getBySecond() { + return $this->getMember('bysecond'); + } + + /** + * Returns the byminute information for the event - corresponds to BYMINUTE in RFC 2445. + * + * @return mixed string if the member has been set, false otherwise + */ + public function getByMinute() { + return $this->getMember('byminute'); + } + + /** + * Corresponds to BYHOUR in RFC 2445. + * + * @return mixed string if the member has been set, false otherwise + */ + public function getByHour() { + return $this->getMember('byhour'); + } + + /** + *Corresponds to BYDAY in RFC 2445. + * + * @return mixed string if the member has been set, false otherwise + */ + public function getByDay() { + return $this->getMember('byday'); + } + + /** + * Corresponds to BYMONTHDAY in RFC 2445. + * + * @return mixed string if the member has been set, false otherwise + */ + public function getByMonthDay() { + return $this->getMember('bymonthday'); + } + + /** + * Corresponds to BYYEARDAY in RFC 2445. + * + * @return mixed string if the member has been set, false otherwise + */ + public function getByYearDay() { + return $this->getMember('byyearday'); + } + + /** + * Corresponds to BYWEEKNO in RFC 2445. + * + * @return mixed string if the member has been set, false otherwise + */ + public function getByWeekNo() { + return $this->getMember('byweekno'); + } + + /** + * Corresponds to BYMONTH in RFC 2445. + * + * @return mixed string if the member has been set, false otherwise + */ + public function getByMonth() { + return $this->getMember('bymonth'); + } + + /** + * Corresponds to BYSETPOS in RFC 2445. + * + * @return mixed string if the member has been set, false otherwise + */ + public function getBySetPos() { + return $this->getMember('bysetpos'); + } + + /** + * Corresponds to WKST in RFC 2445. + * + * @return mixed string if the member has been set, false otherwise + */ + public function getWkst() { + return $this->getMember('wkst'); + } +} diff --git a/includes/ical/src/WindowsTimezones.php b/includes/ical/src/WindowsTimezones.php new file mode 100644 index 0000000..9bfa391 --- /dev/null +++ b/includes/ical/src/WindowsTimezones.php @@ -0,0 +1,214 @@ + 'Etc/GMT+12', + '(UTC-12:00) International Date Line West' => 'Etc/GMT+12', + 'UTC-11' => 'Etc/GMT+11', + '(UTC-11:00) Coordinated Universal Time -11' => 'Etc/GMT+11', + 'Hawaiian Standard Time' => 'Pacific/Honolulu', + '(UTC-10:00) Hawaii' => 'Pacific/Honolulu', + 'Alaskan Standard Time' => 'America/Anchorage', + '(UTC-09:00) Alaska' => 'America/Anchorage', + 'Pacific Standard Time (Mexico)' => 'America/Santa_Isabel', + '(UTC-08:00) Baja California' => 'America/Santa_Isabel', + 'Pacific Standard Time' => 'America/Los_Angeles', + 'Pacific Time' => 'America/Los_Angeles', + '(UTC-08:00) Pacific Time (US and Canada)' => 'America/Los_Angeles', + '(UTC-08:00) Pacific Time (US & Canada)' => 'America/Los_Angeles', + 'US Mountain Standard Time' => 'America/Phoenix', + '(UTC-07:00) Arizona' => 'America/Phoenix', + 'Mountain Standard Time (Mexico)' => 'America/Chihuahua', + '(UTC-07:00) Chihuahua, La Paz, Mazatlan' => 'America/Chihuahua', + 'Mountain Standard Time' => 'America/Denver', + 'Mountain Time' => 'America/Denver', + '(UTC-07:00) Mountain Time (US and Canada)' => 'America/Denver', + '(UTC-07:00) Mountain Time (US & Canada)' => 'America/Denver', + 'Central America Standard Time' => 'America/Guatemala', + '(UTC-06:00) Central America' => 'America/Guatemala', + 'Central Standard Time' => 'America/Chicago', + 'Central Time' => 'America/Chicago', + '(UTC-06:00) Central Time (US and Canada)' => 'America/Chicago', + '(UTC-06:00) Central Time (US & Canada)' => 'America/Chicago', + 'Central Standard Time (Mexico)' => 'America/Mexico_City', + '(UTC-06:00) Guadalajara, Mexico City, Monterrey' => 'America/Mexico_City', + 'Canada Central Standard Time' => 'America/Regina', + '(UTC-06:00) Saskatchewan' => 'America/Regina', + 'SA Pacific Standard Time' => 'America/Bogota', + '(UTC-05:00) Bogota, Lima, Quito' => 'America/Bogota', + 'Eastern Standard Time' => 'America/New_York', + 'Eastern Time' => 'America/New_York', + '(UTC-05:00) Eastern Time (US and Canada)' => 'America/New_York', + '(UTC-05:00) Eastern Time (US & Canada)' => 'America/New_York', + 'US Eastern Standard Time' => 'America/Indianapolis', + '(UTC-05:00) Indiana (East)' => 'America/Indianapolis', + 'Venezuela Standard Time' => 'America/Caracas', + '(UTC-04:30) Caracas' => 'America/Caracas', + 'Paraguay Standard Time' => 'America/Asuncion', + '(UTC-04:00) Asuncion' => 'America/Asuncion', + 'Atlantic Standard Time' => 'America/Halifax', + '(UTC-04:00) Atlantic Time (Canada)' => 'America/Halifax', + 'Central Brazilian Standard Time' => 'America/Cuiaba', + '(UTC-04:00) Cuiaba' => 'America/Cuiaba', + 'SA Western Standard Time' => 'America/La_Paz', + '(UTC-04:00) Georgetown, La Paz, Manaus, San Juan' => 'America/La_Paz', + 'Pacific SA Standard Time' => 'America/Santiago', + '(UTC-04:00) Santiago' => 'America/Santiago', + 'Newfoundland Standard Time' => 'America/St_Johns', + '(UTC-03:30) Newfoundland' => 'America/St_Johns', + 'E. South America Standard Time' => 'America/Sao_Paulo', + '(UTC-03:00) Brasilia' => 'America/Sao_Paulo', + 'Argentina Standard Time' => 'America/Buenos_Aires', + '(UTC-03:00) Buenos Aires' => 'America/Buenos_Aires', + 'SA Eastern Standard Time' => 'America/Cayenne', + '(UTC-03:00) Cayenne, Fortaleza' => 'America/Cayenne', + 'Greenland Standard Time' => 'America/Godthab', + '(UTC-03:00) Greenland' => 'America/Godthab', + 'Montevideo Standard Time' => 'America/Montevideo', + '(UTC-03:00) Montevideo' => 'America/Montevideo', + 'Bahia Standard Time' => 'America/Bahia', + 'UTC-02' => 'Etc/GMT+2', + '(UTC-02:00) Coordinated Universal Time -02' => 'Etc/GMT+2', + 'Azores Standard Time' => 'Atlantic/Azores', + '(UTC-01:00) Azores' => 'Atlantic/Azores', + 'Cape Verde Standard Time' => 'Atlantic/Cape_Verde', + '(UTC-01:00) Cabo Verde Is.' => 'Atlantic/Cape_Verde', + 'Morocco Standard Time' => 'Africa/Casablanca', + '(UTC) Casablanca' => 'Africa/Casablanca', + 'UTC' => 'Etc/GMT', + 'Microsoft/Utc' => 'Etc/GMT', + 'GMT Standard Time' => 'Europe/London', + '(UTC) Dublin, Edinburgh, Lisbon, London' => 'Europe/London', + 'Greenwich Standard Time' => 'Atlantic/Reykjavik', + '(UTC) Monrovia, Reykjavik' => 'Atlantic/Reykjavik', + 'W. Europe Standard Time' => 'Europe/Berlin', + '(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna' => 'Europe/Berlin', + '(UTC+01:00) Amsterdam\, Berlin\, Bern\, Rome\, Stockholm\, Vienna' => 'Europe/Berlin', + '(UTC+01:00) Amsterdam\, Berlin\, Bern\, Rom\, Stockholm\, Wien' => 'Europe/Berlin', + 'Central Europe Standard Time' => 'Europe/Budapest', + '(UTC+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague' => 'Europe/Budapest', + 'Romance Standard Time' => 'Europe/Paris', + '(UTC+01:00) Brussels, Copenhagen, Madrid, Paris' => 'Europe/Paris', + 'Central European Standard Time' => 'Europe/Warsaw', + '(UTC+01:00) Sarajevo, Skopje, Warsaw, Zagreb' => 'Europe/Warsaw', + 'W. Central Africa Standard Time' => 'Africa/Lagos', + '(UTC+01:00) West Central Africa' => 'Africa/Lagos', + 'Namibia Standard Time' => 'Africa/Windhoek', + '(UTC+01:00) Windhoek' => 'Africa/Windhoek', + 'GTB Standard Time' => 'Europe/Bucharest', + '(UTC+02:00) Athens, Bucharest' => 'Europe/Bucharest', + 'Middle East Standard Time' => 'Asia/Beirut', + '(UTC+02:00) Beirut' => 'Asia/Beirut', + 'Egypt Standard Time' => 'Africa/Cairo', + '(UTC+02:00) Cairo' => 'Africa/Cairo', + 'Syria Standard Time' => 'Asia/Damascus', + '(UTC+02:00) Damascus' => 'Asia/Damascus', + 'South Africa Standard Time' => 'Africa/Johannesburg', + '(UTC+02:00) Harare, Pretoria' => 'Africa/Johannesburg', + 'FLE Standard Time' => 'Europe/Kiev', + '(UTC+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius' => 'Europe/Kiev', + 'Turkey Standard Time' => 'Europe/Istanbul', + '(UTC+02:00) Istanbul' => 'Europe/Istanbul', + 'Israel Standard Time' => 'Asia/Jerusalem', + '(UTC+02:00) Jerusalem' => 'Asia/Jerusalem', + 'Libya Standard Time' => 'Africa/Tripoli', + 'Jordan Standard Time' => 'Asia/Amman', + '(UTC+02:00) Amman' => 'Asia/Amman', + 'Arabic Standard Time' => 'Asia/Baghdad', + '(UTC+03:00) Baghdad' => 'Asia/Baghdad', + 'Kaliningrad Standard Time' => 'Europe/Kaliningrad', + '(UTC+03:00) Kaliningrad' => 'Europe/Kaliningrad', + 'Arab Standard Time' => 'Asia/Riyadh', + '(UTC+03:00) Kuwait, Riyadh' => 'Asia/Riyadh', + 'E. Africa Standard Time' => 'Africa/Nairobi', + '(UTC+03:00) Nairobi' => 'Africa/Nairobi', + 'Iran Standard Time' => 'Asia/Tehran', + '(UTC+03:30) Tehran' => 'Asia/Tehran', + 'Arabian Standard Time' => 'Asia/Dubai', + '(UTC+04:00) Abu Dhabi, Muscat' => 'Asia/Dubai', + 'Azerbaijan Standard Time' => 'Asia/Baku', + '(UTC+04:00) Baku' => 'Asia/Baku', + 'Russian Standard Time' => 'Europe/Moscow', + '(UTC+04:00) Moscow, St. Petersburg, Volgograd' => 'Europe/Moscow', + 'Mauritius Standard Time' => 'Indian/Mauritius', + '(UTC+04:00) Port Louis' => 'Indian/Mauritius', + 'Georgian Standard Time' => 'Asia/Tbilisi', + '(UTC+04:00) Tbilisi' => 'Asia/Tbilisi', + 'Caucasus Standard Time' => 'Asia/Yerevan', + '(UTC+04:00) Yerevan' => 'Asia/Yerevan', + 'Afghanistan Standard Time' => 'Asia/Kabul', + '(UTC+04:30) Kabul' => 'Asia/Kabul', + 'West Asia Standard Time' => 'Asia/Tashkent', + '(UTC+05:00) Tashkent' => 'Asia/Tashkent', + 'Pakistan Standard Time' => 'Asia/Karachi', + '(UTC+05:00) Islamabad, Karachi' => 'Asia/Karachi', + 'India Standard Time' => 'Asia/Calcutta', + '(UTC+05:30) Chennai, Kolkata, Mumbai, New Delhi' => 'Asia/Calcutta', + 'Sri Lanka Standard Time' => 'Asia/Colombo', + '(UTC+05:30) Sri Jayawardenepura' => 'Asia/Colombo', + 'Nepal Standard Time' => 'Asia/Katmandu', + '(UTC+05:45) Kathmandu' => 'Asia/Katmandu', + 'Central Asia Standard Time' => 'Asia/Almaty', + '(UTC+06:00) Astana' => 'Asia/Almaty', + 'Bangladesh Standard Time' => 'Asia/Dhaka', + '(UTC+06:00) Dhaka' => 'Asia/Dhaka', + 'Ekaterinburg Standard Time' => 'Asia/Yekaterinburg', + '(UTC+06:00) Ekaterinburg' => 'Asia/Yekaterinburg', + 'Myanmar Standard Time' => 'Asia/Rangoon', + '(UTC+06:30) Yangon (Rangoon)' => 'Asia/Rangoon', + 'SE Asia Standard Time' => 'Asia/Bangkok', + '(UTC+07:00) Bangkok, Hanoi, Jakarta' => 'Asia/Bangkok', + 'N. Central Asia Standard Time' => 'Asia/Novosibirsk', + '(UTC+07:00) Novosibirsk' => 'Asia/Novosibirsk', + 'China Standard Time' => 'Asia/Shanghai', + '(UTC+08:00) Beijing, Chongqing, Hong Kong, Urumqi' => 'Asia/Shanghai', + 'North Asia Standard Time' => 'Asia/Krasnoyarsk', + '(UTC+08:00) Krasnoyarsk' => 'Asia/Krasnoyarsk', + 'Singapore Standard Time' => 'Asia/Singapore', + '(UTC+08:00) Kuala Lumpur, Singapore' => 'Asia/Singapore', + 'W. Australia Standard Time' => 'Australia/Perth', + '(UTC+08:00) Perth' => 'Australia/Perth', + 'Taipei Standard Time' => 'Asia/Taipei', + '(UTC+08:00) Taipei' => 'Asia/Taipei', + 'Ulaanbaatar Standard Time' => 'Asia/Ulaanbaatar', + '(UTC+08:00) Ulaanbaatar' => 'Asia/Ulaanbaatar', + 'North Asia East Standard Time' => 'Asia/Irkutsk', + '(UTC+09:00) Irkutsk' => 'Asia/Irkutsk', + 'Tokyo Standard Time' => 'Asia/Tokyo', + '(UTC+09:00) Osaka, Sapporo, Tokyo' => 'Asia/Tokyo', + 'Korea Standard Time' => 'Asia/Seoul', + '(UTC+09:00) Seoul' => 'Asia/Seoul', + 'Cen. Australia Standard Time' => 'Australia/Adelaide', + '(UTC+09:30) Adelaide' => 'Australia/Adelaide', + 'AUS Central Standard Time' => 'Australia/Darwin', + '(UTC+09:30) Darwin' => 'Australia/Darwin', + 'E. Australia Standard Time' => 'Australia/Brisbane', + '(UTC+10:00) Brisbane' => 'Australia/Brisbane', + 'AUS Eastern Standard Time' => 'Australia/Sydney', + '(UTC+10:00) Canberra, Melbourne, Sydney' => 'Australia/Sydney', + 'West Pacific Standard Time' => 'Pacific/Port_Moresby', + '(UTC+10:00) Guam, Port Moresby' => 'Pacific/Port_Moresby', + 'Tasmania Standard Time' => 'Australia/Hobart', + '(UTC+10:00) Hobart' => 'Australia/Hobart', + 'Yakutsk Standard Time' => 'Asia/Yakutsk', + '(UTC+10:00) Yakutsk' => 'Asia/Yakutsk', + 'Central Pacific Standard Time' => 'Pacific/Guadalcanal', + '(UTC+11:00) Solomon Is., New Caledonia' => 'Pacific/Guadalcanal', + 'Vladivostok Standard Time' => 'Asia/Vladivostok', + '(UTC+11:00) Vladivostok' => 'Asia/Vladivostok', + 'New Zealand Standard Time' => 'Pacific/Auckland', + '(UTC+12:00) Auckland, Wellington' => 'Pacific/Auckland', + 'UTC+12' => 'Etc/GMT-12', + '(UTC+12:00) Coordinated Universal Time +12' => 'Etc/GMT-12', + 'Fiji Standard Time' => 'Pacific/Fiji', + '(UTC+12:00) Fiji' => 'Pacific/Fiji', + 'Magadan Standard Time' => 'Asia/Magadan', + '(UTC+12:00) Magadan' => 'Asia/Magadan', + 'Tonga Standard Time' => 'Pacific/Tongatapu', + '(UTC+13:00) Nuku\'alofa' => 'Pacific/Tongatapu', + 'Samoa Standard Time' => 'Pacific/Apia', + '(UTC-11:00)Samoa' => 'Pacific/Apia', + 'W. Europe Standard Time 1' => 'Europe/Berlin', +]; diff --git a/includes/keywords.php b/includes/keywords.php new file mode 100644 index 0000000..4d6afaf --- /dev/null +++ b/includes/keywords.php @@ -0,0 +1,100 @@ + array_unique($keywords), + "link" => "/-/actions/$action[id]" + ]; + } + + foreach ($toys as $toy) { + $base = strtolower($toy["name"]); + $addKeywords = $toy["keywords"]; + $keywords = [ + $base, + ucfirst($base), + ucwords($base) + ]; + + for ($i = 0; $i < strlen($base); $i++) { + $keywords[] = substr($base, 0, $i) . strtoupper(substr($base, $i, 1)) . substr($base, $i + 1, strlen($base) - $i - 1); + + for ($j = 0; $j < strlen($base); $j++) { + $keywords[] = substr($base, 0, $i) . strtoupper(substr($base, $i, $j)) . substr($base, $i + $j, strlen($base) - $i - $j); + } + } + + foreach ($addKeywords as $keyword) { + $keywords[] = $keyword; + $keywords[] = ucfirst($keyword); + $keywords[] = ucwords($keyword); + + for ($i = 0; $i < strlen($keyword); $i++) { + $keywords[] = substr($keyword, 0, $i) . strtoupper(substr($keyword, $i, 1)) . substr($keyword, $i + 1, strlen($keyword) - $i - 1); + + for ($j = 0; $j < strlen($keyword); $j++) { + $keywords[] = substr($keyword, 0, $i) . strtoupper(substr($keyword, $i, $j)) . substr($keyword, $i + $j, strlen($keyword) - $i - $j); + } + } + } + + $pages[$toy["id"]] = [ + "keywords" => array_unique($keywords), + "link" => "/-/toys/$toy[id]" + ]; + } + + $keywords = []; + foreach ($pages as $page) { + foreach ($page["keywords"] as $keyword) { + $keywords[$keyword] = $page["link"]; + } + } + + return $keywords; +} + +function replaceKeyWords(string $input): string { + $keywords = getKeyWords(); + + foreach ($keywords as $keyword => $url) { + $input = str_replace($keyword, "$keyword", $input); + } + + return $input; +} \ No newline at end of file diff --git a/includes/member.php b/includes/member.php index d027fa7..f8050b0 100644 --- a/includes/member.php +++ b/includes/member.php @@ -165,7 +165,7 @@ if ($memberData["name"] === "fusion") {
  • Score breakdown:
      -
    • Host score:
    • +
    • Most common fronter score:
    • Relationships score:
    • Fictive score:
    • Median score:
    • diff --git a/includes/planner.php b/includes/planner.php index 72575cd..64d87e4 100644 --- a/includes/planner.php +++ b/includes/planner.php @@ -1,11 +1,20 @@ -

      Front Planner

      +

      Front planner

      diff --git a/includes/pleasure.php b/includes/pleasure.php index 1bbfcea..8b4835b 100644 --- a/includes/pleasure.php +++ b/includes/pleasure.php @@ -1,4 +1,4 @@ -

      Pleasure Alert +

      Pleasure alert
      ยท diff --git a/includes/random.php b/includes/random.php new file mode 100644 index 0000000..b6e7905 --- /dev/null +++ b/includes/random.php @@ -0,0 +1,15 @@ + time(), + "timestamp" => microtime(true), "duration" => $time, "restored" => $restored, "times" => $times diff --git a/includes/restore.php b/includes/restore.php new file mode 100644 index 0000000..72748ab --- /dev/null +++ b/includes/restore.php @@ -0,0 +1,130 @@ += 0) { + return $tense = "now"; + } elseif ($difference > 0) { + $tense = "ago"; + } else { + $tense = "later"; + } + + for ($j = 0; $difference >= $lengths[$j] && $j < count($lengths)-1; $j++) { + $difference /= $lengths[$j]; + } + + $difference = round($difference); + + $period = $periods[$j] . ($difference >1 ? "s" :''); + return "{$difference} {$period} {$tense}"; +} + +if (!isset($_SERVER['argv'][1]) || !isset($_SERVER['argv'][2])) { + echo("Usage: php " . $_SERVER['argv'][0] . " \n"); + die(); +} else { + $file = @file_get_contents($_SERVER['argv'][1]); + $raw = @file_get_contents($_SERVER['argv'][2]); + + if ($file === false) { + echo("Unable to open backup file\n"); + die(); + } + + if ($raw === false) { + echo("Unable to open key file\n"); + die(); + } + + $raw2 = base64_decode($raw); + + if (!isJson($raw2)) { + echo("Key file is corrupt\n"); + die(); + } + + $keydata = json_decode($raw2, true); + + if (!is_array($keydata) || !isset($keydata["iv"]) || !isset($keydata["key"])) { + echo("Key file is invalid\n"); + die(); + } + + $iv = hex2bin($keydata["iv"]); + $key = hex2bin($keydata["key"]); + + $decrypted = openssl_decrypt($file, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv); + + if ($decrypted === false) { + echo("Unable to decrypt backup\n"); + die(); + } + + $unpadded = pkcs7_unpad($decrypted); + + if (!is_string($unpadded)) { + echo("Unable to decrypt backup\n"); + die(); + } + + if (!isJson($unpadded)) { + echo("Backup is corrupt\n"); + die(); + } + + $data = json_decode($unpadded, true); + + if (!is_array($data) || !isset($data["date"]) || !isset($data["files"])) { + echo("Backup is invalid\n"); + die(); + } + + echo(realpath($_SERVER['argv'][1]) . "\n Key: " . $_SERVER['argv'][2] . "\n Date: " . date('r', strtotime($data["date"])) . " (" . timeAgo($data["date"]) . ")" . "\n Contents: " . count($data["files"]) . " files\n"); + + @mkdir("./_restored"); + + $index = 0; + foreach ($data["files"] as $file) { + if ($file["dir"] === "") { + print("[$index] /" . $file["file"] . "\n"); + } else { + print("[$index] /" . $file["dir"] . "/" . $file["file"] . "\n"); + } + + $content = base64_decode($file["content"]); + if (sha1($content) !== $file["checksum"][0]) { + print(" Backed up file is corrupted (SHA1 mismatch)\n Expected: " . $file["checksum"][0] . "\n Got: " . sha1($content) . "\n"); + die("Backup aborted.\n"); + } + if (md5($content) !== $file["checksum"][1]) { + print(" Backed up file is corrupted (MD5 mismatch)\n Expected: " . $file["checksum"][1] . "\n Got: " . md5($content) . "\n"); + die("Backup aborted.\n"); + } + + @mkdir("./_restored/" . $file["dir"], 0777, true); + file_put_contents("./_restored/" . $file["dir"] . "/" . $file["file"], $content); + + $index++; + } + + print("Restored backup to ./_restored; review files before restoring to production\n"); +} \ No newline at end of file diff --git a/includes/session.php b/includes/session.php index 2157f5f..b440c9c 100644 --- a/includes/session.php +++ b/includes/session.php @@ -11,6 +11,12 @@ if (isset($_COOKIE['PEH2_SESSION_TOKEN'])) { if (file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . str_replace(".", "", str_replace("/", "", $_COOKIE['PEH2_SESSION_TOKEN'])))) { $_PROFILE = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . str_replace(".", "", str_replace("/", "", $_COOKIE['PEH2_SESSION_TOKEN']))), true); + + if (isset($_GET['invert'])) { + $_PROFILE["login"] = $_PROFILE["login"] === "raindrops" ? "cloudburst" : "raindrops"; + $_PROFILE["name"] = $_PROFILE["name"] === "Raindrops System" ? "Cloudburst System" : "Raindrops System"; + } + $isLoggedIn = true; } else { $isLoggedIn = false; diff --git a/includes/sysbanner.php b/includes/sysbanner.php index 5ffe000..3a2b8c1 100644 --- a/includes/sysbanner.php +++ b/includes/sysbanner.php @@ -18,7 +18,7 @@ $travelling = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includ

      - Host: MCF: Member Member Member - Mmbr. + Mmbr. Species Species Species Species - Spec. - Spec. + Spec. + Spec. Relations Relations Relations Relations - Relt. - Relt. + Relt. + Relt. - Host - Host - Host - Host - Hst. - Hst. + MCF + MCF + MCF + MCF + MCF + MCF Fictive Fictive Fictive Fictive - Fic. - Fic. + Fic. + Fic. Little Little Little Little - Ltl. - Ltl. + Ltl. + Ltl. Protector Protector - Protect. - Protect. - Prt. - Prt. + Protect. + Protect. + Prt. + Prt. $item) { + if ($item["id"] === $id) { + $selectedIndex = $index; + $selected = $item; + break; + } + } + + if ($selected === null) { + header("Location: /-/actions"); + die(); + } + + unset($data[$selectedIndex]); + @mkdir($_SERVER['DOCUMENT_ROOT'] . "/includes/data/oldactions"); + file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/oldactions/" . date('c') . ".json", utf8_encode(json_encode($data))); + file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/actions.json", utf8_encode(json_encode($data))); + header("Location: /-/actions/?d&id=" . $id); + die(); +} + +if (isset($_POST['updateAction'])) { + $data = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/actions.json"), true); + + $selected = null; + $selectedIndex = -1; + $id = $_POST['action']; + + foreach ($data as $index => $item) { + if ($item["id"] === $id) { + $selectedIndex = $index; + $selected = $item; + break; + } + } + + if ($selected === null) { + header("Location: /-/actions"); + die(); + } + + if (isset($_POST["consent"])) { + $selected["consent"] = true; + } else { + $selected["consent"] = false; + } + + if (isset($_POST["name"])) $selected["name"] = strip_tags(trim($_POST["name"])); + if (isset($_POST["example"])) $selected["example"] = strip_tags(trim($_POST["example"])); + if (isset($_POST["irl"])) $selected["irl"] = strip_tags(trim($_POST["irl"])); + if (isset($_POST["keywords"])) $selected["keywords"] = array_map(function ($i) { + return trim($i); + }, explode(",", strip_tags(trim($_POST["keywords"])))); + if (isset($_POST["description"])) $selected["description"] = strip_tags(trim($_POST["description"])); + if (isset($_POST["type"])) $selected["type"] = match ($_POST["type"]) { + "0" => "affectionate", + "1" => "sexual", + "2" => "mixed" + }; + + if (isset($_POST["relations"])) { + $ponies = []; + + foreach ($_POST["relations"] as $relation => $d) { + $ponies[] = [ + "members" => explode("-", $relation), + "deprecated" => isset($d["deprecated"]), + "sexual" => isset($d["sexual"]) + ]; + } + + $selected["ponies"] = $ponies; + } + + global $_PROFILE; + if ($_PROFILE['login'] === "raindrops" && isset($_POST["verified"])) { + $selected["verified"] = true; + } else { + unset($selected["verified"]); + } + + if (isset($_POST["untested"])) { + $selected["untested"] = true; + } else { + unset($selected["untested"]); + } + + $data[$selectedIndex] = $selected; + @mkdir($_SERVER['DOCUMENT_ROOT'] . "/includes/data/oldactions"); + file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/oldactions/" . date('c') . ".json", utf8_encode(json_encode($data))); + file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/actions.json", utf8_encode(json_encode($data))); + + header("Location: /-/actions/" . $id); + die(); +} + +if (isset($_POST['createAction'])) { + require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/random.php"; + $data = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/actions.json"), true); + + if (!isset($_POST["name"]) || !isset($_POST["type"])) { + header("Location: /-/actions"); + die(); + } + if (trim($_POST["name"]) === "" || !is_numeric($_POST["type"])) { + header("Location: /-/actions"); + die(); + } + + $type = match ($_POST["type"]) { + "0" => "affectionate", + "1" => "sexual", + "2" => "mixed" + }; + $name = strip_tags(trim($_POST["name"])); + $id = random(); + + $ponies = []; + + if (isset($_POST["relations"])) { + foreach ($_POST["relations"] as $relation => $_) { + $ponies[] = [ + "members" => explode("-", $relation), + "deprecated" => false, + "sexual" => false + ]; + } + } + + if (isset($_POST["consent"])) { + $consent = true; + } else { + $consent = false; + } + + $data[] = [ + "id" => $id, + "name" => $name, + "type" => $type, + "description" => null, + "ponies" => $ponies, + "example" => null, + "irl" => null, + "keywords" => [], + "consent" => $consent + ]; + + @mkdir($_SERVER['DOCUMENT_ROOT'] . "/includes/data/oldactions"); + file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/oldactions/" . date('c') . ".json", utf8_encode(json_encode($data))); + file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/actions.json", utf8_encode(json_encode($data))); + header("Location: /-/actions/" . $id); + die(); +} + +global $pagename; +$parts = explode("/", $pagename); +$data = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/actions.json"), true); +$toys = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/toys.json"), true); + +$selected = null; +$title = "Actions database"; + +if (isset($parts[1])) { + $id = $parts[1]; + + foreach ($data as $item) { + if ($item["id"] === $id) { + $selected = $item; + break; + } + } + + if ($selected === null) { + header("Location: /-/actions/?nf&id=" . $id); + die(); + } else { + $title = $selected["name"] . " ยท Actions database"; + } +} + +require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/keywords.php'; + +if (!file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/actions.json")) file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/actions.json", "[]"); + +global $_PROFILE; + +?> + + + +
      +
      +
      "> + +
      + + Error: The requested action () was not found, it may have been deleted or has never existed. +
      + + + +
      + + Success: The action with ID has been successfully deleted. +
      + + + + +

      + + Back +

      +

      + Edit ยท + + Affectionate + + + Sexual + + + Affectionate Sexual + + Unverified + Untested +

      + +
      + +
      + This action requires consent. Since this action constitues a sexual act, verbal consent from both parties is absolutely required. Both parties must be in a mental state where they are able to consent. Furthermore, if one of the parties involved wishes to stop, the request must be immediately honoured. +
      + +
      + This action is better with consent. This action is extremely personal to all parties involved, and it is best to ask if they wish to do this. If they ask to stop the action, you must stop immediately and reverse any potential effects. +
      + + + + ", strip_tags($selected["description"]))); ?> + +

      No description provided for this action. Enter edit mode to add a description to this action.

      + + + +
      +

      Available toys:

      + + + +
      + Can be done by: + + +
        +
      • + Affectionately: +
          + +
        • + .png"> + .png"> + + and +
        • + + +
        • + + .png"> + .png"> + + and + + Deprecated +
        • + +
        +
      • +
      • + Sexually: +
          + +
        • + .png"> + .png"> + + and +
        • + + +
        • + + .png"> + .png"> + + and + + Deprecated +
        • + +
        +
      • +
      + +
        + +
      • + .png"> + .png"> + + and +
      • + + +
      • + + .png"> + .png"> + + and + + Deprecated +
      • + +
      + + +
      + + 1; ?> + Example:
      + + +
        + +
      • + +
      + + + + +

      No example provided for this action. Enter edit mode to add an example to this action.

      + + +
      + + Steps to reproduce in real life:
      + + + +

      This action is not reproducible in real life.

      + + +
      + +

      Similar actions

      +
      + $action["id"], + "type" => $action["type"], + "ponies" => $action["ponies"] + ]; + } + + foreach ($names as $name => $data) { + if ($data["type"] === $selected["type"] || $selected["type"] !== "affectionate") { + $namesByDistance[] = [ + "name" => $name, + "distance" => levenshtein($currentName, $name) + ((int)($data["type"] !== $selected["type"]) * 2), + "id" => $data["id"], + "type" => $data["type"], + "ponies" => $data["ponies"] + ]; + } + } + + uasort($namesByDistance, function ($a, $b) use ($selected) { + return $a["distance"] - $b["distance"]; + }); + + foreach ($namesByDistance as $item) { + echo(""); + } + + $index = 0; + foreach ($namesByDistance as $item): if ($index < 3): + ?> + + +
      + + + + + +

      Actions database

      +

      actions ( affectionate, sexual, untested, incomplete)

      + +

      TODO: add ponies for all actions (+ keywords)

      + + + + + +
      + +
      +
      +

      Not finding what you are looking for? Create an action.

      +
      + + + + + + +
      +
      + + + + + + + diff --git a/pages/bitset.php b/pages/bitset.php index 11031e2..8c63bc5 100644 --- a/pages/bitset.php +++ b/pages/bitset.php @@ -1,7 +1,7 @@
      -

      Bitset Calculator

      +

      Bitset calculator

      0
      @@ -27,8 +27,8 @@ $title = "Bitset calculator"; require_once $_SERVER['DOCUMENT_ROOT'] . '/include
      0
      0
      0
      -
      0
      -
      0
      +
      0
      +
      0
      0
      0
      0
      @@ -108,6 +108,8 @@ $title = "Bitset calculator"; require_once $_SERVER['DOCUMENT_ROOT'] . '/include let magic = parseInt(binString.substring(2 + 16, 5 + 16), 2); let sensitivity = parseInt(binString.substring(5 + 16, 8 + 16), 2); let ageSpells = binString.substring(31 + 16, 32 + 16) !== "0"; + let nonverbal = binString.substring(15, 16) !== "0"; + let lessFrequent = binString.substring(14, 15) !== "0"; document.getElementById("value-0").value = sharedMemory; document.getElementById("value-1").value = little; @@ -125,6 +127,8 @@ $title = "Bitset calculator"; require_once $_SERVER['DOCUMENT_ROOT'] . '/include document.getElementById("value-13").value = sensitivity; document.getElementById("value-14").value = species3; document.getElementById("value-15").checked = ageSpells; + document.getElementById("value-16").checked = nonverbal; + document.getElementById("value-17").checked = lessFrequent; } } @@ -171,6 +175,8 @@ $title = "Bitset calculator"; require_once $_SERVER['DOCUMENT_ROOT'] . '/include let species2 = binString.substring(21 + 16, 25 + 16); let species3 = binString.substring(25 + 16, 29 + 16); let food = parseInt(binString.substring(16, 2 + 16), 2); + let nonverbal = binString.substring(15, 16) !== "0"; + let lessFrequent = binString.substring(14, 15) !== "0"; let magic = parseInt(binString.substring(2 + 16, 5 + 16), 2); let sensitivity = parseInt(binString.substring(5 + 16, 8 + 16), 2); @@ -190,6 +196,8 @@ $title = "Bitset calculator"; require_once $_SERVER['DOCUMENT_ROOT'] . '/include document.getElementById("value-10").checked = plush; document.getElementById("value-14").value = species3; document.getElementById("value-15").checked = ageSpells; + document.getElementById("value-16").checked = nonverbal; + document.getElementById("value-17").checked = lessFrequent; calculateOutput(); @@ -213,6 +221,8 @@ $title = "Bitset calculator"; require_once $_SERVER['DOCUMENT_ROOT'] . '/include let val13 = document.getElementById("value-13").value; let val14 = document.getElementById("value-14").value; let val15 = document.getElementById("value-15").checked; + let val16 = document.getElementById("value-16").checked; + let val17 = document.getElementById("value-17").checked; let val0bin = parseInt(val0).toString(2); val0bin = val0bin.length === 1 ? "0" + val0bin : val0bin; @@ -241,8 +251,10 @@ $title = "Bitset calculator"; require_once $_SERVER['DOCUMENT_ROOT'] . '/include let val9bin = val9 ? "1" : "0"; let val10bin = val10 ? "1" : "0"; let val15bin = val15 ? "1" : "0"; + let val16bin = val16 ? "1" : "0"; + let val17bin = val17 ? "1" : "0"; - let bin = "0000000000000000" + val11bin + val12bin + val13bin + val0bin + val4bin + val1bin + val5bin + val6bin + val7bin + val8bin + val2bin + val3bin + val14bin + val9bin + val10bin + val15bin; + let bin = "00000000000000" + val17bin + val16bin + val11bin + val12bin + val13bin + val0bin + val4bin + val1bin + val5bin + val6bin + val7bin + val8bin + val2bin + val3bin + val14bin + val9bin + val10bin + val15bin; console.log(bin, parseInt(bin, 2)); @@ -364,7 +376,15 @@ $title = "Bitset calculator"; require_once $_SERVER['DOCUMENT_ROOT'] . '/include +
      +
      +

      diff --git a/pages/dashboard.php b/pages/dashboard.php new file mode 100644 index 0000000..622cd20 --- /dev/null +++ b/pages/dashboard.php @@ -0,0 +1,299 @@ + + +
      +
      +
      +

      Date

      + + + + +
      +

      Next fronters (edit)

      + + +
      Fronters today
      +
        + $id): $member = getSystemMember($_PROFILE["login"] === "raindrops" ? "gdapd" : "ynmuc", $id); ?> +
      • + .png" style="width:24px; height: 24px; vertical-align: middle;"> + with .png" style="width:24px; height: 24px; vertical-align: middle;"> +
      • + +
      + +
      Fronters tomorrow
      +
        + $id): $member = getSystemMember($_PROFILE["login"] === "raindrops" ? "gdapd" : "ynmuc", $id); ?> +
      • + .png" style="width:24px; height: 24px; vertical-align: middle;"> + with .png" style="width:24px; height: 24px; vertical-align: middle;"> +
      • + +
      + +
      +

      Next events

      + parseFile($_SERVER['DOCUMENT_ROOT'] . "/includes/data/calendar.ics"); + $events = []; + + foreach ($cal->getEvents()->sorted() as $event) { + $events[] = [ + "id" => $event['UID'], + "date" => [ + "created" => $event['CREATED']->format('c'), + "modified" => $event['LAST-MODIFIED']->format('c'), + "start" => $event['DTSTART']->format('c'), + "end" => $event['DTEND']->format('c'), + "duration" => strtotime($event['DTEND']->format('c')) - strtotime($event['DTSTART']->format('c')), + "full_day" => strtotime($event['DTEND']->format('c')) - strtotime($event['DTSTART']->format('c')) >= 86400, + "days" => ( + strtotime($event['DTEND']->format('c')) - strtotime($event['DTSTART']->format('c')) >= 86400 ? + round((strtotime($event['DTEND']->format('c')) - strtotime($event['DTSTART']->format('c'))) / 86400) : + null + ) + ], + "name" => $event['SUMMARY'], + "description" => $event['DESCRIPTION'], + ]; + } + + $events = array_values(array_filter($events, function ($i) { + return ( + strtotime($i["date"]["end"]) > time() && + strtotime($i["date"]["start"]) < time() + 2629800 + ); + })); + + ?> +
        + +
      • + + + + Started , ends + + + + ยท + + + + + ยท + + + for + ยท + +
      • + +
      +
      +
      + + + + diff --git a/pages/debug.php b/pages/debug.php new file mode 100644 index 0000000..338a896 --- /dev/null +++ b/pages/debug.php @@ -0,0 +1,91 @@ + "General info about $systemName", + "members" => "Members in the $systemName", + "fronters" => "Current fronter(s) in the $systemName", + "switches" => "Switch history from the $systemName", + default => "$type in $systemName", + }; + } + + if (str_starts_with($item, "images-")) { + $system = explode("-", $item)[1]; + $id = explode("-", $item)[2]; + $systemName = $system === "gdapd" ? "Raindrops" : "Cloudburst"; + $member = getSystemMember($system, $id) ?? [ "name" => $id, "display_name" => $id, "id" => $id ]; + + if ($member["name"] === "unknown") { + return "Unknown (" . $systemName . ")'s images"; + } else { + return getMiniName($member["display_name"] ?? $member["name"]) . "'s images"; + } + } + + return "$item"; +} + +?> + +
      +
      + + +
      +

      Data updater debugging

      +

      This page provides debugging information to troubleshoot unexpectedly long update times or reported failures.

      + +

      General information

      +
        +
      • Update date: format("l j F Y, G:i:s.u T")); ?>
      • +
      • Total duration: ms
      • +
      • Longest operation: ( ms, %)
      • +
      + +

      Processing times

      +
      + $time): ?>%"> +
      +
        + $time): ?> +
      • : ms
      • + +
      + +

      Reported failures

      + +

      The data updater has not reported any update failure in the last run.

      + +

      The following files have failed to update:

      +
        + +
      • + +
      + +
      +
      + + + + diff --git a/pages/edit.php b/pages/edit.php index 74105e2..ae007be 100644 --- a/pages/edit.php +++ b/pages/edit.php @@ -14,7 +14,7 @@ function getSubsystemByID(string $id) { } require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; global $isLoggedIn; -if (!$isLoggedIn) header("Location: /login") and die(); +if (!$isLoggedIn) header("Location: /-/login") and die(); if (!isset($_GET['_']) || trim($_GET['_']) === "") header("Location: /?error=Invalid request") and die(); diff --git a/pages/emergency.php b/pages/emergency.php index 0405bca..ba2a35e 100644 --- a/pages/emergency.php +++ b/pages/emergency.php @@ -1,7 +1,7 @@ +
      diff --git a/pages/nicknames.php b/pages/nicknames.php new file mode 100644 index 0000000..050c275 --- /dev/null +++ b/pages/nicknames.php @@ -0,0 +1,117 @@ + $i + ]; + $r["type"] = "marefriends"; + return $r; + }, $member["_metadata"]["marefriends"] ?? []), + ...array_map(function ($i) { + $r = [ + "name" => $i + ]; + $r["type"] = "sisters"; + return $r; + }, $member["_metadata"]["sisters"] ?? []), + ...array_map(function ($i) { + $r = [ + "name" => $i + ]; + $r["type"] = "caretaking"; + return $r; + }, $member["_metadata"]["caretakers"] ?? []) + ] as $rel) { + $id = $rel["name"]; + $otherMember = getSystemMember(explode("/", $id)[0], explode("/", $id)[1]); + + $parts = [ + $member["id"], + $otherMember["id"] + ]; + + asort($parts); + + $relations[implode("-", $parts)] = [ + "id" => implode("", $parts), + "name" => getMiniName($member["display_name"] ?? $member["name"]) . " and " . getMiniName($otherMember["display_name"] ?? $otherMember["name"]), + "type" => $rel["type"], + "images" => [ + file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-$member[name].png") ? "/assets/uploads/pt-$member[name].png" : "/assets/uploads/pt.png", + file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-$otherMember[name].png") ? "/assets/uploads/pt-$otherMember[name].png" : "/assets/uploads/pt.png", + ] + ]; + } +} + +$nicknames = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/nicknames.json"), true); + +?> + +
      + + + + + diff --git a/pages/parser.php b/pages/parser.php index cb07179..b49fd00 100644 --- a/pages/parser.php +++ b/pages/parser.php @@ -3,7 +3,7 @@
      -

      Message Parser

      +

      Message parser

      Enter a message here, and we will tell you which system member this message came from.

      diff --git a/pages/pleasure.php b/pages/pleasure.php index db63f5a..295e597 100644 --- a/pages/pleasure.php +++ b/pages/pleasure.php @@ -1,7 +1,7 @@
      -

      Prefix Generator

      +

      Prefix generator

      This prefix generator will take into account the prefixes from the existing members to try to generate new original prefixes for a potential new member.

      diff --git a/pages/rules.php b/pages/rules.php new file mode 100644 index 0000000..b1759dc --- /dev/null +++ b/pages/rules.php @@ -0,0 +1,254 @@ + $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.json", utf8_encode(json_encode($_POST["payload"]))); + + header("Location: /-/rules"); + die(); +} + +$title = "Systems rules"; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php'; + +?> + +
      +
      +
      +

      Systems rules

      +

      Click on a rule to view additional details. Edit rules

      + + + +
        + +
      • +
        + + . + + Unapproved + + + +
        + This rule has not yet been approved. All rules need to be approved by the protectors from both systems. This rule is still missing approval from . +
        + +
        class="list-group-item"> + +
        +
        +
      • + +
      +
      +
      + + + + + + + + diff --git a/pages/score.php b/pages/score.php index 96a9d17..043a695 100644 --- a/pages/score.php +++ b/pages/score.php @@ -1,7 +1,7 @@
      -

      Score System Testing

      +

      Score system testing

      Raindrops System ( $item) { + if ($item["id"] === $id) { + $selectedIndex = $index; + $selected = $item; + break; + } + } + + if ($selected === null) { + header("Location: /-/toys"); + die(); + } + + unset($data[$selectedIndex]); + file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/toys.json", utf8_encode(json_encode($data))); + header("Location: /-/toys/?d&id=" . $id); + die(); +} + +if (isset($_POST['updateAction'])) { + $data = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/toys.json"), true); + + $selected = null; + $selectedIndex = -1; + $id = $_POST['action']; + + foreach ($data as $index => $item) { + if ($item["id"] === $id) { + $selectedIndex = $index; + $selected = $item; + break; + } + } + + if ($selected === null) { + header("Location: /-/toys"); + die(); + } + + if (isset($_POST["sexual"])) { + $selected["sexual"] = true; + } else { + $selected["sexual"] = false; + } + + if (isset($_POST["name"])) $selected["name"] = strip_tags(trim($_POST["name"])); + if (isset($_POST["usage"])) $selected["usage"] = strip_tags(trim($_POST["usage"])); + if (isset($_POST["irl"])) $selected["irl"] = strip_tags(trim($_POST["irl"])); + if (isset($_POST["keywords"])) $selected["keywords"] = array_map(function ($i) { + return trim($i); + }, explode(",", strip_tags(trim($_POST["keywords"])))); + if (isset($_POST["description"])) $selected["description"] = strip_tags(trim($_POST["description"])); + if (isset($_POST["water"])) $selected["water"] = match ($_POST["water"]) { + "0" => "out", + "1" => "in", + "2" => "both", + "3" => "playground" + }; + + if (isset($_POST["relations"])) { + $ponies = []; + + foreach ($_POST["relations"] as $relation => $d) { + $ponies[] = [ + "members" => explode("-", $relation), + "deprecated" => isset($d["deprecated"]), + "sexual" => isset($d["sexual"]) + ]; + } + + $selected["ponies"] = $ponies; + } + + global $_PROFILE; + if ($_PROFILE['login'] === "raindrops" && isset($_POST["verified"])) { + $selected["verified"] = true; + } else { + unset($selected["verified"]); + } + + if (isset($_POST["untested"])) { + $selected["untested"] = true; + } else { + unset($selected["untested"]); + } + + $data[$selectedIndex] = $selected; + file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/toys.json", utf8_encode(json_encode($data))); + + header("Location: /-/toys/" . $id); + die(); +} + +if (isset($_POST['createAction'])) { + require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/random.php"; + $data = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/toys.json"), true); + + if (!isset($_POST["name"]) || !isset($_POST["water"])) { + header("Location: /-/toys"); + die(); + } + if (trim($_POST["name"]) === "" || !is_numeric($_POST["water"])) { + header("Location: /-/toys"); + die(); + } + + $water = match ($_POST["water"]) { + "0" => "out", + "1" => "in", + "2" => "both", + "3" => "playground" + }; + $name = strip_tags(trim($_POST["name"])); + $id = random(); + + $ponies = []; + + if (isset($_POST["relations"])) { + foreach ($_POST["relations"] as $relation => $_) { + $ponies[] = [ + "members" => explode("-", $relation), + "deprecated" => false + ]; + } + } + + if (isset($_POST["sexual"])) { + $sexual = true; + } else { + $sexual = false; + } + + $data[] = [ + "id" => $id, + "name" => $name, + "water" => $water, + "description" => null, + "ponies" => $ponies, + "usage" => null, + "irl" => null, + "keywords" => [], + "sexual" => $sexual + ]; + + file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/toys.json", utf8_encode(json_encode($data))); + header("Location: /-/toys/" . $id); + die(); +} + +global $pagename; +$parts = explode("/", $pagename); +$data = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/toys.json"), true); + +$selected = null; +$title = "Toys database"; + +if (isset($parts[1])) { + $id = $parts[1]; + + foreach ($data as $item) { + if ($item["id"] === $id) { + $selected = $item; + break; + } + } + + if ($selected === null) { + header("Location: /-/toys/?nf&id=" . $id); + die(); + } else { + $title = $selected["name"] . " ยท Toys database"; + } +} + +require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/keywords.php'; + +if (!file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/toys.json")) file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/toys.json", "[]"); + +global $_PROFILE; + +?> + + + +
      +
      +
      "> + +
      + + Error: The requested toy () was not found, it may have been deleted or has never existed. +
      + + + +
      + + Success: The toy with ID has been successfully deleted. +
      + + + + +

      + + Back +

      +

      + Edit ยท + + Sexual + + Pleasurable + + Unverified + Untested + + Underwater + + Outside of water + + In playground + + Underwater + Outside of water + +

      + + + ", strip_tags($selected["description"]))); ?> + +

      No description provided for this toy. Enter edit mode to add a description to this toy.

      + + +
      + Can be used by: + +
        + +
      • + .png"> + .png"> + + and +
      • + + +
      • + + .png"> + .png"> + + and + + Deprecated +
      • + +
      + +
      + + Usage:
      + + 1) echo("
        "); + + foreach ($lines as $line) { + if (count($lines) > 1) echo("
      • "); + + $parts = explode(":", $line); + + if (count($parts) > 1 && strlen($parts[0]) < 30) { + $p0 = $parts[0]; array_shift($parts); + echo(replaceKeyWords("" . $p0 . ":" . implode(":", $parts))); + } else { + echo(replaceKeyWords(implode(":", $parts))); + } + + if (count($lines) > 1) echo("
      • "); + } + + if (count($lines) > 1) echo("
      "); + + ?> + +

      No usage provided for this toy. Enter edit mode to add usage information to this toy.

      + + +
      + + Instructions to craft in real life:
      + + + +

      This toy is not craftable in real life.

      + + +
      + +

      Similar toys

      +
      + $action["id"], + "water" => $action["water"], + "ponies" => $action["ponies"] + ]; + } + + foreach ($names as $name => $data) { + $namesByDistance[] = [ + "name" => $name, + "distance" => levenshtein($currentName, $name) + ((int)($data["type"] !== $selected["type"]) * 2), + "id" => $data["id"], + "water" => $data["water"], + "ponies" => $data["ponies"] + ]; + } + + uasort($namesByDistance, function ($a, $b) use ($selected) { + return $a["distance"] - $b["distance"]; + }); + + foreach ($namesByDistance as $item) { + echo(""); + } + + $index = 0; + foreach ($namesByDistance as $item): if ($index < 3): + ?> + + +
      + + + + + +

      Toys database

      +

      toys ( non-sexual, sexual, untested, incomplete)

      + + + + + +
      + +
      +
      +

      Not finding what you are looking for? Add a toy.

      +
      + + + + + + +
      +
      + + + + + + + diff --git a/pages/travelling.php b/pages/travelling.php index affe92e..c40ebab 100644 --- a/pages/travelling.php +++ b/pages/travelling.php @@ -1,7 +1,7 @@
      -

      System Travelling

      +

      System travelling