From d501da7119afe4fe0554baefadd1425220cb1d24 Mon Sep 17 00:00:00 2001 From: Ben0it-T <37017213+Ben0it-T@users.noreply.github.com> Date: Sun, 6 Oct 2024 09:28:23 +0200 Subject: [PATCH 01/63] Update fr.lang --- .gitignore | 23 ++++-- WEB-INF/resources/fr.lang.php | 138 +++++++++++++++------------------- 2 files changed, 75 insertions(+), 86 deletions(-) diff --git a/.gitignore b/.gitignore index d81178e1..0b3e8f74 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,22 @@ +# General +.DS_Store +._* +_* +*~ +*.sublime-project +*.sublime-workspace +.idea/ +.vscode/ +Thumbs.db +nbproject/ + +# Config config.php + +# WEB-INF WEB-INF/templates_c/*.* WEB-INF/templates_c/import_* WEB-INF/templates_c/tt* WEB-INF/lib/tcpdf/ -nbproject/ upload/ -.vscode/ -Thumbs.db -*.DS_Store -*~ -.idea/ -api/ +api/ \ No newline at end of file diff --git a/WEB-INF/resources/fr.lang.php b/WEB-INF/resources/fr.lang.php index b488be0b..f0c4293a 100644 --- a/WEB-INF/resources/fr.lang.php +++ b/WEB-INF/resources/fr.lang.php @@ -15,19 +15,17 @@ // Menus - short selection strings that are displayed on top of application web pages. // Example: https://timetracker.anuko.com (black menu on top). -'menu.login' => 'Connexion', -'menu.logout' => 'Quitter', +'menu.login' => 'Connexion', // Se connecter (Sign in) +'menu.logout' => 'Déconnexion', // Se déconnecter (Sign out) 'menu.forum' => 'Forum', 'menu.help' => 'Aide', -// TODO: translate the following. -// 'menu.register' => 'Register', +'menu.register' => 'Créer un groupe', // S'inscrire (Sign up) 'menu.profile' => 'Profil', -// TODO: translate the following. -// 'menu.group' => 'Group', +'menu.group' => 'Groupe', 'menu.plugins' => 'Plugins', 'menu.time' => 'Temps', +'menu.puncher' => 'Minuteur', // TODO: translate the following. -// 'menu.puncher' => 'Punch', // 'menu.week' => 'Week', 'menu.expenses' => 'Dépenses', 'menu.reports' => 'Rapports', @@ -37,8 +35,7 @@ 'menu.projects' => 'Projets', 'menu.tasks' => 'Tâches', 'menu.users' => 'Utilisateurs', -// TODO: translate the following. -// 'menu.groups' => 'Groups', +'menu.groups' => 'Groupes', 'menu.export' => 'Exporter', 'menu.clients' => 'Clients', 'menu.options' => 'Options', @@ -53,9 +50,8 @@ 'error.access_denied' => 'Accès refusé.', 'error.sys' => 'Erreur système.', 'error.db' => 'Erreur de base de données.', -// TODO: translate the following. -// 'error.registered_recently' => 'Registered recently.', -// 'error.feature_disabled' => 'Feature is disabled.', +'error.registered_recently' => 'Inscrit récemment.', +'error.feature_disabled' => 'La fonctionnalité est désactivée.', 'error.field' => 'Donnée "{0}" incorrecte.', 'error.empty' => 'Le champ "{0}" est vide.', 'error.not_equal' => 'Le champ "{0}" n\\\'est pas égal au champ "{1}".', @@ -67,18 +63,14 @@ // TODO: translate the following. // 'error.record' => 'Select record.', 'error.auth' => 'Nom d\\\'utilisateur ou mot de passe incorrect.', -// TODO: translate the following. -// 'error.2fa_code' => 'Invalid 2FA code.', -// 'error.weak_password' => 'Weak password.', +'error.2fa_code' => 'Code 2FA invalide.', +'error.weak_password' => 'Mot de passe faible.', 'error.user_exists' => 'Un utilisateur avec cet identifiant existe déjà.', -// TODO: translate the following. -// 'error.object_exists' => 'Object with this name already exists.', +'error.object_exists' => 'Un objet portant ce nom existe déjà.', 'error.invoice_exists' => 'Une facture avec ce numéro existe déjà.', -// TODO: translate the following. -// 'error.role_exists' => 'Role with this rank already exists.', +'error.role_exists' => 'Un rôle avec ce rang existe déjà.', 'error.no_invoiceable_items' => 'Il n\\\'y a pas d\\\'éléments à facturer.', -// TODO: translate the following. -// 'error.no_records' => 'There are no records.', +'error.no_records' => 'Il n\\\'y a aucun enregistrement.', 'error.no_login' => 'Aucun utilisateur avec cet identifiant.', 'error.no_groups' => 'Votre base de données est vide. Connectez-vous comme administrateur et créez une nouvelle équipe.', // TODO: replace "team" with "group". 'error.upload' => 'Erreur de chargement du fichier.', @@ -105,8 +97,7 @@ // 'warn.confirm_save' => 'Date has changed. Confirm saving, not copying this item.', // Success messages. -// TODO: translate the following. -// 'msg.success' => 'Operation completed successfully.', +'msg.success' => 'Opération réalisée avec succès.', // Labels for buttons. 'button.login' => 'Connexion', @@ -125,8 +116,7 @@ 'button.export' => 'Exporter l\\\'équipe', // TODO: replace "team" with "group". 'button.import' => 'Importer une équipe', // TODO: replace "team" with "group". 'button.close' => 'Fermer', -// TODO: translate the following. -// 'button.start' => 'Start', +'button.start' => 'Début', 'button.stop' => 'Arrêter', // TODO: translate the following. // 'button.approve' => 'Approve', @@ -155,10 +145,9 @@ 'label.end_date' => 'Date de fin', 'label.user' => 'Utilisateur', 'label.users' => 'Utilisateurs', -// TODO: translate the following. -// 'label.group' => 'Group', -// 'label.subgroups' => 'Subgroups', -// 'label.roles' => 'Roles', +'label.group' => 'Groupe', +'label.subgroups' => 'Sous-groupes', +'label.roles' => 'Rôles', 'label.client' => 'Client', 'label.clients' => 'Clients', 'label.option' => 'Option', @@ -171,13 +160,11 @@ 'label.start' => 'Début', 'label.finish' => 'Fin', 'label.duration' => 'Durée', -'label.note' => 'Note', -// TODO: translate the following. -// 'label.notes' => 'Notes', +'label.note' => 'Commentaire', +'label.notes' => 'Commentaires', 'label.item' => 'Item', 'label.cost' => 'Coût', -// TODO: translate the following. -// 'label.ip' => 'IP', +'label.ip' => 'Adresse IP', 'label.day_total' => 'Total quotidien', 'label.week_total' => 'Total hebdomadaire', 'label.month_total' => 'Total mensuel', @@ -191,12 +178,12 @@ // TODO: translate the following. // 'label.day_view' => 'Day view', // 'label.week_view' => 'Week view', -// 'label.puncher' => 'Puncher', +'label.puncher' => 'Minuteur', 'label.id' => 'ID', 'label.language' => 'Langage', 'label.decimal_mark' => 'Séparateur de décimal', -'label.date_format' => 'Format date', -'label.time_format' => 'Format heure', +'label.date_format' => 'Format de date', +'label.time_format' => 'Format de l\\\'heure', 'label.week_start' => '1er jour de la semaine', 'label.comment' => 'Commentaire', 'label.status' => 'Statut', @@ -249,9 +236,9 @@ // 'label.template' => 'Template', // 'label.bind_templates_with_projects' => 'Bind templates with projects', // 'label.prepopulate_note' => 'Prepopulate Note field', -// 'label.attachments' => 'Attachments', -// 'label.files' => 'Files', -// 'label.file' => 'File', +'label.attachments' => 'Pièces jointes', +'label.files' => 'Fichiers', +'label.file' => 'Fichier', 'label.active_users' => 'Utilisateurs actifs', 'label.inactive_users' => 'Utilisateurs inactifs', @@ -281,7 +268,7 @@ 'title.delete_time_record' => 'Suppression de l\\\'entrée de temps', // TODO: Translate the following. // 'title.time_files' => 'Time Record Files', -// 'title.puncher' => 'Puncher', +'title.puncher' => 'Minuteur', 'title.expenses' => 'Dépenses', 'title.edit_expense' => 'Modification d\\\'une dépense', 'title.delete_expense' => 'Suppression d\\\'une dépense', @@ -311,11 +298,10 @@ 'title.add_user' => 'Création d\\\'un utilisateur', 'title.edit_user' => 'Modification d\\\'un utilisateur', 'title.delete_user' => 'Suppression d\\\'un utilisateur', -// TODO: translate the following. -// 'title.roles' => 'Roles', -// 'title.add_role' => 'Adding Role', -// 'title.edit_role' => 'Editing Role', -// 'title.delete_role' => 'Deleting Role', +'title.roles' => 'Rôles', +'title.add_role' => 'Ajout d\\\'un rôle', +'title.edit_role' => 'Modification d\\\'un rôle', +'title.delete_role' => 'Suppression d\\\'un rôle', 'title.clients' => 'Clients', 'title.add_client' => 'Ajout d\\\'un client', 'title.edit_client' => 'Modification d\\\'un client', @@ -336,8 +322,7 @@ 'title.export' => 'Exportation des données', 'title.import' => 'Importation des données', 'title.options' => 'Options', -// TODO: translate the following. -// 'title.display_options' => 'Display Options', +'title.display_options' => 'Options d\\\'affichage', 'title.profile' => 'Profil', 'title.plugins' => 'Plugins', 'title.cf_custom_fields' => 'Champs personalisés', @@ -351,7 +336,8 @@ 'title.locking' => 'Vérouillage', // TODO: translate the following. // 'title.week_view' => 'Week View', -// 'title.swap_roles' => 'Swapping Roles', +'title.swap_roles' => 'Échanger les rôles', +// TODO: translate the following. // 'title.work_units' => 'Work Units', // 'title.templates' => 'Templates', // 'title.add_template' => 'Adding Template', @@ -514,8 +500,7 @@ 'form.tasks.inactive_tasks' => 'Tâches inactives', // Users form. See example at https://timetracker.anuko.com/users.php -// TODO: translate the following. -// 'form.users.uncompleted_entry_today' => 'User has an uncompleted time entry today', +'form.users.uncompleted_entry_today' => 'L\\\'utilisateur a une saisie de temps incomplète aujourd\\\'hui', 'form.users.uncompleted_entry' => 'L\\\'utilisateur a une entrée incomplète', 'form.users.role' => 'Rôle', 'form.users.manager' => 'Responsable', @@ -524,17 +509,15 @@ 'form.users.default_rate' => 'Tarif horaire par défaut', // Editing User form. See example at https://timetracker.anuko.com/user_edit.php -// TODO: translate the following. -// 'form.user_edit.swap_roles' => 'Swap roles', +'form.user_edit.swap_roles' => 'Échanger les rôles', // Roles form. See example at https://timetracker.anuko.com/roles.php -// TODO: translate the following. -// 'form.roles.active_roles' => 'Active Roles', -// 'form.roles.inactive_roles' => 'Inactive Roles', -// 'form.roles.rank' => 'Rank', -// 'form.roles.rights' => 'Rights', -// 'form.roles.assigned' => 'Assigned', -// 'form.roles.not_assigned' => 'Not assigned', +'form.roles.active_roles' => 'Rôles actifs', +'form.roles.inactive_roles' => 'Rôles inactifs', +'form.roles.rank' => 'Rang', +'form.roles.rights' => 'Droits', +'form.roles.assigned' => 'Droits attribués', +'form.roles.not_assigned' => 'Droits non attribués', // Clients form. See example at https://timetracker.anuko.com/clients.php 'form.clients.active_clients' => 'Clients actifs', @@ -563,10 +546,9 @@ // Group Settings form. See example at https://timetracker.anuko.com/group_edit.php. 'form.group_edit.12_hours' => '12 heures', 'form.group_edit.24_hours' => '24 heures', -// TODO: translate the following. -// 'form.group_edit.display_options' => 'Display options', -// 'form.group_edit.holidays' => 'Holidays', -'form.group_edit.tracking_mode' => 'Mode suivi', +'form.group_edit.display_options' => 'Options d\\\'affichage', +'form.group_edit.holidays' => 'Jours fériés', +'form.group_edit.tracking_mode' => 'Mode de suivi', 'form.group_edit.mode_time' => 'Heures', 'form.group_edit.mode_projects' => 'Projets', 'form.group_edit.mode_projects_and_tasks' => 'Projets et tâches', @@ -574,20 +556,18 @@ 'form.group_edit.type_all' => 'Tous', 'form.group_edit.type_start_finish' => 'Début et fin', 'form.group_edit.type_duration' => 'Durée', -// TODO: translate the following. -// 'form.group_edit.punch_mode' => 'Punch mode', -// 'form.group_edit.one_uncompleted' => 'One uncompleted', -// 'form.group_edit.allow_overlap' => 'Allow overlap', -// 'form.group_edit.future_entries' => 'Future entries', -// 'form.group_edit.uncompleted_indicators' => 'Uncompleted indicators', -// 'form.group_edit.confirm_save' => 'Confirm saving', -// 'form.group_edit.advanced_settings' => 'Advanced settings', +'form.group_edit.punch_mode' => 'Mode minuteur', +'form.group_edit.one_uncompleted' => 'Une seule entrée incomplète autorisée', +'form.group_edit.allow_overlap' => 'Autoriser le chevauchement', +'form.group_edit.future_entries' => 'Autoriser la saisie pour les dates futures', +'form.group_edit.uncompleted_indicators' => 'Indicateurs d\\\'entrées incomplètes', +'form.group_edit.confirm_save' => 'Confirmer l\\\'enregistrement', +'form.group_edit.advanced_settings' => 'Paramètres avancés', // Advanced Group Settings form. See example at https://timetracker.anuko.com/group_advanced_edit.php. -// TODO: Translate the following. -// 'form.group_advanced_edit.allow_ip' => 'Allow IP', -// 'form.group_advanced_edit.password_complexity' => 'Password complexity', -// 'form.group_advanced_edit.2fa' => 'Two factor authentication', +'form.group_advanced_edit.allow_ip' => 'Adresses IP autorisées', +'form.group_advanced_edit.password_complexity' => 'Complexité des mots de passe', +'form.group_advanced_edit.2fa' => 'Authentification à deux facteurs', // Deleting Group form. See example at https://timetracker.anuko.com/delete_group.php // TODO: translate the following. @@ -609,7 +589,7 @@ // Swap roles form. See example at https://timetracker.anuko.com/swap_roles.php. // TODO: translate the following. // 'form.swap.hint' => 'Demote yourself to a lower role by swapping roles with someone else. This cannot be undone.', -// 'form.swap.swap_with' => 'Swap roles with', +'form.swap.swap_with' => 'Échanger les rôles avec', // Work Units configuration form. See example at https://timetracker.anuko.com/work_units.php after enabling Work units plugin. // TODO: translate the following. @@ -650,9 +630,9 @@ // 'form.timesheet_view.disapprove_body' => "Your timesheet %s was not approved.

