diff --git a/Controllers/Api/ApiController.php b/Controllers/Api/ApiController.php index 65d94f7..4e31bfa 100644 --- a/Controllers/Api/ApiController.php +++ b/Controllers/Api/ApiController.php @@ -12,13 +12,14 @@ abstract class ApiController extends Controller /* All API keys go here */ //Line reporting keys - const REPORTING_API_KEY = 'testing'; const COLLECTING_API_KEY = 'testing'; const UPDATING_API_KEY = 'testing'; //Usage analysis api keys const AGGREGATE_API_KEY = 'testing'; - //Discord Integration Key + //Discord integration key const DISCORD_INTEGRATION_API_KEY = 'testing'; + //Premium authenticator key + const PREMIUM_AUTHENTICATOR_API_KEY = 'testing'; /** * Controller constructor enabling output buffering and setting the Content-Type header diff --git a/Controllers/Api/LineReporting/LineReporter.php b/Controllers/Api/LineReporting/LineReporter.php index 40d0558..13a0a6c 100644 --- a/Controllers/Api/LineReporting/LineReporter.php +++ b/Controllers/Api/LineReporting/LineReporter.php @@ -14,7 +14,7 @@ public function process(array $args): int { parse_str(file_get_contents("php://input"),$_PUT); - if (!isset($_REQUEST['apiKey']) && !isset($_PUT['apiKey'])) { + if (!isset($_REQUEST['apiKey']) && !isset($_PUT['apiKey']) && $args[0] !== 'newUnvoicedLineReport') { return 401; } @@ -45,9 +45,6 @@ private function newReport(): int if ($_SERVER['REQUEST_METHOD'] !== 'POST') { return 405; } - if ($_REQUEST['apiKey'] !== self::REPORTING_API_KEY) { - return 401; - } $reportAdder = new ReportAdder(); return $reportAdder->createReport($_POST['full'], $_POST['npc'], $_POST['player'], $_POST['x'], $_POST['y'], $_POST['z']); } diff --git a/Controllers/Api/PremiumAuthenticator/Authenticator.php b/Controllers/Api/PremiumAuthenticator/Authenticator.php new file mode 100644 index 0000000..c57e8fe --- /dev/null +++ b/Controllers/Api/PremiumAuthenticator/Authenticator.php @@ -0,0 +1,118 @@ +checkCode(); + $result = ['valid' => ($valid === 200) ? 'true' : 'false']; + if ($valid === 200) { + $result['ip'] = self::STREAM_SERVER_IP; + } else { + $result['reason'] = ($valid === 402) ? 'expired' : 'invalid'; + } + echo json_encode($result); + return 200; + case 'get-code': + if ($_SERVER['REQUEST_METHOD'] !== 'GET') { + return 405; + } + + $result = $this->getCodeForUser($discordUserId); + $status = $result['status']; + $code = $result['code'] ?? null; + if ($status === 404) { + $code = $this->generateCodeForUser($discordUserId); + $status = 201; + } + echo json_encode(["code" => $code]); + return $status; + case 'deactivate-user': + if ($_SERVER['REQUEST_METHOD'] !== 'PUT') { + return 405; + } + + return ($this->deactivateCodeForUser($discordUserId)) ? 204 : 500; + case 'reactivate-user': + if ($_SERVER['REQUEST_METHOD'] !== 'PUT') { + return 405; + } + + return ($this->activateCodeForUser($discordUserId)) ? 204 : 500; + default: + return 400; + } + } + + private function getCodeForUser(string $discordUserId): array + { + $manager = new PremiumCodeManager(); + $codeInfo = $manager->getCode($discordUserId); + if (is_null($codeInfo)) { + return ['status' => 404]; + } else if (!$codeInfo['active']) { + return ['status' => 402, 'code' => $codeInfo['code']]; + } else { + return ['status' => 200, 'code' => $codeInfo['code']]; + } + } + + private function generateCodeForUser(string $discordUserId): string + { + $manager = new PremiumCodeManager(); + return $manager->createNew($discordUserId); + } + + private function checkCode(): int + { + $code = $_REQUEST['code'] ?? null; + if (is_null($code) || strlen($code) !== 16) { + return 400; + } + + $manager = new PremiumCodeManager(); + return $manager->verify($code); + } + + private function deactivateCodeForUser(string $discordUserId): bool + { + $manager = new PremiumCodeManager(); + return $manager->deactivate($discordUserId); + } + + private function activateCodeForUser(string $discordUserId): bool + { + $manager = new PremiumCodeManager(); + return $manager->activate($discordUserId); + } +} + diff --git a/Controllers/Controller.php b/Controllers/Controller.php index a9060f2..c57c371 100644 --- a/Controllers/Controller.php +++ b/Controllers/Controller.php @@ -15,7 +15,7 @@ abstract class Controller /** * Public method processing passed data, specific for each controller * @param array $args Arguments to process - * @return int 1 (or TRUE), if everything worked as expected, HTTP error code otherwise + * @return int 1 (or TRUE) or HTTP success status code (2xx) if everything worked as expected, HTTP error code otherwise */ public abstract function process(array $args): int; } diff --git a/Controllers/Errors/Error402.php b/Controllers/Errors/Error402.php new file mode 100644 index 0000000..884c7f1 --- /dev/null +++ b/Controllers/Errors/Error402.php @@ -0,0 +1,22 @@ +upload($_FILES['recordings']); + $questId = (empty($_POST['questId']) ? null : $_POST['questId']); + $npcId = (empty($_POST['npcId']) ? null : $_POST['npcId']); + $overwriteFiles = isset($_POST['overwrite']) && $_POST['overwrite'] === 'on'; + $uploader->upload($_FILES['recordings'], $overwriteFiles, $questId, $npcId); self::$data['upload_uploadErrors'] = $uploader->getErrors(); self::$data['upload_uploadSuccesses'] = $uploader->getSuccesses(); diff --git a/Controllers/Website/Downloads.php b/Controllers/Website/Downloads.php index d562e92..fac7832 100644 --- a/Controllers/Website/Downloads.php +++ b/Controllers/Website/Downloads.php @@ -13,10 +13,6 @@ class Downloads extends WebpageController public function process(array $args): int { switch (array_shift($args)) { - case 'junction': - self::$views[] = 'downloads'; - self::$cssFiles[] = 'downloads'; - break; case 'forge': $downloadsManager = new DownloadsManager(); self::$data['downloadslist_versions'] = $downloadsManager->listDownloads(DownloadsManager::FORGE_VERSIONS); diff --git a/Controllers/Website/Npc.php b/Controllers/Website/Npc.php index 1a41e90..faadba6 100644 --- a/Controllers/Website/Npc.php +++ b/Controllers/Website/Npc.php @@ -89,29 +89,6 @@ private function get(array $args): int return true; } - /** - * Processing method for POST requests to this controller (new recordings were uploaded) - * @param array $args - * @return int|bool - */ - private function post(array $args): int - { - if ($this->disallowAdministration) { - return 403; - } - - $uploader = new RecordingUploader(); - $questId = $_POST['questId']; - $npcId = $_POST['npcId']; - $overwriteFiles = isset($_POST['overwrite']) && $_POST['overwrite'] === 'on'; - - $uploader->upload($_FILES['recordings'], $overwriteFiles, $questId, $npcId); - self::$data['npc_uploadErrors'][$questId] = $uploader->getErrors(); - self::$data['npc_uploadSuccesses'][$questId] = $uploader->getSuccesses(); - - return $this->get($args); - } - /** * Processing method for PUT requests to this controller (recast, archivation of NPC or archivation of recordings) * @param array $args Voice actor id as the first element diff --git a/Controllers/Website/WebpageController.php b/Controllers/Website/WebpageController.php index 8657684..814b5ee 100644 --- a/Controllers/Website/WebpageController.php +++ b/Controllers/Website/WebpageController.php @@ -154,6 +154,7 @@ private function sanitize($value) $attr['id'] = $this->sanitize($value->getId()); $attr['name'] = $this->sanitize($value->getName()); $attr['archived'] = $this->sanitize($value->isArchived()); + $attr['recordings_count'] = $this->sanitize($value->getRecordingsCount()); $voiceActor = $this->sanitize($value->getVoiceActor()); $npc = new Npc($attr); if ($voiceActor !== null) { diff --git a/Models/Api/PremiumAuthenticator/DbInfo.ini b/Models/Api/PremiumAuthenticator/DbInfo.ini new file mode 100644 index 0000000..6a52634 --- /dev/null +++ b/Models/Api/PremiumAuthenticator/DbInfo.ini @@ -0,0 +1,4 @@ +host=localhost +database=premium-authenticator +username=root +password= \ No newline at end of file diff --git a/Models/Api/PremiumAuthenticator/PremiumCodeManager.php b/Models/Api/PremiumAuthenticator/PremiumCodeManager.php new file mode 100644 index 0000000..48accbb --- /dev/null +++ b/Models/Api/PremiumAuthenticator/PremiumCodeManager.php @@ -0,0 +1,94 @@ +executeQuery('INSERT INTO access_codes(code,discord_id) VALUES (?,?);', [$code, $discordUserId]); + } catch (PDOException $ex) { + return false; + } + + return $code; + } + + /** + * Method loading an premium access code for the given Discord user + * @param string $discordUserId Discord user ID of the user whose code should be loaded (numerical string or BIGINT + * in MySQL) + * @return array|null Associative array with elements "code" (the 16-character code) and "active" (boolean) or NULL, + * if no code for the user exists + */ + public function getCode(string $discordUserId): ?array + { + $db = new Db('Api/PremiumAuthenticator/DbInfo.ini'); + $result = $db->fetchQuery('SELECT code,active FROM access_codes WHERE discord_id = ? LIMIT 1;', [$discordUserId]); + if ($result === false) { + return null; + } + return $result; + } + + /** + * Method checking whether a premium access code is valid + * @param string $code Access code to check + * @return int An HTTP-like code indicator: + * 200 if the code is valid, 402 if the code is disabled, 404 if code does not exist + */ + public function verify(string $code): int + { + $code = strtoupper($code); + $db = new Db('Api/PremiumAuthenticator/DbInfo.ini'); + $result = $db->fetchQuery('SELECT active FROM access_codes WHERE code = ? LIMIT 1;', [$code]); + if ($result === false) { + return 404; + } else { + return ($result['active']) ? 200 : 402; + } + } + + /** + * Method marking a certain user's access code as disabled + * @param string $discordUserId Discord ID of the user whose access code should be deactivated + * @return bool TRUE on success, FALSE on failure + */ + public function deactivate(string $discordUserId) + { + $db = new Db('Api/PremiumAuthenticator/DbInfo.ini'); + return $db->executeQuery('UPDATE access_codes SET active = 0 WHERE discord_id = ?;', [$discordUserId]); + } + + /** + * Method marking a certain user's access code as enabled + * @param string $discordUserId Discord ID of the user whose access code should be activated + * @return bool TRUE on success, FALSE on failure + */ + public function activate(string $discordUserId) + { + $db = new Db('Api/PremiumAuthenticator/DbInfo.ini'); + return $db->executeQuery('UPDATE access_codes SET active = 1 WHERE discord_id = ?;', [$discordUserId]); + } +} + diff --git a/Models/Website/ContentManager.php b/Models/Website/ContentManager.php index 44dbdbe..38491f4 100644 --- a/Models/Website/ContentManager.php +++ b/Models/Website/ContentManager.php @@ -9,13 +9,15 @@ class ContentManager public function getQuests(?int $questId = null): array { $query = ' - SELECT quest.quest_id, quest.name AS "qname", npc.npc_id, npc.name AS "nname", npc.voice_actor_id, npc.archived, user.user_id, user.display_name, user.picture - FROM quest - JOIN npc_quest ON npc_quest.quest_id = quest.quest_id - JOIN npc ON npc.npc_id = npc_quest.npc_id - LEFT JOIN user ON npc.voice_actor_id = user.user_id - '.(is_null($questId) ? '' : 'WHERE quest.quest_id = ?').' - ORDER BY quest.quest_id, npc_quest.sorting_order; + SELECT quest.quest_id, quest.name AS "qname", npc.npc_id, npc.name AS "nname", npc.voice_actor_id, npc.archived, user.user_id, user.display_name, COUNT(DISTINCT recording.recording_id) AS "recordings_count" + FROM quest + JOIN npc_quest ON npc_quest.quest_id = quest.quest_id + JOIN npc ON npc.npc_id = npc_quest.npc_id + JOIN recording ON recording.npc_id = npc.npc_id AND recording.quest_id = quest.quest_id + LEFT JOIN user ON npc.voice_actor_id = user.user_id + '.(is_null($questId) ? '' : 'WHERE quest.quest_id = ?').' + GROUP BY quest.quest_id, npc.npc_id, npc_quest.sorting_order + ORDER BY quest.quest_id, npc_quest.sorting_order; '; $result = (new Db('Website/DbInfo.ini'))->fetchQuery($query, (is_null($questId) ? array() : array($questId)), true); @@ -29,9 +31,8 @@ public function getQuests(?int $questId = null): array } $currentQuest = new Quest($npc); } - $npcObj = new Npc($npc); - if ($npc['user_id'] !== null) { + if ($npc['user_id'] !== null) { $voiceActor = new User(); $voiceActor->setData($npc); $npcObj->setVoiceActor($voiceActor); diff --git a/Models/Website/DownloadsManager.php b/Models/Website/DownloadsManager.php index d7d34f8..adae5c2 100644 --- a/Models/Website/DownloadsManager.php +++ b/Models/Website/DownloadsManager.php @@ -14,7 +14,7 @@ class DownloadsManager private const FILE_NAME_FORMATS = 'VoicesOfWynn-MC{mcVersion}-v{version}.jar'; public const FORGE_VERSIONS = ['1.12.2']; - public const FABRIC_VERSIONS = ['1.19.4', '1.19.3', '1.18.2']; + public const FABRIC_VERSIONS = ['1.20.2', '1.19.4', '1.19.3', '1.18.2']; /** diff --git a/Models/Website/Npc.php b/Models/Website/Npc.php index 6136641..1a2dc96 100644 --- a/Models/Website/Npc.php +++ b/Models/Website/Npc.php @@ -15,6 +15,8 @@ class Npc implements JsonSerializable private array $recordings = array(); + private int $recordingsCount = 0; + /** * @param array $data Data returned from database, invalid items are skipped, multiple key names are supported for * each attribute @@ -39,6 +41,9 @@ public function __construct(array $data) case 'archived': case 'hidden': $this->archived = $value; + break; + case 'recordings_count': + $this->recordingsCount = $value; break; } } @@ -90,7 +95,7 @@ public function archive() : int //Create new NPC $this->loadName(); //Needed to be copied - $replacementId = $db->executeQuery('INSERT INTO npc(name) VALUES (?);', array($this->name), true); + $replacementId = $db->executeQuery('INSERT INTO npc(name, degenerated_name) VALUES (?,?);', array($this->name, $this->degeneratedName), true); $replacementNpc = new Npc(array('id' => $replacementId, 'name' => $this->name)); unset($replacementId); @@ -145,7 +150,7 @@ public function getId() : ?int } /** - * Method loading the name of this NPC from the database. + * Method loading the name and degenerated name of this NPC from the database. * The ID property must be filled for this method to work * @return bool TRUE on success, FALSE on failure (ID is not known or no database results) */ @@ -156,10 +161,11 @@ private function loadName() : bool } $db = new Db('Website/DbInfo.ini'); - $result = $db->fetchQuery('SELECT name FROM npc WHERE npc_id = ?;', array($this->id)); + $result = $db->fetchQuery('SELECT name, degenerated_name FROM npc WHERE npc_id = ?;', array($this->id)); if (!empty($result)) { $this->name = $result['name']; + $this->degeneratedName = $result['degenerated_name']; return true; } return false; @@ -214,5 +220,14 @@ public function getRecordings() : array { return $this->recordings; } + + /** + * Recordings count getter + * @return int + */ + public function getRecordingsCount(): int + { + return $this->recordingsCount; + } } diff --git a/Models/Website/RecordingUploader.php b/Models/Website/RecordingUploader.php index c18ba07..abd9fb8 100644 --- a/Models/Website/RecordingUploader.php +++ b/Models/Website/RecordingUploader.php @@ -19,14 +19,17 @@ class RecordingUploader * Use the provided getters for these properties after calling this method to retrieve the status of the operation. * @param array $uploadedFiles Array from $_FILES['recording'] with unchanged structure, which contains the files to process * @param bool $overwrite TRUE, if files with the same name should be overwritten, FALSE, if they should be renamed instead, default FALSE - * @param int|null $questId ID of the quest to assign new recordings to, if left as null, the program will try to decide the ID based on the filename of every single recording - * @param int|null $npcId ID of the NPC to assign new recordings to, if left as null, the program will try to decide the ID based on the filename of every single recording + * @param int|null $overrideQuestId ID of the quest to assign new recordings to, if left as null, the program will try to decide the ID based on the filename of every single recording + * @param int|null $overrideNpcId ID of the NPC to assign new recordings to, if left as null, the program will try to decide the ID based on the filename of every single recording * @return int Number of successfully uploaded recordings */ - public function upload(array $uploadedFiles, bool $overwrite = false, ?int $questId = null, ?int $npcId = null) : int + public function upload(array $uploadedFiles, bool $overwrite = false, ?int $overrideQuestId = null, ?int $overrideNpcId = null): int { $recordingsCount = count($uploadedFiles['name']); for ($i = 0; $i < $recordingsCount; $i++) { + $questId = $overrideQuestId; + $npcId = $overrideNpcId; + $filename = $uploadedFiles['name'][$i]; $tempName = $uploadedFiles['tmp_name'][$i]; $type = $uploadedFiles['type'][$i]; diff --git a/Views/comments.phtml b/Views/comments.phtml index 233bf5e..e7db9aa 100644 --- a/Views/comments.phtml +++ b/Views/comments.phtml @@ -77,10 +77,11 @@ - - - Avatar - + + + + Avatar + diff --git a/Views/downloads-list.phtml b/Views/downloads-list.phtml index b4c6a58..ebe9142 100644 --- a/Views/downloads-list.phtml +++ b/Views/downloads-list.phtml @@ -1,15 +1,25 @@

