diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..63370ee --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +.git +.gitignore +vendor +node_modules +Dockerfile +docker-compose.yml +*.md +*.log +tests diff --git a/CHANGELOG.md b/CHANGELOG.md index 268af8e..c335c57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,45 @@ # CHANGELOG +## v8.3.1 + +* Removed deprecated `app` attribute from `general` section within `app.json` +* Fixed issue stating that the account got disabled when logging out or when accessing the web UI + +## v8.3 + +**This update requires DB migration** - see `README.md` section `Database` + +* Fixed an issue causing the plugin system to be always enabled +* Sickness and vacation entries can now have a type like worktime entries +* Fixed dispatching of `WorktimeAddedEvent` event with incorrect parameters +* Added Database migration instructions when using Docker within the `README.md` `Database` section +* Removed the `setup` folder entirely (deprecated) +* Fixed DB migrations for Docker setups + +## v8.2.3 + +**This update requires DB migration** - see `README.md` section `Database` + +* Fixed missing `status` i18n entries +* Fixed incorrect theme loading within error pages +* Removed outdated `index.css` file +* Added missing footer to project management pages +* Admins can now enable or disable users within the `userdetail` plugin + +## v8.2.2 + +* Fixed deprecation warning for `WorktimeAddedEvent` event +* Dockerized TimeTrack: + * Added `Dockerfile` and `docker-compose.yml` to run TimeTrack within Docker + * Added `entrypoint.sh` to handle database migrations and start Apache + * Updated `README.md` with Docker instructions + +## v8.2.1 + +* Added events for worktime correction proposals: `WorktimeCorrectionProposed` +* Sanitized outputs to prevent XSS attacks +* Added a link to the documentation within the settings page + ## v8.2 * Users are now able to propose corrections to worktimes when they have been marked as for "in review". diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..4afdbc7 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +github@openducks.org. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f2a9e3e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,86 @@ +# ---- Base image ------------------------------------------------------------ +FROM php:8.2-apache + +# ---- System packages & PHP extensions ------------------------------------- +RUN apt-get update && apt-get install -y \ + libcurl4-openssl-dev \ + libgd-dev \ + libgmp-dev \ + libicu-dev \ + libonig-dev \ + libxml2-dev \ + libxslt1-dev \ + libldap2-dev \ + libzip-dev \ + zip \ + unzip \ + git \ + locales \ + && docker-php-ext-configure intl \ + && docker-php-ext-configure gd --with-jpeg --with-freetype \ + && docker-php-ext-install \ + curl \ + gd \ + gmp \ + intl \ + mbstring \ + mysqli \ + pdo \ + pdo_mysql \ + xsl \ + gettext \ + dom \ + ldap \ + zip \ + && a2enmod rewrite + +RUN apt-get update && apt-get install -y default-mysql-client + + + # ---- Add Python and smartcard support -------------------------------------- +RUN apt-get update && apt-get install -y \ + python3 \ + python3-pip \ + pcscd \ + pcsc-tools \ + libpcsclite-dev \ + && pip3 install pyscard \ + && systemctl enable pcscd || true \ +&& rm -rf /var/lib/apt/lists/* + + +RUN { \ + echo "display_errors = Off"; \ + echo "display_startup_errors = Off"; \ + echo "log_errors = On"; \ + echo "error_reporting = E_ALL & ~E_DEPRECATED & ~E_WARNING & ~E_NOTICE"; \ + echo "error_log = /var/log/php_errors.log"; \ + } > /usr/local/etc/php/conf.d/99-production.ini + + +# ---- Locale configuration -------------------------------------------------- +RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen + +# ---- Working directory ----------------------------------------------------- +WORKDIR /var/www/html + +# ---- Copy application files ------------------------------------------------ +COPY . /var/www/html + +# ---- Permissions ----------------------------------------------------------- +RUN chown -R www-data:www-data /var/www/html \ + && chmod -R 755 /var/www/html + +# ---- Apache vHost ---------------------------------------------------------- +COPY docker/apache2.conf /etc/apache2/sites-available/000-default.conf + +# ---- Composer -------------------------------------------------------------- +COPY --from=composer:2 /usr/bin/composer /usr/bin/composer +RUN composer install --no-dev --no-interaction --optimize-autoloader + +# ---- Entry point ----------------------------------------------------------- +COPY docker/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +EXPOSE 80 +ENTRYPOINT ["/entrypoint.sh"] diff --git a/README.md b/README.md index 4ee7138..0456851 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,23 @@ TimeTrack aims to be an easy-to-use time recording software for small enterprise ## Installation +### Quick Install with Docker + +You can quickly get started with TimeTrack using Docker. Follow these steps: + +* Ensure you have Docker and Docker Compose installed on your system. +* Clone the TimeTrack repository: `git clone https://github.com/Ente/timetrack.git` & `cd timetrack` +* Build the Docker image: `docker build -t openducks/timetrack .` +* Create a `app.json` configuration file based on the provided sample below: `cp api/v1/inc/app.json.sample api/v1/inc/app.json` and edit it to fit your needs. + * Adjust the database settings if needed (at least `db_password`) + * Change the base_url to match your setup (e.g. `localhost:8080`) + * **Change the DB password before using in production within the `docker-compose.yml` and `app.json` file!** +* Start the services using Docker Compose: `docker-compose up -d` +* Access TimeTrack in your web browser at `http://localhost:8080` +* Login with username `admin` and password `admin` + +Certain features, like the NFC login may require additional setup for parsing the USB device. + ### Requirements - PHP 8.2 (`curl|gd|gmp|intl|mbstring|mysqli|openssl|xsl|gettext|dom|ldap`) - tested with PHP 8.2.26 @@ -101,8 +118,6 @@ LDAP authentication works with OpenLDAP and Active Directory. If done correctly, you should now be able to access the application via http://BASE_URL/ - redirects to http://BASE_URL/suite/ -**Please delete the whole `/setup/` folder after installation** - After configuring, please rename the `app.json.sample` to `app.json` (`mv app.json.sample app.json`) ## Maintenance Mode @@ -210,6 +225,7 @@ If downloaded any other way, just make sure to copy and paste the new files into ### Database You can update the database by using `vendor/bin/phinx migrate` to migrate to latest release or `vendor/bin/phinx rollback` to rollback. +If you are using docker, you can just restart the container after updating the files, as the entrypoint will try to do the migration automatically. ## Managed Hosting diff --git a/VERSION b/VERSION index 0dc0f32..905c243 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.2 \ No newline at end of file +8.3.1 \ No newline at end of file diff --git a/api/v1/class/arbeitszeit.inc.php b/api/v1/class/arbeitszeit.inc.php index 8810531..8222d10 100644 --- a/api/v1/class/arbeitszeit.inc.php +++ b/api/v1/class/arbeitszeit.inc.php @@ -363,7 +363,7 @@ public function add_worktime($start, $end, $location, $date, $username, $type, $ Exceptions::error_rep("An error occurred while creating an worktime entry. See previous message for more information."); return false; } else { - EventDispatcherService::get()->dispatch(new WorktimeAddedEvent($username, ["start" => $start, "end" => $end], $Wtype), WorktimeAddedEvent::NAME); + EventDispatcherService::get()->dispatch(new WorktimeAddedEvent($username, $Wtype, ["start" => $start, "end" => $end]), WorktimeAddedEvent::NAME); Exceptions::error_rep("Worktime entry for user '{$username}' created successfully."); return true; } diff --git a/api/v1/class/auth/auth.arbeit.inc.php b/api/v1/class/auth/auth.arbeit.inc.php index e56169d..277436c 100644 --- a/api/v1/class/auth/auth.arbeit.inc.php +++ b/api/v1/class/auth/auth.arbeit.inc.php @@ -71,9 +71,14 @@ public static function login($username, $password, $option){ # "option"-> array $_SESSION["username"] = $username; $_SESSION["time"] = date("d.m.Y H:i:s", $ts); self::store_state($username); - + # check if user active + if(!$data["active"]) { + EventDispatcherService::get()->dispatch(new LoggedInUserEvent($username, "failed")); + Exceptions::error_rep("Login failed for username '$username' - User inactive. Redirecting..."); + die(header("Location: http://{$base_url}/suite/login.php?" . $sM->URIBuilder("userinactive"))); + } if(@isset($option["remember"])){ - if($ini["general"]["app"] == "true"){ + if($ini["mobile"]["allow_app_use"] == "true"){ EventDispatcherService::get()->dispatch(new LoggedInUserEvent($username, "success")); Exceptions::error_rep("Successfully authenticated user '" . $username . "' - LDAP Auth"); @ini_set("session.cookie_samesite", "None"); @@ -103,6 +108,12 @@ public static function login($username, $password, $option){ # "option"-> array nfclogin: Exceptions::error_rep("Authenticated user via NFC login '" . $username . "'"); } + # check if user active + if(!$data["active"]) { + EventDispatcherService::get()->dispatch(new LoggedInUserEvent($username, "failed")); + Exceptions::error_rep("Login failed for username '$username' - User inactive. Redirecting..."); + die(header("Location: http://{$base_url}/suite/login.php?" . $sM->URIBuilder("userinactive"))); + } Exceptions::error_rep("Successfully authenticated user '" . $username . "'"); $ini = Arbeitszeit::get_app_ini(); $ts = time(); @@ -113,7 +124,7 @@ public static function login($username, $password, $option){ # "option"-> array self::store_state($username); if(@isset($option["remember"])){ - if($ini["general"]["app"] == "true"){ + if($ini["mobile"]["allow_app_use"] == "true"){ EventDispatcherService::get()->dispatch(new LoggedInUserEvent($username, "success")); Exceptions::error_rep("Successfully authenticated user '" . $username . "'"); @ini_set("session.cookie_samesite", "None"); @@ -141,29 +152,50 @@ public static function login($username, $password, $option){ # "option"-> array } } - public function login_validation(){ + public function login_validation() + { Exceptions::error_rep("Validating login..."); $ini = Arbeitszeit::get_app_ini(); $baseurl = $ini["general"]["base_url"]; - if($ini["general"]["app"] == "true"){ + + if ($ini["mobile"]["allow_app_use"] == "true") { @ini_set("session.cookie_samesite", "None"); header('P3P: CP="CAO PSA OUR"'); - @session_set_cookie_params(["path" => "/", "domain" => $ini["general"]["base_url"], "secure" => true, "samesite" => "None"]); + @session_set_cookie_params([ + "path" => "/", + "domain" => $ini["general"]["base_url"], + "secure" => true, + "samesite" => "None" + ]); } + @session_start(); - if(isset($_SESSION["logged_in"]) == false){ + + if (empty($_SESSION["logged_in"]) || empty($_SESSION["username"])) { EventDispatcherService::get()->dispatch(new ValidatedLoginEvent($_SESSION["username"] ?? "N/A", "failed")); Exceptions::error_rep("User not logged in. Redirecting..."); header("Location: http://{$baseurl}/suite/login.php?" . $this->statusMessages()->URIBuilder("notloggedin")); + exit; } - if($this->get_state($_SESSION["username"]) != $_COOKIE["state"]){ - EventDispatcherService::get()->dispatch(new ValidatedLoginEvent($_SESSION["username"] ?? "N/A", "failed")); + + if ($this->get_state($_SESSION["username"]) !== ($_COOKIE["state"] ?? null)) { + EventDispatcherService::get()->dispatch(new ValidatedLoginEvent($_SESSION["username"], "failed")); $this->remove_state($_SESSION["username"]); Exceptions::error_rep("State mismatch on user {$_SESSION["username"]}. Removing state and redirecting..."); header("Location: http://{$baseurl}/suite/login.php?" . $this->statusMessages()->URIBuilder("statemismatch")); + exit; + } + + if (!$this->benutzer()->user_active($_SESSION["username"])) { + EventDispatcherService::get()->dispatch(new ValidatedLoginEvent($_SESSION["username"], "failed")); + $this->remove_state($_SESSION["username"]); + Exceptions::error_rep("User {$_SESSION["username"]} is inactive. Removing state and redirecting..."); + header("Location: http://{$baseurl}/suite/login.php?" . $this->statusMessages()->URIBuilder("userinactive")); + exit; } } + /** * logout() - Logs out user * @@ -202,13 +234,13 @@ public static function store_state($user){ $db = new DB; $ini = self::get_app_ini(); $state = bin2hex(random_bytes(12)); - if($ini["general"]["app"] == "true"){ + if($ini["mobile"]["allow_app_use"] == "true"){ @ini_set("session.cookie_samesite", "None"); @session_set_cookie_params(["path" => "/", "domain" => $ini["general"]["base_url"], "secure" => true, "samesite" => "None"]); - setcookie("state", $state, null, "/"); + setcookie("state", $state, time()+(60*60*24*30), "/"); session_regenerate_id(true); } else { - setcookie("state", $state, null, "/"); + setcookie("state", $state, time()+(60*60*24*30), "/"); $_SESSION["state"] = $state; } $sql = "UPDATE `users` SET `state` = ? WHERE `username` = ?;"; diff --git a/api/v1/class/benutzer/benutzer.arbeit.inc.php b/api/v1/class/benutzer/benutzer.arbeit.inc.php index 3a7a05b..36b62eb 100644 --- a/api/v1/class/benutzer/benutzer.arbeit.inc.php +++ b/api/v1/class/benutzer/benutzer.arbeit.inc.php @@ -47,6 +47,23 @@ public function create_user($username, $name, $email, $password, $isAdmin = 0) } } + public function user_active($username){ + $user = $this->get_user($username); + if($user["active"] == true || $user["active"] == 1){ + return true; + } else { + return false; + } + } + + public function activate_user($username){ + return $this->editUserProperties($username, "active", 1); + } + + public function deactivate_user($username){ + return $this->editUserProperties($username, "active", 0); + } + /** * delete_user() - Deletes a user from the database * @@ -326,7 +343,7 @@ public function editUserProperties(mixed $username_or_id, string $name, mixed $v return false; } - $allowed_types = ["username", "email", "isAdmin", "name"]; + $allowed_types = ["username", "email", "isAdmin", "name", "active"]; if (!in_array($name, $allowed_types)) { Exceptions::error_rep("Could not update user entry – invalid property '{$name}'"); return false; diff --git a/api/v1/class/events/worktimes/WorktimeAddedEvent.php b/api/v1/class/events/worktimes/WorktimeAddedEvent.php index 80f9543..4b615f7 100644 --- a/api/v1/class/events/worktimes/WorktimeAddedEvent.php +++ b/api/v1/class/events/worktimes/WorktimeAddedEvent.php @@ -12,7 +12,7 @@ class WorktimeAddedEvent extends Event private string $Wtype; - public function __construct(string $username, array $dates = [], int $Wtype){ + public function __construct(string $username, int $Wtype, array $dates = []){ $this->username = $username; $this->dates = $dates; $this->Wtype = $Wtype; diff --git a/api/v1/class/i18n/admin/worktime/sick/all/snippets_DE.json b/api/v1/class/i18n/admin/worktime/sick/all/snippets_DE.json index 950aec6..55e7126 100644 --- a/api/v1/class/i18n/admin/worktime/sick/all/snippets_DE.json +++ b/api/v1/class/i18n/admin/worktime/sick/all/snippets_DE.json @@ -6,6 +6,7 @@ "t2": "Krankheit Beginn", "t3": "Krankheit Ende", "t4": "Status", + "t5": "Krankheitsart", "status": { "set_to": "Setzen zu", "pending": "In Prüfung", diff --git a/api/v1/class/i18n/admin/worktime/sick/all/snippets_EN.json b/api/v1/class/i18n/admin/worktime/sick/all/snippets_EN.json index 374819a..288a6aa 100644 --- a/api/v1/class/i18n/admin/worktime/sick/all/snippets_EN.json +++ b/api/v1/class/i18n/admin/worktime/sick/all/snippets_EN.json @@ -6,6 +6,7 @@ "t2": "Sickness Start", "t3": "Sickness End", "t4": "Status", + "t5": "Sickness Type", "status": { "set_to": "Set to", "pending": "Pending", diff --git a/api/v1/class/i18n/admin/worktime/sick/all/snippets_NL.json b/api/v1/class/i18n/admin/worktime/sick/all/snippets_NL.json index 26e7a56..638617d 100644 --- a/api/v1/class/i18n/admin/worktime/sick/all/snippets_NL.json +++ b/api/v1/class/i18n/admin/worktime/sick/all/snippets_NL.json @@ -6,6 +6,7 @@ "t2": "Begin van de ziekte", "t3": "Ziekteeinde", "t4": "Status", + "t5": "Soort ziekte", "status": { "set_to": "Instellen op", "pending": "Wordt beoordeeld", diff --git a/api/v1/class/i18n/admin/worktime/vacation/all/snippets_DE.json b/api/v1/class/i18n/admin/worktime/vacation/all/snippets_DE.json index 6dc12f8..0a54328 100644 --- a/api/v1/class/i18n/admin/worktime/vacation/all/snippets_DE.json +++ b/api/v1/class/i18n/admin/worktime/vacation/all/snippets_DE.json @@ -6,6 +6,7 @@ "t2": "Urlaub Beginn", "t3": "Urlaub Ende", "t4": "Status", + "t5": "Urlaubsart", "status": { "set_to": "Setzen zu", "pending": "In Prüfung", diff --git a/api/v1/class/i18n/admin/worktime/vacation/all/snippets_EN.json b/api/v1/class/i18n/admin/worktime/vacation/all/snippets_EN.json index 71706ce..e815b3c 100644 --- a/api/v1/class/i18n/admin/worktime/vacation/all/snippets_EN.json +++ b/api/v1/class/i18n/admin/worktime/vacation/all/snippets_EN.json @@ -6,6 +6,7 @@ "t2": "Vacation Start", "t3": "Vacation End", "t4": "Status", + "t5": "Vacation Type", "status": { "set_to": "Set to", "pending": "Pending", diff --git a/api/v1/class/i18n/admin/worktime/vacation/all/snippets_NL.json b/api/v1/class/i18n/admin/worktime/vacation/all/snippets_NL.json index 414e648..d486437 100644 --- a/api/v1/class/i18n/admin/worktime/vacation/all/snippets_NL.json +++ b/api/v1/class/i18n/admin/worktime/vacation/all/snippets_NL.json @@ -6,6 +6,7 @@ "t2": "Vakantiebegin", "t3": "Einde van de vakantie", "t4": "Status", + "t5": "Soort vakantie", "status": { "set_to": "Instellen op", "pending": "Wordt beoordeeld", diff --git a/api/v1/class/i18n/suite/sickness/snippets_DE.json b/api/v1/class/i18n/suite/sickness/snippets_DE.json index 55a417d..365c46b 100644 --- a/api/v1/class/i18n/suite/sickness/snippets_DE.json +++ b/api/v1/class/i18n/suite/sickness/snippets_DE.json @@ -4,5 +4,6 @@ "note1": "Bitte gib hier das Start-Datum deines Ausfalls an. Solltest du nur einen Tag ausfallen, dann fülle 'End-Datum' bitte [RED]nicht[/RED] aus.", "note2": "End-Datum deiner Ausfalls*", "button_submit": "Krankheit einreichen", - "note3": "Nach dem Absenden wird dein Arbeitgeber die Krankheit überprüfen und sich bei dir, bei Bedarf, melden." + "note3": "Nach dem Absenden wird dein Arbeitgeber die Krankheit überprüfen und sich bei dir, bei Bedarf, melden.", + "label_type": "Krankheitsart" } \ No newline at end of file diff --git a/api/v1/class/i18n/suite/sickness/snippets_EN.json b/api/v1/class/i18n/suite/sickness/snippets_EN.json index a09a66d..1dd4eef 100644 --- a/api/v1/class/i18n/suite/sickness/snippets_EN.json +++ b/api/v1/class/i18n/suite/sickness/snippets_EN.json @@ -4,5 +4,6 @@ "note1": "Please enter the Start-Date of your sickness. If you do not know or you are just leaving for one day. Please do [RED]not[/RED] fill out the 'End-Date field.", "note2": "End-Date of your sickness*", "button_submit": "Submit sickness report", - "note3": "After submitting your report the responsible person will check and if required reach out to you" + "note3": "After submitting your report the responsible person will check and if required reach out to you", + "label_type": "Sickness Type" } \ No newline at end of file diff --git a/api/v1/class/i18n/suite/sickness/snippets_NL.json b/api/v1/class/i18n/suite/sickness/snippets_NL.json index 3611ffa..eb5500d 100644 --- a/api/v1/class/i18n/suite/sickness/snippets_NL.json +++ b/api/v1/class/i18n/suite/sickness/snippets_NL.json @@ -4,5 +4,6 @@ "note1": "Vul hier de startdatum van uw storing in. Bent u maar één dag afwezig, vul dan [RED]niet[/RED] 'Einddatum' in." , "note2": "Einddatum van uw mislukking*", "button_submit": "Ziekte indienen", - "note3": "Na het indienen zal uw werkgever de ziekte controleren en indien nodig contact met u opnemen." + "note3": "Na het indienen zal uw werkgever de ziekte controleren en indien nodig contact met u opnemen.", + "label_type": "Soort ziekte" } \ No newline at end of file diff --git a/api/v1/class/i18n/suite/status/snippets_DE.json b/api/v1/class/i18n/suite/status/snippets_DE.json index 293e834..76439ef 100644 --- a/api/v1/class/i18n/suite/status/snippets_DE.json +++ b/api/v1/class/i18n/suite/status/snippets_DE.json @@ -15,8 +15,8 @@ "password_reset": "Hinweis: Es wurde eine E-Mail verschickt, damit du dein Passwort zurücksetzen kannst.", "nodata": "Fehler: Es wurde kein Benutzername oder Passwort eingegeben.", "wrongdata": "Fehler: Falsche Anmeldedaten.", - "worktime_review": "Hinweis: Arbeitszeit erfolgreich auf Prüfung gestellt!", - "worktime_review_unlock": "Hinweis: Prüfung erfolgreich aufgehoben für Arbeitszeit!", + "worktime_reviewed": "Hinweis: Arbeitszeit erfolgreich auf Prüfung gestellt!", + "worktime_review_unlocked": "Hinweis: Prüfung erfolgreich aufgehoben für Arbeitszeit!", "worktime_easymode_start": "Hinweis: Deine Schicht wurde erfolgreich gestartet!", "worktime_easymode_end": "Hinweis: Deine Schicht wurde erfolgreich beendet!", "worktime_easymode_pause_start": "Hinweis: Deine Pause wurde erfolgreich gestartet!", @@ -38,5 +38,16 @@ "projects_item_added": "Hinweis: Projektaufgabe hinzugefügt.", "projects_item_failed": "Fehler: Die Projektaufgabe konnte nicht hinzugefügt werden. Bitte wende dich an deinen Administrator.", "mapWorktimeToItem_success": "Hinweis: Arbeitszeit erfolgreich mit der Projektaufgabe verbunden.", - "mapWorktimeToItem_failed": "Fehler: Die Arbeitszeit konnte nicht mit der Projektaufgabe verbunden werden. Bitte wende dich an deinen Administrator." + "mapWorktimeToItem_failed": "Fehler: Die Arbeitszeit konnte nicht mit der Projektaufgabe verbunden werden. Bitte wende dich an deinen Administrator.", + "notloggedin": "Fehler: Du bist nicht eingeloggt.", + "loggedout": "Hinweis: Du wurdest ausgeloggt.", + "worktime_updated": "Hinweis: Arbeitszeit erfolgreich aktualisiert!", + "error_worktime_update": "Fehler: Deine Arbeitszeit konnte nicht aktualisiert werden. Bitte kontaktiere deinen Administrator!", + "notifications_entry_added": "Hinweis: Benachrichtigungeneintrag hinzugefügt!", + "notifications_entry_edited": "Hinweis: Benachrichtigungeneintrag aktualisiert!", + "user_added": "Hinweis: Neuer Benutzer erfolgreich hinzugefügt!", + "changed_sickness": "Hinweis: Die Krankheit wurde erfolgreich aktualisiert!", + "changed_vacation": "Hinweis: Der Urlaub wurde erfolgreich aktualisiert!", + "userinactive": "Fehler: Dein Konto wurde deaktiviert. Bitte wende dich an deinen Administrator!", + "plugins_disabled": "Fehler: Das Plugin-System ist deaktiviert. Bitte wende dich an deinen Administrator!" } diff --git a/api/v1/class/i18n/suite/status/snippets_EN.json b/api/v1/class/i18n/suite/status/snippets_EN.json index d89396f..97df70d 100644 --- a/api/v1/class/i18n/suite/status/snippets_EN.json +++ b/api/v1/class/i18n/suite/status/snippets_EN.json @@ -38,5 +38,16 @@ "projects_item_added": "Note: The item has been added to the project.", "projects_item_failed": "Error: An error occurred while creating the item. Please contact your administrator.", "mapWorktimeToItem_success": "Note: Worktime successfully mapped to project item.", - "mapWorktimeToItem_failed": "Error: An error occurred while mapping the worktime to the project itme. Please contact your administrator." + "mapWorktimeToItem_failed": "Error: An error occurred while mapping the worktime to the project itme. Please contact your administrator.", + "notloggedin": "Error: You are not logged in.", + "loggedout": "Note: You have been logged out.", + "worktime_updated": "Note: Work time successfully updated!", + "error_worktime_update": "Error: Your work time could not be updated. Please contact your administrator!", + "notifications_entry_added": "Note: Notification entry added!", + "notifications_entry_edited": "Note: Notification entry updated!", + "user_added": "Note: New user successfully added!", + "changed_sickness": "Note: Sickness successfully updated!", + "changed_vacation": "Note: Vacation successfully updated!", + "userinactive": "Error: Your account has been disabled. Please contact your administrator!", + "plugins_disabled": "Error: The plugin system is disabled. Please contact your administrator!" } diff --git a/api/v1/class/i18n/suite/status/snippets_NL.json b/api/v1/class/i18n/suite/status/snippets_NL.json index 8dee092..36dcb03 100644 --- a/api/v1/class/i18n/suite/status/snippets_NL.json +++ b/api/v1/class/i18n/suite/status/snippets_NL.json @@ -26,5 +26,28 @@ "statemismatch": "Fout: Beveiligingsfout", "ldapauth": "Fout: LDAP-authenticatie mislukt.", "ldapcreated": "Opmerking: Log opnieuw in. Je account is nu aangemaakt. (LDAP self-login toegestaan)", - "notification_not_found": "Fout: Notificatie niet gevonden." + "notification_not_found": "Fout: Notificatie niet gevonden.", + "project_deleted": "Opmerking: Project succesvol verwijderd.", + "project_deleted_failed": "Fout: Het project kon niet worden verwijderd. Neem contact op met je beheerder.", + "project_edited": "Opmerking: Wijzigingen aan het project zijn doorgevoerd.", + "project_edited_error": "Fout: Er is een fout opgetreden bij het doorvoeren van de wijzigingen aan het project. Neem contact op met je beheerder.", + "project_added": "Opmerking: Het project is succesvol toegevoegd.", + "project_added_error": "Fout: Er is een fout opgetreden bij het aanmaken van het nieuwe project. Neem contact op met je beheerder.", + "project_userAdded": "Opmerking: De gebruiker is succesvol toegevoegd aan het project.", + "project_userAdded_failed": "Fout: Er is een fout opgetreden bij het toewijzen van de rechten aan de gebruiker. Neem contact op met je beheerder.", + "projects_item_added": "Opmerking: Het item is toegevoegd aan het project.", + "projects_item_failed": "Fout: Er is een fout opgetreden bij het aanmaken van het item. Neem contact op met je beheerder.", + "mapWorktimeToItem_success": "Opmerking: Werkuren succesvol gekoppeld aan projectitem.", + "mapWorktimeToItem_failed": "Fout: Er is een fout opgetreden bij het koppelen van de werkuren aan het projectitem. Neem contact op met je beheerder.", + "notloggedin": "Fout: Je bent niet ingelogd.", + "loggedout": "Opmerking: Je bent uitgelogd.", + "worktime_updated": "Opmerking: Werkuren succesvol bijgewerkt!", + "error_worktime_update": "Fout: Je werkuren konden niet worden bijgewerkt. Neem contact op met je beheerder!", + "notifications_entry_added": "Opmerking: Notificatie-item toegevoegd!", + "notifications_entry_edited": "Opmerking: Notificatie-item bijgewerkt!", + "user_added": "Opmerking: Nieuwe gebruiker succesvol toegevoegd!", + "changed_sickness": "Opmerking: Ziekte succesvol bijgewerkt!", + "changed_vacation": "Opmerking: Vakantie succesvol bijgewerkt!", + "userinactive": "Fout: Je account is uitgeschakeld. Neem contact op met je beheerder!", + "plugins_disabled": "Fout: Het plugin-systeem is uitgeschakeld. Neem contact op met je beheerder!" } diff --git a/api/v1/class/i18n/suite/users/settings/snippets_DE.json b/api/v1/class/i18n/suite/users/settings/snippets_DE.json index ff8b5b1..f3785ba 100644 --- a/api/v1/class/i18n/suite/users/settings/snippets_DE.json +++ b/api/v1/class/i18n/suite/users/settings/snippets_DE.json @@ -13,6 +13,7 @@ "note2_b": "hier", "note2_c": "um die Aktuellsten Änderungen der Webseite zu sehen.", "github_note": "Hilfe via GitHub", - "roadmap_note": "TimeTrack Roadmap" + "roadmap_note": "TimeTrack Roadmap", + "documentation": "TimeTrack Dokumentation" } \ No newline at end of file diff --git a/api/v1/class/i18n/suite/users/settings/snippets_EN.json b/api/v1/class/i18n/suite/users/settings/snippets_EN.json index 020505c..efe163d 100644 --- a/api/v1/class/i18n/suite/users/settings/snippets_EN.json +++ b/api/v1/class/i18n/suite/users/settings/snippets_EN.json @@ -13,6 +13,7 @@ "note2_b": "here", "note2_c": "to see the latest changes to the website.", "github_note": "Help via GitHub", - "roadmap_note": "TimeTrack Roadmap" + "roadmap_note": "TimeTrack Roadmap", + "documentation": "TimeTrack Documentation" } \ No newline at end of file diff --git a/api/v1/class/i18n/suite/users/settings/snippets_NL.json b/api/v1/class/i18n/suite/users/settings/snippets_NL.json index 4ee3f7f..839971e 100644 --- a/api/v1/class/i18n/suite/users/settings/snippets_NL.json +++ b/api/v1/class/i18n/suite/users/settings/snippets_NL.json @@ -13,6 +13,7 @@ "note2_b": "hier", "note2_c": "om de laatste wijzigingen aan de website te zien.", "github_note": "Hulp via GitHub", - "roadmap_note": "TimeTrack Roadmap" + "roadmap_note": "TimeTrack Roadmap", + "documentation": "TimeTrack Documentatie" } \ No newline at end of file diff --git a/api/v1/class/i18n/suite/vacation/snippets_DE.json b/api/v1/class/i18n/suite/vacation/snippets_DE.json index 438c179..7de198d 100644 --- a/api/v1/class/i18n/suite/vacation/snippets_DE.json +++ b/api/v1/class/i18n/suite/vacation/snippets_DE.json @@ -4,5 +4,6 @@ "note1": "Bitte gib hier das Start-Datum deines Urlaubs an. Solltest du nur einen Tag Urlaub nehmen wollen, dann fülle das 'End-Datum' bitte [RED]nicht[/RED] aus.", "note2": "End-Datum deines Urlaubs*", "button_submit": "Urlaub einreichen.", - "note3": "Nach dem Absenden wird dein Arbeitgeber deinen Urlaub prüfen und sich bei dir, bei Bedarf, melden." + "note3": "Nach dem Absenden wird dein Arbeitgeber deinen Urlaub prüfen und sich bei dir, bei Bedarf, melden.", + "label_type": "Urlaubsart" } \ No newline at end of file diff --git a/api/v1/class/i18n/suite/vacation/snippets_EN.json b/api/v1/class/i18n/suite/vacation/snippets_EN.json index fc87581..cfa4191 100644 --- a/api/v1/class/i18n/suite/vacation/snippets_EN.json +++ b/api/v1/class/i18n/suite/vacation/snippets_EN.json @@ -4,5 +4,6 @@ "note1": "Please enter the start date of your vacation. If you plan to just use one day, do [RED]not[/RED] fill out the 'End-Date' field.", "note2": "End-Date of your vacation*", "button_submit": "Request Vacation.", - "note3": "After submitting the responsible person will check and if required contact you." + "note3": "After submitting the responsible person will check and if required contact you.", + "label_type": "Vacation Type" } \ No newline at end of file diff --git a/api/v1/class/i18n/suite/vacation/snippets_NL.json b/api/v1/class/i18n/suite/vacation/snippets_NL.json index 0882ef6..179740a 100644 --- a/api/v1/class/i18n/suite/vacation/snippets_NL.json +++ b/api/v1/class/i18n/suite/vacation/snippets_NL.json @@ -4,5 +4,6 @@ "note1": "Vul hier de startdatum van uw vakantie in. Als u slechts één vakantiedag wilt opnemen, [RED]vul dan niet[/RED] de 'einddatum' in 'uit'", "note2": "Einddatum van uw vakantie*", "button_submit": "Vakantie indienen.", - "note3": "Na verzending zal uw werkgever uw vakantie controleren en indien nodig contact met u opnemen." + "note3": "Na verzending zal uw werkgever uw vakantie controleren en indien nodig contact met u opnemen.", + "label_type": "Soort vakantie" } \ No newline at end of file diff --git a/api/v1/class/plugins/PluginBuilder.plugins.arbeit.inc.php b/api/v1/class/plugins/PluginBuilder.plugins.arbeit.inc.php index 8a264c2..9b19037 100644 --- a/api/v1/class/plugins/PluginBuilder.plugins.arbeit.inc.php +++ b/api/v1/class/plugins/PluginBuilder.plugins.arbeit.inc.php @@ -26,7 +26,7 @@ class PluginBuilder{ * * @var boolean */ - private bool $plugins = true; + private static bool $plugins = true; /** * Path of the plugin directory specified in the app.ini @@ -58,6 +58,7 @@ class PluginBuilder{ public function __construct(){ $this->set_basepath(); $this->set_testing(); + $this->config = Arbeitszeit::get_app_ini()["plugins"]; } /** @@ -132,7 +133,7 @@ final public function load_class($class, $name): void{ final public function initialize_plugins(): bool { if ($this->testing == true) { $plugins = $this->get_plugins(); - if ($plugins == false) { + if ($plugins == false || $plugins == "false") { $this->logger("{$this->la} Could not get plugins. Please verify the plugin path given in the app.ini"); return false; } @@ -375,16 +376,20 @@ final public static function logger($message): void{ } final public static function check_plugins_enabled(){ - if(!isset(self::$plugins)){ - self::$plugins = Arbeitszeit::get_app_ini()["plugins"]["plugins"]; - } - if(self::$plugins == true){ + if(Arbeitszeit::get_app_ini()["plugins"]["plugins"] == "true" || Arbeitszeit::get_app_ini()["plugins"]["plugins"] == true){ return true; } else { return false; } } + final public static function redirect_if_disabled(){ + if(!self::check_plugins_enabled()){ + StatusMessages::redirect("plugins_disabled"); + exit(); + } + } + final public function get_plugin_nav($name) { $this->logger("{$this->la} Getting nav links for plugin '{$name}'"); $conf = $this->read_plugin_configuration($name); diff --git a/api/v1/class/plugins/plugins/userdetail/views/user.php b/api/v1/class/plugins/plugins/userdetail/views/user.php index 231d449..296aad1 100644 --- a/api/v1/class/plugins/plugins/userdetail/views/user.php +++ b/api/v1/class/plugins/plugins/userdetail/views/user.php @@ -26,6 +26,7 @@ $username = $_POST["username"]; $email = $_POST["email"]; $name = $_POST["name"]; + @$active = $_POST["active"]; if (!empty($username)) { if ($benutzer->editUserProperties($id, "username", $username)) { @@ -50,6 +51,16 @@ } } + if (!empty($_POST["active"])) { + if ($benutzer->editUserProperties($id, "active", 1)) { + echo "Enabled user account for ID {$id}.
"; + } + } elseif(empty($_POST["active"])){ + if($benutzer->editUserProperties($id, "active", 0)) { + echo "Disabled user account for ID {$id}.
"; + } + } + $payload = [ "id" => $id, "username" => $username, @@ -83,6 +94,7 @@

