Skip to content

Commit 58d4306

Browse files
authored
Merge pull request #1233 from itflow-org/develop
25.09 Release
2 parents 6df0439 + 6a5ce1d commit 58d4306

File tree

935 files changed

+18436
-26687
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

935 files changed

+18436
-26687
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,6 @@ plugins/htmlpurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/CSS/*
2525
xcustom/*
2626
!xcustom/readme.php
2727
post/xcustom
28+
custom/*
2829
!post/xcustom/readme.php
2930
.zed

CHANGELOG.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,92 @@
22

33
This file documents all notable changes made to ITFlow.
44

5+
## [25.09]
6+
7+
***BACK UP*** before updating.
8+
9+
---
10+
11+
### Breaking Changes and Notes
12+
- We strongly recommend updating from the command line, however if performed via the webui and after performed it will return a 404. thats normal as the directory structure has changed, just close your browser then log back in then go back to update to perform the many database updates.
13+
- This is a major release with significant changes. While the community has done a great job identifying bugs, some may still remain — continued testing is encouraged.
14+
- All AI settings will be **reset** and must be reconfigured using the new AI provider backend.
15+
- The `xcustom` directory has been renamed to `custom`. All custom libraries and post-processing scripts should now be placed here.
16+
17+
---
18+
19+
### Added / Changed
20+
- Numerous UI improvements and refinements across the application.
21+
- Enhanced visual clarity by thickening the left border on ticket comments to help identify comment types.
22+
- Ticket details UI redesigned to use less space at the top of the screen.
23+
- Introduced tracking for the **first response date/time** on tickets.
24+
- New reporting feature: **Average time to first response** on tickets.
25+
- Stripe integration rebuilt using the new **payment provider backend**.
26+
- Clients can now save and manage **multiple payment methods**.
27+
- Support for selecting saved cards for **recurring invoices** in both the client and agent portals.
28+
- Initial database structure and logic added for **credit management** (feature not yet enabled).
29+
- Major **backend directory restructuring**.
30+
- Introduced **stock/inventory management**, including a stock ledger backend.
31+
- Stock quantities now update automatically when invoice items are added or removed.
32+
- Invoice autocomplete now includes: **name, description, price, tax, stock levels**, and links `product_id` to `item_id`.
33+
- Added a **category filter** to invoices.
34+
- Linked stock to related expenses.
35+
- New product fields: **location, code, and type**.
36+
- Products now separated into two types: **Service** and **Product**.
37+
- **Dark mode** introduced.
38+
- Projects: Now support linking **closed tickets**.
39+
- Clients: Added bulk actions for tags, referral source, industry, hourly rate, email, archive, and restore.
40+
- Invoices: Bulk action added to **assign categories**.
41+
- Assets: New `client_uri` field, visible in both the agent and client portals.
42+
- Client Portal: Clients can now **select an asset** during ticket creation.
43+
- Client Portal: Company logo now **displays in the header**.
44+
- Client Portal: Dashboard cards are now **clickable** for more detail.
45+
- Assets: Option added to include **MAC Address** in additional columns.
46+
- Asset Interface: Bulk actions added — set DHCP, network type, and delete.
47+
- API:
48+
- Added `/location` endpoint.
49+
- Ticket content now supports **HTML formatting**.
50+
- New option to filter and display **500 records per page** in the footer.
51+
- Payment methods are now treated as a **separate entity** instead of being grouped under categories.
52+
- Updated libraries:
53+
- **TinyMCE**
54+
- **Chart.js** (major upgrade)
55+
- **DataTables**
56+
- **Bootstrap**
57+
- **FullCalendar**
58+
- **php-stripe**
59+
60+
---
61+
62+
### Fixed
63+
- Several security vulnerabilities patched.
64+
- Ticket status is no longer updated when scheduling.
65+
- Client Portal: Tech contacts can no longer edit their own details.
66+
- Fixed overlapping logo issue in Invoice/Quote PDF exports.
67+
- Refactored `check_login.php` into multiple files for modular login functionality.
68+
- Removed redundant logging comments for redirects.
69+
- Renamed `get_settings.php` to `load_global_settings.php`.
70+
- Simplified syntax for `ajax-modal` and updated usage throughout the app.
71+
- Fixed issue where primary contact text wasn’t displaying.
72+
- Corrected client **Net Terms** display.
73+
- Fixed logic for recurring expense **next run date**.
74+
- Resolved broken **IMAP test button**.
75+
- Archived clients can no longer log into the portal.
76+
- Searching closed tickets no longer reverts to open tickets.
77+
- Fixed project search filter not showing completed projects.
78+
- Fixed issue where company logo was not being removed correctly.
79+
- Resolved API bugs:
80+
- Default rate and net terms.
81+
- Contact location.
82+
- Document endpoint.
83+
84+
---
85+
86+
### Developer Updates
87+
- Replaced legacy code with newer functions like `redirect()`, `getFieldById()`, and `flash_alert()`.
88+
- Significantly improved performance of queries used for filter selection boxes.
89+
90+
591
## [25.06.1]
692

793
### Fixed

admin/ai_model.php

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
3+
// Default Column Sortby Filter
4+
$sort = "ai_model_name";
5+
$order = "ASC";
6+
7+
require_once "includes/inc_all_admin.php";
8+
9+
$sql = mysqli_query($mysqli, "SELECT * FROM ai_models LEFT JOIN ai_providers ON ai_model_ai_provider_id = ai_provider_id ORDER BY $sort $order");
10+
11+
$num_rows = mysqli_num_rows($sql);
12+
13+
?>
14+
15+
<div class="card card-dark">
16+
<div class="card-header py-2">
17+
<h3 class="card-title mt-2"><i class="fas fa-fw fa-robot mr-2"></i>AI Models</h3>
18+
<div class="card-tools">
19+
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#addAIModelModal"><i class="fas fa-plus mr-2"></i>Add Model</button>
20+
</div>
21+
</div>
22+
<div class="card-body">
23+
<div class="table-responsive-sm">
24+
<table class="table table-striped table-borderless table-hover">
25+
<thead class="text-dark <?php if ($num_rows == 0) { echo "d-none"; } ?>">
26+
<tr>
27+
<th>
28+
<a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=ai_model_name&order=<?php echo $disp; ?>">
29+
Model <?php if ($sort == 'ai_model_name') { echo $order_icon; } ?>
30+
</a>
31+
</th>
32+
<th>
33+
<a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=ai_provider_name&order=<?php echo $disp; ?>">
34+
Provider <?php if ($sort == 'ai_provider_name') { echo $order_icon; } ?>
35+
</a>
36+
</th>
37+
<th>
38+
<a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=ai_model_use_case&order=<?php echo $disp; ?>">
39+
Use Case<?php if ($sort == 'ai_model_use_case') { echo $order_icon; } ?>
40+
</a>
41+
</th>
42+
<th>
43+
<a class="text-dark">Prompt</a>
44+
</th>
45+
<th class="text-center">Action</th>
46+
</tr>
47+
</thead>
48+
<tbody>
49+
<?php
50+
51+
while ($row = mysqli_fetch_array($sql)) {
52+
$provider_id = intval($row['ai_provider_id']);
53+
$provider_name = nullable_htmlentities($row['ai_provider_name']);
54+
$model_id = intval($row['ai_model_id']);
55+
$model_name = nullable_htmlentities($row['ai_model_name']);
56+
$use_case = nullable_htmlentities($row['ai_model_use_case']);
57+
$prompt = nl2br(nullable_htmlentities($row['ai_model_prompt']));
58+
59+
?>
60+
<tr>
61+
<td>
62+
<a class="text-dark text-bold ajax-modal" href="#"
63+
data-modal-url="modals/ai/ai_model_edit.php?id=<?= $model_id ?>">
64+
<?php echo $model_name; ?>
65+
</a>
66+
</td>
67+
<td><?php echo $provider_name; ?></td>
68+
<td><?php echo $use_case; ?></td>
69+
<td><?php echo $prompt; ?></td>
70+
<td>
71+
<div class="dropdown dropleft text-center">
72+
<button class="btn btn-secondary btn-sm" type="button" data-toggle="dropdown">
73+
<i class="fas fa-ellipsis-h"></i>
74+
</button>
75+
<div class="dropdown-menu">
76+
<a class="dropdown-item ajax-modal" href="#"
77+
data-modal-url="modals/ai/ai_model_edit.php?id=<?= $model_id ?>">
78+
<i class="fas fa-fw fa-edit mr-2"></i>Edit
79+
</a>
80+
<div class="dropdown-divider"></div>
81+
<a class="dropdown-item text-danger confirm-link" href="post.php?delete_ai_model=<?php echo $model_id; ?>&csrf_token=<?php echo $_SESSION['csrf_token'] ?>">
82+
<i class="fas fa-fw fa-trash mr-2"></i>Delete
83+
</a>
84+
</div>
85+
</div>
86+
</td>
87+
</tr>
88+
89+
<?php
90+
91+
}
92+
93+
if ($num_rows == 0) {
94+
echo "<h3 class='text-secondary mt-3' style='text-align: center'>No Records Here</h3>";
95+
}
96+
97+
?>
98+
99+
</tbody>
100+
</table>
101+
102+
</div>
103+
</div>
104+
</div>
105+
106+
<?php
107+
require_once "modals/ai/ai_model_add.php";
108+
require_once "../includes/footer.php";

admin/ai_provider.php

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<?php
2+
3+
// Default Column Sortby Filter
4+
$sort = "ai_provider_name";
5+
$order = "ASC";
6+
7+
require_once "includes/inc_all_admin.php";
8+
9+
$sql = mysqli_query($mysqli, "SELECT * FROM ai_providers ORDER BY $sort $order");
10+
11+
$num_rows = mysqli_num_rows($sql);
12+
13+
?>
14+
15+
<div class="card card-dark">
16+
<div class="card-header py-2">
17+
<h3 class="card-title mt-2"><i class="fas fa-fw fa-robot mr-2"></i>AI Providers</h3>
18+
<div class="card-tools">
19+
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#addAIProviderModal"><i class="fas fa-plus mr-2"></i>Add Provider</button>
20+
</div>
21+
</div>
22+
<div class="card-body">
23+
<div class="table-responsive-sm">
24+
<table class="table table-striped table-borderless table-hover">
25+
<thead class="text-dark <?php if ($num_rows == 0) { echo "d-none"; } ?>">
26+
<tr>
27+
<th>
28+
<a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=ai_provider_name&order=<?php echo $disp; ?>">
29+
Provider <?php if ($sort == 'ai_provider_name') { echo $order_icon; } ?>
30+
</a>
31+
</th>
32+
<th>
33+
<a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=ai_provider_api_url&order=<?php echo $disp; ?>">
34+
URL <?php if ($sort == 'ai_provider_api_url') { echo $order_icon; } ?>
35+
</a>
36+
</th>
37+
<th>
38+
<a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=ai_provider_api_key&order=<?php echo $disp; ?>">
39+
Key <?php if ($sort == 'ai_provider_api_key') { echo $order_icon; } ?>
40+
</a>
41+
</th>
42+
<th>
43+
<a class="text-dark">Models</a>
44+
</th>
45+
<th class="text-center">Action</th>
46+
</tr>
47+
</thead>
48+
<tbody>
49+
<?php
50+
51+
while ($row = mysqli_fetch_array($sql)) {
52+
$provider_id = intval($row['ai_provider_id']);
53+
$provider_name = nullable_htmlentities($row['ai_provider_name']);
54+
$url = nullable_htmlentities($row['ai_provider_api_url']);
55+
$key = nullable_htmlentities($row['ai_provider_api_key']);
56+
57+
$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT COUNT('ai_model_id') AS ai_model_count FROM ai_models WHERE ai_model_ai_provider_id = $provider_id"));
58+
$ai_model_count = intval($row['ai_model_count']);
59+
60+
?>
61+
<tr>
62+
<td>
63+
<a class="text-dark text-bold ajax-modal" href="#"
64+
data-modal-url="modals/ai/ai_provider_edit.php?id=<?= $provider_id ?>">
65+
<?php echo $provider_name; ?>
66+
</a>
67+
</td>
68+
<td><?php echo $url; ?></td>
69+
<td><?php echo $key; ?></td>
70+
<td><?php echo $ai_model_count; ?></td>
71+
<td>
72+
<div class="dropdown dropleft text-center">
73+
<button class="btn btn-secondary btn-sm" type="button" data-toggle="dropdown">
74+
<i class="fas fa-ellipsis-h"></i>
75+
</button>
76+
<div class="dropdown-menu">
77+
<a class="dropdown-item ajax-modal" href="#"
78+
data-modal-url="modals/ai/ai_provider_edit.php?id=<?= $provider_id ?>">
79+
<i class="fas fa-fw fa-edit mr-2"></i>Edit
80+
</a>
81+
<div class="dropdown-divider"></div>
82+
<a class="dropdown-item text-danger confirm-link" href="post.php?delete_ai_provider=<?php echo $provider_id; ?>&csrf_token=<?php echo $_SESSION['csrf_token'] ?>">
83+
<i class="fas fa-fw fa-trash mr-2"></i>Delete
84+
</a>
85+
</div>
86+
</div>
87+
</td>
88+
</tr>
89+
90+
<?php
91+
92+
}
93+
94+
if ($num_rows == 0) {
95+
echo "<h3 class='text-secondary mt-3' style='text-align: center'>No Records Here</h3>";
96+
}
97+
98+
?>
99+
100+
</tbody>
101+
</table>
102+
103+
</div>
104+
</div>
105+
</div>
106+
107+
<?php
108+
require_once "modals/ai/ai_provider_add.php";
109+
require_once "../includes/footer.php";

admin_api.php renamed to admin/api_keys.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,15 +162,15 @@
162162
</form>
163163

164164
</div>
165-
<?php require_once "includes/filter_footer.php";
165+
<?php require_once "../includes/filter_footer.php";
166166
?>
167167
</div>
168168
</div>
169169

170-
<script src="js/bulk_actions.js"></script>
170+
<script src="../js/bulk_actions.js"></script>
171171

172172
<?php
173-
require_once "modals/admin_api_key_add_modal.php";
173+
require_once "modals/api/api_key_add.php";
174174

175-
require_once "includes/footer.php";
175+
require_once "../includes/footer.php";
176176

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,10 +183,10 @@
183183
</tbody>
184184
</table>
185185
</div>
186-
<?php require_once "includes/filter_footer.php";
186+
<?php require_once "../includes/filter_footer.php";
187187
?>
188188
</div>
189189
</div>
190190

191191
<?php
192-
require_once "includes/footer.php";
192+
require_once "../includes/footer.php";
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@
266266
if (empty($client_name)) {
267267
$client_name_display = "-";
268268
} else {
269-
$client_name_display = "<a href='client_overview.php?client_id=$client_id'>$client_name</a>";
269+
$client_name_display = "<a href='../user/client_overview.php?client_id=$client_id'>$client_name</a>";
270270
}
271271
$log_entity_id = intval($row['log_entity_id']);
272272

@@ -292,11 +292,11 @@
292292
</tbody>
293293
</table>
294294
</div>
295-
<?php require_once "includes/filter_footer.php";
295+
<?php require_once "../includes/filter_footer.php";
296296
?>
297297
</div>
298298
</div>
299299

300300
<?php
301-
require_once "includes/footer.php";
301+
require_once "../includes/footer.php";
302302

admin_backup.php renamed to admin/backup.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,5 @@
3434
</div>
3535

3636
<?php
37-
require_once "includes/footer.php";
37+
require_once "../includes/footer.php";
3838

0 commit comments

Comments
 (0)