diff --git a/.vscode/settings.json b/.vscode/settings.json index bfdb0a8..f7ab589 100755 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,7 @@ "cSpell.words": [ "acaches", "aindex", + "akram", "apikeys", "asuccessful", "asuccessfully", @@ -10,13 +11,17 @@ "autoloader", "Bcmath", "Bethropolis", + "bradlc", "Cassian", "ccaches", + "chuaple", "configpath", "cronfile", "dbconfigure", + "devsense", "doesinfoexist", "ealready", + "eamodio", "ERRMODE", "firstname", "fklmnor", @@ -25,12 +30,16 @@ "gravatarphp", "Ifsnop", "Inno", + "intelli", "jsyaml", "kosmapanel", "Kutzu", "Lastname", + "linyang", + "lkrms", "mailserver", "mbstring", + "mohd", "myprofile", "Mysqldump", "mythicalclient", @@ -40,17 +49,22 @@ "mythicalsystems", "Nahh", "nayskutzu", + "oderwat", "ontry", + "Outedated", "phpdocs", "phpmailer", "phprouter", + "phptools", "phpunit", "posible", "Repobeats", "rfiles", + "rifi", "sitesecret", "skey", "skiptwofactorcheck", + "sophisticode", "Swal", "tailwindcss", "twofauser", @@ -64,13 +78,15 @@ "unverification", "unverify", "useractivity", - "userids" + "userids", + "xdebug", + "zarifprogrammer", + "zobo" ], "cSpell.useGitignore": true, "cSpell.ignorePaths": [ "/storage/settings.json", "/composer.lock", - ], "window.title": "${appName}", "window.autoDetectColorScheme": true, diff --git a/.vscode/vscode.code-snippets b/.vscode/vscode.code-snippets index c8b595b..383b42a 100755 --- a/.vscode/vscode.code-snippets +++ b/.vscode/vscode.code-snippets @@ -103,6 +103,94 @@ ], "description": "Get a specific value from the language file!" }, + "Get translation PHP": { + "prefix": "S_GetLang", + "scope": "php", + "body": [ + "\\MythicalSystemsFramework\\Language\\Manager::getInstance()->get('$1')" + ], + "description": "Get a specific value from the language file!" + }, + "Decrypt": { + "prefix": "S_Decrypt", + "scope": "php", + "body": [ + "\\MythicalSystemsFramework\\Encryption\\XChaCha20::decrypt('$1')" + ], + "description": "Decrypt a string!" + }, + "Encrypt": { + "prefix": "S_Encrypt", + "scope": "php", + "body": [ + "\\MythicalSystemsFramework\\Encryption\\XChaCha20::encrypt('$1')" + ], + "description": "Encrypt a string!" + }, + "Get User IP": { + "prefix": "S_GetUserIP", + "scope": "php", + "body": [ + "\\MythicalSystemsFramework\\CloudFlare\\CloudFlare::getUserIP()" + ], + "description": "Get the user's IP address!" + }, + "Debug Display Value Info": { + "prefix": "S_DebugDisplayValueInfo", + "scope": "php", + "body": [ + "Debugger::display_info($1,false);" + ], + "description": "Display the value info!" + }, + "Debug Show All Errors": { + "prefix": "S_DebugShowAllErrors", + "scope": "php", + "body": [ + "Debugger::ShowAllErrors();" + ], + "description": "Show all errors!" + }, + "Debug Hide All Errors": { + "prefix": "S_DebugHideAllErrors", + "scope": "php", + "body": [ + "Debugger::HideAllErrors();" + ], + "description": "Hide all errors!" + }, + "Does role have permission": { + "prefix": "S_DoesRoleHavePermission", + "scope": "php", + "body": [ + "RolesPermissionDataHandler::doesRoleHavePermission('$1','$2')" + ], + "description": "Check if a role has a permission!" + }, + "Add new activity": { + "prefix": "S_AddNewActivity", + "scope": "php", + "body": [ + "\\MythicalSystemsFramework\\User\\Activity\\UserActivity::addActivity(\\MythicalSystemsFramework\\User\\UserDataHandler::getUUIDFromToken(\\$_COOKIE['token']),'$1',\\MythicalSystemsFramework\\CloudFlare\\CloudFlare::getUserIP(),'$2');" + ], + "description": "Add a new activity!" + }, + "Update specific user data": { + "prefix": "S_UpdateSpecificUserData", + "scope": "php", + "body": [ + "\\MythicalSystemsFramework\\User\\UserDataHandler::updateSpecificUserData(\\$_COOKIE['token'], '$1', '$2',$3);" + ], + "description": "Update specific user data!" + }, + "Get specific user data": { + "prefix": "S_GetSpecificUserData", + "scope": "php", + "body": [ + "\\MythicalSystemsFramework\\User\\UserDataHandler::getSpecificUserData(\\$_COOKIE['token'], '$1',$2);" + ], + "description": "Get specific user data!" + }, "Get setting": { "prefix": "S_GetSetting", "scope": "twig", @@ -167,5 +255,24 @@ "body": [ "{{ user('$1',false) }}" ], + }, + "S_PageTemplate": { + "prefix": "S_PageTemplate", + "scope": "twig", + "body": [ + "{% extends 'components/dashboard.twig' %}", + "{% block head %}", + "", + "$1", + "{% endblock %}", + "{% block dashboard %}", + "", + "$2", + "{% endblock %}", + "{% block footer %}", + "", + "$3", + "{% endblock %}", + ], } } \ No newline at end of file diff --git a/app/CloudFlare/CloudFlare.php b/app/CloudFlare/CloudFlare.php index 3a7351f..6b0d268 100755 --- a/app/CloudFlare/CloudFlare.php +++ b/app/CloudFlare/CloudFlare.php @@ -45,14 +45,12 @@ public static function getUserIP(): ?string // Check if the ip is valid if (filter_var($ip, FILTER_VALIDATE_IP)) { $event->emit('cloudflare.onGetUserIP', [$ip]); - return $ip; } - return null; - } + /** * DEPRECATED: Use getUserIP() instead. */ diff --git a/app/Kernel/Debugger.php b/app/Kernel/Debugger.php index 055aa7c..c627fbc 100755 --- a/app/Kernel/Debugger.php +++ b/app/Kernel/Debugger.php @@ -56,7 +56,6 @@ public static function display_info($input, $collapse = false): void echo 'document.getElementById("plus"+id).style.display = state == "inline" ? "inline" : "none";'; echo '}' . "\n"; } - $type = !is_string($data) && is_callable($data) ? 'Callable' : ucfirst(gettype($data)); $type_data = null; $type_color = null; diff --git a/app/Language/Manager.php b/app/Language/Manager.php index f5797ac..13d85db 100755 --- a/app/Language/Manager.php +++ b/app/Language/Manager.php @@ -42,11 +42,16 @@ class Manager public string $default_lang = 'en_US'; public string $language; + private Manager $instance; + public function __construct() { + $this->instance = $this; + if (!$this->doesLanguageDirectoryExist()) { $this->createLanguageDirectory(); } + $this->renameYAMLToYML(); $this->langs = $this->getLangs(); foreach ($this->langs as $lang) { @@ -215,7 +220,7 @@ private function renameYAMLToYML(): void } } } - + /** * Check if the language directory exists. */ @@ -231,4 +236,12 @@ private function createLanguageDirectory(): void { mkdir($this->lang_dir); } + /** + * Get the instance of the language manager. + * + * @return \MythicalSystemsFramework\Language\Manager + */ + public static function getInstance(): Manager { + return self::$instance; + } } diff --git a/app/User/Activity/UserActivity.php b/app/User/Activity/UserActivity.php index 68cba8c..8efc7ba 100755 --- a/app/User/Activity/UserActivity.php +++ b/app/User/Activity/UserActivity.php @@ -49,9 +49,7 @@ class UserActivity public static function addActivity(string $userId, string $description, string $ipv4, string $action): void { global $event; // This is a global variable that is used to emit events. - try { - $mysqli = new MySQL(); $conn = $mysqli->connectMYSQLI(); $stmt = $conn->prepare('INSERT INTO framework_users_activities (user_id, description, action, ip_address) VALUES (?, ?, ?, ?)'); diff --git a/app/User/UserDataHandler.php b/app/User/UserDataHandler.php index da7825b..5a31086 100755 --- a/app/User/UserDataHandler.php +++ b/app/User/UserDataHandler.php @@ -616,4 +616,28 @@ public static function doesUUIDExist(string $uuid): bool return false; } } + /** + * Get the uuid by token. + * + * @param string $token The user token + * + * @return string The uuid + */ + public static function getUUIDFromToken(string $token): string + { + try { + $database = new MySQL(); + $mysqli = $database->connectMYSQLI(); + $stmt = $mysqli->prepare('SELECT uuid FROM framework_users WHERE token = ?'); + $stmt->bind_param('s', $token); + $stmt->execute(); + $stmt->bind_result($uuid); + $stmt->fetch(); + $stmt->close(); + return $uuid; + } catch (\Exception $e) { + logger::log(LoggerLevels::CRITICAL, LoggerTypes::DATABASE, '(App/User/UserDataHandler.php) Failed to get UUID from token: ' . $e->getMessage()); + return ''; + } + } } diff --git a/app/Web/Routes/admin/users.php b/app/Web/Routes/admin/users.php index 6ed1ac4..79e48d6 100755 --- a/app/Web/Routes/admin/users.php +++ b/app/Web/Routes/admin/users.php @@ -73,6 +73,7 @@ return UserDataHandler::getSpecificUserData($userToken, $info, $encrypted); })); + $activity = UserActivity::getActivities($uuid); $itemCount = count($activity); $renderer->addGlobal('activity_count', NumberFormatter::format($itemCount)); @@ -95,6 +96,56 @@ } }); +$router->add('/admin/users/(.*)/update', function (string $uuid): void { + global $router, $event, $renderer; + + if (!isset($_GET['info'], $_GET['value'], $_GET['encrypted'])) { + exit(header('location: /admin/users?e=missing_values')); + } + + if (empty($_GET['info']) || empty($_GET['value']) || empty($_GET['encrypted'])) { + exit(header('location: /admin/users?e=invalid_input')); + } + + $info = $_GET['info']; + $value = $_GET['value']; + $encrypted = filter_var($_GET['encrypted'], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); + + $template = 'admin/users/edit.twig'; + if (isset($_COOKIE['token']) === false) { + exit(header('location: /auth/login')); + } + + $user = new UserHelper($_COOKIE['token'], $renderer); + UserDataHandler::requireAuthorization($renderer, $_COOKIE['token']); + + if ( + !UserDataHandler::hasPermission($_COOKIE['token'], 'mythicalframework.admin.users.edit') + ) { + exit(header('location: /errors/403')); + } + + if ($uuid == '') { + exit(header('location: /admin/users')); + } + if (UserDataHandler::doesUUIDExist($uuid) === false) { + exit(header('location: /admin/users?e=not_found')); + } + $userToken = UserDataHandler::getTokenUUID($uuid); + if ($userToken === null) { + exit(header('location: /admin/users?e=not_found')); + } + + $renderer->addFunction(new TwigFunction('other_info', function ($info, $encrypted) use ($userToken) { + return UserDataHandler::getSpecificUserData($userToken, $info, $encrypted); + })); + + UserDataHandler::updateSpecificUserData($userToken, $info, $value, $encrypted); + \MythicalSystemsFramework\Api\Api::init(); + MythicalSystemsFramework\Api\Api::OK("User data updated successfully",null); + +}); + $router->add('/admin/users', function (): void { global $router, $event, $renderer; $template = 'admin/users/list.twig'; diff --git a/app/Web/Template/Engine.php b/app/Web/Template/Engine.php index a18bf96..4cbfa4c 100755 --- a/app/Web/Template/Engine.php +++ b/app/Web/Template/Engine.php @@ -91,6 +91,7 @@ public static function getRenderer(?string $cache_dir = null, ?string $theme_dir self::registerLanguage($renderer); self::registerGlobals($renderer); self::registerCryptographic($renderer); + self::registerFunctionReplace($renderer); return $renderer; } @@ -112,7 +113,7 @@ public static function registerLanguage(Environment $renderer): void { $renderer->addFunction(new TwigFunction('lang', function ($key): ?string { $lang = new Manager(); - + return $lang->get($key); })); } @@ -146,6 +147,13 @@ public static function registerCryptographic(Environment $renderer): void $renderer->addFunction(new TwigFunction('decrypt', function ($value) { return \MythicalSystemsFramework\Encryption\XChaCha20::decrypt($value); })); } + public static function registerFunctionReplace(Environment $renderer): void + { + $renderer->addFunction(new TwigFunction('replace', function ($value, $search, $replace) { + return str_replace($search, $replace, $value); + })); + } + public static function registerAlerts(Environment $renderer, string $template_name): void { $warnings = []; diff --git a/composer.lock b/composer.lock index ae6fb30..112cd21 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "composer/ca-bundle", - "version": "1.5.2", + "version": "1.5.3", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "48a792895a2b7a6ee65dd5442c299d7b835b6137" + "reference": "3b1fc3f0be055baa7c6258b1467849c3e8204eb2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/48a792895a2b7a6ee65dd5442c299d7b835b6137", - "reference": "48a792895a2b7a6ee65dd5442c299d7b835b6137", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/3b1fc3f0be055baa7c6258b1467849c3e8204eb2", + "reference": "3b1fc3f0be055baa7c6258b1467849c3e8204eb2", "shasum": "" }, "require": { @@ -64,7 +64,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.5.2" + "source": "https://github.com/composer/ca-bundle/tree/1.5.3" }, "funding": [ { @@ -80,7 +80,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T07:49:53+00:00" + "time": "2024-11-04T10:15:26+00:00" }, { "name": "composer/class-map-generator", diff --git a/public/.gitignore b/public/.gitignore new file mode 100644 index 0000000..a49ed8f --- /dev/null +++ b/public/.gitignore @@ -0,0 +1 @@ +cloudflare.php \ No newline at end of file diff --git a/public/assets/MythicalFramework.js b/public/assets/MythicalFramework.js index d36729b..1720a9e 100755 --- a/public/assets/MythicalFramework.js +++ b/public/assets/MythicalFramework.js @@ -328,4 +328,160 @@ document.addEventListener('DOMContentLoaded', function () { } } }); -}); \ No newline at end of file +}); +/** + * Replace text on page + * + * @param {String} from # The text to replace + * @param {String} to # The text to replace with + * + * @returns {void} # Nothing + */ +function replaceTextOnPage(from, to) { + getAllTextNodes().forEach((node) => { + node.nodeValue = node.nodeValue.replace(new RegExp(escapeRegExp(from), 'g'), to); + }); + + function getAllTextNodes() { + const result = []; + (function scanSubTree(node) { + if (node.childNodes.length) for (let i = 0; i < node.childNodes.length; i++) scanSubTree(node.childNodes[i]); + else if (node.nodeType == Node.TEXT_NODE) result.push(node); + })(document); + return result; + } + + function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + } +} + +function showDataMayBeOutdatedBadge() { + var badge = document.createElement("div"); + badge.style.position = "fixed"; + badge.style.bottom = "60px"; + badge.style.right = "20px"; + badge.style.width = "30px"; + badge.style.height = "30px"; + badge.style.backgroundColor = "rgba(255, 165, 0, 0.25)"; // Orange color + badge.style.color = "white"; + badge.style.borderRadius = "50%"; + badge.style.textAlign = "center"; + badge.style.lineHeight = "30px"; + badge.style.cursor = "pointer"; + badge.style.fontFamily = "Arial, sans-serif"; + badge.style.fontSize = "14px"; + badge.style.fontWeight = "bold"; + badge.style.opacity = "0.25"; + badge.innerHTML = "⚠"; // Warning sign + + var tooltip = document.createElement("div"); + tooltip.style.position = "fixed"; + tooltip.style.bottom = "80px"; + tooltip.style.right = "20px"; + tooltip.style.width = "300px"; + tooltip.style.backgroundColor = "white"; + tooltip.style.color = "black"; + tooltip.style.borderRadius = "10px"; + tooltip.style.padding = "20px"; + tooltip.style.opacity = "0"; + tooltip.style.transform = "scale(0)"; + tooltip.style.transition = "opacity 0.3s, transform 0.3s"; + tooltip.style.transformOrigin = "bottom right"; + tooltip.style.zIndex = "999"; + + var icon = document.createElement("span"); + icon.style.display = "block"; + icon.style.textAlign = "center"; + icon.style.fontSize = "60px"; + icon.style.marginBottom = "20px"; + icon.style.color = "#FFA500"; // Orange color + icon.innerHTML = "⚠"; // Warning sign + + var title = document.createElement("h3"); + title.style.textAlign = "center"; + title.style.fontFamily = "Arial, sans-serif"; + title.style.fontSize = "24px"; + title.style.color = "#FFA500"; // Orange color + title.innerHTML = "Data May Be Outdated"; + + var message = document.createElement("p"); + message.style.textAlign = "center"; + message.style.fontFamily = "Arial, sans-serif"; + message.style.fontSize = "16px"; + message.style.color = "black"; + message.innerHTML = "The data displayed on this website may be outdated. Please refresh the page for the latest information."; + + var refreshButton = document.createElement("button"); + refreshButton.style.backgroundColor = "#FFA500"; // Orange color + refreshButton.style.color = "white"; + refreshButton.style.border = "none"; + refreshButton.style.borderRadius = "5px"; + refreshButton.style.padding = "10px 20px"; + refreshButton.style.margin = "0 auto"; + refreshButton.style.display = "block"; + refreshButton.style.fontFamily = "Arial, sans-serif"; + refreshButton.style.fontSize = "14px"; + refreshButton.innerHTML = "Refresh"; + + tooltip.appendChild(icon); + tooltip.appendChild(title); + tooltip.appendChild(message); + tooltip.appendChild(refreshButton); + + badge.addEventListener("mouseenter", function () { + badge.style.opacity = "1"; + tooltip.style.opacity = "1"; + tooltip.style.transform = "scale(1)"; + }); + + tooltip.addEventListener("mouseleave", function () { + badge.style.opacity = "0.25"; + tooltip.style.opacity = "0"; + tooltip.style.transform = "scale(0)"; + }); + + tooltip.addEventListener("mouseenter", function () { + tooltip.style.opacity = "1"; + }); + + refreshButton.addEventListener("click", function () { + location.reload(); + }); + badge.addEventListener("mouseenter", function () { + badge.style.opacity = "1"; + tooltip.style.opacity = "1"; + tooltip.style.transform = "scale(1)"; + }); + + tooltip.addEventListener("mouseleave", function () { + badge.style.opacity = "0.25"; + tooltip.style.opacity = "0"; + tooltip.style.transform = "scale(0)"; + }); + + tooltip.addEventListener("mouseenter", function () { + tooltip.style.opacity = "1"; + }); + + refreshButton.addEventListener("click", function () { + location.reload(); + }); + + // Animation to change visibility from 0% to 100% + badge.style.transition = "opacity 1s ease-in-out"; + badge.style.opacity = "1"; + + // Cool animation to make people notice it + setTimeout(() => { + badge.style.transition = "transform 0.5s ease-in-out"; + badge.style.transform = "scale(1.2)"; + setTimeout(() => { + badge.style.transform = "scale(1)"; + }, 500); + }, 1000); + document.body.appendChild(badge); + document.body.appendChild(tooltip); +} + +setTimeout(showDataMayBeOutdatedBadge, 30000); \ No newline at end of file diff --git a/storage/lang/en_US.yml b/storage/lang/en_US.yml index 4c96745..292dc0b 100755 --- a/storage/lang/en_US.yml +++ b/storage/lang/en_US.yml @@ -503,6 +503,10 @@ Pages: Alerts: Success: + UpdatedSuccessfully: + Title: "Updated Successfully" + Message: "The object was updated successfully." + Footer: "Object Name: " ActionSuccessful: Title: "Action Successful" Message: "The action was successful." @@ -550,8 +554,10 @@ Alerts: Message: "You have successfully verified two factor authentication." Error: - Plugins: - + UpdatedFailed: + Title: "Updated Failed" + Message: "The object was not updated. Please try again." + Footer: "Object Name: " Social: CannotLikeYourSelf: Title: "Cannot Like Yourself" diff --git a/storage/themes/v2/admin/legal.twig b/storage/themes/v2/admin/legal.twig index e1dc83d..b3dbc09 100644 --- a/storage/themes/v2/admin/legal.twig +++ b/storage/themes/v2/admin/legal.twig @@ -42,4 +42,4 @@ // Optional: Make the editor unselectable (for full read-only effect) editor.renderer.$cursorLayer.element.style.display = "none"; -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/storage/themes/v2/admin/settings/edit.twig b/storage/themes/v2/admin/settings/edit.twig index fa41065..c7fd634 100644 --- a/storage/themes/v2/admin/settings/edit.twig +++ b/storage/themes/v2/admin/settings/edit.twig @@ -51,8 +51,8 @@ - {% endif %} - {% elseif category_name == "mail" %} + {% endif %} + {% if category_name == "mail" %} {% endif %}
@@ -39,7 +56,7 @@