From a7888e1a5849d3a78fb50a49af4a574aa1b72c53 Mon Sep 17 00:00:00 2001 From: Meteo Date: Mon, 13 Jul 2026 23:44:30 +0200 Subject: [PATCH 1/7] fix: List of travel entries in travel reimbursement PDF is not sorted on date. --- pub/api/classes/API.php | 26 +++++++++++++++++++ pub/api/v1/office/travel-reimburse/index.php | 12 ++++++++- .../v1/office/travel-reimburse/send/index.php | 6 +++-- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/pub/api/classes/API.php b/pub/api/classes/API.php index cd9dab1..14e219d 100644 --- a/pub/api/classes/API.php +++ b/pub/api/classes/API.php @@ -751,12 +751,38 @@ class API $values[] = $from; $values[] = $to; } + + // ORDER BY + if (isset($builder['orderBy']) && is_array($builder['orderBy'])) { + + if (count($builder['orderBy']) !== 2) { + continue; + } + + $column = $builder['orderBy'][0]; + $direction = strtoupper($builder['orderBy'][1]); + + if (!in_array($column, $this->allowedGetColumns, true)) { + $this->apiOutput(400, ['error' => "The column $column is not allowed."]); + } + + if (!in_array($direction, ['ASC', 'DESC'], true)) { + $this->apiOutput(400, ['error' => "Invalid order direction."]); + } + + $orderByClauses[] = "$column $direction"; + } + } if (!empty($whereClauses)) { $this->baseQuery .= " WHERE " . implode(" AND ", $whereClauses); } + if (!empty($orderByClauses)) { + $this->baseQuery .= " ORDER BY " . implode(", ", $orderByClauses); + } + return [$this->baseQuery, $types, $values]; } diff --git a/pub/api/v1/office/travel-reimburse/index.php b/pub/api/v1/office/travel-reimburse/index.php index b960765..485d239 100644 --- a/pub/api/v1/office/travel-reimburse/index.php +++ b/pub/api/v1/office/travel-reimburse/index.php @@ -17,7 +17,17 @@ if (!$GLOBALS['modules_enabled']['office']) { if ($API_office_travel_reimburse->request_method === 'GET') { $API_office_travel_reimburse->checkPermissions('office-travel-reimburse', 'RO'); - $_GET['builder'] = [1 => ['where' => [0 => 'user_uuid', 1 => $_SESSION['user']['user_uuid']]]]; + if (!isset($_GET['builder']) || !is_array($_GET['builder'])) { + $_GET['builder'] = []; + } + + # Append user_uuid where builder to always search for the user_uuid that is requesting the travel entries + $_GET['builder'][] = [ + 'where' => [ + 0 => 'user_uuid', + 1 => $_SESSION['user']['user_uuid'] + ] + ]; $travel_reimbursements = $API_office_travel_reimburse->getTravelReimburse(); $API_office_travel_reimburse->apiOutput($code = 200, $travel_reimbursements); diff --git a/pub/api/v1/office/travel-reimburse/send/index.php b/pub/api/v1/office/travel-reimburse/send/index.php index 914a2da..e74ec96 100644 --- a/pub/api/v1/office/travel-reimburse/send/index.php +++ b/pub/api/v1/office/travel-reimburse/send/index.php @@ -20,8 +20,6 @@ if (!$GLOBALS['modules_enabled']['office']) { } if ($API_office_travel_reimburse->request_method === 'POST') { - - ini_set('display_errors', 1); $API_office_travel_reimburse->checkPermissions('office-travel-reimburse', 'RO'); $portal_settings = $GLOBALS['conn']->query("SELECT * FROM system_settings")->fetch_assoc(); $month = $_POST['calender_month']; @@ -38,6 +36,10 @@ if ($API_office_travel_reimburse->request_method === 'POST') { 0 => 'travel_date', 1 => $firstDay, 2 => $lastDay + ], + 'orderBy' => [ + 0 => 'travel_date', + 1 => 'ASC' ] ] ]; From 074129d33bf922c0d835493eecfa3777489e3fac Mon Sep 17 00:00:00 2001 From: Meteo Date: Mon, 13 Jul 2026 23:44:40 +0200 Subject: [PATCH 2/7] Name of the travel reimbursement PDF attachment changed. --- pub/api/v1/office/travel-reimburse/send/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pub/api/v1/office/travel-reimburse/send/index.php b/pub/api/v1/office/travel-reimburse/send/index.php index e74ec96..b323b4d 100644 --- a/pub/api/v1/office/travel-reimburse/send/index.php +++ b/pub/api/v1/office/travel-reimburse/send/index.php @@ -201,7 +201,7 @@ if ($API_office_travel_reimburse->request_method === 'POST') { $mail->mailText = 'Your travel reimbursement is attached.'; try { - $mail->addAttachment($pdfFile, 'travel_reimbursement.pdf'); + $mail->addAttachment($pdfFile, 'Travel reimbursement ' . $_SESSION['user']['user_full_name'] . ' - ' . $invoiceNumber . '.pdf'); } catch (\PHPMailer\PHPMailer\Exception $e) { } From 206eb7f9cd4123ba41311f621d0dc8ee4631acd0 Mon Sep 17 00:00:00 2001 From: Meteo Date: Mon, 13 Jul 2026 23:46:05 +0200 Subject: [PATCH 3/7] Changes in server license creation due to Inserve removing creation of cloud distributor companies. --- pub/api/classes/API_inserve.php | 75 ++++++++++++------- .../inserve/sync-cloud-distributor/index.php | 22 ------ .../inserve/sync-server-licenses/index.php | 1 - .../sync-server-subscriptions/index.php | 1 - pub/bin/locales/en.php | 2 - pub/bin/locales/nl.php | 2 - .../sources/pageSourceInserve.php | 19 ----- 7 files changed, 49 insertions(+), 73 deletions(-) delete mode 100644 pub/api/v1/sources/inserve/sync-cloud-distributor/index.php diff --git a/pub/api/classes/API_inserve.php b/pub/api/classes/API_inserve.php index e7390ff..e5df3c4 100644 --- a/pub/api/classes/API_inserve.php +++ b/pub/api/classes/API_inserve.php @@ -286,7 +286,7 @@ class API_inserve extends API $sizes = array_column($disks, 'disk_space'); $server_disks_count = array_sum($sizes); } - return $server_disks_count; + return (int)ceil($server_disks_count); } private function buildCountObject(string $serverUuid, string $key): array @@ -294,11 +294,11 @@ class API_inserve extends API return [ 'countSentri' => 0, 'countInserve' => 0, - 'sentriCompanyId' => 0, 'SentriStatus' => 0, 'subscriptionInserveExists' => false, 'subscriptionInserveId' => false, - 'subscriptionInserveCompanyId' => false, + 'subscriptionSentriCompanyName' => false, + 'subscriptionInserveCompanyName' => false, 'subscriptionInserveName' => false, 'subscriptionInserveStatus' => 0, 'md5' => md5($serverUuid . ':' . $key), @@ -338,11 +338,6 @@ class API_inserve extends API public function syncServerLicencesToInserve() { - # Get all the linked companies - $this->getLinkedCompanies(); - $allCompanies = json_decode($this->response, true); - $allCompaniesIds = array_column($allCompanies['matched'], 'id', 'company_id'); - # first get the current subscriptions $this->getCloudSubscriptions(); $allInserveCloudSubscriptions = json_decode($this->response, true); @@ -361,7 +356,7 @@ class API_inserve extends API 'id' => (int)$subscription['id'], 'quantity' => (int)$subscription['quantity'], 'status' => (int)$subscription['status'], - 'cloud_distribution_company_id' => (int)$subscription['cloud_distribution_company_id'], + 'company_name' => $subscription['company_name'], 'name' => $subscription['name'], ]; } @@ -421,19 +416,18 @@ class API_inserve extends API if (isset($inserveLookup[$md5])) { # Subscription already exists in Inserve $item['SentriStatus'] = $sentriStatus; - $item['sentriCompanyId'] = (int)$allCompaniesIds[$row['company_source_id']] ?? 0; $item['subscriptionInserveExists'] = true; $item['subscriptionInserveId'] = $inserveLookup[$item['md5']]['id']; $item['countInserve'] = $inserveLookup[$item['md5']]['quantity']; - $item['subscriptionInserveCompanyId'] = $inserveLookup[$item['md5']]['cloud_distribution_company_id']; + $item['subscriptionSentriCompanyName'] = $row['company_name']; + $item['subscriptionInserveCompanyName'] = $inserveLookup[$item['md5']]['company_name']; $item['subscriptionInserveName'] = $inserveLookup[$item['md5']]['name']; - //$item['subscriptionInserveStatus'] = $inserveLookup[$item['md5']]['status']; + $item['subscriptionInserveStatus'] = $inserveLookup[$item['md5']]['status']; } else { # Subscription does not exist in Inserve - $item['sentriCompanyId'] = (int)$allCompaniesIds[$row['company_source_id']] ?? 0; + $item['subscriptionSentriCompanyName'] = $row['company_name']; $item['subscriptionInserveExists'] = false; $item['subscriptionInserveId'] = false; $item['countInserve'] = 0; - $item['subscriptionInserveCompanyId'] = false; } } unset($item); @@ -487,9 +481,9 @@ class API_inserve extends API "cloud_distribution_id" => $item['md5'], #md5 hash based on the server_uuid from sentri and the subscription name (e.g. server_cpu_count) "cloud_subscription_id" => "sentri-servers", # Mark all the sentri-servers subscriptions so we can filter the subscriptions better "name" => $item['subscriptionSentriName'], + "company_name" => $item['subscriptionSentriCompanyName'], "quantity" => ($row['server_state'] === 'deleted') ? 0 : $item['countSentri'], "cloud_distributor" => $this->cloudDistributor, - "cloud_distribution_company_id" => $item['sentriCompanyId'], # this is generated by inserve "status" => $item['SentriStatus'], "period_type" => 0, # 0 = monthly, 1 = annual, 2 = one time cost "start_date" => date('Y-m-d') @@ -500,26 +494,55 @@ class API_inserve extends API continue; } - // update the subscription if the countInserve and countSentri don't match - // Or when sentriCompanyId and subscriptionInserveCompanyId don't match - if (( - (int)$item['countInserve'] !== (int)$item['countSentri'] || - (int)$item['sentriCompanyId'] !== (int)$item['subscriptionInserveCompanyId'] || - (int)$item['SentriStatus'] !== (int)$item['subscriptionInserveStatus'] || - $item['subscriptionSentriName'] !== $item['subscriptionInserveName'] - ) - && $item['subscriptionInserveExists'] !== false + $countMismatch = + (int)$item['subscriptionInserveStatus'] !== 5 && // Do not update the cound when license status is 5 (removed) in Inserve + (int)$item['countInserve'] !== (int)$item['countSentri']; + $statusMismatch = (int)$item['SentriStatus'] !== (int)$item['subscriptionInserveStatus']; + $nameMismatch = $item['subscriptionSentriName'] !== $item['subscriptionInserveName']; + $companyMismatch = $item['subscriptionSentriCompanyName'] !== $item['subscriptionInserveCompanyName']; + $subscriptionExists = $item['subscriptionInserveExists'] !== false; + + if ( + ($countMismatch || + $statusMismatch || + $nameMismatch || + $companyMismatch) + && $subscriptionExists ) { + $output = false; + if ($output) { + echo "