Download Voices of Wynn

For

- +

+ +

Looking for versions for Forge 1.12.2? Click here.

+ +
+

Support for Voices of Wynn Forge has officially been dropped and version 1.5 was the last.

+

We recommend switching to Fabric, as most popular Wynncraft mods are already using newer versions of Minecraft and connecting to Wynncraft on 1.12.2 is deprecated.

+
+ +

Older Versions of the Mod

Want an older version of the mod? They are linked below! The oldest are at the bottom and the newest are at the top.

- + +

- +

diff --git a/Views/downloads.phtml b/Views/downloads.phtml deleted file mode 100644 index 8261de9..0000000 --- a/Views/downloads.phtml +++ /dev/null @@ -1,53 +0,0 @@ -

Download Voices of Wynn

- -

Choose your platform

-

- Voices of Wynn is supported for Forge client and Fabric client. -

-

- Forge version is meant for Minecraft version 1.12.2. -

- -

- Fabric version is usually meant for the latest Minecraft release version. - Check the exact version in the download details. -

- - - diff --git a/Views/error402.phtml b/Views/error402.phtml new file mode 100644 index 0000000..cff8d5b --- /dev/null +++ b/Views/error402.phtml @@ -0,0 +1,33 @@ + + + + + + + <?= ${basename(__FILE__, '.phtml').'_title'} ?> + + + + + + + + + + + + + + + + + + +