%s", // Display Options form. See example at https://timetracker.anuko.com/display_options.php. +'form.display_options.note_on_separate_row' => 'Commentaire sur une ligne séparée', +'form.display_options.not_complete_days' => 'Journées incomplètes', // TODO: translate the following. -// 'form.display_options.note_on_separate_row' => 'Note on separate row', -// 'form.display_options.not_complete_days' => 'Not complete days', // 'form.display_options.inactive_projects' => 'Inactive projects', // 'form.display_options.cost_per_hour' => 'Cost per hour', // 'form.display_options.custom_css' => 'Custom CSS', From 8e491b73adecac6287f534669b3f82828a5fa131 Mon Sep 17 00:00:00 2001 From: Ben0it-T <37017213+Ben0it-T@users.noreply.github.com> Date: Sun, 6 Oct 2024 09:39:54 +0200 Subject: [PATCH 02/63] Fix navigation to Puncher Fix: navigation to Puncher via the link on Time page and Week page --- WEB-INF/templates/time.tpl | 4 +--- WEB-INF/templates/week.tpl | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/WEB-INF/templates/time.tpl b/WEB-INF/templates/time.tpl index fa24e806..bf68708d 100644 --- a/WEB-INF/templates/time.tpl +++ b/WEB-INF/templates/time.tpl @@ -13,9 +13,7 @@ function handleStop(buttonElement) { {if $show_navigation}