" placeholder="box@mail.com">
+ >
" hidden>

HR

diff --git a/api/v1/class/plugins/plugins/utility/plugin.yml b/api/v1/class/plugins/plugins/utility/plugin.yml index 19c0fa4..26d0e15 100644 --- a/api/v1/class/plugins/plugins/utility/plugin.yml +++ b/api/v1/class/plugins/plugins/utility/plugin.yml @@ -12,4 +12,3 @@ custom.values: license: LIC nav_links: 'Open Utility Plugin': views/overview.php -path: 'C:\Users\diege\Documents\GitHub\timetrack/api/v1/class/plugins/plugins/utility/plugin.yml' diff --git a/api/v1/class/projects/projects.arbeit.inc.php b/api/v1/class/projects/projects.arbeit.inc.php index d6b5282..edb248d 100644 --- a/api/v1/class/projects/projects.arbeit.inc.php +++ b/api/v1/class/projects/projects.arbeit.inc.php @@ -9,7 +9,7 @@ public function __construct() $this->db = new DB; } - public function addProject($name, $description = null, $items_assoc, $deadline = null, $owner = null) + public function addProject($name, $items_assoc, $description = null, $deadline = null, $owner = null) { Exceptions::error_rep("[PROJECTS] Adding project..."); if ($items_assoc == null) { @@ -163,7 +163,7 @@ public function getCurrentUserProjects() public function addProjectItem($project_id, $title, $description, $assignee = null) { - $sql = "INSERT INTO `projects_items` (id, title, description, assignee) VALUES (?, ?, ?, ?)"; + $sql = "INSERT INTO `projects_items` (pid, title, description, assignee) VALUES (?, ?, ?, ?)"; $res = $this->db->sendQuery($sql)->execute([$project_id, $title, $description, $assignee]); if (!$res) { @@ -297,7 +297,7 @@ public function getProjectItems($project_id): array|bool { Exceptions::error_rep("[PROJECTS] Fetching project items for project '{$project_id}'..."); - $sql = "SELECT * FROM projects_items WHERE id = ?"; + $sql = "SELECT * FROM projects_items WHERE pid = ?"; $stmt = $this->db->sendQuery($sql); $res = $stmt->execute([$project_id]); @@ -320,7 +320,7 @@ public function getProjectItems($project_id): array|bool public function getUserProjectItems($project_id, $user) { Exceptions::error_rep("[PROJECTS] Fetching project items for user '{$user}' and project '{$project_id}'..."); - $sql = "SELECT * FROM projects_items WHERE assignee = ? AND id = ?"; + $sql = "SELECT * FROM projects_items WHERE assignee = ? AND pid = ?"; $stmt = $this->db->sendQuery($sql); if (!$stmt->execute([$user, $project_id])) { @@ -336,7 +336,7 @@ public function getUserProjectItems($project_id, $user) public function getItem($id): array|bool { - $sql = "SELECT * FROM projects_items WHERE id = ?"; + $sql = "SELECT * FROM projects_items WHERE pid = ?"; $stmt = $this->db->sendQuery($sql); $res = $stmt->execute([$id]); diff --git a/api/v1/class/sickness/sickness.arbeit.inc.php b/api/v1/class/sickness/sickness.arbeit.inc.php index 9b3ad61..0fe7d12 100644 --- a/api/v1/class/sickness/sickness.arbeit.inc.php +++ b/api/v1/class/sickness/sickness.arbeit.inc.php @@ -13,7 +13,7 @@ class Sickness extends Arbeitszeit { - public function add_sickness($start, $stop, $user = null) + public function add_sickness($start, $stop, $user = null, $type = null) { if($this->nodes()->checkNode("sickness.inc", "add_sickness") == false){ return false; @@ -30,7 +30,7 @@ public function add_sickness($start, $stop, $user = null) return false; } - $data = $this->db()->sendQuery("INSERT INTO sick (id, username, start, stop, status) VALUES (0, ?, ?, ?, 'pending')")->execute([$user, $start, $stop]); + $data = $this->db()->sendQuery("INSERT INTO sick (id, username, start, stop, status, Stype) VALUES (0, ?, ?, ?, 'pending', ?)")->execute([$user, $start, $stop, $type]); if($data == false){ Exceptions::error_rep("[SICK] An error occurred while adding an sickness for user '$user'. See previous message for more information."); return false; @@ -100,6 +100,7 @@ public function display_sickness_all(){ # admin function only $stop = @strftime("%d.%m.%Y", strtotime($row["stop"])); $status = $row["status"]; $id = $row["id"]; + $type = $this->sickness()->type_from_int($row["Stype"]); switch($status){ case "pending": @@ -124,6 +125,7 @@ public function display_sickness_all(){ # admin function only {$start} {$stop} {$status} | {$i18n["status"]["set_to"]} {$i18n["status"]["pending"]} {$i18n["status"]["or"]} {$i18n["status"]["approved"]} {$i18n["status"]["or"]} {$i18n["status"]["rejected"]} + {$type} @@ -153,6 +155,33 @@ public function get_sickness($id, $mode = 1){ } } } + + public static function get_all_types(){ + $types = json_decode(file_get_contents($_SERVER["DOCUMENT_ROOT"] . Arbeitszeit::get_app_ini()["config"]["sickness_types"]), true); + if(isset($types)){ + return $types; + } else { + return false; + } + } + + public function compute_html_sickness_types(){ + $types = self::get_all_types(); + $html = ""; + foreach($types as $key => $type){ + $html .= "\n"; + } + return $html; + } + + public static function type_from_int($int){ + $types = self::get_all_types(); + if(isset($types[$int])){ + return $types[$int]; + } else { + return $types[0]; + } + } } } diff --git a/api/v1/class/vacation/vacation.arbeit.inc.php b/api/v1/class/vacation/vacation.arbeit.inc.php index b5470f9..c7be1e2 100644 --- a/api/v1/class/vacation/vacation.arbeit.inc.php +++ b/api/v1/class/vacation/vacation.arbeit.inc.php @@ -19,7 +19,7 @@ public function __construct(){ $this->db = new DB; } - public function add_vacation($start, $stop, $username = null) + public function add_vacation($start, $stop, $username = null, $type = null) { if($this->nodes()->checkNode("vacation.inc", "add_vacation") == false){ return false; @@ -37,8 +37,8 @@ public function add_vacation($start, $stop, $username = null) Exceptions::error_rep("[VACATION] An error occurred while adding an vacation for user '$user'. Could not validate dateFormat! | String: '{$dateString}', expected: d-m-Y"); return false; } - $sql = "INSERT INTO `vacation` (`id`, `username`, `start`, `stop`, `status`) VALUES ('0', ?, ?, ?, 'pending') "; - $data = $this->db->sendQuery($sql)->execute([$user, $start, $stop]); + $sql = "INSERT INTO `vacation` (`id`, `username`, `start`, `stop`, `status`, `Vtype`) VALUES ('0', ?, ?, ?, 'pending', ?) "; + $data = $this->db->sendQuery($sql)->execute([$user, $start, $stop, $type]); if(!$data){ Exceptions::error_rep("[VACATION] An error occurred while adding an vacation for user '$user'. See previous message for more information."); return false; @@ -127,6 +127,7 @@ public function display_vacation_all(){ # admin function only $stop = @strftime("%d.%m.%Y", strtotime($row["stop"])); $status = $row["status"]; $id = $row["id"]; + $type = $this->vacation()->type_from_int($row["Vtype"]); switch($status){ case "pending": @@ -151,6 +152,7 @@ public function display_vacation_all(){ # admin function only {$start} {$stop} {$status} | {$i18n["status"]["set_to"]} {$i18n["status"]["pending"]} {$i18n["status"]["or"]} {$i18n["status"]["approved"]} {$i18n["status"]["or"]} {$i18n["status"]["rejected"]} + {$type} @@ -180,6 +182,34 @@ public function get_all_vacation() } return $arr; } + + public static function get_all_types(): array|false + { + $types = json_decode(file_get_contents($_SERVER["DOCUMENT_ROOT"] . Arbeitszeit::get_app_ini()["config"]["vacation_types"]), true); + if(isset($types)){ + return $types; + } else { + return false; + } + } + + public function compute_html_vacation_types() + { + $data = ""; + foreach(self::get_all_types() as $key => $value){ + $data .= ""; + } + return $data; + } + + public static function type_from_int($int){ + $types = self::get_all_types(); + if(isset($types[$int])){ + return $types[$int]; + } else { + return $types[0]; + } + } } } diff --git a/api/v1/inc/app.json.sample b/api/v1/inc/app.json.sample index 1303b7c..e35a914 100644 --- a/api/v1/inc/app.json.sample +++ b/api/v1/inc/app.json.sample @@ -5,15 +5,14 @@ "support_email": "support@your-website.com", "debug": "false", "auto_update": "false", - "app": "false", "timezone": "UTC", "theme_file": "/assets/css/v8.css", "force_theme": "false" }, "mysql": { - "db_host": "localhost", - "db_user": "root", - "db_password": "", + "db_host": "db", + "db_user": "timetool", + "db_password": "yourpassword", "db": "ab" }, "smtp": { diff --git a/api/v1/inc/config/sickness_types.json b/api/v1/inc/config/sickness_types.json index 5589514..8b9d44a 100644 --- a/api/v1/inc/config/sickness_types.json +++ b/api/v1/inc/config/sickness_types.json @@ -1,5 +1,7 @@ { "0": "Initial medical certificate", "1": "Follow-up medical certificate", - "2": "Other" + "2": "Sick without medical certificate", + "3": "Sick child care", + "4": "Other" } \ No newline at end of file diff --git a/api/v1/inc/config/vacation_types.json b/api/v1/inc/config/vacation_types.json index 6e5cc2d..e73f273 100644 --- a/api/v1/inc/config/vacation_types.json +++ b/api/v1/inc/config/vacation_types.json @@ -2,7 +2,8 @@ "0": "Vacation", "1": "Public Holiday", "2": "Unpaid leave", - "3": "Overtime", + "3": "Overtime Compensation", "4": "Parental Leave", - "5": "Other" + "5" : "Special Leave", + "6": "Other" } \ No newline at end of file diff --git a/assets/css/index.css b/assets/css/index.css deleted file mode 100644 index 118b15a..0000000 --- a/assets/css/index.css +++ /dev/null @@ -1,239 +0,0 @@ -span { - color: red; -} - -* { - text-align: center; - - @import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&family=Rubik:ital,wght@0,300..900;1,300..900&display=swap'); - font-family:"Roboto", sans-serif; - color: white; - background-color: black; -} -.progress-bar { - width: 100%; - background-color: #ddd; - padding: 3px; - border-radius: 5px; - box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); -} - -.progress-bar-inner { - height: 20px; - background-color: #4caf50; - border-radius: 2px; - animation: progressAnimation 2s linear infinite; -} - -@keyframes progressAnimation { - 0% { - transform: translateX(100%); - } - 100% { - transform: translateX(-100%); - } -} - -.box { - width: auto; - max-width: 800px; - height: auto; - border: 5px solid; - padding: 2px; - margin: auto; - border-radius: 5px; - margin-left: auto; - margin-right: auto; - opacity: 1; - border-color: rgba(255, 255, 255, 0.64); - transition: all 0.5s; -} - -/*.button { - background: #258cd1; - position: relative; - color: white; - top: 100px; - font-size: 23px; - border-radius: 10px; - border-width: 10px; - border-color: rgba(0, 0, 0, 0); - box-shadow: 0px 15px 0px 0px rgba(102, 104, 111, 0.3), 0px 0px 20px 0px rgba(102, 104, 111, 0.3); - transition: all 0.1s; - } - - .button:hover { - top: 108px; - box-shadow: 0px 7px 0px 0px rgba(102, 104, 111, 0.7); - background-color: rgb(66, 67, 71, 0.7); - }*/ - -.button { - background-color: #4caf50; /* Grüne Hintergrundfarbe */ - border: none; - color: white; /* Weiße Schrift */ - padding: 15px 32px; - text-align: center; - text-decoration: none; - display: inline-block; - font-size: 16px; - margin: 4px 2px; - cursor: pointer; - border-radius: 8px; /* Abgerundete Ecken */ - transition: background-color 0.5s ease; /* Transition für'n sanften Farbwechsel */ -} - -.button:hover { - animation: hoverAnimation 0.5s ease forwards; -} - -@keyframes hoverAnimation { - from { - background-color: #4caf50; - } - to { - background-color: #45a049; - } -} - -.button { - background-color: rgb(66, 67, 71, 0.3); -} - -.input { - color: white; - height: 40px; - text-align: center; - width: auto; - border-color: rgba(133, 133, 133, 0.2); - background-color: rgba(102, 104, 111); - opacity: 0.5; - border-radius: 10px; - transition: all 0.5s; - font-size: 20px; -} - -.input:hover { - opacity: 1; - transition: 0s; - font-size: 23px; -} - -ul { - list-style-type: none; - margin: 0; - padding: 0; - overflow: hidden; - background-color: rgb(0, 0, 0); - opacity: 0.6; - position: sticky; - color: rgb(255, 255, 255); -} - -li { - float: left; -} - -li a { - display: block; - padding: 14px; - color: white; - opacity: 0.6; -} - -li a:hover { - background-color: grey; - color: white; -} - -li.b { - float: right; -} - -li.active { - background-color: grey; -} - -/* CSS für den Blur-Effekt */ -.blur { - filter: blur(8px); /* Du kannst die Stärke des Blur-Effekts ändern */ - transition: filter 0.5s; /* Für einen sanften Übergang */ -} - -/* CSS für den "Freigeben"-Button */ -.button-blur { - background-color: #0074d9; /* Eine passende Farbe für den Button */ - color: #fff; /* Textfarbe für den Button */ - padding: 10px 20px; - border: none; - cursor: pointer; -} - -/* CSS für den "Freigeben"-Button bei Hover */ -.button-blur:hover { - background-color: #0056b3; /* Ändert sich, wenn du mit der Maus darüber fährst */ -} - -.bottom-link { - text-align: left !important; - position: absolute !important; - bottom: 0 !important; - left: 0 !important; -} - -hr { - color: white; - margin: auto; - margin-top: 5px; - margin-bottom: 5px; - width: 75%; - text-align: center; -} - -.text-red { color: red; font-weight: bold;} -.text-green { color: green; font-weight: bold;} -.text-yellow { color: yellow; font-weight: bold;} -.text-blue { color: blue; font-weight: bold;} - -.status-message { - font-family: "Rubik", sans-serif; - font-size: 16px; - padding: 15px 20px; - margin: 20px auto; - border-radius: 10px; - max-width: 700px; - border: 2px solid rgba(255, 255, 255, 0.3); - box-shadow: 0 0 10px rgba(255, 255, 255, 0.1); - background: rgba(255, 255, 255, 0.05); - backdrop-filter: blur(6px); - color: white; - text-align: center; - position: relative; - transition: all 0.3s ease-in-out; - cursor: default; -} - -.status-message .dismiss-button { - position: absolute; - top: 8px; - right: 12px; - font-size: 20px; - color: white; - cursor: pointer; - transition: color 0.3s; -} - -.status-message .dismiss-button:hover { - color: red; -} - -.status-message.dismissed { - opacity: 0; - transform: scale(0.95); - height: 0; - margin: 0; - padding: 0; - border: 0; - pointer-events: none; -} - diff --git a/assets/gui/standard_nav.php b/assets/gui/standard_nav.php index 267e03d..9e5dd82 100644 --- a/assets/gui/standard_nav.php +++ b/assets/gui/standard_nav.php @@ -29,7 +29,9 @@ + + ADMIN | diff --git a/composer.json b/composer.json index 15b8cb9..1ab6355 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.2", + "version": "8.3.1", "authors": [ { "name": "Bryan Boehnke-Avan", diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..55be128 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,29 @@ +version: "3.9" + +services: + timetrack: + image: openducks/timetrack:latest + container_name: timetrack + ports: + - "8080:80" + depends_on: + - db + volumes: + - .:/var/www/html + restart: unless-stopped + + db: + image: mariadb:11 + container_name: timetrack-db + restart: unless-stopped + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: ab + MYSQL_USER: timetool + MYSQL_PASSWORD: yourpassword + volumes: + - db_data:/var/lib/mysql + command: ['--character-set-server=utf8mb4', '--collation-server=utf8mb4_unicode_ci'] + +volumes: + db_data: diff --git a/docker/apache2.conf b/docker/apache2.conf new file mode 100644 index 0000000..4cccfe4 --- /dev/null +++ b/docker/apache2.conf @@ -0,0 +1,11 @@ + + DocumentRoot /var/www/html + + Options Indexes FollowSymLinks + AllowOverride All + Require all granted + + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100644 index 0000000..c76b41e --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,23 @@ +#!/bin/bash +set -e + +# Waiting for the database to be ready +echo "Waiting for database to be ready..." +sleep 10 + +# Run Phinx migrations +echo "Running database migrations..." +if [ -f /var/www/html/vendor/bin/phinx ]; then + php /var/www/html/vendor/bin/phinx migrate || true +else + echo "Phinx not found — skipping migration." +fi + +# Start PC/SC daemon for smartcard/NFC +echo "Starting pcscd..." +pcscd --daemon & + + +# Start Apache +echo "Starting Apache..." +exec apache2-foreground diff --git a/errors/500.php b/errors/500.php index c76512d..3a6c977 100644 --- a/errors/500.php +++ b/errors/500.php @@ -15,7 +15,7 @@ 500 | Internal Server Error - + ?v=1">
diff --git a/migrations/migrations/20250331164242_init_first_user.php b/migrations/migrations/20250331164242_init_first_user.php index 5bcb32e..d727259 100644 --- a/migrations/migrations/20250331164242_init_first_user.php +++ b/migrations/migrations/20250331164242_init_first_user.php @@ -19,6 +19,7 @@ public function change(): void 'isAdmin' => true, 'state' => null, 'easymode' => false, + "active" => 1 ]; $users = $this->fetchRow("SELECT COUNT(*) as count FROM users WHERE username = 'admin'"); diff --git a/migrations/migrations/20250905190407_init_projects_scheme.php b/migrations/migrations/20250905190407_init_projects_scheme.php index 89e8f88..43feb05 100644 --- a/migrations/migrations/20250905190407_init_projects_scheme.php +++ b/migrations/migrations/20250905190407_init_projects_scheme.php @@ -17,8 +17,7 @@ public function change(): void $this->execute("SET SQL_MODE = 'NO_AUTO_VALUE_ON_ZERO';"); $this->execute("SET time_zone = '+00:00';"); - $this->table("projects", ["id" => false, "primary_key" => "id"]) - ->addColumn("id", "integer", ["identity" => true]) + $this->table("projects") ->addColumn("name", "string", ["limit" => 255]) ->addColumn("description", "text", ["null" => true]) ->addColumn("members", "text", ["null" => true]) diff --git a/migrations/migrations/20250905190436_init_projects_items_scheme.php b/migrations/migrations/20250905190436_init_projects_items_scheme.php index 894ceb2..d7f4828 100644 --- a/migrations/migrations/20250905190436_init_projects_items_scheme.php +++ b/migrations/migrations/20250905190436_init_projects_items_scheme.php @@ -17,12 +17,12 @@ public function change(): void $this->execute("SET SQL_MODE = 'NO_AUTO_VALUE_ON_ZERO';"); $this->execute("SET time_zone = '+00:00';"); - $this->table("projects_items", ["id" => false]) - ->addColumn("id", "integer") // PROJECT ID + $this->table("projects_items", ["id" => true]) + ->addColumn("pid", "integer") // PROJECT ID ->addColumn("title", "string", ["limit" => 255]) ->addColumn("description", "text", ["null" => true]) ->addColumn("assignee", "integer") // USER ID - ->addColumn("itemid", "integer", ["id" => true]) + ->addColumn("itemid", "integer") ->addColumn("deadline", "date") ->create(); } diff --git a/migrations/migrations/20251025162511_add_active_users_column.php b/migrations/migrations/20251025162511_add_active_users_column.php new file mode 100644 index 0000000..5a1a72d --- /dev/null +++ b/migrations/migrations/20251025162511_add_active_users_column.php @@ -0,0 +1,25 @@ +table('users')->hasColumn('active')) { + $this->table('users') + ->addColumn('active', 'boolean', [ + 'default' => true, + 'null' => false, + 'after' => 'password' + ]) + ->update(); + $this->execute('UPDATE users SET active = 1 WHERE active IS NULL'); + echo "Added active column to users table\n"; + } else { + echo "active column already exists in users table\n"; + } + } +} diff --git a/migrations/migrations/20251026205455_add_vacation_type_row.php b/migrations/migrations/20251026205455_add_vacation_type_row.php new file mode 100644 index 0000000..887112f --- /dev/null +++ b/migrations/migrations/20251026205455_add_vacation_type_row.php @@ -0,0 +1,26 @@ +table("vacation")->hasColumn("Vtype")){ + $this->table("vacation") + ->addColumn("Vtype", "enum", [ + "values" => ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"], + "default" => "0", + "null" => false, + "after" => "status" + ]) + ->update(); + $this->execute('UPDATE vacation SET Vtype = "0" WHERE Vtype IS NULL'); + echo "Added Vtype column to vacation table\n"; + } else { + echo "Vtype column already exists in vacation table\n"; + } + } +} diff --git a/migrations/migrations/20251026205503_add_sickness_type_row.php b/migrations/migrations/20251026205503_add_sickness_type_row.php new file mode 100644 index 0000000..b0c167b --- /dev/null +++ b/migrations/migrations/20251026205503_add_sickness_type_row.php @@ -0,0 +1,27 @@ +table("sick")->hasColumn("Stype")){ + $this->table("sick") + ->addColumn("Stype", "enum", [ + "values" => ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"], + "default" => "0", + "null" => false, + "after" => "status" + ]) + ->update(); + $this->execute('UPDATE sick SET Stype = "0" WHERE Stype IS NULL'); + echo "Added Stype column to sick table\n"; + } else { + echo "Stype column already exists in sick table\n"; + } + } +} diff --git a/setup/usercreate.php b/setup/usercreate.php deleted file mode 100644 index baf0464..0000000 --- a/setup/usercreate.php +++ /dev/null @@ -1,22 +0,0 @@ -get_app_ini()["general"]["base_url"]; $worktime->auth()->login_validation(); -$sick = $worktime->sickness()->add_sickness(start: $_POST["date-start"], stop: $_POST["date-end"], user: $_SESSION["username"]); +$sick = $worktime->sickness()->add_sickness(start: $_POST["date-start"], stop: $_POST["date-end"], user: $_SESSION["username"], type: $_POST["Stype"]); if(!$sick){ header("Location: http://{$base_url}/suite/?" . $worktime->statusMessages()->URIBuilder("error_sickness")); } else { diff --git a/suite/actions/worktime/add_vacation.php b/suite/actions/worktime/add_vacation.php index e45b69f..2465f31 100644 --- a/suite/actions/worktime/add_vacation.php +++ b/suite/actions/worktime/add_vacation.php @@ -4,7 +4,7 @@ $worktime = new Arbeitszeit; $base_url = $worktime->get_app_ini()["general"]["base_url"]; $worktime->auth()->login_validation(); -$vacation = $worktime->vacation()->add_vacation(start: $_POST["date-start"], stop: $_POST["date-end"], username: $_SESSION["username"]); +$vacation = $worktime->vacation()->add_vacation(start: $_POST["date-start"], stop: $_POST["date-end"], username: $_SESSION["username"], type: $_POST["Vtype"]); if(!$vacation){ header("Location: http://{$base_url}/suite/?" . $worktime->statusMessages()->URIBuilder('error_vacation')); } else { diff --git a/suite/admin/projects/addUser.php b/suite/admin/projects/addUser.php index f29795d..bbcf30f 100644 --- a/suite/admin/projects/addUser.php +++ b/suite/admin/projects/addUser.php @@ -45,5 +45,6 @@ + diff --git a/suite/admin/projects/admin.php b/suite/admin/projects/admin.php index fbda32c..fbf2089 100644 --- a/suite/admin/projects/admin.php +++ b/suite/admin/projects/admin.php @@ -107,6 +107,7 @@ function confirmDelete(e, projectId) { + \ No newline at end of file diff --git a/suite/admin/projects/edit.php b/suite/admin/projects/edit.php index dd776bc..92cfea3 100644 --- a/suite/admin/projects/edit.php +++ b/suite/admin/projects/edit.php @@ -58,5 +58,6 @@ + diff --git a/suite/admin/worktime/sick/all.php b/suite/admin/worktime/sick/all.php index fbd7877..bc9da1d 100644 --- a/suite/admin/worktime/sick/all.php +++ b/suite/admin/worktime/sick/all.php @@ -51,6 +51,7 @@ + diff --git a/suite/admin/worktime/vacation/all.php b/suite/admin/worktime/vacation/all.php index fb32322..ffd447a 100644 --- a/suite/admin/worktime/vacation/all.php +++ b/suite/admin/worktime/vacation/all.php @@ -49,6 +49,7 @@ + diff --git a/suite/plugins/index.php b/suite/plugins/index.php index f134e6b..488cb61 100644 --- a/suite/plugins/index.php +++ b/suite/plugins/index.php @@ -6,6 +6,7 @@ $arbeit = new Arbeitszeit; $arbeit->auth()->login_validation(); $pl = new PluginBuilder(); +$pl->redirect_if_disabled(); ?> diff --git a/suite/projects/createItem.php b/suite/projects/createItem.php index 687454a..2dae3ac 100644 --- a/suite/projects/createItem.php +++ b/suite/projects/createItem.php @@ -40,5 +40,6 @@ + diff --git a/suite/projects/item.php b/suite/projects/item.php index 1daf28d..14b1a7d 100644 --- a/suite/projects/item.php +++ b/suite/projects/item.php @@ -73,5 +73,6 @@ + diff --git a/suite/projects/mapWorktimeToItem.php b/suite/projects/mapWorktimeToItem.php index ee4421d..03d90c9 100644 --- a/suite/projects/mapWorktimeToItem.php +++ b/suite/projects/mapWorktimeToItem.php @@ -39,5 +39,6 @@ Cancel + diff --git a/suite/projects/overview.php b/suite/projects/overview.php index 8147062..3613024 100644 --- a/suite/projects/overview.php +++ b/suite/projects/overview.php @@ -95,5 +95,6 @@ + diff --git a/suite/projects/view.php b/suite/projects/view.php index 05abe17..91d9301 100644 --- a/suite/projects/view.php +++ b/suite/projects/view.php @@ -132,7 +132,7 @@ - +