402 – Payment Required

+

That'll be nine emerald blocks.

+

You need to be our patron to be able to access this content.

+

Check out our Patreon and make a pledge that will grant you access.

+An illustration of two Wynncraft characters looking for something.
+Art by Gleeokenspiel
+ + diff --git a/Views/faq.phtml b/Views/faq.phtml index 74ebf88..9d6cfe5 100644 --- a/Views/faq.phtml +++ b/Views/faq.phtml @@ -1,18 +1,19 @@

FAQ

How many characters are voiced in the mod?
-

Currently, all quest NPCs are voiced. We’re hard at work keeping all quest dialogues up to date while adding dialogues to non-quest NPCs.

-

In addition to quest NPCs, we’ve also added full voice acting for the Seaskipper Captain and the Talking Mushroom. Other significant NPCs will be added soon!

+

Currently, all quest NPCs and dungeon NPCs are voiced. We’re working hard to keep all quest and dungeon dialogues up to date while adding voicelines to other NPCs as well, but sometimes, there is is slight change in dialogue that breaks a voiceline here and there.

+

In addition to quest and dungeon NPCs, we’ve also added full voice acting for the Seaskipper Captain and the Talking Mushroom. Other significant NPCs will be added soon!


Do you plan to voice all NPCs in Wynncraft?
-