Update triggered because:

"; + + if ($countMismatch) { + echo "Count differs: Inserve={$item['countInserve']} Sentri={$item['countSentri']}
"; + } + + if ($statusMismatch) { + echo "Status differs: Inserve={$item['subscriptionInserveStatus']} Sentri={$item['SentriStatus']}
"; + } + + if ($nameMismatch) { + echo "Name differs: Inserve='{$item['subscriptionInserveName']}' Sentri='{$item['subscriptionSentriName']}'
"; + } + + if ($companyMismatch) { + echo "Company differs: Inserve='{$item['subscriptionInserveCompanyName']}' Sentri='{$item['subscriptionSentriCompanyName']}'
"; + } + + if (!$subscriptionExists) { + echo "Subscription does not exist.
"; + } + } + $payload = [ "quantity" => ($row['server_state'] === 'deleted') ? 0 : $item['countSentri'], "cloud_distributor" => $this->cloudDistributor, - "cloud_distribution_company_id" => (int)$item['sentriCompanyId'], + "company_name" => $item['subscriptionSentriCompanyName'], "name" => $item['subscriptionSentriName'], "status" => $item['SentriStatus'] ]; $this->updateCloudSubscription($item['subscriptionInserveId'], $payload); - } } } diff --git a/pub/api/v1/sources/inserve/sync-cloud-distributor/index.php b/pub/api/v1/sources/inserve/sync-cloud-distributor/index.php deleted file mode 100644 index eda868b..0000000 --- a/pub/api/v1/sources/inserve/sync-cloud-distributor/index.php +++ /dev/null @@ -1,22 +0,0 @@ -request_method === 'GET' || $API_inserve->request_method === 'POST') { - # This syncs the company id's from Sentri to the Inserve cloudDistributor - # These are the same id's but it Inserve requires it to be synced to the cloudDistributor - $API_inserve->checkPermissions('servers', 'RW'); - $API_inserve->setupConnection(); - - $API_inserve->syncCompaniesFromSentri(); - - - $API_inserve->apiOutput(200, ['success' => 'Sync is done successfully']); -} \ No newline at end of file diff --git a/pub/api/v1/sources/inserve/sync-server-licenses/index.php b/pub/api/v1/sources/inserve/sync-server-licenses/index.php index 2216828..17345d6 100644 --- a/pub/api/v1/sources/inserve/sync-server-licenses/index.php +++ b/pub/api/v1/sources/inserve/sync-server-licenses/index.php @@ -13,7 +13,6 @@ if ($API_inserve->request_method === 'GET' || $API_inserve->request_method === ' $API_inserve->checkPermissions('servers', 'RW'); $API_inserve->setupConnection(); - $API_inserve->syncCompaniesFromSentri(); $API_inserve->syncServerLicencesToInserve(); $API_inserve->apiOutput(200, ['success' => 'Sync is done successfully']); diff --git a/pub/api/v1/sources/inserve/sync-server-subscriptions/index.php b/pub/api/v1/sources/inserve/sync-server-subscriptions/index.php index bb22a37..9ff8a11 100644 --- a/pub/api/v1/sources/inserve/sync-server-subscriptions/index.php +++ b/pub/api/v1/sources/inserve/sync-server-subscriptions/index.php @@ -13,7 +13,6 @@ if ($API_inserve->request_method === 'GET' || $API_inserve->request_method === ' $API_inserve->checkPermissions('servers', 'RW'); $API_inserve->setupConnection(); - $API_inserve->syncCompaniesFromSentri(); $API_inserve->syncServerSubscriptionsToInserve(); $API_inserve->apiOutput(200, ['success' => 'Sync is done successfully']); diff --git a/pub/bin/locales/en.php b/pub/bin/locales/en.php index 41c32d0..4211b98 100644 --- a/pub/bin/locales/en.php +++ b/pub/bin/locales/en.php @@ -300,8 +300,6 @@ return [ 'source_custom_data' => "Custom source data", 'settings' => 'settings', 'source_description' => 'Enter the necessary API details to set up and configure your connection to the Inserve API. This allows Sentri to communicate with Inserve and retrieve the data it needs. ', - 'source_inserve_sync_cloud' => 'Sync cloud distributor companies', - 'source_inserve_sync_cloud_desc' => 'This API call synchronizes active companies in Sentri with the corresponding cloud distributor companies in Inserve. These cloud distributor companies are required to associate Sentri server licenses with companies in Inserve.', 'source_inserve_sync_companies' => 'Sync companies from Inserve to Sentri', 'source_inserve_sync_companies_desc' => 'This API call retrieves all companies from Inserve and creates or updates them in Sentri.', 'source_inserve_sync_licenses' => 'Sync servers licenses', diff --git a/pub/bin/locales/nl.php b/pub/bin/locales/nl.php index 4aaeae9..74ba575 100644 --- a/pub/bin/locales/nl.php +++ b/pub/bin/locales/nl.php @@ -300,8 +300,6 @@ return [ 'source_custom_data' => "Custom bron data", 'settings' => 'insetellingen', 'source_description' => 'Voer de benodigde API-gegevens in om uw verbinding met de Inserve API in te stellen en te configureren. Dit stelt Sentri in staat om te communiceren met Inserve en de benodigde gegevens op te halen.', - 'source_inserve_sync_cloud' => 'Synchroniseer cloud-distributeurbedrijven', - 'source_inserve_sync_cloud_desc' => 'Deze API-call synchroniseert actieve bedrijven in Sentri met de bijbehorende cloud-distributeurbedrijven in Inserve. Deze cloud-distributeurbedrijven zijn nodig om Sentri-serverlicenties te koppelen aan bedrijven in Inserve.', 'source_inserve_sync_companies' => 'Synchroniseer bedrijven van Inserve naar Sentri.', 'source_inserve_sync_companies_desc' => 'Deze API-call haalt alle bedrijven op uit Inserve en maakt ze aan of werkt ze bij in Sentri.', 'source_inserve_sync_licenses' => 'Synchroniseer serverlicenties.', diff --git a/pub/bin/pages/portal-management/sources/pageSourceInserve.php b/pub/bin/pages/portal-management/sources/pageSourceInserve.php index 82dde1d..425698e 100644 --- a/pub/bin/pages/portal-management/sources/pageSourceInserve.php +++ b/pub/bin/pages/portal-management/sources/pageSourceInserve.php @@ -129,25 +129,6 @@ $pageNavbar->outPutNavbar(); - checkPermissions('servers', 'RW', true)) { ?> -
-
-
-
-
- -