- {$i18n.label.day_view} - {if $user->isPluginEnabled('pu')} / {$i18n.label.puncher}{/if} - {if $user->isPluginEnabled('wv')} / {$i18n.label.week_view}{/if} + {$i18n.label.day_view}{if $user->isPluginEnabled('pu')} / {$i18n.label.puncher}{/if}{if $user->isPluginEnabled('wv')} / {$i18n.label.week_view}{/if}
{/if} diff --git a/WEB-INF/templates/week.tpl b/WEB-INF/templates/week.tpl index 6032d4eb..01234e19 100644 --- a/WEB-INF/templates/week.tpl +++ b/WEB-INF/templates/week.tpl @@ -20,7 +20,7 @@ function fillDropdowns() { {if $show_navigation}
- {$i18n.label.day_view} / {$i18n.label.week_view} + {$i18n.label.day_view}{if $user->isPluginEnabled('pu')} / {$i18n.label.puncher}{/if} / {$i18n.label.week_view}
{/if} From 7a2bc489b762b4e839dc7759b3379ef8444cf596 Mon Sep 17 00:00:00 2001 From: Ben0it-T <37017213+Ben0it-T@users.noreply.github.com> Date: Sun, 6 Oct 2024 09:56:57 +0200 Subject: [PATCH 03/63] Update database structure --- dbinstall.php | 63 ++++++++++++++++++++++++++++++--------------------- mysql.sql | 14 +++++++++++- 2 files changed, 50 insertions(+), 27 deletions(-) diff --git a/dbinstall.php b/dbinstall.php index 44fede0c..8eba7e9b 100644 --- a/dbinstall.php +++ b/dbinstall.php @@ -1202,6 +1202,12 @@ function ttGenerateKeys() { ttExecute("UPDATE `tt_site_config` SET param_value = '1.22.3', modified = now() where param_name = 'version_db' and param_value = '1.21.7'"); } + if (array_key_exists('convert12203to12204', $_POST)) { + // Feature store sessions in database + ttExecute("CREATE TABLE `tt_sessions` (`id` varchar(32) NOT NULL, `expire` int(10) unsigned, `data` text NOT NULL, PRIMARY KEY (`id`))"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.22.4', modified = now() where param_name = 'version_db' and param_value = '1.22.3'"); + } + if (array_key_exists('cleanup', $_POST)) { $mdb2 = getConnection(); $inactive_orgs = ttOrgHelper::getInactiveOrgs(); @@ -1220,32 +1226,33 @@ function ttGenerateKeys() { // ttExecute("delete from tt_custom_field_log where status is null"); // ttExecute("delete from tt_expense_items where status is null"); - ttExecute("OPTIMIZE TABLE tt_client_project_binds"); - ttExecute("OPTIMIZE TABLE tt_clients"); - ttExecute("OPTIMIZE TABLE tt_config"); - ttExecute("OPTIMIZE TABLE tt_cron"); - ttExecute("OPTIMIZE TABLE tt_timesheets"); - ttExecute("OPTIMIZE TABLE tt_custom_field_log"); - ttExecute("OPTIMIZE TABLE tt_custom_field_options"); - ttExecute("OPTIMIZE TABLE tt_custom_fields"); - ttExecute("OPTIMIZE TABLE tt_expense_items"); - ttExecute("OPTIMIZE TABLE tt_fav_reports"); - ttExecute("OPTIMIZE TABLE tt_invoices"); - ttExecute("OPTIMIZE TABLE tt_log"); // This locks the production table for 4 minutes. Impossible to login, etc. + ttExecute("OPTIMIZE TABLE `tt_client_project_binds`"); + ttExecute("OPTIMIZE TABLE `tt_clients`"); + ttExecute("OPTIMIZE TABLE `tt_config`"); + ttExecute("OPTIMIZE TABLE `tt_cron`"); + ttExecute("OPTIMIZE TABLE `tt_timesheets`"); + ttExecute("OPTIMIZE TABLE `tt_custom_field_log`"); + ttExecute("OPTIMIZE TABLE `tt_custom_field_options`"); + ttExecute("OPTIMIZE TABLE `tt_custom_fields`"); + ttExecute("OPTIMIZE TABLE `tt_expense_items`"); + ttExecute("OPTIMIZE TABLE `tt_fav_reports`"); + ttExecute("OPTIMIZE TABLE `tt_invoices`"); + ttExecute("OPTIMIZE TABLE `tt_log`"); // This locks the production table for 4 minutes. Impossible to login, etc. // TODO: what should we do about it? - ttExecute("OPTIMIZE TABLE tt_monthly_quotas"); - ttExecute("OPTIMIZE TABLE tt_templates"); - ttExecute("OPTIMIZE TABLE tt_project_template_binds"); - ttExecute("OPTIMIZE TABLE tt_predefined_expenses"); - ttExecute("OPTIMIZE TABLE tt_project_task_binds"); - ttExecute("OPTIMIZE TABLE tt_projects"); - ttExecute("OPTIMIZE TABLE tt_tasks"); - ttExecute("OPTIMIZE TABLE tt_groups"); - ttExecute("OPTIMIZE TABLE tt_tmp_refs"); - ttExecute("OPTIMIZE TABLE tt_user_project_binds"); - ttExecute("OPTIMIZE TABLE tt_users"); - ttExecute("OPTIMIZE TABLE tt_roles"); - ttExecute("OPTIMIZE TABLE tt_files"); + ttExecute("OPTIMIZE TABLE `tt_monthly_quotas`"); + ttExecute("OPTIMIZE TABLE `tt_templates`"); + ttExecute("OPTIMIZE TABLE `tt_project_template_binds`"); + ttExecute("OPTIMIZE TABLE `tt_predefined_expenses`"); + ttExecute("OPTIMIZE TABLE `tt_project_task_binds`"); + ttExecute("OPTIMIZE TABLE `tt_projects`"); + ttExecute("OPTIMIZE TABLE `tt_tasks`"); + ttExecute("OPTIMIZE TABLE `tt_groups`"); + ttExecute("OPTIMIZE TABLE `tt_tmp_refs`"); + ttExecute("OPTIMIZE TABLE `tt_user_project_binds`"); + ttExecute("OPTIMIZE TABLE `tt_users`"); + ttExecute("OPTIMIZE TABLE `tt_roles`"); + ttExecute("OPTIMIZE TABLE `tt_files`"); + ttExecute("OPTIMIZE TABLE `tt_sessions`"); } print "Done.
\n"; @@ -1258,7 +1265,7 @@ function ttGenerateKeys() {

DB Install

-
Create database structure (v1.22.3) + Create database structure (v1.22.4)
(applies only to new installations, do not execute when updating)
@@ -1310,6 +1317,10 @@ function ttGenerateKeys() { Update database structure (v1.19 to v1.22.3) + + Update database structure (v1.22.3 to v1.22.4) + +

DB Maintenance

diff --git a/mysql.sql b/mysql.sql index f614d0ac..6b9e4da6 100644 --- a/mysql.sql +++ b/mysql.sql @@ -623,4 +623,16 @@ CREATE TABLE `tt_site_config` ( PRIMARY KEY (`param_name`) ); -INSERT INTO `tt_site_config` (`param_name`, `param_value`, `created`) VALUES ('version_db', '1.22.3', now()); # TODO: change when structure changes. +INSERT INTO `tt_site_config` (`param_name`, `param_value`, `created`) VALUES ('version_db', '1.22.4', now()); # TODO: change when structure changes. + + +# +# Structure for table tt_sessions. +# This table stores sessions data +# +CREATE TABLE `tt_sessions` ( + `id` varchar(32) NOT NULL, # session id + `expire` int(10) unsigned, # session expire + `data` text NOT NULL, # session + PRIMARY KEY (`id`) +) From 8ae09e4e0d5e209a06c810cef747c4f85d3a7963 Mon Sep 17 00:00:00 2001 From: Ben0it-T <37017213+Ben0it-T@users.noreply.github.com> Date: Sun, 6 Oct 2024 09:57:45 +0200 Subject: [PATCH 04/63] Add ttSession class --- WEB-INF/lib/ttSession.class.php | 80 +++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 WEB-INF/lib/ttSession.class.php diff --git a/WEB-INF/lib/ttSession.class.php b/WEB-INF/lib/ttSession.class.php new file mode 100644 index 00000000..06e7078e --- /dev/null +++ b/WEB-INF/lib/ttSession.class.php @@ -0,0 +1,80 @@ +db = getConnection(); + $this->_gc(PHPSESSID_TTL); + return true; + } + + public function _close() { + $this->db = null; + return true; + } + + public function _read($id) { + $id = preg_replace('/[^a-zA-Z0-9,\-]/', '', $id); + $sql = "SELECT data FROM tt_sessions WHERE id = '".$id."'"; + $res = $this->db->query($sql); + if (is_a($res, 'PEAR_Error')) { + return ""; // Return an empty string + } + $row = $res->fetchRow(); + if (empty($row['data'])) { + return ""; // Return an empty string + } + else { + return $row['data']; + } + } + + public function _write($id, $data) { + $id = preg_replace('/[^a-zA-Z0-9,\-]/', '', $id); + $expire = time(); // Create time stamp + $sql = "REPLACE INTO tt_sessions (id, expire, data) VALUES ('".$id."', $expire, '".$data."')"; + $affected = $this->db->exec($sql); + if (is_a($affected, 'PEAR_Error')) { + return false; + } + return true; + } + + public function _destroy($id) { + $id = preg_replace('/[^a-zA-Z0-9,\-]/', '', $id); + $sql = "DELETE FROM tt_sessions WHERE id = '".$id."'"; + $affected = $this->db->exec($sql); + if (is_a($affected, 'PEAR_Error')) { + return false; + } + return true; + } + + public function _gc($lifetime) { + $old = time() - intval($lifetime); + $sql = "DELETE FROM tt_sessions WHERE expire < $old"; + $affected = $this->db->exec($sql); + if (is_a($affected, 'PEAR_Error')) { + return false; + } + return true; + } +} \ No newline at end of file From dea53354037373b71bec0f49c93cf49094c0300e Mon Sep 17 00:00:00 2001 From: Ben0it-T <37017213+Ben0it-T@users.noreply.github.com> Date: Sun, 6 Oct 2024 09:59:10 +0200 Subject: [PATCH 05/63] Add session handler stuff --- WEB-INF/config.php.dist | 7 +++++++ initialize.php | 27 ++++++++++++++++++++------- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/WEB-INF/config.php.dist b/WEB-INF/config.php.dist index 6b6a68d8..b5f078d2 100644 --- a/WEB-INF/config.php.dist +++ b/WEB-INF/config.php.dist @@ -68,6 +68,13 @@ define('WEEKEND_START_DAY', 6); // define('PHP_SESSION_PATH', '/tmp/timetracker'); // Directory must exist and be writable. +// SESSION_HANDLER +// Set session storage. +// 'file' : file stroage. Default value +// 'db' : db stroage +define('SESSION_HANDLER', 'file'); + + // LOGIN_COOKIE_NAME // // Cookie name for user login to remember it between browser sessions. diff --git a/initialize.php b/initialize.php index 18946798..f19456a1 100644 --- a/initialize.php +++ b/initialize.php @@ -66,9 +66,10 @@ // Change http cache expiration time to 1 minute. session_cache_expire(1); -$phpsessid_ttl = defined('PHPSESSID_TTL') ? PHPSESSID_TTL : 60*60*24; // Set lifetime for garbage collection. -ini_set('session.gc_maxlifetime', $phpsessid_ttl); +if (!defined('PHPSESSID_TTL')) define('PHPSESSID_TTL', 60*60*24); +ini_set('session.gc_maxlifetime', PHPSESSID_TTL); + // Set PHP session path, if defined to avoid garbage collection interference from other scripts. if (defined('PHP_SESSION_PATH') && realpath(PHP_SESSION_PATH)) { ini_set('session.save_path', realpath(PHP_SESSION_PATH)); @@ -80,14 +81,21 @@ if (!defined('LOGIN_COOKIE_NAME')) define('LOGIN_COOKIE_NAME', 'tt_login'); // Set session cookie lifetime. -session_set_cookie_params($phpsessid_ttl); +session_set_cookie_params(PHPSESSID_TTL); if (isset($_COOKIE[SESSION_COOKIE_NAME])) { // Extend PHP session cookie lifetime by PHPSESSID_TTL (if defined, otherwise 24 hours) // so that users don't have to re-login during this period from now. - setcookie(SESSION_COOKIE_NAME, $_COOKIE[SESSION_COOKIE_NAME], time() + $phpsessid_ttl, '/'); + setcookie(SESSION_COOKIE_NAME, $_COOKIE[SESSION_COOKIE_NAME], time() + PHPSESSID_TTL, '/'); +} + +// Set session storage +if (!defined('SESSION_HANDLER')) define('SESSION_HANDLER', 'file'); +if (SESSION_HANDLER === 'db') { + import('ttSession'); + $ttSession = new ttSession(); } -// Start or resume PHP session. +// Start session session_name(SESSION_COOKIE_NAME); @session_start(); @@ -136,8 +144,13 @@ import('ttUser'); $user = new ttUser(null, $auth->getUserId()); if ($user->custom_logo) { - $smarty->assign('custom_logo', 'img/'.$user->group_id.'.png'); - $smarty->assign('mobile_custom_logo', '../img/'.$user->group_id.'.png'); + if (file_exists('img/'.$user->group_id.'.png')) { + $smarty->assign('custom_logo', 'img/'.$user->group_id.'.png'); + $smarty->assign('mobile_custom_logo', '../img/'.$user->group_id.'.png'); + } + else { + $user->custom_logo = 0; + } } $smarty->assign('user', $user); From 3f454ff73cb6ebf591e49001e802cc6dd985c65d Mon Sep 17 00:00:00 2001 From: Ben0it-T <37017213+Ben0it-T@users.noreply.github.com> Date: Sun, 6 Oct 2024 10:15:59 +0200 Subject: [PATCH 06/63] Fix custom_logo Fix custom_logo if file not exists --- initialize.php | 9 +++++++-- password_change.php | 9 +++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/initialize.php b/initialize.php index 18946798..362c9f7a 100644 --- a/initialize.php +++ b/initialize.php @@ -136,8 +136,13 @@ import('ttUser'); $user = new ttUser(null, $auth->getUserId()); if ($user->custom_logo) { - $smarty->assign('custom_logo', 'img/'.$user->group_id.'.png'); - $smarty->assign('mobile_custom_logo', '../img/'.$user->group_id.'.png'); + if (file_exists('img/'.$user->group_id.'.png')) { + $smarty->assign('custom_logo', 'img/'.$user->group_id.'.png'); + $smarty->assign('mobile_custom_logo', '../img/'.$user->group_id.'.png'); + } + else { + $user->custom_logo = 0; + } } $smarty->assign('user', $user); diff --git a/password_change.php b/password_change.php index d395b639..c527e9d5 100644 --- a/password_change.php +++ b/password_change.php @@ -29,8 +29,13 @@ $smarty->assign('i18n', $i18n->keys); } if ($user->custom_logo) { - $smarty->assign('custom_logo', 'img/'.$user->group_id.'.png'); - $smarty->assign('mobile_custom_logo', '../img/'.$user->group_id.'.png'); + if (file_exists('img/'.$user->group_id.'.png')) { + $smarty->assign('custom_logo', 'img/'.$user->group_id.'.png'); + $smarty->assign('mobile_custom_logo', '../img/'.$user->group_id.'.png'); + } + else { + $user->custom_logo = 0; + } } $smarty->assign('user', $user); From 86797d7e834d125957d927fb224a897db4e2b3ab Mon Sep 17 00:00:00 2001 From: Ben0it-T <37017213+Ben0it-T@users.noreply.github.com> Date: Tue, 8 Oct 2024 12:38:15 +0200 Subject: [PATCH 07/63] Fix setRowBackground not defined --- WEB-INF/lib/form/Table.class.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WEB-INF/lib/form/Table.class.php b/WEB-INF/lib/form/Table.class.php index acdf4813..d164efb2 100644 --- a/WEB-INF/lib/form/Table.class.php +++ b/WEB-INF/lib/form/Table.class.php @@ -157,8 +157,9 @@ function getHtml() { // Print rows. if (is_array($this->mData)) { + $rowHoverBackgroundColor = ($this->isInteractive() ? "onmouseover=\"setRowBackground(this, '".$this->mBgColorOver."')\" onmouseout=\"setRowBackground(this, null)\"" : ""); for ($row = 0; $row < count($this->mData); $row++) { - $html .= "\nmBgColor."\" onmouseover=\"setRowBackground(this, '".$this->mBgColorOver."')\" onmouseout=\"setRowBackground(this, null)\">\n"; + $html .= "\nmBgColor."\" ".$rowHoverBackgroundColor.">\n"; for ($col = 0; $col < $this->getColumnCount(); $col++) { if (0 == $col && strtolower(get_class($this->mColumns[$col]->getRenderer())) == 'checkboxcellrenderer') { // Checkbox for the row. Determine if selected. From 0288f09d321785089106e2ca226acdfec641e70b Mon Sep 17 00:00:00 2001 From: Ben0it-T <37017213+Ben0it-T@users.noreply.github.com> Date: Tue, 8 Oct 2024 12:55:34 +0200 Subject: [PATCH 08/63] Fix calendar.gif not found --- WEB-INF/lib/form/DateField.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WEB-INF/lib/form/DateField.class.php b/WEB-INF/lib/form/DateField.class.php index d349d568..b14b0bb7 100644 --- a/WEB-INF/lib/form/DateField.class.php +++ b/WEB-INF/lib/form/DateField.class.php @@ -382,9 +382,9 @@ function adjustiFrame(pickerDiv, iFrameDiv) { if (defined('DIR_NAME')) $dir_name = trim(constant('DIR_NAME'), '/'); if (!empty($dir_name)) - $app_root = '/'.$dir_name; + $app_root = '/'.$dir_name.'/'; - $html .= " name."');\">\n"; + $html .= " name."');\">\n"; } return $html; From 42ac5b5f3aaf345b4a2e08beec5cf392a1d03c4d Mon Sep 17 00:00:00 2001 From: Ben0it-T <37017213+Ben0it-T@users.noreply.github.com> Date: Tue, 8 Oct 2024 18:49:18 +0200 Subject: [PATCH 09/63] Update htaccess --- .htaccess | 1 + WEB-INF/.htaccess | 1 + 2 files changed, 2 insertions(+) create mode 100644 WEB-INF/.htaccess diff --git a/.htaccess b/.htaccess index 3b6168c7..aaca1cef 100644 --- a/.htaccess +++ b/.htaccess @@ -1,3 +1,4 @@ +Options -Indexes AddDefaultCharset utf-8 # Restrict access to Time Tracker only from certain IPs. diff --git a/WEB-INF/.htaccess b/WEB-INF/.htaccess new file mode 100644 index 00000000..14249c50 --- /dev/null +++ b/WEB-INF/.htaccess @@ -0,0 +1 @@ +Deny from all \ No newline at end of file From 1bff9a2cd5844db4e12501dea44f588921627d67 Mon Sep 17 00:00:00 2001 From: Ben0it-T <37017213+Ben0it-T@users.noreply.github.com> Date: Thu, 10 Oct 2024 10:18:20 +0200 Subject: [PATCH 10/63] Feature add notes to puncher --- WEB-INF/templates/puncher.tpl | 14 ++++++++ puncher.php | 63 +++++++++++++++++++++++++++++++++-- 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/WEB-INF/templates/puncher.tpl b/WEB-INF/templates/puncher.tpl index 98bd5ca2..926708a9 100644 --- a/WEB-INF/templates/puncher.tpl +++ b/WEB-INF/templates/puncher.tpl @@ -103,11 +103,25 @@ startTimer();
{/if} +{if (isset($template_dropdown) && $template_dropdown)} + + + + {$forms.timeRecordForm.template.control} + +
+{/if} + + + + {$forms.timeRecordForm.note.control} + {$i18n.label.required_fields}
{if isset($forms.timeRecordForm.btn_start.control)}
{$forms.timeRecordForm.btn_start.control}
{/if} {if isset($forms.timeRecordForm.btn_stop.control)}
{$forms.timeRecordForm.btn_stop.control}
{/if} + {if $time_records}
diff --git a/puncher.php b/puncher.php index 26b371fc..6e5633b2 100644 --- a/puncher.php +++ b/puncher.php @@ -4,6 +4,7 @@ require_once('initialize.php'); import('form.Form'); +import('ttConfigHelper'); import('ttUserHelper'); import('ttGroupHelper'); import('ttClientHelper'); @@ -41,6 +42,8 @@ } // End of access checks. +$config = new ttConfigHelper($user->getConfig()); + $showClient = $user->isPluginEnabled('cl'); $showBillable = $user->isPluginEnabled('iv'); $trackingMode = $user->getTrackingMode(); @@ -68,10 +71,34 @@ // Obtain first found uncompleted record, not necessarily for today. Used to prohibit entry in "One uncompleted" mode. $uncompleted = ttTimeHelper::getUncompleted($user->getUser()); +$showNoteColumn = !$config->getDefinedValue('time_note_on_separate_row'); +$showNoteRow = $config->getDefinedValue('time_note_on_separate_row'); +if ($showNoteRow) { + // Determine column span for note field. + $colspan = 0; + if ($showClient) $colspan++; + if ($showRecordCustomFields && isset($custom_fields) && $custom_fields->timeFields) { + foreach ($custom_fields->timeFields as $timeField) { + $colspan++; + } + } + if ($showProject) $colspan++; + if ($showTask) $colspan++; + if ($showStart) $colspan += 2; // Another for show finish. + $colspan++; // There is always a duration. + if ($showFiles) $colspan++; + $colspan++; // There is always an edit column. + // $colspan++; // There is always a delete column. + // $colspan--; // Remove one column for label. + $smarty->assign('colspan', $colspan); +} + + // Initialize variables. $cl_start = $browser_time; $cl_finish = $browser_time; -$cl_duration = $cl_note = null; +$cl_duration = null; + // Disabled controls are not posted. Therefore, && $enable_controls condition in several places below. // This allows us to get values from session when controls are disabled and reset to null when not. $cl_billable = 1; @@ -85,6 +112,8 @@ $_SESSION['project'] = $cl_project; $cl_task = $request->getParameter('task', ($request->isPost() && $enable_controls ? null : @$_SESSION['task'])); $_SESSION['task'] = $cl_task; +$cl_note = $request->getParameter('note', ($request->isPost() && $enable_controls ? null : @$_SESSION['note'])); +$_SESSION['note'] = $cl_note; // Handle time custom fields. $timeCustomFields = array(); @@ -147,9 +176,10 @@ $project_list = $client_list = array(); if ($showProject) { // Dropdown for projects assigned to user. - $project_list = $user->getAssignedProjects(); + $options['include_templates'] = $user->isPluginEnabled('tp') && $config->getDefinedValue('bind_templates_with_projects'); + $project_list = $user->getAssignedProjects($options); $form->addInput(array('type'=>'combobox', - 'onchange'=>'fillTaskDropdown(this.value);', + 'onchange'=>'fillTaskDropdown(this.value);fillTemplateDropdown(this.value);prepopulateNote();', 'name'=>'project', 'enable'=>$enable_controls, 'value'=>$cl_project, @@ -204,6 +234,27 @@ // A hidden control for current time from user's browser. $form->addInput(array('type'=>'hidden','name'=>'browser_time','value'=>'')); // User current time, which gets filled in on button click. +// If we have templates, add a dropdown to select one. +if ($user->isPluginEnabled('tp')){ + $template_list = ttGroupHelper::getActiveTemplates(); + if (count($template_list) >= 1) { + $form->addInput(array('type'=>'combobox', + 'onchange'=>'fillNote(this.value);', + 'name'=>'template', + 'enable'=>$enable_controls, + 'data'=>$template_list, + 'datakeys'=>array('id','name'), + 'empty'=>array(''=>$i18n->get('dropdown.select')))); + $smarty->assign('template_dropdown', 1); + $smarty->assign('bind_templates_with_projects', $config->getDefinedValue('bind_templates_with_projects')); + $smarty->assign('prepopulate_note', $config->getDefinedValue('prepopulate_note')); + $smarty->assign('template_list', $template_list); + } +} + +// Note control. +$form->addInput(array('type'=>'textarea','name'=>'note','value'=>$cl_note,'enable'=>$enable_controls)); + // Start and stop buttons. $enable_start = $uncompletedToday ? false : true; if (!$uncompletedToday) @@ -233,6 +284,10 @@ if ($showTask && $taskRequired) { if (!$cl_task) $err->add($i18n->get('error.task')); } + if (!ttValidString($cl_note, true)) $err->add($i18n->get('error.field'), $i18n->get('label.note')); + if ($user->isPluginEnabled('tp') && !ttValidTemplateText($cl_note)) { + $err->add($i18n->get('error.field'), $i18n->get('label.note')); + } // Finished validating user input. // Prohibit creating entries in future. @@ -328,6 +383,8 @@ $smarty->assign('time_records', $timeRecords); $smarty->assign('show_record_custom_fields', $user->isOptionEnabled('record_custom_fields')); $smarty->assign('show_start', true); +$smarty->assign('show_note_column', $showNoteColumn); +$smarty->assign('show_note_row', $showNoteRow); $smarty->assign('client_list', $client_list); $smarty->assign('project_list', $project_list); $smarty->assign('task_list', $task_list); From 2f831f65d337e7878345053970659ceceab9e8eb Mon Sep 17 00:00:00 2001 From: Ben0it-T <37017213+Ben0it-T@users.noreply.github.com> Date: Thu, 10 Oct 2024 21:25:46 +0200 Subject: [PATCH 11/63] fix puncher get values when uncompletedToday --- puncher.php | 62 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/puncher.php b/puncher.php index 6e5633b2..67848a9c 100644 --- a/puncher.php +++ b/puncher.php @@ -67,6 +67,7 @@ // Obtain first uncompleted record for today. If there are multiples, we operate with first found. $uncompletedToday = ttTimeHelper::getFirstUncompletedForDate($user->getUser(), $date_today->toString()); $enable_controls = ($uncompletedToday == null); +$time_rec = ($uncompletedToday == null ? null : ttTimeHelper::getRecord($uncompletedToday['id'])); // Obtain first found uncompleted record, not necessarily for today. Used to prohibit entry in "One uncompleted" mode. $uncompleted = ttTimeHelper::getUncompleted($user->getUser()); @@ -101,27 +102,64 @@ // Disabled controls are not posted. Therefore, && $enable_controls condition in several places below. // This allows us to get values from session when controls are disabled and reset to null when not. + +//$cl_billable = 1; +//if ($user->isPluginEnabled('iv')) { +// $cl_billable = $request->getParameter('billable', ($request->isPost() && $enable_controls ? null : @$_SESSION['billable'])); +// $_SESSION['billable'] = $cl_billable; +//} +//$cl_client = $request->getParameter('client', ($request->isPost() && $enable_controls ? null : @$_SESSION['client'])); +//$_SESSION['client'] = $cl_client; +//$cl_project = $request->getParameter('project', ($request->isPost() && $enable_controls ? null : @$_SESSION['project'])); +//$_SESSION['project'] = $cl_project; +//$cl_task = $request->getParameter('task', ($request->isPost() && $enable_controls ? null : @$_SESSION['task'])); +//$_SESSION['task'] = $cl_task; +//$cl_note = $request->getParameter('note', ($request->isPost() && $enable_controls ? null : @$_SESSION['note'])); +//$_SESSION['note'] = $cl_note; + +// if there is an uncompleted record for today (enable_controls is false) +// then get values from the first uncompleted record for today +// else get get values from session $cl_billable = 1; -if ($user->isPluginEnabled('iv')) { - $cl_billable = $request->getParameter('billable', ($request->isPost() && $enable_controls ? null : @$_SESSION['billable'])); +$cl_note = ""; +if ($enable_controls) { + // get get values from session + if ($user->isPluginEnabled('iv')) { + $cl_billable = $request->getParameter('billable', ($request->isPost() ? null : @$_SESSION['billable'])); $_SESSION['billable'] = $cl_billable; + } + $cl_client = $request->getParameter('client', ($request->isPost() ? null : @$_SESSION['client'])); + $_SESSION['client'] = $cl_client; + $cl_project = $request->getParameter('project', ($request->isPost() ? null : @$_SESSION['project'])); + $_SESSION['project'] = $cl_project; + $cl_task = $request->getParameter('task', ($request->isPost() ? null : @$_SESSION['task'])); + $_SESSION['task'] = $cl_task; + $cl_note = trim($request->getParameter('note', ($request->isPost() ? null : @$_SESSION['note']))); + $_SESSION['note'] = $cl_note; +} +else { + // get values from the first uncompleted record for today + $cl_billable = $time_rec['billable']; + $cl_client = $time_rec['client_id']; + $cl_project = $time_rec['project_id']; + $cl_task = $time_rec['task_id']; + $cl_note = trim($time_rec['comment']); } -$cl_client = $request->getParameter('client', ($request->isPost() && $enable_controls ? null : @$_SESSION['client'])); -$_SESSION['client'] = $cl_client; -$cl_project = $request->getParameter('project', ($request->isPost() && $enable_controls ? null : @$_SESSION['project'])); -$_SESSION['project'] = $cl_project; -$cl_task = $request->getParameter('task', ($request->isPost() && $enable_controls ? null : @$_SESSION['task'])); -$_SESSION['task'] = $cl_task; -$cl_note = $request->getParameter('note', ($request->isPost() && $enable_controls ? null : @$_SESSION['note'])); -$_SESSION['note'] = $cl_note; // Handle time custom fields. $timeCustomFields = array(); if (isset($custom_fields) && $custom_fields->timeFields) { foreach ($custom_fields->timeFields as $timeField) { $control_name = 'time_field_'.$timeField['id']; - $cl_control_name = $request->getParameter($control_name, ($request->isPost() && $enable_controls ? null : @$_SESSION[$control_name])); - $_SESSION[$control_name] = $cl_control_name; + //$cl_control_name = $request->getParameter($control_name, ($request->isPost() && $enable_controls ? null : @$_SESSION[$control_name])); + //$_SESSION[$control_name] = $cl_control_name; + if ($enable_controls) { + $cl_control_name = $request->getParameter($control_name, ($request->isPost() ? null : @$_SESSION[$control_name])); + $_SESSION[$control_name] = $cl_control_name; + } + else { + $cl_control_name = $custom_fields->getTimeFieldValue($uncompletedToday['id'], $timeField['id'], $timeField['type']); + } $timeCustomFields[$timeField['id']] = array('field_id' => $timeField['id'], 'control_name' => $control_name, 'label' => $timeField['label'], From c6928cbf3d91835ebe88819db25022304298a368 Mon Sep 17 00:00:00 2001 From: Ben0it-T <37017213+Ben0it-T@users.noreply.github.com> Date: Thu, 10 Oct 2024 21:41:18 +0200 Subject: [PATCH 12/63] fix-line-breaks-in-comments --- WEB-INF/templates/puncher.tpl | 2 +- WEB-INF/templates/time.tpl | 2 +- WEB-INF/templates/week.tpl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/WEB-INF/templates/puncher.tpl b/WEB-INF/templates/puncher.tpl index 926708a9..4e894061 100644 --- a/WEB-INF/templates/puncher.tpl +++ b/WEB-INF/templates/puncher.tpl @@ -178,7 +178,7 @@ startTimer(); {/if} {if $show_note_column} - + {/if} {if $show_files} {if $record.has_files} diff --git a/WEB-INF/templates/time.tpl b/WEB-INF/templates/time.tpl index bf68708d..5cdb9fc8 100644 --- a/WEB-INF/templates/time.tpl +++ b/WEB-INF/templates/time.tpl @@ -177,7 +177,7 @@ function handleStop(buttonElement) { {/if} {if $show_note_column} - + {/if} {if $show_files} {if $record.has_files} diff --git a/WEB-INF/templates/week.tpl b/WEB-INF/templates/week.tpl index 01234e19..0d095dbb 100644 --- a/WEB-INF/templates/week.tpl +++ b/WEB-INF/templates/week.tpl @@ -151,7 +151,7 @@ function fillDropdowns() { {/if} - + {if $show_files} {if $record.has_files} From c707867d01ba235d4376d99dc2755608f370f8cd Mon Sep 17 00:00:00 2001 From: Ben0it-T <37017213+Ben0it-T@users.noreply.github.com> Date: Fri, 11 Oct 2024 06:33:36 +0200 Subject: [PATCH 13/63] fix-line-breaks-in-comments-2 --- WEB-INF/templates/report.tpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WEB-INF/templates/report.tpl b/WEB-INF/templates/report.tpl index b63e505f..29592efe 100644 --- a/WEB-INF/templates/report.tpl +++ b/WEB-INF/templates/report.tpl @@ -171,7 +171,7 @@ License: See license.txt *} {if $bean->getAttribute('chfinish')}{/if} {if $bean->getAttribute('chduration')}{/if} {if $bean->getAttribute('chunits')}{/if} - {if $bean->getAttribute('chnote') && !$note_on_separate_row}{/if} + {if $bean->getAttribute('chnote') && !$note_on_separate_row}{/if} {if $bean->getAttribute('chcost')} {if $show_cost_per_hour}{/if} @@ -199,7 +199,7 @@ License: See license.txt *} {if $note_on_separate_row && $bean->getAttribute('chnote') && $item.note} - + {/if} {$prev_date = $item.date} From cc7e5916359080911126d34e4ddbb742fc0da850 Mon Sep 17 00:00:00 2001 From: Ben0it-T <37017213+Ben0it-T@users.noreply.github.com> Date: Fri, 11 Oct 2024 06:49:11 +0200 Subject: [PATCH 14/63] fix-line-breaks-in-comments-3 --- WEB-INF/lib/ttReportHelper.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WEB-INF/lib/ttReportHelper.class.php b/WEB-INF/lib/ttReportHelper.class.php index fb802f95..a8554fcf 100644 --- a/WEB-INF/lib/ttReportHelper.class.php +++ b/WEB-INF/lib/ttReportHelper.class.php @@ -1521,7 +1521,7 @@ static function prepareReportBody($options, $comment = null) if ($options['show_work_units']) $body .= ''; if ($show_note_column) - $body .= ''; + $body .= ''; if ($show_cost_per_hour) $body .= ''; if ($options['show_cost']) @@ -1549,7 +1549,7 @@ static function prepareReportBody($options, $comment = null) if ($show_note_row && $record['note']) { $body .= ''; $body .= ''; - $body .= ''; + $body .= ''; $body .= ''; } $prev_date = $record['date']; From 060af9a7835d83341284c393177408266f7b4000 Mon Sep 17 00:00:00 2001 From: Ben0it-T <37017213+Ben0it-T@users.noreply.github.com> Date: Fri, 11 Oct 2024 07:59:02 +0200 Subject: [PATCH 15/63] Fix reports fillDropdowns --- WEB-INF/templates/reports.tpl | 3 ++- reports.php | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/WEB-INF/templates/reports.tpl b/WEB-INF/templates/reports.tpl index 485cfc12..c4cc86ed 100644 --- a/WEB-INF/templates/reports.tpl +++ b/WEB-INF/templates/reports.tpl @@ -211,7 +211,8 @@ function fillDropdowns() { if(document.body.contains(document.reportForm.client)) fillProjectDropdown(document.reportForm.client.value); - fillTaskDropdown(document.reportForm.project.value); + if(document.body.contains(document.reportForm.project)) + fillTaskDropdown(document.reportForm.project.value); } // Build JavaScript array for assigned projects out of passed in PHP array. diff --git a/reports.php b/reports.php index aabeec08..4566b2a1 100644 --- a/reports.php +++ b/reports.php @@ -532,7 +532,7 @@ $smarty->assign('task_list', $task_list); $smarty->assign('assigned_projects', $assigned_projects); $smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('onload', 'onLoad="handleCheckboxes();fillDropdowns()"'); +$smarty->assign('onload', 'onLoad="handleCheckboxes();fillDropdowns();"'); $smarty->assign('title', $i18n->get('title.reports')); $smarty->assign('content_page_name', 'reports.tpl'); $smarty->display('index.tpl'); From 774ccc2a1113509fc811be739c1479f5c294c6a8 Mon Sep 17 00:00:00 2001 From: Ben0it-T <37017213+Ben0it-T@users.noreply.github.com> Date: Fri, 11 Oct 2024 19:42:51 +0200 Subject: [PATCH 16/63] Fix group_edit handlePluginCheckboxes is not defined --- group_edit.php | 1 - 1 file changed, 1 deletion(-) diff --git a/group_edit.php b/group_edit.php index dbfca56b..6018ce4a 100644 --- a/group_edit.php +++ b/group_edit.php @@ -235,7 +235,6 @@ $smarty->assign('group_dropdown', count($groups) > 1); $smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('onload', 'onLoad="handlePluginCheckboxes();"'); $smarty->assign('title', $i18n->get('title.edit_group')); $smarty->assign('content_page_name', 'group_edit.tpl'); $smarty->display('index.tpl'); From d60e20bc9e89f681f71eed950dc1ebf6084424c7 Mon Sep 17 00:00:00 2001 From: Ben0it-T <37017213+Ben0it-T@users.noreply.github.com> Date: Sat, 12 Oct 2024 00:27:01 +0200 Subject: [PATCH 17/63] Fix reports incorrect use of label --- WEB-INF/templates/reports.tpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WEB-INF/templates/reports.tpl b/WEB-INF/templates/reports.tpl index c4cc86ed..1f18edc0 100644 --- a/WEB-INF/templates/reports.tpl +++ b/WEB-INF/templates/reports.tpl @@ -394,9 +394,9 @@ function handleCheckboxes() { {/if} {if $show_project} - + - + From 057ab8e2e5c2665a66151caf57366f151b7889fb Mon Sep 17 00:00:00 2001 From: Ben0it-T <37017213+Ben0it-T@users.noreply.github.com> Date: Sat, 12 Oct 2024 11:25:06 +0200 Subject: [PATCH 18/63] Update database structure --- dbinstall.php | 12 +++++++++++- mysql.sql | 4 ++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/dbinstall.php b/dbinstall.php index 8eba7e9b..15cfa623 100644 --- a/dbinstall.php +++ b/dbinstall.php @@ -1208,6 +1208,12 @@ function ttGenerateKeys() { ttExecute("UPDATE `tt_site_config` SET param_value = '1.22.4', modified = now() where param_name = 'version_db' and param_value = '1.22.3'"); } + if (array_key_exists('convert12204to12205', $_POST)) { + // Feature store sessions in database + ttExecute("ALTER TABLE `tt_users` CHANGE `password` `password` VARCHAR(255)"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.22.5', modified = now() where param_name = 'version_db' and param_value = '1.22.4'"); + } + if (array_key_exists('cleanup', $_POST)) { $mdb2 = getConnection(); $inactive_orgs = ttOrgHelper::getInactiveOrgs(); @@ -1265,7 +1271,7 @@ function ttGenerateKeys() {

DB Install

{if ($record.duration == '0:00' && $record.start <> '')}{$i18n.form.time.uncompleted}{else}{$record.duration}{/if}{if $record.comment}{$record.comment|escape}{else} {/if}{if $record.comment}{nl2br($record.comment|escape)}{else} {/if}{if ($record.duration == '0:00' && $record.start <> '')}{$i18n.form.time.uncompleted}{else}{$record.duration}{/if}{if $record.comment}{$record.comment|escape}{else} {/if}{if $record.comment}{nl2br($record.comment|escape)}{else} {/if}{if $record.finish}{$record.finish}{else} {/if}{if ($record.duration == '0:00' && $record.start <> '')}{$i18n.form.time.uncompleted}{else}{$record.duration}{/if}{if $record.comment}{$record.comment|escape}{else} {/if}{if $record.comment}{nl2br($record.comment|escape)}{else} {/if}{$i18n.label.files}{$item.finish}{$item.duration}{$item.units}{$item.note|escape}{nl2br($item.note|escape)}{if $user->can('manage_invoices') || $user->isClient()}{$item.cost_per_hour}{/if}{if $user->can('manage_invoices') || $user->isClient()}{$item.cost}{else}{$item.expense}{/if}
{$i18n.label.note}{$item.note|escape}{nl2br($item.note|escape)}
'.$record['units'].''.htmlspecialchars($record['note']).''.nl2br(htmlspecialchars($record['note'])).''.$record['cost_per_hour'].'
'.$i18n->get('label.note').':'.$record['note'].''.nl2br(htmlspecialchars($record['note'])).'
{$forms.reportForm.project.control}
-
Create database structure (v1.22.4) + Create database structure (v1.22.5)
(applies only to new installations, do not execute when updating)
@@ -1321,6 +1327,10 @@ function ttGenerateKeys() { Update database structure (v1.22.3 to v1.22.4) + + Update database structure (v1.22.4 to v1.22.5) + +

DB Maintenance

diff --git a/mysql.sql b/mysql.sql index 6b9e4da6..f25a526f 100644 --- a/mysql.sql +++ b/mysql.sql @@ -88,7 +88,7 @@ INSERT INTO `tt_roles` (`group_id`, `name`, `rank`, `rights`) VALUES (0, 'Top ma CREATE TABLE `tt_users` ( `id` int(11) NOT NULL auto_increment, # user id `login` varchar(80) COLLATE utf8mb4_bin NOT NULL,# user login - `password` varchar(50) default NULL, # password hash + `password` varchar(255) default NULL, # password hash `name` varchar(80) default NULL, # user name `group_id` int(11) NOT NULL, # group id `org_id` int(11) default NULL, # organization id @@ -623,7 +623,7 @@ CREATE TABLE `tt_site_config` ( PRIMARY KEY (`param_name`) ); -INSERT INTO `tt_site_config` (`param_name`, `param_value`, `created`) VALUES ('version_db', '1.22.4', now()); # TODO: change when structure changes. +INSERT INTO `tt_site_config` (`param_name`, `param_value`, `created`) VALUES ('version_db', '1.22.5', now()); # TODO: change when structure changes. # From 297863bfd0d6a170bd0c447384aadf477809f8b7 Mon Sep 17 00:00:00 2001 From: Ben0it-T <37017213+Ben0it-T@users.noreply.github.com> Date: Sat, 12 Oct 2024 15:39:07 +0200 Subject: [PATCH 19/63] Add option to set hash algorithm --- WEB-INF/config.php.dist | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/WEB-INF/config.php.dist b/WEB-INF/config.php.dist index b5f078d2..e2f756fc 100644 --- a/WEB-INF/config.php.dist +++ b/WEB-INF/config.php.dist @@ -139,6 +139,15 @@ define('REPORT_FOOTER', true); // ldap - authentication against an LDAP directory such as OpenLDAP or Windows Active Directory. define('AUTH_MODULE', 'db'); +// Password hash algorithm +// Possible values +// - DEFAULT ; bcrypt algorithm +// - BCRYPT : crypt blowfish algorithm +// - ARGON2I : Argon2i hashing algorithm (only available if PHP has been compiled with Argon2 support) +// - ARGON2ID : Argon2id hashing algorithm (only available if PHP has been compiled with Argon2 support) +define('AUTH_DB_HASH_ALGORITHM', 'BCRYPT'); + + // LDAP authentication examples. // Go to https://www.anuko.com/time-tracker/install-guide/ldap-auth/index.htm for detailed configuration instructions. From 6cac07ca09fcc848af0cfeb86f0417c18d224d4a Mon Sep 17 00:00:00 2001 From: Ben0it-T <37017213+Ben0it-T@users.noreply.github.com> Date: Sat, 12 Oct 2024 22:10:07 +0200 Subject: [PATCH 20/63] Add password hash stuff --- WEB-INF/lib/auth/Auth_db.class.php | 37 +++++++++++++++++-- WEB-INF/lib/ttAdmin.class.php | 32 ++++++++++++++--- WEB-INF/lib/ttRegistrator.class.php | 8 ++++- WEB-INF/lib/ttUserHelper.class.php | 32 +++++++++++++---- dbinstall.php | 6 ++++ initialize.php | 55 ++++++++++++++++++++++++++++- 6 files changed, 155 insertions(+), 15 deletions(-) diff --git a/WEB-INF/lib/auth/Auth_db.class.php b/WEB-INF/lib/auth/Auth_db.class.php index b5fbdaef..ed0904ad 100644 --- a/WEB-INF/lib/auth/Auth_db.class.php +++ b/WEB-INF/lib/auth/Auth_db.class.php @@ -19,9 +19,41 @@ function authenticate($login, $password) { $mdb2 = getConnection(); + if (AUTH_DB_HASH_ALGORITHM !== '') { + $sql = "SELECT id, password as hash FROM tt_users"." WHERE login = ".$mdb2->quote($login)." AND status = 1"; + $res = $mdb2->query($sql); + if (is_a($res, 'PEAR_Error')) { + die($res->getMessage()); + } + $val = $res->fetchRow(); + if (isset($val['id']) && $val['id'] > 0) { + if (password_verify($password, $val['hash'])) { + if (password_needs_rehash($val['hash'], PASSWORD_ALGORITHM, AUTH_DB_HASH_ALGORITHM_OPTIONS)) { + $sql = "update `tt_users` set `password` = '".password_hash($password, PASSWORD_ALGORITHM, AUTH_DB_HASH_ALGORITHM_OPTIONS)."' where `id` = " . $mdb2->quote($val['id']); + $affected = $mdb2->exec($sql); + if (is_a($res, 'PEAR_Error')) die($res->getMessage()); + } + return array('login'=>$login,'id'=>$val['id']); + } + } + } + else { + // md5 hash + $sql = "SELECT id FROM tt_users"." WHERE login = ".$mdb2->quote($login)." AND password = md5(".$mdb2->quote($password).") AND status = 1"; + $res = $mdb2->query($sql); + if (is_a($res, 'PEAR_Error')) { + die($res->getMessage()); + } + $val = $res->fetchRow(); + if (isset($val['id']) && $val['id'] > 0) { + return array('login'=>$login,'id'=>$val['id']); + } + } + return false; + + /* // Try md5 password match first. - $sql = "SELECT id FROM tt_users". - " WHERE login = ".$mdb2->quote($login)." AND password = md5(".$mdb2->quote($password).") AND status = 1"; + $sql = "SELECT id FROM tt_users"." WHERE login = ".$mdb2->quote($login)." AND password = md5(".$mdb2->quote($password).") AND status = 1"; $res = $mdb2->query($sql); if (is_a($res, 'PEAR_Error')) { @@ -74,6 +106,7 @@ function authenticate($login, $password) } return false; + */ } function isPasswordExternal() { diff --git a/WEB-INF/lib/ttAdmin.class.php b/WEB-INF/lib/ttAdmin.class.php index 4a631d5e..b3c3e293 100644 --- a/WEB-INF/lib/ttAdmin.class.php +++ b/WEB-INF/lib/ttAdmin.class.php @@ -119,8 +119,15 @@ static function updateGroup($fields) { $user_id = $fields['user_id']; $login_part = 'login = '.$mdb2->quote($fields['new_login']); $password_part = ''; - if ($fields['password1']) - $password_part = ', password = md5('.$mdb2->quote($fields['password1']).')'; + if ($fields['password1']) { + if (AUTH_DB_HASH_ALGORITHM !== '') { + $password_part = ', password = ' . $mdb2->quote(password_hash($fields['password1'], PASSWORD_ALGORITHM, AUTH_DB_HASH_ALGORITHM_OPTIONS)); + } + else { + // md5 hash + $password_part = ', password = md5('.$mdb2->quote($fields['password1']).')'; + } + } $name_part = ', name = '.$mdb2->quote($fields['user_name']); $email_part = ', email = '.$mdb2->quote($fields['email']); $sql = 'update tt_users set '.$login_part.$password_part.$name_part.$email_part.$modified_part.' where id = '.$user_id; @@ -139,8 +146,15 @@ static function updateSelf($fields) { $user_id = $user->id; $login_part = 'login = '.$mdb2->quote($fields['login']); $password_part = ''; - if (isset($fields['password1'])) - $password_part = ', password = md5('.$mdb2->quote($fields['password1']).')'; + if (isset($fields['password1'])) { + if (AUTH_DB_HASH_ALGORITHM !== '') { + $password_part = ', password = ' . $mdb2->quote(password_hash($fields['password1'], PASSWORD_ALGORITHM, AUTH_DB_HASH_ALGORITHM_OPTIONS)); + } + else { + // md5 hash + $password_part = ', password = md5('.$mdb2->quote($fields['password1']).')'; + } + } $name_part = ', name = '.$mdb2->quote($fields['name']); $email_part = ', email = '.$mdb2->quote($fields['email']); $modified_part = ', modified = now(), modified_ip = '.$mdb2->quote($_SERVER['REMOTE_ADDR']).', modified_by = '.$user->id; @@ -274,7 +288,15 @@ static function createOrgManager($fields) { $role_id = ttRoleHelper::getTopManagerRoleID(); $login = $mdb2->quote($fields['login']); - $password = 'md5('.$mdb2->quote($fields['password']).')'; + + if (AUTH_DB_HASH_ALGORITHM !== '') { + $password = $mdb2->quote(password_hash($fields['password'], PASSWORD_ALGORITHM, AUTH_DB_HASH_ALGORITHM_OPTIONS)); + } + else { + // md5 hash + $password = 'md5('.$mdb2->quote($fields['password']).')'; + } + $name = $mdb2->quote($fields['user_name']); $group_id = (int) $fields['group_id']; $org_id = $group_id; diff --git a/WEB-INF/lib/ttRegistrator.class.php b/WEB-INF/lib/ttRegistrator.class.php index 8afbf665..2c490535 100644 --- a/WEB-INF/lib/ttRegistrator.class.php +++ b/WEB-INF/lib/ttRegistrator.class.php @@ -142,7 +142,13 @@ function createUser() { $mdb2 = getConnection(); $login = $mdb2->quote($this->login); - $password = 'md5('.$mdb2->quote($this->password1).')'; + if (AUTH_DB_HASH_ALGORITHM !== '') { + $password = $mdb2->quote(password_hash($this->password1, PASSWORD_ALGORITHM, AUTH_DB_HASH_ALGORITHM_OPTIONS)); + } + else { + // md5 hash + $password = 'md5('.$mdb2->quote($this->password1).')'; + } $name = $mdb2->quote($this->user_name); $email = $mdb2->quote($this->email); $created = 'now()'; diff --git a/WEB-INF/lib/ttUserHelper.class.php b/WEB-INF/lib/ttUserHelper.class.php index 2aeada43..3d11abd0 100644 --- a/WEB-INF/lib/ttUserHelper.class.php +++ b/WEB-INF/lib/ttUserHelper.class.php @@ -79,9 +79,16 @@ static function insert($fields, $hash = true) { global $user; $mdb2 = getConnection(); - $password = $mdb2->quote($fields['password']); - if($hash) - $password = 'md5('.$password.')'; + if($hash) { + if (AUTH_DB_HASH_ALGORITHM !== '') { + $password = $mdb2->quote(password_hash($fields['password'], PASSWORD_ALGORITHM, AUTH_DB_HASH_ALGORITHM_OPTIONS)); + } + else { + // md5 hash + $password = 'md5('.$mdb2->quote($fields['password']).')'; + } + } + $email = isset($fields['email']) ? $fields['email'] : ''; $group_id = (int) $fields['group_id']; $org_id = (int) $fields['org_id']; @@ -142,8 +149,15 @@ static function update($user_id, $fields) { $login_part = ", login = ".$mdb2->quote($fields['login']); } - if (isset($fields['password'])) - $pass_part = ', password = md5('.$mdb2->quote($fields['password']).')'; + if (isset($fields['password'])) { + if (AUTH_DB_HASH_ALGORITHM !== '') { + $pass_part = ', password = ' . $mdb2->quote(password_hash($fields['password'], PASSWORD_ALGORITHM, AUTH_DB_HASH_ALGORITHM_OPTIONS)); + } + else { + // md5 hash + $pass_part = ', password = md5('.$mdb2->quote($fields['password']).')'; + } + } if (isset($fields['name'])) $name_part = ', name = '.$mdb2->quote($fields['name']); @@ -342,7 +356,13 @@ static function saveTmpRef($ref, $user_id) { static function setPassword($user_id, $password) { $mdb2 = getConnection(); - $sql = "update tt_users set password = md5(".$mdb2->quote($password).") where id = $user_id"; + if (AUTH_DB_HASH_ALGORITHM !== '') { + $sql = "update `tt_users` set `password` = '".password_hash($password, PASSWORD_ALGORITHM, AUTH_DB_HASH_ALGORITHM_OPTIONS)."' where `id` = $user_id"; + } + else { + // md5 hash + $sql = "update tt_users set password = md5(".$mdb2->quote($password).") where id = $user_id"; + } $affected = $mdb2->exec($sql); if (!is_a($affected, 'PEAR_Error')) { diff --git a/dbinstall.php b/dbinstall.php index 15cfa623..7d77d954 100644 --- a/dbinstall.php +++ b/dbinstall.php @@ -212,6 +212,12 @@ function ttGenerateKeys() { } } } + + if (AUTH_DB_HASH_ALGORITHM !== '') { + // Update admin password + $sql = "update `tt_users` set `password` = '".password_hash("secret", PASSWORD_ALGORITHM, AUTH_DB_HASH_ALGORITHM_OPTIONS)."' where `login` = 'admin'"; + ttExecute($sql); + } } if (array_key_exists('convert5to7', $_POST)) { diff --git a/initialize.php b/initialize.php index f19456a1..9993c86c 100644 --- a/initialize.php +++ b/initialize.php @@ -51,8 +51,61 @@ check_extension('mbstring'); // If auth params are not defined (in config.php) - initialize with an empty array. -if (!isset($GLOBALS['AUTH_MODULE_PARAMS']) || !is_array($GLOBALS['AUTH_MODULE_PARAMS'])) +if (!isset($GLOBALS['AUTH_MODULE_PARAMS']) || !is_array($GLOBALS['AUTH_MODULE_PARAMS'])) { $GLOBALS['AUTH_MODULE_PARAMS'] = array(); +} + +// if password hash algorithm is not defined (in config.php) +if (!defined('AUTH_DB_HASH_ALGORITHM')) define('AUTH_DB_HASH_ALGORITHM', ''); + +if (AUTH_DB_HASH_ALGORITHM !== '') { + if (in_array(AUTH_DB_HASH_ALGORITHM, array("DEFAULT", "BCRYPT", "ARGON2I", "ARGON2ID"))) { + switch (AUTH_DB_HASH_ALGORITHM) { + case 'BCRYPT': + define('PASSWORD_ALGORITHM', PASSWORD_BCRYPT); + if (!defined('AUTH_DB_HASH_ALGORITHM_OPTIONS')) { + define('AUTH_DB_HASH_ALGORITHM_OPTIONS', array( + 'cost' => 10 + )); + } + break; + + case 'ARGON2I': + define('PASSWORD_ALGORITHM', PASSWORD_ARGON2I); + if (!defined('AUTH_DB_HASH_ALGORITHM_OPTIONS')) { + define('AUTH_DB_HASH_ALGORITHM_OPTIONS', array( + 'memory_cost' => PASSWORD_ARGON2_DEFAULT_MEMORY_COST, + 'time_cost' => PASSWORD_ARGON2_DEFAULT_TIME_COST, + 'threads' => PASSWORD_ARGON2_DEFAULT_THREADS + )); + } + break; + + case 'ARGON2ID': + define('PASSWORD_ALGORITHM', PASSWORD_ARGON2ID); + if (!defined('AUTH_DB_HASH_ALGORITHM_OPTIONS')) { + define('AUTH_DB_HASH_ALGORITHM_OPTIONS', array( + 'memory_cost' => PASSWORD_ARGON2_DEFAULT_MEMORY_COST, + 'time_cost' => PASSWORD_ARGON2_DEFAULT_TIME_COST, + 'threads' => PASSWORD_ARGON2_DEFAULT_THREADS + )); + } + break; + + default: + define('PASSWORD_ALGORITHM', PASSWORD_DEFAULT); + if (!defined('AUTH_DB_HASH_ALGORITHM_OPTIONS')) { + define('AUTH_DB_HASH_ALGORITHM_OPTIONS', array( + 'cost' => 10 + )); + } + break; + } + } + else { + die ("This hash algorithm is not alowed. Check your config file."); + } +} // Smarty initialization. import('smarty.Smarty'); From 17f0fd1ba155fd85c9b23d40b184730bdba4c16c Mon Sep 17 00:00:00 2001 From: Ben0it-T <37017213+Ben0it-T@users.noreply.github.com> Date: Sat, 12 Oct 2024 22:14:27 +0200 Subject: [PATCH 21/63] Add option to set hash options --- WEB-INF/config.php.dist | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/WEB-INF/config.php.dist b/WEB-INF/config.php.dist index e2f756fc..db81e5d4 100644 --- a/WEB-INF/config.php.dist +++ b/WEB-INF/config.php.dist @@ -147,6 +147,10 @@ define('AUTH_MODULE', 'db'); // - ARGON2ID : Argon2id hashing algorithm (only available if PHP has been compiled with Argon2 support) define('AUTH_DB_HASH_ALGORITHM', 'BCRYPT'); +// Password hash options +// +define('AUTH_DB_HASH_ALGORITHM_OPTIONS', array('cost' => 10)); + // LDAP authentication examples. // Go to https://www.anuko.com/time-tracker/install-guide/ldap-auth/index.htm for detailed configuration instructions. From bfda5b7fee9b89788a23b0704011e531f61c8cba Mon Sep 17 00:00:00 2001 From: Ben0it-T <37017213+Ben0it-T@users.noreply.github.com> Date: Sat, 12 Oct 2024 23:03:34 +0200 Subject: [PATCH 22/63] Update fr.lang --- WEB-INF/resources/fr.lang.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/WEB-INF/resources/fr.lang.php b/WEB-INF/resources/fr.lang.php index f0c4293a..f9f4c51a 100644 --- a/WEB-INF/resources/fr.lang.php +++ b/WEB-INF/resources/fr.lang.php @@ -632,9 +632,8 @@ // Display Options form. See example at https://timetracker.anuko.com/display_options.php. 'form.display_options.note_on_separate_row' => 'Commentaire sur une ligne séparée', 'form.display_options.not_complete_days' => 'Journées incomplètes', -// TODO: translate the following. -// 'form.display_options.inactive_projects' => 'Inactive projects', -// 'form.display_options.cost_per_hour' => 'Cost per hour', -// 'form.display_options.custom_css' => 'Custom CSS', -// 'form.display_options.custom_translation' => 'Custom translation', +'form.display_options.inactive_projects' => 'Projets inactifs', +'form.display_options.cost_per_hour' => 'Coût par heure', +'form.display_options.custom_css' => 'CSS personnalisé', +'form.display_options.custom_translation' => 'Traduction personnalisée', ); From b780ed9e9315feb6635d65cbcaac8c2055d0ddfe Mon Sep 17 00:00:00 2001 From: Ben0it-T <37017213+Ben0it-T@users.noreply.github.com> Date: Sun, 13 Oct 2024 17:30:10 +0200 Subject: [PATCH 23/63] Upgrade smarty 4.5.1 --- WEB-INF/lib/smarty/Smarty.class.php | 7 ++-- WEB-INF/lib/smarty/debug.tpl | 4 +-- WEB-INF/lib/smarty/plugins/function.math.php | 2 +- .../lib/smarty/plugins/modifier.escape.php | 4 ++- .../lib/smarty/plugins/modifier.implode.php | 15 ++++++++ .../lib/smarty/plugins/modifier.truncate.php | 2 +- .../modifiercompiler.count_characters.php | 4 +-- .../plugins/modifiercompiler.count_words.php | 2 +- .../plugins/modifiercompiler.escape.php | 4 ++- .../plugins/modifiercompiler.json_encode.php | 11 ++++++ .../smarty/plugins/modifiercompiler.lower.php | 4 +-- .../plugins/modifiercompiler.strip_tags.php | 2 +- .../plugins/modifiercompiler.substr.php | 12 +++++++ .../smarty/plugins/modifiercompiler.upper.php | 4 +-- .../plugins/outputfilter.trimwhitespace.php | 2 +- .../plugins/shared.escape_special_chars.php | 2 +- .../variablefilter.htmlspecialchars.php | 2 +- ...arty_internal_compile_private_modifier.php | 8 +++-- ...ernal_compile_private_print_expression.php | 2 +- .../sysplugins/smarty_internal_debug.php | 9 +++-- .../smarty_internal_errorhandler.php | 34 +++++++++++++++++-- .../smarty_internal_templatecompilerbase.php | 12 ++++++- .../smarty_internal_templateparser.php | 3 ++ .../lib/smarty/sysplugins/smarty_security.php | 2 +- .../sysplugins/smarty_template_compiled.php | 2 +- 25 files changed, 121 insertions(+), 34 deletions(-) create mode 100644 WEB-INF/lib/smarty/plugins/modifier.implode.php create mode 100644 WEB-INF/lib/smarty/plugins/modifiercompiler.json_encode.php create mode 100644 WEB-INF/lib/smarty/plugins/modifiercompiler.substr.php diff --git a/WEB-INF/lib/smarty/Smarty.class.php b/WEB-INF/lib/smarty/Smarty.class.php index 5d2e3a4b..1bfc5646 100644 --- a/WEB-INF/lib/smarty/Smarty.class.php +++ b/WEB-INF/lib/smarty/Smarty.class.php @@ -107,7 +107,7 @@ class Smarty extends Smarty_Internal_TemplateBase /** * smarty version */ - const SMARTY_VERSION = '4.3.0'; + const SMARTY_VERSION = '4.5.1'; /** * define variable scopes */ @@ -1386,8 +1386,7 @@ private function _normalizeTemplateConfig($isConfig) } /** - * Activates PHP7 compatibility mode: - * - converts E_WARNINGS for "undefined array key" and "trying to read property of null" errors to E_NOTICE + * Mutes errors for "undefined index", "undefined array key" and "trying to read property of null". * * @void */ @@ -1396,7 +1395,7 @@ public function muteUndefinedOrNullWarnings(): void { } /** - * Indicates if PHP7 compatibility mode is set. + * Indicates if Smarty will mute errors for "undefined index", "undefined array key" and "trying to read property of null". * @bool */ public function isMutingUndefinedOrNullWarnings(): bool { diff --git a/WEB-INF/lib/smarty/debug.tpl b/WEB-INF/lib/smarty/debug.tpl index 4f82a582..cd932566 100644 --- a/WEB-INF/lib/smarty/debug.tpl +++ b/WEB-INF/lib/smarty/debug.tpl @@ -167,9 +167,7 @@ {/capture} diff --git a/WEB-INF/lib/smarty/plugins/function.math.php b/WEB-INF/lib/smarty/plugins/function.math.php index f9cf67fe..34912d23 100644 --- a/WEB-INF/lib/smarty/plugins/function.math.php +++ b/WEB-INF/lib/smarty/plugins/function.math.php @@ -67,7 +67,7 @@ function smarty_function_math($params, $template) $equation = preg_replace('/\s+/', '', $equation); // Adapted from https://www.php.net/manual/en/function.eval.php#107377 - $number = '(?:\d+(?:[,.]\d+)?|pi|π)'; // What is a number + $number = '-?(?:\d+(?:[,.]\d+)?|pi|π)'; // What is a number $functionsOrVars = '((?:0x[a-fA-F0-9]+)|([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*))'; $operators = '[,+\/*\^%-]'; // Allowed math operators $regexp = '/^(('.$number.'|'.$functionsOrVars.'|('.$functionsOrVars.'\s*\((?1)*\)|\((?1)*\)))(?:'.$operators.'(?1))?)+$/'; diff --git a/WEB-INF/lib/smarty/plugins/modifier.escape.php b/WEB-INF/lib/smarty/plugins/modifier.escape.php index 11e44682..e168679c 100644 --- a/WEB-INF/lib/smarty/plugins/modifier.escape.php +++ b/WEB-INF/lib/smarty/plugins/modifier.escape.php @@ -115,7 +115,9 @@ function smarty_modifier_escape($string, $esc_type = 'html', $char_set = null, $ // see https://html.spec.whatwg.org/multipage/scripting.html#restrictions-for-contents-of-script-elements '