diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d52d45..268af8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ ## v8.2 * Users are now able to propose corrections to worktimes when they have been marked as for "in review". +* Added project management. Take a look into the README.md for more information. +* Administrators can now change the theme globally within TimeTrack. See more within `README.md`. +* Added `theme_file` and `force_theme` keys to the `app.json` `[general]` section +* Users can choose their own theme, if `force_theme` is set to `false` ## v8.1 diff --git a/README.md b/README.md index 61220cf..4ee7138 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,8 @@ In step 2, you need to configure the `app.json.sample` within the `api/v1/inc` f - `db_*`: Set the connection details for your mysql instance - `app`: If set to true, users will be able to use the TimeTrack mobile application - `timezone`: Set the timezone of your application, e.g. `Europe/Berlin` or `America/New_York` (default: `UTC`) +- `force_theme`: Force a theme for all users, this disables the feature allowing users to set their own theme. +- `theme_file`: If `force_theme` is true, the specified theme is used (default: `/assets/css/v8.css`) #### **SMTP section** @@ -186,6 +188,15 @@ Users can access their projects within the `Projects` tab. You can create items for their projects and map worktimes to it. The feature will be reworked in the future. +## Themes + +Users can now select their own theme within the `Settings` page. It loads all available themes that reside within the `/assets/css` folder. +Administrators can enforce a theme globally by setting `force_theme` to `true`. If so, only the theme specified within `theme_file` is available. + +To upload a new theme, simply place it into the `/assets/css` folder. + +The theme the user selected is saved as a cookie, meaning it is only selected on the current device. On mobile or on another device, the user has to set the desired theme again. + ## Updates TimeTrack has to be updated in two ways: database and application. diff --git a/VERSION b/VERSION index 8d1eec6..0dc0f32 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.1 \ No newline at end of file +8.2 \ No newline at end of file diff --git a/api/v1/class/arbeitszeit.inc.php b/api/v1/class/arbeitszeit.inc.php index d83c29d..8810531 100644 --- a/api/v1/class/arbeitszeit.inc.php +++ b/api/v1/class/arbeitszeit.inc.php @@ -25,6 +25,7 @@ use Arbeitszeit\Events\WorktimeMarkedForReviewEvent; use Arbeitszeit\Events\WorktimeUnlockedFromReviewEvent; use Arbeitszeit\Events\FixEasymodeWorktimeEvent; + use Arbeitszeit\Events\WorktimeCorrectionProposedEvent; /** * Beinhaltet wesentliche Inhalte, wie Einstellungen, Arbeitszeiten erstellen, etc. * @@ -409,6 +410,8 @@ public function update_worktime($id, $array) return false; } + EventDispatcherService::get()->dispatch(new WorktimeCorrectionProposedEvent($_SESSION["username"], (int) $id), WorktimeCorrectionProposedEvent::NAME); + Exceptions::error_rep("Worktime entry with ID '{$id}' updated successfully."); return true; } catch (\PDOException $e) { Exceptions::error_rep("[WORKTIME] Update failed: " . $e->getMessage()); diff --git a/api/v1/class/benutzer/benutzer.arbeit.inc.php b/api/v1/class/benutzer/benutzer.arbeit.inc.php index e5c410f..3a7a05b 100644 --- a/api/v1/class/benutzer/benutzer.arbeit.inc.php +++ b/api/v1/class/benutzer/benutzer.arbeit.inc.php @@ -360,6 +360,50 @@ public function editUserProperties(mixed $username_or_id, string $name, mixed $v return false; } } + + public function loadUserTheme(){ + + $themes = scandir($_SERVER["DOCUMENT_ROOT"] . "/assets/css"); + $themes = array_diff($themes, [".", ".."]); + $check = in_array($_COOKIE["theme"], $themes); + if($this->get_app_ini()["general"]["force_theme"] == "true"){ + return $this->get_app_ini()["general"]["theme_file"]; + } + + if(!isset($_COOKIE["theme"]) || !$check){ + return "/assets/css/v8.css"; + } else { + return "/assets/css/" . $_COOKIE["theme"]; + } + } + + public function computeUserThemes(){ + $themes = scandir($_SERVER["DOCUMENT_ROOT"] . "/assets/css"); + $themes = array_diff($themes, [".", ".."]); + $currentTheme = basename($this->loadUserTheme()); + foreach($themes as $theme){ + if($currentTheme == $theme){ + echo ""; + } else { + echo ""; + } + } + + return true; + } + + public function setUserTheme($theme){ + setcookie("theme", $theme, time()+60*60*24*30, "/"); + return true; + } + + public function checkThemeForce(){ + if($this->get_app_ini()["general"]["force_theme"] == "true" || $this->get_app_ini()["general"]["force_theme"] == true){ + return true; + } else { + return false; + } + } } } diff --git a/api/v1/class/events/loader.events.arbeit.inc.php b/api/v1/class/events/loader.events.arbeit.inc.php index aa3aa3d..bb4ae9e 100644 --- a/api/v1/class/events/loader.events.arbeit.inc.php +++ b/api/v1/class/events/loader.events.arbeit.inc.php @@ -21,6 +21,7 @@ require_once dirname(__DIR__, 1) . "/events/worktimes/WorktimeMarkedForReviewEvent.php"; require_once dirname(__DIR__, 1) . "/events/worktimes/WorktimeAddedEvent.php"; require_once dirname(__DIR__, 1) . "/events/worktimes/WorktimeDeletedEvent.php"; +require_once dirname(__DIR__, 1) . "/events/worktimes/WorktimeCorrectionProposedEvent.php"; require_once dirname(__DIR__, 1) . "/events/notifications/CreatedNotificationEvent.php"; require_once dirname(__DIR__, 1) . "/events/notifications/DeletedNotificationEvent.php"; diff --git a/api/v1/class/events/worktimes/WorktimeCorrectionProposedEvent.php b/api/v1/class/events/worktimes/WorktimeCorrectionProposedEvent.php new file mode 100644 index 0000000..100771c --- /dev/null +++ b/api/v1/class/events/worktimes/WorktimeCorrectionProposedEvent.php @@ -0,0 +1,27 @@ +username = $username; + $this->id = $id; + } + + public function getUsername(): string + { + return $this->username; + } + + public function getId(): int + { + return $this->id; + } + +} diff --git a/api/v1/class/i18n/suite/emails/worktime/uncompliant/snippets_DE.json b/api/v1/class/i18n/suite/emails/worktime/uncompliant/snippets_DE.json index 23e1c99..b1811e4 100644 --- a/api/v1/class/i18n/suite/emails/worktime/uncompliant/snippets_DE.json +++ b/api/v1/class/i18n/suite/emails/worktime/uncompliant/snippets_DE.json @@ -2,7 +2,7 @@ "subject1": "Deine Arbeitszeit muss geprüft werden!", "suject2": "Die Prüfung deiner Arbeitszeit wurde aufgehoben!", "greetings": "Hallo", - "message1": "dein Vorgesetzter hat deine Arbeitszeit auf Prüfung gesetzt. Die betroffene Arbeitszeit ist unten aufgelistet", + "message1": "dein Vorgesetzter hat deine Arbeitszeit auf Prüfung gesetzt. Die betroffene Arbeitszeit ist unten aufgelistet. Du kannst eine Änderung vorschlagen.", "message2": "dein Vorgesetzter hat die Prüfung deiner Arbeitszeit aufgehoben. Die betroffene Arbeitszeit ist unten aufgelistet", "id": "ID", "username": "Benutzername", diff --git a/api/v1/class/i18n/suite/emails/worktime/uncompliant/snippets_EN.json b/api/v1/class/i18n/suite/emails/worktime/uncompliant/snippets_EN.json index 5414862..cea7f52 100644 --- a/api/v1/class/i18n/suite/emails/worktime/uncompliant/snippets_EN.json +++ b/api/v1/class/i18n/suite/emails/worktime/uncompliant/snippets_EN.json @@ -2,7 +2,7 @@ "subject1": "Your working hours need to be reviewed!", "suject2": "Your working hours have been unchecked!", "greetings": "Hello", - "message1": "Your supervisor has put your working hours under review. The affected working hours are listed below", + "message1": "Your supervisor has put your working hours under review. The affected working hours are listed below. You can suggest a change.", "message2": "Your supervisor has unchecked your working hours. The affected working hours are listed below", "id": "ID", "username": "Username", diff --git a/api/v1/class/i18n/suite/emails/worktime/uncompliant/snippets_NL.json b/api/v1/class/i18n/suite/emails/worktime/uncompliant/snippets_NL.json index ae67ab2..3a65415 100644 --- a/api/v1/class/i18n/suite/emails/worktime/uncompliant/snippets_NL.json +++ b/api/v1/class/i18n/suite/emails/worktime/uncompliant/snippets_NL.json @@ -2,7 +2,7 @@ "subject1": "Uw werktijden moeten worden gecontroleerd!", "suject2": "De controle op uw werktijden is geannuleerd!", "greetings": "Hallo", - "message1": "uw manager heeft uw werktijden beoordeeld. De betreffende werktijden staan hieronder vermeld", + "message1": "uw manager heeft uw werktijden beoordeeld. De betreffende werktijden staan hieronder vermeld. U kunt een wijziging voorstellen.", "message2": "Uw manager heeft de beoordeling van uw werktijden geannuleerd. De betreffende werktijden worden hieronder vermeld", "id": "ID", "username": "Gebruikersnaam", diff --git a/api/v1/inc/app.json.sample b/api/v1/inc/app.json.sample index 6241a4a..1303b7c 100644 --- a/api/v1/inc/app.json.sample +++ b/api/v1/inc/app.json.sample @@ -6,7 +6,9 @@ "debug": "false", "auto_update": "false", "app": "false", - "timezone": "UTC" + "timezone": "UTC", + "theme_file": "/assets/css/v8.css", + "force_theme": "false" }, "mysql": { "db_host": "localhost", diff --git a/assets/css/cyberpunk.css b/assets/css/cyberpunk.css new file mode 100644 index 0000000..4e6745d --- /dev/null +++ b/assets/css/cyberpunk.css @@ -0,0 +1,207 @@ +:root { + --primary: #ff2e92; + --secondary: #00fff7; + --bg: #0a0a0f; + --text: #f2f2f2; + --card: #141421; + --radius: 14px; + --font-main: "Orbitron", sans-serif; + --font-mono: "JetBrains Mono", monospace; +} + +html, body { + margin: 0; + padding: 0; + font-family: var(--font-main); + background: radial-gradient(circle at top left, #0f0f1a, #0a0a0f 80%); + color: var(--text); + min-height: 100vh; + overflow-x: hidden; + animation: fadeBg 10s ease-in-out infinite alternate; +} + +@keyframes fadeBg { + 0% { background: radial-gradient(circle at top left, #0f0f1a, #0a0a0f 80%); } + 100% { background: radial-gradient(circle at top right, #1a0f1f, #0a0a0f 80%); } +} + +a { + color: var(--secondary); + text-decoration: none; + transition: all 0.3s ease; +} +a:hover { + color: var(--primary); + text-shadow: 0 0 8px var(--primary); +} + +h1, h2, h3 { + font-weight: 700; + letter-spacing: 1px; + text-transform: uppercase; + color: var(--secondary); + text-shadow: 0 0 12px rgba(0,255,247,0.6); + animation: fadeSlideUp 0.6s ease-out both; +} + +input, button, textarea { + border-radius: var(--radius); + border: none; + padding: 0.8rem 1rem; + margin-top: 0.5rem; + font-family: var(--font-main); + background: #1a1a2a; + color: var(--text); + transition: all 0.3s ease; +} +input:focus, textarea:focus { + outline: none; + box-shadow: 0 0 12px var(--secondary); +} + +button { + background: var(--primary); + color: black; + font-weight: bold; + cursor: pointer; + letter-spacing: 1px; + transition: all 0.3s ease, transform 0.2s ease; + text-shadow: 0 0 4px rgba(255,255,255,0.2); +} +button:hover { + background: var(--secondary); + color: #0a0a0f; + transform: translateY(-2px) scale(1.03); + box-shadow: 0 0 15px var(--secondary); +} + +.card { + background: var(--card); + padding: 2rem; + border-radius: var(--radius); + border: 1px solid rgba(255,255,255,0.08); + box-shadow: 0 0 20px rgba(0,0,0,0.6), 0 0 12px rgba(255,46,146,0.25); + transition: transform 0.3s ease, box-shadow 0.3s ease; +} +.card:hover { + transform: translateY(-3px); + box-shadow: 0 0 25px rgba(0,255,247,0.3); +} +.card h2 { + color: var(--primary); +} + +footer { + font-size: 0.85rem; + text-align: center; + padding: 2rem; + color: #aaa; + border-top: 1px solid rgba(255,255,255,0.1); + background: rgba(20,20,30,0.7); + backdrop-filter: blur(8px); + animation: glowPulse 4s infinite; +} + +.topnav { + display: flex; + justify-content: space-between; + flex-wrap: wrap; + background: rgba(20,20,40,0.8); + padding: 0.75rem 1.5rem; + border-bottom: 2px solid var(--primary); + font-size: 0.95rem; + backdrop-filter: blur(6px); + animation: slideGlow 6s linear infinite; +} +.topnav a { + color: var(--text); + margin-right: 1rem; + padding: 0.3rem 0.5rem; +} +.topnav a:hover { + color: var(--primary); + text-shadow: 0 0 8px var(--primary); +} + +.user-label { + font-weight: bold; + font-family: var(--font-mono); + color: var(--secondary); + text-shadow: 0 0 8px var(--secondary); +} + +.nav-version { + font-size: 0.8rem; + color: var(--primary); + font-family: var(--font-mono); + margin-left: 1rem; +} + +/* Status Messages */ +.status-message { + border-left: 4px solid var(--primary); + padding: 1rem 1.5rem; + margin-bottom: 1.5rem; + border-radius: var(--radius); + font-family: var(--font-mono); + position: relative; + background: rgba(255,255,255,0.05); + color: var(--text); + animation: fadeSlideUp 0.4s ease; +} +.status-message.error { border-left-color: #ff5555; color: #ff9999; } +.status-message.warn { border-left-color: #ffdd57; color: #ffeaa7; } +.status-message.info { border-left-color: var(--secondary); } + +/* Tables */ +.v8-table { + width: 100%; + border-collapse: collapse; + font-size: 0.95rem; + font-family: var(--font-mono); + background-color: #151522; + border: 1px solid rgba(255,255,255,0.1); +} +.v8-table th, +.v8-table td { + padding: 0.75rem 1rem; + border-bottom: 1px solid #222; +} +.v8-table thead { + background-color: #1f1f2f; + color: var(--primary); + border-bottom: 2px solid var(--primary); +} +.v8-table tr:hover { + background: rgba(255,46,146,0.07); +} + +/* Log Output */ +.log-output { + font-family: var(--font-mono); + font-size: 0.85rem; + background: #111; + padding: 1rem; + border-radius: var(--radius); + border: 1px solid rgba(255,255,255,0.08); + color: var(--secondary); + max-height: 300px; + overflow-y: auto; +} +.log-output .error { color: #ff2e92; font-weight: bold; } +.log-output .warn { color: #ffdd57; } +.log-output .info { color: var(--secondary); } + +/* Animations */ +@keyframes fadeSlideUp { + from { opacity: 0; transform: translateY(12px); } + to { opacity: 1; transform: translateY(0); } +} +@keyframes glowPulse { + 0%,100% { box-shadow: 0 0 0px rgba(255,46,146,0); } + 50% { box-shadow: 0 0 12px rgba(255,46,146,0.4); } +} +@keyframes slideGlow { + 0% { border-image: linear-gradient(90deg, var(--primary), var(--secondary)) 1; } + 100% { border-image: linear-gradient(90deg, var(--secondary), var(--primary)) 1; } +} diff --git a/assets/css/glassy.css b/assets/css/glassy.css new file mode 100644 index 0000000..8bcddd2 --- /dev/null +++ b/assets/css/glassy.css @@ -0,0 +1,167 @@ +:root { + --primary: #4ae; + --bg: #0d1117; + --text: #e6edf3; + --card: rgba(255, 255, 255, 0.05); + --radius: 14px; + --font-main: "Inter", sans-serif; + --font-mono: "JetBrains Mono", monospace; + --glass-blur: 12px; +} + +/* === Base Layout === */ +html, body { + margin: 0; + padding: 0; + font-family: var(--font-main); + background: linear-gradient(135deg, #0d1117 0%, #161b22 100%); + color: var(--text); + min-height: 100vh; + overflow-x: hidden; +} + +/* === Links === */ +a { + color: var(--primary); + text-decoration: none; + font-weight: 500; + transition: color 0.2s ease; +} +a:hover { + color: #7bcfff; + text-decoration: underline; +} + +/* === Headings === */ +h1, h2, h3 { + font-weight: 700; + letter-spacing: -0.5px; +} + +/* === Inputs & Buttons === */ +input, button, textarea, select { + border-radius: var(--radius); + border: none; + padding: 0.75rem 1rem; + margin-top: 0.5rem; + font-family: var(--font-main); + backdrop-filter: blur(var(--glass-blur)); + background: rgba(255, 255, 255, 0.05); + color: var(--text); + transition: all 0.3s ease; +} + +input:focus, textarea:focus, select:focus { + outline: none; + border: 1px solid var(--primary); + box-shadow: 0 0 10px rgba(74, 174, 255, 0.4); +} + +button { + background: var(--primary); + color: #0d1117; + cursor: pointer; + font-weight: 600; +} +button:hover { + background: #3cb0ff; + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(74,174,255,0.3); +} + +/* === Cards === */ +.card { + background: var(--card); + backdrop-filter: blur(var(--glass-blur)); + padding: 2rem; + border-radius: var(--radius); + box-shadow: 0 6px 25px rgba(0,0,0,0.35); + border: 1px solid rgba(255,255,255,0.1); + transition: transform 0.2s ease; +} +.card:hover { + transform: translateY(-3px); +} + +/* === Top Navigation === */ +.topnav { + display: flex; + justify-content: space-between; + flex-wrap: wrap; + background: rgba(255, 255, 255, 0.04); + backdrop-filter: blur(var(--glass-blur)); + padding: 0.75rem 1.5rem; + border-bottom: 1px solid rgba(255,255,255,0.08); +} + +.topnav a { + color: var(--text); + margin-right: 1rem; + padding: 0.4rem 0.6rem; + border-radius: 6px; + transition: background 0.2s ease; +} +.topnav a:hover { + background: rgba(255,255,255,0.08); +} + +.user-label { + font-weight: bold; + font-family: var(--font-mono); + color: var(--primary); +} + +/* === Tables === */ +.v8-table { + width: 100%; + border-collapse: collapse; + font-size: 0.95rem; + font-family: var(--font-mono); + backdrop-filter: blur(var(--glass-blur)); + background-color: rgba(255,255,255,0.05); + border: 1px solid rgba(255,255,255,0.1); + border-radius: var(--radius); + overflow: hidden; +} + +.v8-table th { + background: rgba(255,255,255,0.08); + color: var(--primary); + padding: 1rem; + text-align: left; +} +.v8-table td { + padding: 0.9rem 1rem; + border-bottom: 1px solid rgba(255,255,255,0.07); +} +.v8-table tr:hover { + background: rgba(74,174,255,0.05); +} + +/* === Status Messages === */ +.status-message { + border-left: 4px solid var(--primary); + padding: 1rem 1.5rem; + margin-bottom: 1.5rem; + border-radius: var(--radius); + background: rgba(255, 255, 255, 0.04); + backdrop-filter: blur(var(--glass-blur)); +} + +.status-message.error { border-left-color: #e74c3c; } +.status-message.warn { border-left-color: #f1c40f; } +.status-message.info { border-left-color: #3498db; } + +/* === Footer === */ +footer { + text-align: center; + padding: 2rem; + font-size: 0.85rem; + color: #aaa; + backdrop-filter: blur(var(--glass-blur)); + background: rgba(255,255,255,0.03); + border-top: 1px solid rgba(255,255,255,0.07); +} +footer:hover { + background: rgba(255,255,255,0.06); +} diff --git a/assets/css/nord.css b/assets/css/nord.css new file mode 100644 index 0000000..8940f82 --- /dev/null +++ b/assets/css/nord.css @@ -0,0 +1,209 @@ +:root { + --primary: #88c0d0; + --bg: #2e3440; + --text: #d8dee9; + --card: #3b4252; + --radius: 12px; + --font-main: "Fira Sans", sans-serif; + --font-mono: "JetBrains Mono", monospace; +} + +html, body { + margin: 0; + padding: 0; + font-family: var(--font-main); + background: var(--bg); + color: var(--text); + min-height: 100vh; + overflow-x: hidden; +} + +a { + color: var(--primary); + text-decoration: none; +} +a:hover { + text-decoration: underline; +} + +h1, h2, h3 { + font-weight: 600; + color: #eceff4; +} + +input, button, textarea { + border-radius: var(--radius); + border: none; + padding: 0.75rem; + margin-top: 0.5rem; + font-family: var(--font-main); + background: #434c5e; + color: var(--text); +} + +button { + background: var(--primary); + color: #2e3440; + cursor: pointer; + font-weight: bold; + transition: background 0.3s; +} +button:hover { + background: #81a1c1; +} + +.card { + background-color: var(--card); + padding: 2rem; + border-radius: var(--radius); + box-shadow: 0 0 12px rgba(0,0,0,0.4); +} +.card h1, .card h2, .card h3 { + color: var(--primary); +} +.card p, .card ul, .card code { + line-height: 1.6; + font-size: 0.95rem; +} + +footer { + font-size: 0.85rem; + text-align: center; + padding: 2rem; + color: #88c0d0; + border-top: 1px solid #4c566a; + margin-top: 2rem; +} + +/* === Navigation === */ +.topnav { + display: flex; + justify-content: space-between; + flex-wrap: wrap; + background: #3b4252; + padding: 0.75rem 1.5rem; + box-shadow: 0 2px 4px rgba(0,0,0,0.5); + font-size: 0.95rem; + border-bottom: 1px solid #4c566a; +} +.topnav a { + color: var(--text); + margin-right: 1rem; + padding: 0.3rem 0.5rem; + transition: color 0.2s; +} +.topnav a:hover { + color: var(--primary); +} +.topnav .topnav-right { + display: flex; + align-items: center; + gap: 1rem; +} +.user-label { + font-weight: bold; + font-family: var(--font-mono); + color: var(--primary); +} +.nav-version { + font-size: 0.8rem; + color: #bf616a; + font-family: var(--font-mono); + margin-left: 1rem; +} + +/* === Status Messages === */ +.status-message { + background-color: rgba(136, 192, 208, 0.12); + border-left: 4px solid var(--primary); + padding: 1rem 1.5rem; + margin-bottom: 1.5rem; + border-radius: var(--radius); + font-family: var(--font-mono); + position: relative; + animation: fadeIn 0.4s ease; + color: var(--text); +} +.status-message.error { + background-color: rgba(191, 97, 106, 0.12); + border-left-color: #bf616a; +} +.status-message.warn { + background-color: rgba(235, 203, 139, 0.12); + border-left-color: #ebcb8b; +} +.status-message.info { + background-color: rgba(129, 161, 193, 0.12); + border-left-color: #81a1c1; +} +.status-message .dismiss-button { + position: absolute; + top: 0.7rem; + right: 1rem; + background: none; + border: none; + color: #aaa; + font-size: 1.2rem; + cursor: pointer; + transition: color 0.2s ease; +} +.status-message .dismiss-button:hover { + color: var(--primary); +} + +/* === Tables === */ +.v8-table { + width: 100%; + border-collapse: collapse; + font-size: 0.95rem; + font-family: var(--font-mono); + background-color: var(--card); + border: 1px solid #4c566a; +} +.v8-table th, +.v8-table td { + padding: 0.75rem 1rem; + text-align: left; + border-bottom: 1px solid #434c5e; +} +.v8-table thead { + background-color: #434c5e; + color: var(--primary); + border-bottom: 2px solid var(--primary); +} +.v8-table tr:hover { + background-color: rgba(136, 192, 208, 0.08); + transition: background 0.2s; +} + +/* === Log Output === */ +.log-output { + font-family: var(--font-mono); + font-size: 0.85rem; + background-color: #3b4252; + padding: 1rem; + border-radius: var(--radius); + margin-top: 1rem; + white-space: pre-wrap; + word-break: break-word; + border: 1px solid #4c566a; + color: #eceff4; + max-height: 300px; + overflow-y: auto; +} +.log-output .error { + color: #bf616a; + font-weight: bold; +} +.log-output .warn { + color: #ebcb8b; +} +.log-output .info { + color: #81a1c1; +} + +/* === Animations === */ +@keyframes fadeIn { + from { opacity: 0; transform: translateY(-5px); } + to { opacity: 1; transform: translateY(0); } +} diff --git a/composer.json b/composer.json index 857d0ea..15b8cb9 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "TimeTrack is a PHP-written time recording tool for small businesses", "type": "software", "license": "GNU GPL", - "version": "8.1", + "version": "8.2", "authors": [ { "name": "Bryan Boehnke-Avan", diff --git a/errors/500.php b/errors/500.php index 6f928bd..c76512d 100644 --- a/errors/500.php +++ b/errors/500.php @@ -15,7 +15,7 @@
= htmlspecialchars($item["description"] ?? $language["no_description"]); ?>
-= $language["project"]; ?>: = $project["name"] ?? "-"; ?>
+= $language["project"]; ?>: = $arbeit->i18n()->sanitizeOutput($project["name"] ?? "-"); ?>
= $language["assignee"]; ?>: = $arbeit->benutzer()->get_user_from_id($item["assignee"])["name"] ?? "-"; ?>
-= $language["status"]; ?>: = $item["status"] ?? "Open"; ?>
-= $language["id"]; ?>: = $item["itemid"]; ?>
+= $language["status"]; ?>: = $arbeit->i18n()->sanitizeOutput($item["status"] ?? "Open"); ?>
+= $language["id"]; ?>: = $arbeit->i18n()->sanitizeOutput($item["itemid"] ?? ""); ?>