-
- -
-
-
- - checkPermissions('servers', 'RW', true)) { ?>
From f97bc6a7de8d3ed92a24b7f3633fc783efe14d78 Mon Sep 17 00:00:00 2001 From: Meteo Date: Mon, 13 Jul 2026 23:46:28 +0200 Subject: [PATCH 4/7] fix: Error when deleting last travel entry. --- pub/src/js/sentri/travelReimburse.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pub/src/js/sentri/travelReimburse.js b/pub/src/js/sentri/travelReimburse.js index e416ba4..4902fea 100644 --- a/pub/src/js/sentri/travelReimburse.js +++ b/pub/src/js/sentri/travelReimburse.js @@ -97,6 +97,9 @@ document.addEventListener('DOMContentLoaded', async function () { }) .then(res => res.json()) .then(data => { + if (!Array.isArray(data)) { + data = []; + } let totalDistanceHomework = 0; let totalDistanceBusiness = 0; From b94a3bdab72eabe26a86c24202e5d97eb66bdaf0 Mon Sep 17 00:00:00 2001 From: Meteo Date: Mon, 13 Jul 2026 23:46:58 +0200 Subject: [PATCH 5/7] fix: Error when posting server without server_state. --- pub/api/classes/API_servers.php | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/pub/api/classes/API_servers.php b/pub/api/classes/API_servers.php index 1396968..6f17dd8 100644 --- a/pub/api/classes/API_servers.php +++ b/pub/api/classes/API_servers.php @@ -59,20 +59,21 @@ class API_servers extends API { # if the server_state that is posted is 'deleted' check if the current server state is 'new' if so, delete it perm. - if ($this->data['server_state'] == 'deleted') { - $stmt = $GLOBALS['pdo']->prepare("SELECT server_state FROM servers WHERE server_vm_id = ? AND server_state = 'new'"); - $stmt->execute([$this->data['server_vm_id']]); + if (isset($this->data['server_state'])) { + if ($this->data['server_state'] == 'deleted') { + $stmt = $GLOBALS['pdo']->prepare("SELECT server_state FROM servers WHERE server_vm_id = ? AND server_state = 'new'"); + $stmt->execute([$this->data['server_vm_id']]); - if ($stmt->fetch()) { - $deleteStmt = $GLOBALS['pdo']->prepare("DELETE FROM servers WHERE server_vm_id = ? AND server_state = 'new'"); - $deleteStmt->execute([$this->data['server_vm_id']]); - return; + if ($stmt->fetch()) { + $deleteStmt = $GLOBALS['pdo']->prepare("DELETE FROM servers WHERE server_vm_id = ? AND server_state = 'new'"); + $deleteStmt->execute([$this->data['server_vm_id']]); + return; + } } - } - - if (isset($this->data['company_uuid'])) { - if (strlen($this->data['company_uuid']) == 0) { - $this->data['company_uuid'] = NULL; + if (isset($this->data['company_uuid'])) { + if (strlen($this->data['company_uuid']) == 0) { + $this->data['company_uuid'] = NULL; + } } } From e2bd475236efdea18027443753bf1f432b96e8b9 Mon Sep 17 00:00:00 2001 From: Meteo Date: Mon, 13 Jul 2026 23:48:37 +0200 Subject: [PATCH 6/7] fix: Return url of portal-management forms went to /systemconfig/. --- pub/bin/pages/portal-management/pageSystemConfig.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pub/bin/pages/portal-management/pageSystemConfig.php b/pub/bin/pages/portal-management/pageSystemConfig.php index 6c35985..1ff480b 100644 --- a/pub/bin/pages/portal-management/pageSystemConfig.php +++ b/pub/bin/pages/portal-management/pageSystemConfig.php @@ -87,7 +87,7 @@ while ($module = $system_modules_data->fetch_assoc()) {
- +
@@ -176,7 +176,7 @@ while ($module = $system_modules_data->fetch_assoc()) { - +
@@ -276,7 +276,7 @@ while ($module = $system_modules_data->fetch_assoc()) { - +
@@ -313,7 +313,7 @@ while ($module = $system_modules_data->fetch_assoc()) { - + From d23456ce435dca4dc1ef9d7adca31660a540768c Mon Sep 17 00:00:00 2001 From: Meteo Date: Mon, 13 Jul 2026 23:54:01 +0200 Subject: [PATCH 7/7] final 1.3.1 release --- pub/bin/pages/pageChangelog.php | 2 +- pub/bin/php/Classes/pageBuilder.php | 2 +- pub/src/css/fullcalendar/theme/theme.css | 8 ++++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/pub/bin/pages/pageChangelog.php b/pub/bin/pages/pageChangelog.php index 90d1b62..8d89ed9 100644 --- a/pub/bin/pages/pageChangelog.php +++ b/pub/bin/pages/pageChangelog.php @@ -2,7 +2,7 @@
Sentri
Made by: - Marco Mooij
Version 1.3.0 + Marco Mooij
Version 1.3.1
diff --git a/pub/bin/php/Classes/pageBuilder.php b/pub/bin/php/Classes/pageBuilder.php index 0cf454b..a2abc30 100644 --- a/pub/bin/php/Classes/pageBuilder.php +++ b/pub/bin/php/Classes/pageBuilder.php @@ -309,7 +309,7 @@ class pageBuilder
Sentri diff --git a/pub/src/css/fullcalendar/theme/theme.css b/pub/src/css/fullcalendar/theme/theme.css index 163ea0e..bfa8e54 100644 --- a/pub/src/css/fullcalendar/theme/theme.css +++ b/pub/src/css/fullcalendar/theme/theme.css @@ -1083,3 +1083,11 @@ rotate: 180deg } +.fc .fc-daygrid-day-frame { + padding: 2px; +} + +.fc .fc-daygrid-day-number { + padding: 2px; + font-size: 0.85rem; +}