We’re currently adding voices to Secret Discoveries. The next thing on our agenda is boss altars and dungeons. After that, it is our hope that we will voice all NPCs with white name tags that players are most likely to talk to. It’s not likely we’ll do every single one.

+

We’re currently adding voices to Secret Discoveries and Boss Altars. After that, it is our hope that we will voice all NPCs with white name tags that players are most likely to talk to. It’s not likely we’ll manage to do every single one because of dialogue changes.


What Minecraft version is needed for the mod to work?
-

The mod was first developed for Forge (Minecraft version 1.12.2). At the current time, support for Fabric (Minecraft version 1.19.4) is also available. Pay attention to the "Minecraft version" information when downloading the mod to make sure you get the correct one.

+

The mod is currently developed for Fabric (Minecraft version 1.20.2).

+

In the past, it was first developed for Forge (Minecraft version 1.12.2), but support for Forge was dropped recently. You can still download the old versions on our Downloads page.

Voices of Wynn is also compatible with Wynntils.


@@ -44,7 +45,8 @@
Some dialogue isn’t playing. Where can I report it?
-

Be aware that some dialogue, such as pre-and-post-quest dialogue is not voiced. However, all dialogue during a quest should be voiced, and if not you should report it. Join our Discord server and navigate down to #🚨bugs-and-suggestions💡.

+

Be aware that some dialogue, especially dialogue said by most of the white-named NPCs (non-quest NPCs) is not voiced. However, all dialogue related to quests and dungeons should be voiced. If you enabled line reporting after installing Voices of Wynn, you don't need to do anything, because the reports will be sent automatically.

+

If you run into a different issue, join our Discord server and navigate down to #🙋issues-and-support.


I saw a login button. How can I create an account?
@@ -54,12 +56,12 @@
I have an account, but forgot my password. There’s no “forgot password” button, so what do I do?
-

If you forgot your password, contact shady_medic on Discord and explain. It’ll be easier if you contact him from the same account you used to apply to the project. Shady will reset your password.

+

If you forgot your password, contact @shady_medic on Discord and explain. It’ll be easier if you contact him from the same account you used to apply to the project. Shady will reset your password.


I found offensive, insulting, or inappropriate content on this webpage. Where do I report it?
-

Currently, there is no report option. Contact shady_medic on Discord to report inappropriate content.

+

Currently, there is no report option. Contact @shady_medic on Discord to report inappropriate content.


I need to contact an administrator, but I don’t have nor want to create a Discord account. What should I do?
diff --git a/Views/npcs.phtml b/Views/npcs.phtml index 092ab9a..5555ebd 100644 --- a/Views/npcs.phtml +++ b/Views/npcs.phtml @@ -1,9 +1,9 @@ -
+

getName() ?>

- + getNpcs() as $npc) : ?> @@ -12,16 +12,14 @@
- - + getVoiceActor() === null) : ?> - - + - - + - + +
RearrangeNPCVoice actorRearrangeNPCVoice actorRecordingsAction
NPC picturegetName() ?>getName() ?> Profile pictureNobodyNobody Profile picturegetVoiceActor()->getName() ?>getVoiceActor()->getName() ?> ManagegetRecordingsCount() ?>Manage

diff --git a/Views/upload.phtml b/Views/upload.phtml index f6a52e9..5844b15 100644 --- a/Views/upload.phtml +++ b/Views/upload.phtml @@ -63,6 +63,16 @@

These will be applied to all recordings you upload this time and will override the names in their filenames!

+ Allow overwriting: + +

+ Overwriting files allows you to seamlessly replace old recording files with new versions of them. + However, you need to be absolutely sure that the file names are exatly the same! + The system sometimes renames the audio files (uploding with overwriting disabled or archiving), + so make sure you check the current name of the files on the server. To do this, see the src + attribute of the <source> elements within the <audio> + elements on the NPCs' webpages. +

diff --git a/composer.lock b/composer.lock index 368a657..4a8e52e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,33 +4,43 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a6c5cc896a5450cb8c267cda6e6409ff", + "content-hash": "79a15d1cad08678193b283a1af287d88", "packages": [ { "name": "ezyang/htmlpurifier", - "version": "v4.14.0", + "version": "v4.17.0", "source": { "type": "git", "url": "https://github.com/ezyang/htmlpurifier.git", - "reference": "12ab42bd6e742c70c0a52f7b82477fcd44e64b75" + "reference": "bbc513d79acf6691fa9cf10f192c90dd2957f18c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/12ab42bd6e742c70c0a52f7b82477fcd44e64b75", - "reference": "12ab42bd6e742c70c0a52f7b82477fcd44e64b75", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/bbc513d79acf6691fa9cf10f192c90dd2957f18c", + "reference": "bbc513d79acf6691fa9cf10f192c90dd2957f18c", "shasum": "" }, "require": { - "php": ">=5.2" + "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0" + }, + "require-dev": { + "cerdic/css-tidy": "^1.7 || ^2.0", + "simpletest/simpletest": "dev-master" + }, + "suggest": { + "cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.", + "ext-bcmath": "Used for unit conversion and imagecrash protection", + "ext-iconv": "Converts text to and from non-UTF-8 encodings", + "ext-tidy": "Used for pretty-printing HTML" }, "type": "library", "autoload": { - "psr-0": { - "HTMLPurifier": "library/" - }, "files": [ "library/HTMLPurifier.composer.php" ], + "psr-0": { + "HTMLPurifier": "library/" + }, "exclude-from-classmap": [ "/library/HTMLPurifier/Language/" ] @@ -53,22 +63,22 @@ ], "support": { "issues": "https://github.com/ezyang/htmlpurifier/issues", - "source": "https://github.com/ezyang/htmlpurifier/tree/v4.14.0" + "source": "https://github.com/ezyang/htmlpurifier/tree/v4.17.0" }, - "time": "2021-12-25T01:21:49+00:00" + "time": "2023-11-17T15:01:25+00:00" }, { "name": "phpmailer/phpmailer", - "version": "v6.5.3", + "version": "v6.9.1", "source": { "type": "git", "url": "https://github.com/PHPMailer/PHPMailer.git", - "reference": "baeb7cde6b60b1286912690ab0693c7789a31e71" + "reference": "039de174cd9c17a8389754d3b877a2ed22743e18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/baeb7cde6b60b1286912690ab0693c7789a31e71", - "reference": "baeb7cde6b60b1286912690ab0693c7789a31e71", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/039de174cd9c17a8389754d3b877a2ed22743e18", + "reference": "039de174cd9c17a8389754d3b877a2ed22743e18", "shasum": "" }, "require": { @@ -78,22 +88,25 @@ "php": ">=5.5.0" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "doctrine/annotations": "^1.2", - "php-parallel-lint/php-console-highlighter": "^0.5.0", - "php-parallel-lint/php-parallel-lint": "^1.3", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "doctrine/annotations": "^1.2.6 || ^1.13.3", + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", "phpcompatibility/php-compatibility": "^9.3.5", "roave/security-advisories": "dev-latest", - "squizlabs/php_codesniffer": "^3.6.0", - "yoast/phpunit-polyfills": "^1.0.0" + "squizlabs/php_codesniffer": "^3.7.2", + "yoast/phpunit-polyfills": "^1.0.4" }, "suggest": { + "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication", "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "ext-openssl": "Needed for secure SMTP sending and DKIM signing", + "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication", "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", "league/oauth2-google": "Needed for Google XOAUTH2 authentication", "psr/log": "For optional PSR-3 debug logging", - "stevenmaguire/oauth2-microsoft": "Needed for Microsoft XOAUTH2 authentication", - "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)" + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", + "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" }, "type": "library", "autoload": { @@ -125,7 +138,7 @@ "description": "PHPMailer is a full-featured email creation and transfer class for PHP", "support": { "issues": "https://github.com/PHPMailer/PHPMailer/issues", - "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.5.3" + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.1" }, "funding": [ { @@ -133,7 +146,7 @@ "type": "github" } ], - "time": "2021-11-25T16:34:11+00:00" + "time": "2023-11-25T22:23:28+00:00" } ], "packages-dev": [], @@ -144,8 +157,10 @@ "prefer-lowest": false, "platform": { "ext-pdo": "*", - "ext-mbstring": "*" + "ext-mbstring": "*", + "ext-curl": "*", + "ext-json": "*" }, "platform-dev": [], - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.3.0" } diff --git a/css/administration.css b/css/administration.css index 09701b3..aef9f0a 100644 --- a/css/administration.css +++ b/css/administration.css @@ -69,6 +69,23 @@ td th { .deletebtn { background: rgb(255, 0, 0);} .deletebtn:hover { border-color: rgb(221, 55, 55);} +.table-npc-management { + width: fit-content; + margin: auto; +} + +.table-npc-management td { + text-align: center; +} + +.va-name { + color: #117743; +} + +.npc-name { + color: purple; +} + #filename { width: 26rem; } diff --git a/index.php b/index.php index a1b01c4..b3baa8a 100644 --- a/index.php +++ b/index.php @@ -49,6 +49,8 @@ function autoloader(string $name): void if ($router->isWebpageRequest) { $website = $errorController->getResult(); + } else { + $website = $router->getResult(); } } else if ($result === 204) { @@ -57,6 +59,9 @@ function autoloader(string $name): void //This is mostly used for AJAX calls } else if ($result < 300) { + if ($result >= 100) { + http_response_code($result); + } $website = $router->getResult(); } diff --git a/routes.ini b/routes.ini index 9fce621..49ea09a 100644 --- a/routes.ini +++ b/routes.ini @@ -14,9 +14,8 @@ /contents/npc/<0>/comments/<1>/new=Website\Rating?<1>?c /contents/npc/<0>/comments/<1>/delete/<2>=Website\Comments?<2> /cast/<0>=Website\Cast?<0> -/downloads=Website\Downloads?junction +/downloads=Website\Downloads?fabric /downloads/forge=Website\Downloads?forge -/downloads/fabric=Website\Downloads?fabric /downloads/<0>=Website\Download?view?<0> /download/<0>/jar=Website\Download?get?<0>?direct /download/<0>/exe=Website\Download?get?<0>?installer @@ -60,3 +59,8 @@ /api/content/quest-info=Api\Other\Content?quests /api/discord-integration=Api\DiscordIntegration\DiscordIntegration + +/api/premium/load=Api\PremiumAuthenticator\Authenticator?get-code +/api/premium/check=Api\PremiumAuthenticator\Authenticator?check-code +/api/premium/deactivate=Api\PremiumAuthenticator\Authenticator?deactivate-user +/api/premium/reactivate=Api\PremiumAuthenticator\Authenticator?reactivate-user \ No newline at end of file