34 Commits

Author SHA1 Message Date
Johnny
ff80a3db3f Merge pull request #1186 from itflow-org/develop
v25.02.4 - Stable Minor Release
2025-03-08 18:11:38 -05:00
johnnyq
c7d00d7b0d Fixed Changelog 2025-03-08 17:53:20 -05:00
johnnyq
1c6e74b08e Update Changelog put back the missing parts for 25.02.3 2025-03-08 17:40:36 -05:00
johnnyq
f8d054f8aa Bump minor version and update the Changelog 2025-03-08 17:25:10 -05:00
johnnyq
e0dfaf2d22 Fixed a few var errors in ajax contact details 2025-03-07 16:58:52 -05:00
johnnyq
757a62c35b Fix Ticket Count under contact listing 2025-03-07 16:44:38 -05:00
johnnyq
52a62fc23c Fix Client URL in recurring Invoices 2025-03-07 14:47:47 -05:00
johnnyq
ad9e4b4fb4 Added new php function to retrieve and escape a single record from a specified table using an id getFieldById(table, id, field_to_retrieve, escape_method) escape method defaults to SQL but html and json and int can be specified 2025-03-04 13:45:21 -05:00
johnnyq
4fdd5ae769 Fix Bug adding software license if no vendor is selected 2025-03-04 00:13:21 -05:00
Johnny
9f2b9e3b3e Merge pull request #1181 from itflow-org/develop
v25.02.3 - Stable Minor Release
2025-03-03 15:57:26 -05:00
johnnyq
2c074e9dc4 Spelling 2025-03-03 15:55:01 -05:00
johnnyq
0fad31d683 Update changelog new minor release 2025-03-03 15:53:58 -05:00
johnnyq
b154930a4c Fix Notifications 2025-03-03 15:36:32 -05:00
Johnny
359b04e7d1 Merge pull request #1180 from itflow-org/develop
v25.02.2 Maint / Small Feature Release
2025-03-03 15:22:57 -05:00
johnnyq
cc00e3bf75 Add Periods to the changelog 2025-03-03 15:19:18 -05:00
johnnyq
0454685039 Update Changelog 2025-03-03 15:09:40 -05:00
johnnyq
b5eb325c5e Fix Dismissed Notification Search Filter 2025-03-03 15:00:33 -05:00
johnnyq
ed6276a3e4 Add Active Inactive filter to recurring invoices 2025-03-03 14:56:04 -05:00
johnnyq
5da1310e34 Combine Notifications dismissed and notifications into 1 and add link to Notifications Ajax modal 2025-03-03 14:36:38 -05:00
wrongecho
a69b09c9e6 Bugfix: When exporting to CSV, the first asset isn't shown 2025-03-03 09:42:45 +00:00
wrongecho
8da3bb15e9 Add physical location field to asset csv import/export 2025-03-03 09:29:28 +00:00
wrongecho
8488445bf4 Start March changelog 2025-03-03 09:06:07 +00:00
wrongecho
546d21adac Fix client notes on edit modal 2025-03-03 09:03:49 +00:00
johnnyq
580f50b187 Added Link button to WYSIWYG Document Creation and editing 2025-03-03 00:22:51 -05:00
johnnyq
4744276f2a Don't count Non-Billable Invoices in overdue count 2025-03-03 00:07:22 -05:00
johnnyq
6106b8aebb Fix broken client link for the report clients with a balance 2025-03-02 21:03:02 -05:00
Johnny
dd2b203321 Merge pull request #1178 from itflow-org/43-ssl-history
Add SSL certificate history tracking
2025-03-02 11:22:34 -05:00
Marcus Hill
7994c9c7a8 Add SSL certificate history tracking 2025-03-02 10:15:26 +00:00
Marcus Hill
ae59aa3326 Add SSL certificate history tracking 2025-03-02 10:12:47 +00:00
Marcus Hill
0ab9a1c97d Temp fix warnings on num_domains expiring / num_domains_expired vars not defined yet 2025-03-02 09:50:50 +00:00
Marcus Hill
2908568e2a Fix certificates not showing notes 2025-03-02 09:40:21 +00:00
Marcus Hill
2b673a1b6c Merge branch 'develop' of https://github.com/itflow-org/itflow into develop 2025-03-02 09:26:28 +00:00
Marcus Hill
bece8abfe2 Prune db updates older than a year 2025-03-02 09:26:16 +00:00
johnnyq
ac2b355399 updated Changelog and bumped app version to 25.02.2 2025-03-01 21:21:03 -05:00
25 changed files with 494 additions and 398 deletions

View File

@@ -2,6 +2,39 @@
This file documents all notable changes made to ITFlow. This file documents all notable changes made to ITFlow.
## [25.02.4]
### Fixed
- Resolved issue preventing the addition or editing of licenses when no vendor was selected.
- Fixed several undeclared variables in AJAX contact details.
- Corrected the contact ticket count display.
- Addressed an issue where clicking "More Details" in AJAX contact/asset details failed to include the `client_id` in the URL.
- Fixed an issue with recurring invoices in the client URL: clicking "Inactive" or "Active" would unexpectedly navigate away from the client section.
- Added new php function getFieldById() to return a record using just an id and sanitized as well.
## [25.02.3]
### Fixed
- Fixed notifications being reversed as dismissed notifications.
## [25.02.2]
### Fixed
- Corrected some edit modals not showing notes correctly.
- Bugfix: When exporting to CSV, the first asset wasn't being shown.
- Fix broken create / edit credentials.
- Fixed missing Notificatons link.
- Fixed a few dead links.
- Fixed Overdue count also counting Non-Billable Invoices.
- Fix Edit Client Notes.
### Added / Changed
- Implemented SSL certificate history tracking.
- Added Inactive / Active Filter to Recurring Invoices.
- Merged Dismissed notifications and notification in one.
- Added Link Button to addd / edit Document WYSIWYG.
- Added Physical location to the asset export / import.
## [25.02.1] ## [25.02.1]
### Fixed ### Fixed
- Resolved broken links in the client overview, project and client listings, and rack details. - Resolved broken links in the client overview, project and client listings, and rack details.
@@ -24,33 +57,33 @@ This file documents all notable changes made to ITFlow.
- Added a Vendor Quick Details Modal. - Added a Vendor Quick Details Modal.
- Enabled vendor linking and added a License Purchase Reference in the Software Licenses section. - Enabled vendor linking and added a License Purchase Reference in the Software Licenses section.
- Added download original, optimized and thumbnail option for images. - Added download original, optimized and thumbnail option for images.
- Added Paid status to the top corner of Invoice PDFs - Added Paid status to the top corner of Invoice PDFs.
## [25.02] ## [25.02]
### Fixed ### Fixed
- Migrated several reports to the new permissions/roles system - Migrated several reports to the new permissions/roles system.
- Resolved issue with empty task box showing for closed/resolved tickets - Resolved issue with empty task box showing for closed/resolved tickets.
- Corrected ticket priority sorting - Corrected ticket priority sorting.
- Cloned asset interfaces when transferring assets between clients - Cloned asset interfaces when transferring assets between clients.
### Added / Changed ### Added / Changed
- Restored max number of records per page option back to 500 since we dont have repeating modals. - Restored max number of records per page option back to 500 since we dont have repeating modals.
- Bulk Categorize Tickets feature - Bulk Categorize Tickets feature.
- Renamed "Interface port" to "Interface Description." "Interface Name" should now refer to port name and/or number - Renamed "Interface port" to "Interface Description." "Interface Name" should now refer to port name and/or number.
- Changed "Transfer Asset to Client" from a single action to a bulk action - Changed "Transfer Asset to Client" from a single action to a bulk action.
- Updated Filter Footer UI to show "Showing x to x of x records" instead of just the total records - Updated Filter Footer UI to show "Showing x to x of x records" instead of just the total records.
- Added Client Overview section to view client assets, contacts, licenses, credentials, etc. - Added Client Overview section to view client assets, contacts, licenses, credentials, etc.
- Introduced Quick Peek for asset details, contact information, and document viewing throughout the ITFlow App, all made possible by AJAX - Introduced Quick Peek for asset details, contact information, and document viewing throughout the ITFlow App, all made possible by AJAX.
- Enabled Simple Drag-and-Drop Ordering for Invoices, Recurring Invoices, Quotes, Ticket Tasks, and Ticket Template Tasks - Enabled Simple Drag-and-Drop Ordering for Invoices, Recurring Invoices, Quotes, Ticket Tasks, and Ticket Template Tasks.
- Added new Ticket View options: Kanban and Simple View - Added new Ticket View options: Kanban and Simple View.
- Migrated all repeating modals to the new AJAX modal function for faster loading times and quicker development - Migrated all repeating modals to the new AJAX modal function for faster loading times and quicker development.
- Allowed clients to upload PDF documents to accepted quotes - Allowed clients to upload PDF documents to accepted quotes.
- Client Portal now shows ticket category - Client Portal now shows ticket category.
- Custom links can now be added to the Client Portal navbar - Custom links can now be added to the Client Portal navbar.
- Lots of little tweaks to UI, performance, bugs, etc. - Lots of little tweaks to UI, performance, bugs, etc.
### Breaking Changes ### Breaking Changes
- Cron scripts have officially been moved to the /scripts folder and are no longer in the root directory; they must be updated to function properly - Cron scripts have officially been moved to the /scripts folder and are no longer in the root directory; they must be updated to function properly.
## [25.01.3] ## [25.01.3]
### Fixed ### Fixed

View File

@@ -848,7 +848,7 @@ ob_start();
</div> </div>
<div class="modal-footer bg-white"> <div class="modal-footer bg-white">
<a href="asset_details.php?<?php echo $client_url; ?>asset_id=<?php echo $asset_id; ?>" class="btn btn-primary text-bold"><span class="text-white"><i class="fas fa-info-circle mr-2"></i>More Details</span></a> <a href="asset_details.php?client_id=<?php echo $client_id; ?>&asset_id=<?php echo $asset_id; ?>" class="btn btn-primary text-bold"><span class="text-white"><i class="fas fa-info-circle mr-2"></i>More Details</span></a>
<a href="#" class="btn btn-secondary" <a href="#" class="btn btn-secondary"
data-toggle="ajax-modal" data-ajax-url="ajax/ajax_asset_edit.php" data-ajax-id="<?php echo $asset_id; ?>"> data-toggle="ajax-modal" data-ajax-url="ajax/ajax_asset_edit.php" data-ajax-id="<?php echo $asset_id; ?>">
<span class="text-white"><i class="fas fa-edit mr-2"></i>Edit</span> <span class="text-white"><i class="fas fa-edit mr-2"></i>Edit</span>

View File

@@ -13,10 +13,13 @@ $certificate_domain = nullable_htmlentities($row['certificate_domain']);
$certificate_domain_id = intval($row['certificate_domain_id']); $certificate_domain_id = intval($row['certificate_domain_id']);
$certificate_issued_by = nullable_htmlentities($row['certificate_issued_by']); $certificate_issued_by = nullable_htmlentities($row['certificate_issued_by']);
$certificate_public_key = nullable_htmlentities($row['certificate_public_key']); $certificate_public_key = nullable_htmlentities($row['certificate_public_key']);
$certificate_notes = nullable_htmlentities($row['certificate_notes']);
$certificate_expire = nullable_htmlentities($row['certificate_expire']); $certificate_expire = nullable_htmlentities($row['certificate_expire']);
$certificate_created_at = nullable_htmlentities($row['certificate_created_at']); $certificate_created_at = nullable_htmlentities($row['certificate_created_at']);
$client_id = intval($row['certificate_client_id']); $client_id = intval($row['certificate_client_id']);
$history_sql = mysqli_query($mysqli, "SELECT * FROM certificate_history WHERE certificate_history_certificate_id = $certificate_id");
// Generate the HTML form content using output buffering. // Generate the HTML form content using output buffering.
ob_start(); ob_start();
?> ?>
@@ -42,6 +45,9 @@ ob_start();
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" data-toggle="pill" href="#pillsEditNotes<?php echo $certificate_id; ?>">Notes</a> <a class="nav-link" data-toggle="pill" href="#pillsEditNotes<?php echo $certificate_id; ?>">Notes</a>
</li> </li>
<li class="nav-item">
<a class="nav-link" data-toggle="pill" href="#pillsEditHistory<?php echo $certificate_id; ?>">History</a>
</li>
</ul> </ul>
<hr> <hr>
@@ -141,11 +147,40 @@ ob_start();
</div> </div>
<div class="tab-pane fade" id="pillsEditNotes<?php echo $certificate_id; ?>"> <div class="tab-pane fade" id="pillsEditNotes<?php echo $certificate_id; ?>">
<div class="form-group"> <div class="form-group">
<textarea class="form-control" name="notes" rows="12" placeholder="Enter some notes"><?php echo $certificate_notes; ?></textarea> <textarea class="form-control" name="notes" rows="12" placeholder="Enter some notes"><?php echo $certificate_notes; ?></textarea>
</div> </div>
</div>
<div class="tab-pane fade" id="pillsEditHistory<?php echo $certificate_id; ?>">
<div class="table-responsive">
<table class='table table-sm table-striped border table-hover'>
<thead class='thead-dark'>
<tr>
<th>Date</th>
<th>Field</th>
<th>Before</th>
<th>After</th>
</tr>
</thead>
<tbody>
<?php
while ($row = mysqli_fetch_array($history_sql)) {
$certificate_modified_at = nullable_htmlentities($row['certificate_history_modified_at']);
$certificate_field = nullable_htmlentities($row['certificate_history_column']);
$certificate_before_value = nullable_htmlentities($row['certificate_history_old_value']);
$certificate_after_value = nullable_htmlentities($row['certificate_history_new_value']);
?>
<tr>
<td><?php echo $certificate_modified_at; ?></td>
<td><?php echo $certificate_field; ?></td>
<td><?php echo $certificate_before_value; ?></td>
<td><?php echo $certificate_after_value; ?></td>
</tr>
<?php } ?>
</tbody>
</table>
</div>
</div> </div>
</div> </div>

View File

@@ -251,9 +251,7 @@ ob_start();
<div class="tab-pane fade" id="pills-client-notes<?php echo $client_id; ?>"> <div class="tab-pane fade" id="pills-client-notes<?php echo $client_id; ?>">
<div class="form-group"> <div class="form-group">
<textarea class="form-control" rows="10" placeholder="Enter some notes" <textarea class="form-control" rows="10" placeholder="Enter some notes" name="notes"><?php echo $client_notes; ?></textarea>
name="notes"><?php echo $client_notes; ?>
</textarea>
</div> </div>
</div> </div>

View File

@@ -9,7 +9,7 @@ $sql = mysqli_query($mysqli, "SELECT * FROM contacts
LEFT JOIN locations ON location_id = contact_location_id LEFT JOIN locations ON location_id = contact_location_id
LEFT JOIN users ON user_id = contact_user_id LEFT JOIN users ON user_id = contact_user_id
WHERE contact_id = $contact_id WHERE contact_id = $contact_id
$client_query LIMIT 1
"); ");
$row = mysqli_fetch_array($sql); $row = mysqli_fetch_array($sql);
@@ -114,7 +114,7 @@ $sql_linked_services = mysqli_query($mysqli, "SELECT * FROM service_contacts, se
AND service_contacts.service_id = services.service_id AND service_contacts.service_id = services.service_id
ORDER BY service_name ASC" ORDER BY service_name ASC"
); );
$service_count = mysqli_num_rows($sql_linked_services); $services_count = mysqli_num_rows($sql_linked_services);
$linked_services = array(); $linked_services = array();
@@ -846,7 +846,7 @@ ob_start();
</div> </div>
<div class="modal-footer bg-white"> <div class="modal-footer bg-white">
<a href="contact_details.php?<?php echo $client_url; ?>contact_id=<?php echo $contact_id; ?>" class="btn btn-primary text-bold"> <a href="contact_details.php?client_id=<?php echo $client_id; ?>&contact_id=<?php echo $contact_id; ?>" class="btn btn-primary text-bold">
<span class="text-white"><i class="fas fa-info-circle mr-2"></i>More Details</span> <span class="text-white"><i class="fas fa-info-circle mr-2"></i>More Details</span>
</a> </a>
<a href="#" class="btn btn-secondary" <a href="#" class="btn btn-secondary"

View File

@@ -66,13 +66,17 @@ ob_start();
</div> </div>
<div class="modal-footer bg-white justify-content-end"> <div class="modal-footer bg-white justify-content-end">
<?php if ($num_notifications) { ?> <?php if ($num_notifications) { ?>
<a href="post.php?dismiss_all_notifications&csrf_token=<?php echo $_SESSION[ <a href="post.php?dismiss_all_notifications&csrf_token=<?php echo $_SESSION[
"csrf_token" "csrf_token"
]; ?>" class="btn btn-primary"> ]; ?>" class="btn btn-primary">
<span class="text-white text-bold"><i class="fas fa-check mr-2"></i>Dismiss all</span> <span class="text-white text-bold"><i class="fas fa-check mr-2"></i>Dismiss all</span>
</a> </a>
<a href="notifications.php" class="btn btn-secondary">
<span class="text-white">See all Notifications</span>
</a>
<?php } else { ?> <?php } else { ?>
<a href="notifications_dismissed.php" class="btn btn-dark"> <a href="notifications.php?dismissed" class="btn btn-dark">
<span class="text-white text-bold">See Dismissed Notifications</span> <span class="text-white text-bold">See Dismissed Notifications</span>
</a> </a>
<?php } ?> <?php } ?>

View File

@@ -385,7 +385,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
if ($ticket_count) { if ($ticket_count) {
$ticket_count_display = "<span class='mr-2 badge badge-pill badge-secondary p-2' title='$ticket_count Tickets'><i class='fas fa-fw fa-life-ring mr-2'></i>$ticket_count</span>"; $ticket_count_display = "<span class='mr-2 badge badge-pill badge-secondary p-2' title='$ticket_count Tickets'><i class='fas fa-fw fa-life-ring mr-2'></i>$ticket_count</span>";
} else { } else {
$software_count_display = ''; $ticket_count_display = '';
} }
// Related Documents Query // Related Documents Query

View File

@@ -17,117 +17,6 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) {
// We need updates! // We need updates!
if (CURRENT_DATABASE_VERSION == '0.1.0') {
// Insert queries here required to update to DB version 0.1.1
// Logs don't get archived
mysqli_query($mysqli, "ALTER TABLE `logs` DROP `log_archived_at`");
// Assets will eventualy have file associatons which could include a receipt.
mysqli_query($mysqli, "ALTER TABLE `assets` DROP `asset_reciept`");
mysqli_query($mysqli, "ALTER TABLE `campaign_messages` DROP `message_updated_at`");
// This will be a seperate table eventually called contact_documents because contact can have several documents
mysqli_query($mysqli, "ALTER TABLE `documents` DROP `document_contact_id`");
mysqli_query($mysqli, "ALTER TABLE `expenses` DROP `expense_asset_id`");
mysqli_query($mysqli, "ALTER TABLE `files` DROP `file_contact_id`");
mysqli_query($mysqli, "ALTER TABLE `history` DROP `history_archived_at`");
// Then, update the database to the next sequential version
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '0.1.1'");
}
if (CURRENT_DATABASE_VERSION == '0.1.1') {
// Insert queries here required to update to DB version 0.1.2
// Create Many to Many Relationship tables for Assets, Contacts, Software and Vendors
mysqli_query($mysqli, "CREATE TABLE `asset_documents` (`asset_id` int(11) NOT NULL,`document_id` int(11) NOT NULL, PRIMARY KEY (`asset_id`,`document_id`))");
mysqli_query($mysqli, "CREATE TABLE `asset_logins` (`asset_id` int(11) NOT NULL,`login_id` int(11) NOT NULL, PRIMARY KEY (`asset_id`,`login_id`))");
mysqli_query($mysqli, "CREATE TABLE `asset_files` (`asset_id` int(11) NOT NULL,`file_id` int(11) NOT NULL, PRIMARY KEY (`asset_id`,`file_id`))");
mysqli_query($mysqli, "CREATE TABLE `contact_documents` (`contact_id` int(11) NOT NULL,`document_id` int(11) NOT NULL, PRIMARY KEY (`contact_id`,`document_id`))");
mysqli_query($mysqli, "CREATE TABLE `contact_logins` (`contact_id` int(11) NOT NULL,`login_id` int(11) NOT NULL, PRIMARY KEY (`contact_id`,`login_id`))");
mysqli_query($mysqli, "CREATE TABLE `contact_files` (`contact_id` int(11) NOT NULL,`file_id` int(11) NOT NULL, PRIMARY KEY (`contact_id`,`file_id`))");
mysqli_query($mysqli, "CREATE TABLE `software_documents` (`software_id` int(11) NOT NULL,`document_id` int(11) NOT NULL, PRIMARY KEY (`software_id`,`document_id`))");
mysqli_query($mysqli, "CREATE TABLE `software_logins` (`software_id` int(11) NOT NULL,`login_id` int(11) NOT NULL, PRIMARY KEY (`software_id`,`login_id`))");
mysqli_query($mysqli, "CREATE TABLE `software_files` (`software_id` int(11) NOT NULL,`file_id` int(11) NOT NULL, PRIMARY KEY (`software_id`,`file_id`))");
mysqli_query($mysqli, "CREATE TABLE `vendor_documents` (`vendor_id` int(11) NOT NULL,`document_id` int(11) NOT NULL, PRIMARY KEY (`vendor_id`,`document_id`))");
mysqli_query($mysqli, "CREATE TABLE `vendor_logins` (`vendor_id` int(11) NOT NULL,`login_id` int(11) NOT NULL, PRIMARY KEY (`vendor_id`,`login_id`))");
mysqli_query($mysqli, "CREATE TABLE `vendor_files` (`vendor_id` int(11) NOT NULL,`file_id` int(11) NOT NULL, PRIMARY KEY (`vendor_id`,`file_id`))");
// Then, update the database to the next sequential version
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '0.1.2'");
}
if (CURRENT_DATABASE_VERSION == '0.1.2') {
// Insert queries here required to update to DB version 0.1.3
mysqli_query($mysqli, "ALTER TABLE `logs` ADD `log_entity_id` INT NOT NULL DEFAULT '0' AFTER `log_user_id`");
// Then, update the database to the next sequential version
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '0.1.3'");
}
if (CURRENT_DATABASE_VERSION == '0.1.3') {
// Insert queries here required to update to DB version 0.1.4
mysqli_query($mysqli, "ALTER TABLE assets ADD asset_status VARCHAR(200) NULL AFTER asset_mac");
///Then, update the database to the next sequential version
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '0.1.4'");
}
if (CURRENT_DATABASE_VERSION == '0.1.4') {
// Insert queries here required to update to DB version 0.1.5
mysqli_query($mysqli, "ALTER TABLE `domains` ADD `domain_txt` TEXT NULL DEFAULT NULL AFTER `domain_mail_servers`");
// Then, update the database to the next sequential version
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '0.1.5'");
}
if (CURRENT_DATABASE_VERSION == '0.1.5') {
// Insert queries here required to update to DB version 0.1.6
// Remove Mailing List Tables
mysqli_query($mysqli, "DROP TABLE campaigns");
mysqli_query($mysqli, "DROP TABLE campaign_messages");
// Then, update the database to the next sequential version
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '0.1.6'");
}
if (CURRENT_DATABASE_VERSION == '0.1.6') {
// Insert queries here required to update to DB version 0.1.7
//Remove custom links
mysqli_query($mysqli, "DROP TABLE custom_links");
// Then, update the database to the next sequential version
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '0.1.7'");
}
if (CURRENT_DATABASE_VERSION == '0.1.7') {
// Insert queries here required to update to DB version 0.1.8
mysqli_query($mysqli, "ALTER TABLE `settings` DROP `config_backup_enable`");
mysqli_query($mysqli, "ALTER TABLE `settings` DROP `config_backup_path`");
// Then, update the database to the next sequential version
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '0.1.8'");
}
if (CURRENT_DATABASE_VERSION == '0.1.8') {
// Insert queries here required to update to DB version 0.1.9
mysqli_query($mysqli, "ALTER TABLE `settings` DROP `config_base_url`");
// Then, update the database to the next sequential version
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '0.1.9'");
}
if (CURRENT_DATABASE_VERSION == '0.1.9') {
// Insert queries here required to update to DB version 0.2.0
// Allow contacts to reset their portal password
mysqli_query($mysqli, "ALTER TABLE contacts ADD contact_password_reset_token VARCHAR(200) NULL DEFAULT NULL AFTER contact_password_hash");
// Then, update the database to the next sequential version
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '0.2.0'");
}
if (CURRENT_DATABASE_VERSION == '0.2.0') { if (CURRENT_DATABASE_VERSION == '0.2.0') {
//Insert queries here required to update to DB version 0.2.1 //Insert queries here required to update to DB version 0.2.1
@@ -2520,10 +2409,24 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) {
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '1.8.6'"); mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '1.8.6'");
} }
// if (CURRENT_DATABASE_VERSION == '1.8.6') { if (CURRENT_DATABASE_VERSION == '1.8.6') {
// // Insert queries here required to update to DB version 1.8.7 mysqli_query($mysqli, "
CREATE TABLE `certificate_history` (`certificate_history_id` INT(11) NOT NULL AUTO_INCREMENT,
`certificate_history_column` VARCHAR(200) NOT NULL,
`certificate_history_old_value` TEXT NOT NULL,
`certificate_history_new_value` TEXT NOT NULL,
`certificate_history_certificate_id` INT(11) NOT NULL,
`certificate_history_modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`certificate_history_id`)) ENGINE = InnoDB CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci;
");
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '1.8.7'");
}
// if (CURRENT_DATABASE_VERSION == '1.8.7') {
// // Insert queries here required to update to DB version 1.8.8
// // Then, update the database to the next sequential version // // Then, update the database to the next sequential version
// mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '1.8.7'"); // mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '1.8.8'");
// } // }
} else { } else {

18
db.sql
View File

@@ -321,6 +321,24 @@ CREATE TABLE `categories` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `certificate_history`
--
DROP TABLE IF EXISTS `certificate_history`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `certificate_history` (
`certificate_history_id` int(11) NOT NULL AUTO_INCREMENT,
`certificate_history_column` varchar(200) NOT NULL,
`certificate_history_old_value` text NOT NULL,
`certificate_history_new_value` text NOT NULL,
`certificate_history_certificate_id` int(11) NOT NULL,
`certificate_history_modified_at` datetime NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`certificate_history_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
-- --
-- Table structure for table `certificates` -- Table structure for table `certificates`
-- --

View File

@@ -1414,3 +1414,90 @@ function logAuth($status, $details) {
function getFallback($data) { function getFallback($data) {
return !empty($data) ? $data : '<span class="text-muted">N/A</span>'; return !empty($data) ? $data : '<span class="text-muted">N/A</span>';
} }
/**
* Retrieves a specified field's value from a table based on the record's id.
* It validates the table and field names, automatically determines the primary key (or uses the first column as fallback),
* and returns the field value with an appropriate escaping method.
*
* @param string $table The name of the table.
* @param int $id The record's id.
* @param string $field The field (column) to retrieve.
* @param string $escape_method The escape method: 'sql' (default, auto-detects int), 'html', 'json', or 'int'.
*
* @return mixed The escaped field value, or null if not found or invalid input.
*/
function getFieldById($table, $id, $field, $escape_method = 'sql') {
global $mysqli; // Use the global MySQLi connection
// Validate table and field names to allow only letters, numbers, and underscores
if (!preg_match('/^[a-zA-Z0-9_]+$/', $table) || !preg_match('/^[a-zA-Z0-9_]+$/', $field)) {
return null; // Invalid table or field name
}
// Sanitize id as an integer
$id = (int)$id;
// Get the list of columns and their details from the table
$columns_result = mysqli_query($mysqli, "SHOW COLUMNS FROM `$table`");
if (!$columns_result || mysqli_num_rows($columns_result) == 0) {
return null; // Table not found or has no columns
}
// Build an associative array with column details
$columns = [];
while ($row = mysqli_fetch_assoc($columns_result)) {
$columns[$row['Field']] = [
'type' => $row['Type'],
'key' => $row['Key']
];
}
// Find the primary key field if available
$id_field = null;
foreach ($columns as $col => $details) {
if ($details['key'] === 'PRI') {
$id_field = $col;
break;
}
}
// Fallback: if no primary key is found, use the first column
if (!$id_field) {
reset($columns);
$id_field = key($columns);
}
// Ensure the requested field exists; if not, default to the id field
if (!array_key_exists($field, $columns)) {
$field = $id_field;
}
// Build and execute the query to fetch the specified field value
$query = "SELECT `$field` FROM `$table` WHERE `$id_field` = $id";
$sql = mysqli_query($mysqli, $query);
if ($sql && mysqli_num_rows($sql) > 0) {
$row = mysqli_fetch_assoc($sql);
$value = $row[$field];
// Apply the desired escaping method or auto-detect integer type if using SQL escaping
switch ($escape_method) {
case 'html':
return htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); // Escape for HTML
case 'json':
return json_encode($value); // Escape for JSON
case 'int':
return (int)$value; // Explicitly cast value to integer
case 'sql':
default:
// Auto-detect if the field type is integer
if (stripos($columns[$field]['type'], 'int') !== false) {
return (int)$value;
} else {
return sanitizeInput($value); // Escape for SQL using a custom function
}
}
}
return null; // Return null if no record was found
}

View File

@@ -5,4 +5,4 @@
* Update this file each time we merge develop into master. Format is YY.MM (add a .v if there is more than one release a month. * Update this file each time we merge develop into master. Format is YY.MM (add a .v if there is more than one release a month.
*/ */
DEFINE("APP_VERSION", "25.02.1"); DEFINE("APP_VERSION", "25.02.4");

View File

@@ -202,7 +202,7 @@
<?php <?php
if ($num_domains > 0) { ?> if ($num_domains > 0) { ?>
<span class="right badge <?php if ($num_domains_expiring > 0) { ?> badge-warning text-dark<?php } ?> <?php if ($num_domains_expired > 0) { ?> badge-danger <?php } ?> text-white"><?php echo $num_domains; ?></span> <span class="right badge <?php if (isset($num_domains_expiring)) { ?> badge-warning text-dark<?php } ?> <?php if (isset($num_domains_expired)) { ?> badge-danger <?php } ?> text-white"><?php echo $num_domains; ?></span>
<?php } ?> <?php } ?>
</p> </p>
</a> </a>

View File

@@ -5,4 +5,4 @@
* It is used in conjunction with database_updates.php * It is used in conjunction with database_updates.php
*/ */
DEFINE("LATEST_DATABASE_VERSION", "1.8.6"); DEFINE("LATEST_DATABASE_VERSION", "1.8.7");

View File

@@ -33,7 +33,7 @@ $draft_count = $row['num'];
$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT COUNT('invoice_id') AS num FROM invoices WHERE invoice_status = 'Cancelled' $client_query")); $row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT COUNT('invoice_id') AS num FROM invoices WHERE invoice_status = 'Cancelled' $client_query"));
$cancelled_count = $row['num']; $cancelled_count = $row['num'];
$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT COUNT('invoice_id') AS num FROM invoices WHERE invoice_status NOT LIKE 'Draft' AND invoice_status NOT LIKE 'Paid' AND invoice_status NOT LIKE 'Cancelled' AND invoice_due < CURDATE() $client_query")); $row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT COUNT('invoice_id') AS num FROM invoices WHERE invoice_status NOT LIKE 'Draft' AND invoice_status NOT LIKE 'Paid' AND invoice_status NOT LIKE 'Cancelled' AND invoice_status NOT LIKE 'Non-Billable' AND invoice_due < CURDATE() $client_query"));
$overdue_count = $row['num']; $overdue_count = $row['num'];
$sql_total_draft_amount = mysqli_query($mysqli, "SELECT SUM(invoice_amount) AS total_draft_amount FROM invoices WHERE invoice_status = 'Draft' $client_query"); $sql_total_draft_amount = mysqli_query($mysqli, "SELECT SUM(invoice_amount) AS total_draft_amount FROM invoices WHERE invoice_status = 'Draft' $client_query");

View File

@@ -28,6 +28,7 @@ tinymce.init({
toolbar: [ toolbar: [
{ name: 'styles', items: [ 'styles' ] }, { name: 'styles', items: [ 'styles' ] },
{ name: 'formatting', items: [ 'bold', 'italic', 'forecolor' ] }, { name: 'formatting', items: [ 'bold', 'italic', 'forecolor' ] },
{ name: 'link', items: [ 'link'] },
{ name: 'lists', items: [ 'bullist', 'numlist' ] }, { name: 'lists', items: [ 'bullist', 'numlist' ] },
{ name: 'alignment', items: [ 'alignleft', 'aligncenter', 'alignright', 'alignjustify' ] }, { name: 'alignment', items: [ 'alignleft', 'aligncenter', 'alignright', 'alignjustify' ] },
{ name: 'indentation', items: [ 'outdent', 'indent' ] }, { name: 'indentation', items: [ 'outdent', 'indent' ] },
@@ -59,6 +60,7 @@ tinymce.init({
toolbar: [ toolbar: [
{ name: 'styles', items: [ 'styles' ] }, { name: 'styles', items: [ 'styles' ] },
{ name: 'formatting', items: [ 'bold', 'italic', 'forecolor' ] }, { name: 'formatting', items: [ 'bold', 'italic', 'forecolor' ] },
{ name: 'link', items: [ 'link'] },
{ name: 'lists', items: [ 'bullist', 'numlist' ] }, { name: 'lists', items: [ 'bullist', 'numlist' ] },
{ name: 'alignment', items: [ 'alignleft', 'aligncenter', 'alignright', 'alignjustify' ] }, { name: 'alignment', items: [ 'alignleft', 'aligncenter', 'alignright', 'alignjustify' ] },
{ name: 'indentation', items: [ 'outdent', 'indent' ] }, { name: 'indentation', items: [ 'outdent', 'indent' ] },
@@ -275,20 +277,6 @@ tinymce.init({
} }
}); });
// Initialize TinyMCE
tinymce.init({
selector: '.tinymcePreview',
resize: false,
promotion: false,
branding: false,
menubar: false,
toolbar: false,
statusbar: false,
readonly: false,
plugins: 'autoresize',
license_key: 'gpl',
});
// DateTime // DateTime
$('.datetimepicker').datetimepicker({ $('.datetimepicker').datetimepicker({
}); });

View File

@@ -14,7 +14,7 @@
<input type="hidden" name="client_id" value="<?php echo $client_id; ?>"> <input type="hidden" name="client_id" value="<?php echo $client_id; ?>">
<?php } ?> <?php } ?>
<div class="modal-body bg-white"> <div class="modal-body bg-white">
<p><strong>Format csv file with headings & data:</strong><br>Name, Description, Type, Make, Model, Serial, OS, Assigned To, Location</p> <p><strong>Format csv file with headings & data:</strong><br>Name, Description, Type, Make, Model, Serial, OS, Assigned To, Location, Physical Location</p>
<hr> <hr>
<div class="form-group my-4"> <div class="form-group my-4">
<input type="file" class="form-control-file" name="file" accept=".csv" required> <input type="file" class="form-control-file" name="file" accept=".csv" required>

View File

@@ -1,76 +1,159 @@
<?php <?php
// Default Column Sortby Filter
$sort = "notification_timestamp";
$order = "DESC";
require_once "includes/inc_all.php"; require_once "includes/inc_all.php";
// Dismissed Filter
if (isset($_GET['dismissed'])) {
$dismissed_query = 'AND notification_dismissed_at IS NOT NULL';
$dismissed_filter = 1;
} else {
// Default - any
$dismissed_query = 'AND notification_dismissed_at IS NULL';
$dismissed_filter = 0;
}
$sql = mysqli_query($mysqli, "SELECT * FROM notifications LEFT JOIN clients ON notification_client_id = client_id WHERE notification_dismissed_at IS NULL AND notification_user_id = $session_user_id ORDER BY notification_id DESC"); $sql = mysqli_query(
$mysqli,
"SELECT SQL_CALC_FOUND_ROWS * FROM notifications
LEFT JOIN clients ON notification_client_id = client_id
WHERE (notification_type LIKE '%$q%' OR notification LIKE '%$q%')
AND DATE(notification_timestamp) BETWEEN '$dtf' AND '$dtt'
AND notification_user_id = $session_user_id
$dismissed_query
ORDER BY $sort $order
LIMIT $record_from, $record_to
");
$num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
?> ?>
<div class="card card-dark"> <div class="card card-dark">
<div class="card-header py-2"> <div class="card-header py-2">
<h3 class="card-title mt-2"><i class="fas fa-fw fa-bell mr-2"></i>Notifications</h3> <h3 class="card-title mt-2">
<div class="card-tools"> <i class="fas fa-fw fa-bell mr-2"></i><?php if($dismissed_filter) { echo "Dismissed "; } ?>Notifications
</h3>
<?php if (mysqli_num_rows($sql) > 0) { ?><a href="post.php?dismiss_all_notifications" class="btn btn-primary"><i class="fas fa-fw fa-check mr-2"></i>Dismiss All</a><?php } ?> <div class="card-tools">
<a href="notifications_dismissed.php" class="btn btn-secondary"><i class="fas fa-fw fa-history mr-2"></i>Dismissed</a> <?php if($dismissed_filter) { ?>
</div> <a href="notifications.php" class="btn btn-primary"><i class="fas fa-fw fa-history mr-2"></i>Dismissed</a>
<?php } else { ?>
<a href="notifications.php?dismissed" class="btn btn-outline-secondary"><i class="fas fa-fw fa-history mr-2"></i>Dismissed</a>
<?php } ?>
</div> </div>
<div class="card-body">
<?php if (mysqli_num_rows($sql) > 0) { ?>
<div class="table-responsive-sm">
<table class="table table-striped table-borderless table-hover">
<thead>
<tr>
<th>Timestamp</th>
<th>Type</th>
<th>Notification</th>
<th>Client</th>
<th class="text-center">Dismiss</th>
</tr>
</thead>
<tbody>
<?php
while ($row = mysqli_fetch_array($sql)) {
$notification_id = intval($row['notification_id']);
$notification_type = nullable_htmlentities($row['notification_type']);
$notification = nullable_htmlentities($row['notification']);
$notification_timestamp = nullable_htmlentities($row['notification_timestamp']);
$client_name = nullable_htmlentities($row['client_name']);
$client_id = intval($row['client_id']);
if (empty($client_name)) {
$client_name_display = "-";
} else {
$client_name_display = "<a href='client_overview.php?client_id=$client_id'>$client_name</a>";
}
?>
<tr class="row-danger">
<td><?php echo $notification_timestamp; ?></td>
<td><?php echo $notification_type; ?></td>
<td><?php echo $notification; ?></td>
<td><?php echo $client_name_display; ?></td>
<td class="text-center"><a class="btn btn-info btn-sm" href="post.php?dismiss_notification=<?php echo $notification_id; ?>"><i class="fas fa-check"></a></td>
</tr>
<?php } ?>
</tbody>
</table>
</div>
</div>
<?php } else { ?>
<div class="my-5" style="text-align: center">
<i class='far fa-fw fa-6x fa-bell-slash text-secondary'></i><h3 class='text-secondary mt-3'>No Notifications</h3>
</div>
<?php } ?>
</div> </div>
<div class="card-body">
<form class="mb-4" autocomplete="off">
<?php if ($dismissed_filter) { ?>
<input type="hidden" name="dismissed" value="">
<?php } ?>
<div class="row">
<div class="col-sm-4">
<div class="input-group">
<input type="search" class="form-control" name="q" value="<?php if (isset($q)) { echo stripslashes(nullable_htmlentities($q)); } ?>" placeholder="Search <?php if($dismissed_filter) { echo "Dismissed "; } ?>Notifications">
<div class="input-group-append">
<button class="btn btn-primary text-strong"><i class="fa fa-search"></i></button>
<button class="btn btn-secondary" type="button" data-toggle="collapse" data-target="#advancedFilter"><i class="fas fa-filter"></i></button>
</div>
</div>
</div>
<div class="col-sm-8">
</div>
</div>
<div class="collapse mt-3 <?php if (!empty($_GET['dtf'])) { echo "show"; } ?>" id="advancedFilter">
<div class="row">
<div class="col-md-2">
<div class="form-group">
<label>Date From</label>
<input type="date" class="form-control" name="dtf" max="2999-12-31" value="<?php echo nullable_htmlentities($dtf); ?>">
</div>
</div>
<div class="col-md-2">
<div class="form-group">
<label>Date To</label>
<input type="date" class="form-control" name="dtt" max="2999-12-31" value="<?php echo nullable_htmlentities($dtt); ?>">
</div>
</div>
</div>
</div>
</form>
<div class="table-responsive-sm">
<table class="table table-hover">
<thead class="<?php if ($num_rows[0] == 0) { echo "d-none"; } ?>">
<tr>
<th>
<a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=notification_timestamp&order=<?php echo $disp; ?>">
Timestamp <?php if ($sort == 'notification_timestamp') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=notification_type&order=<?php echo $disp; ?>">
Type <?php if ($sort == 'notification_type') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=notification&order=<?php echo $disp; ?>">
Notification <?php if ($sort == 'notification') { echo $order_icon; } ?>
</a>
</th>
<?php if($dismissed_filter) { ?>
<th>
<a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=notification_dismissed_at&order=<?php echo $disp; ?>">
Dismissed At <?php if ($sort == 'notification_dismissed_at') { echo $order_icon; } ?>
</a>
</th>
<?php } ?>
<?php if(!$dismissed_filter) { ?>
<th class="text-center p-0">
<?php if (mysqli_num_rows($sql) > 0) { ?>
<a href="post.php?dismiss_all_notifications&csrf_token=<?php echo $_SESSION["csrf_token"]; ?>"
class="btn btn-sm btn-dark mb-2" title="Dismiss All">
<i class="fas fa-fw fa-check-double"></i>
</a>
<?php } ?>
</th>
<?php } ?>
</tr>
</thead>
<tbody>
<?php
while ($row = mysqli_fetch_array($sql)) {
$notification_id = intval($row['notification_id']);
$notification_timestamp = nullable_htmlentities($row['notification_timestamp']);
$notification_type = nullable_htmlentities($row['notification_type']);
$notification = nullable_htmlentities($row['notification']);
$notification_dismissed_at = nullable_htmlentities($row['notification_dismissed_at']);
$client_name = nullable_htmlentities($row['client_name']);
$client_id = intval($row['client_id']);
?>
<tr>
<td><?php echo $notification_timestamp; ?></td>
<td><?php echo $notification_type; ?></td>
<td><?php echo $notification; ?></td>
<?php if($dismissed_filter) { ?>
<td><?php echo $notification_dismissed_at; ?></td>
<?php } ?>
<?php if(!$dismissed_filter) { ?>
<td class="text-center"><a class="btn btn-secondary btn-sm" href="post.php?dismiss_notification=<?php echo $notification_id; ?>" title="Dismiss"><i class="fas fa-check"></i></a></td>
<?php } ?>
</tr>
<?php } ?>
</tbody>
</table>
</div>
<?php require_once "includes/filter_footer.php"; ?>
</div>
</div>
<?php <?php
require_once "includes/footer.php";
require_once "includes/footer.php";

View File

@@ -1,132 +0,0 @@
<?php
// Default Column Sortby Filter
$sort = "notification_timestamp";
$order = "DESC";
require_once "includes/inc_all.php";
//Rebuild URL
$url_query_strings_sort = http_build_query($get_copy);
$sql = mysqli_query(
$mysqli,
"SELECT SQL_CALC_FOUND_ROWS * FROM notifications
LEFT JOIN clients ON notification_client_id = client_id
WHERE (notification_type LIKE '%$q%' OR notification LIKE '%$q%' OR client_name LIKE '%$q%')
AND DATE(notification_timestamp) BETWEEN '$dtf' AND '$dtt'
AND notification_user_id = $session_user_id
AND notification_dismissed_at IS NOT NULL
ORDER BY $sort $order
LIMIT $record_from, $record_to
");
$num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
?>
<div class="card card-dark">
<div class="card-header py-3">
<h3 class="card-title"><i class="fas fa-fw fa-bell mr-2"></i>Dismissed Notications</h3>
</div>
<div class="card-body">
<form class="mb-4" autocomplete="off">
<div class="row">
<div class="col-sm-4">
<div class="input-group">
<input type="search" class="form-control" name="q" value="<?php if (isset($q)) { echo stripslashes(nullable_htmlentities($q)); } ?>" placeholder="Search Dismissed Notifications">
<div class="input-group-append">
<button class="btn btn-primary"><i class="fa fa-search"></i></button>
</div>
</div>
</div>
<div class="col-sm-8">
<button class="btn btn-primary float-right" type="button" data-toggle="collapse" data-target="#advancedFilter"><i class="fas fa-filter"></i></button>
</div>
</div>
<div class="collapse mt-3 <?php if (!empty($_GET['dtf'])) { echo "show"; } ?>" id="advancedFilter">
<div class="row">
<div class="col-md-2">
<div class="form-group">
<label>Date From</label>
<input type="date" class="form-control" name="dtf" max="2999-12-31" value="<?php echo nullable_htmlentities($dtf); ?>">
</div>
</div>
<div class="col-md-2">
<div class="form-group">
<label>Date To</label>
<input type="date" class="form-control" name="dtt" max="2999-12-31" value="<?php echo nullable_htmlentities($dtt); ?>">
</div>
</div>
</div>
</div>
</form>
<div class="table-responsive-sm">
<table class="table table-hover">
<thead class="<?php if ($num_rows[0] == 0) { echo "d-none"; } ?>">
<tr>
<th>
<a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=notification_timestamp&order=<?php echo $disp; ?>">
Timestamp <?php if ($sort == 'notification_timestamp') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=notification_type&order=<?php echo $disp; ?>">
Type <?php if ($sort == 'notification_type') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=notification&order=<?php echo $disp; ?>">
Notification <?php if ($sort == 'notification') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=client_name&order=<?php echo $disp; ?>">
Client <?php if ($sort == 'client_name') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=notification_dismissed_at&order=<?php echo $disp; ?>">
Dismissed At <?php if ($sort == 'notification_dismissed_at') { echo $order_icon; } ?>
</a>
</th>
</tr>
</thead>
<tbody>
<?php
while ($row = mysqli_fetch_array($sql)) {
$notification_id = intval($row['notification_id']);
$notification_timestamp = nullable_htmlentities($row['notification_timestamp']);
$notification_type = nullable_htmlentities($row['notification_type']);
$notification = nullable_htmlentities($row['notification']);
$notification_dismissed_at = nullable_htmlentities($row['notification_dismissed_at']);
$client_name = nullable_htmlentities($row['client_name']);
$client_id = intval($row['client_id']);
if (empty($client_name)) {
$client_name_display = "-";
} else {
$client_name_display = "<a href='client_overview.php?client_id=$client_id'>$client_name</a>";
}
?>
<tr>
<td><?php echo $notification_timestamp; ?></td>
<td><?php echo $notification_type; ?></td>
<td><?php echo $notification; ?></td>
<td><?php echo $client_name_display; ?></td>
<td><?php echo $notification_dismissed_at; ?></td>
<?php } ?>
</tbody>
</table>
</div>
<?php require_once "includes/filter_footer.php"; ?>
</div>
</div>
<?php
require_once "includes/footer.php";

View File

@@ -740,7 +740,6 @@ if (isset($_GET['unlink_asset_from_file'])) {
if (isset($_POST["import_assets_csv"])) { if (isset($_POST["import_assets_csv"])) {
enforceUserPermission('module_support', 2); enforceUserPermission('module_support', 2);
validateCSRFToken($_POST['csrf_token']); validateCSRFToken($_POST['csrf_token']);
$client_id = intval($_POST['client_id']); $client_id = intval($_POST['client_id']);
@@ -774,9 +773,9 @@ if (isset($_POST["import_assets_csv"])) {
//(Else)Check column count (name, desc, type, make, model, serial, os, assigned to, location) //(Else)Check column count (name, desc, type, make, model, serial, os, assigned to, location)
$f = fopen($file_name, "r"); $f = fopen($file_name, "r");
$f_columns = fgetcsv($f, 1000, ","); $f_columns = fgetcsv($f, 1000, ",");
if (!$error & count($f_columns) != 9) { if (!$error & count($f_columns) != 10) {
$error = true; $error = true;
$_SESSION['alert_message'] = "Bad column count."; $_SESSION['alert_message'] = "Invalid column count.";
} }
//Else, parse the file //Else, parse the file
@@ -832,11 +831,14 @@ if (isset($_POST["import_assets_csv"])) {
$location_id = intval($row['location_id']); $location_id = intval($row['location_id']);
} }
} }
if (!empty($column[9])) {
$physical_location = sanitizeInput($column[9]);
}
// Check if duplicate was detected // Check if duplicate was detected
if ($duplicate_detect == 0) { if ($duplicate_detect == 0) {
//Add //Add
mysqli_query($mysqli,"INSERT INTO assets SET asset_name = '$name', asset_description = '$description', asset_type = '$type', asset_make = '$make', asset_model = '$model', asset_serial = '$serial', asset_os = '$os', asset_contact_id = $contact_id, asset_location_id = $location_id, asset_client_id = $client_id"); mysqli_query($mysqli,"INSERT INTO assets SET asset_name = '$name', asset_description = '$description', asset_type = '$type', asset_make = '$make', asset_model = '$model', asset_serial = '$serial', asset_os = '$os', asset_physical_location = '$physical_location', asset_contact_id = $contact_id, asset_location_id = $location_id, asset_client_id = $client_id");
$asset_id = mysqli_insert_id($mysqli); $asset_id = mysqli_insert_id($mysqli);
@@ -864,7 +866,7 @@ if (isset($_POST["import_assets_csv"])) {
} }
if (isset($_GET['download_assets_csv_template'])) { if (isset($_GET['download_assets_csv_template'])) {
$client_id = intval($_GET['download_client_assets_csv_template']); $client_id = intval($_GET['download_assets_csv_template']);
//get records from database //get records from database
$sql = mysqli_query($mysqli,"SELECT client_name FROM clients WHERE client_id = $client_id"); $sql = mysqli_query($mysqli,"SELECT client_name FROM clients WHERE client_id = $client_id");
@@ -879,7 +881,7 @@ if (isset($_GET['download_assets_csv_template'])) {
$f = fopen('php://memory', 'w'); $f = fopen('php://memory', 'w');
//set column headers //set column headers
$fields = array('Name', 'Description', 'Type', 'Make', 'Model', 'Serial', 'OS', 'Assigned To', 'Location'); $fields = array('Name', 'Description', 'Type', 'Make', 'Model', 'Serial', 'OS', 'Assigned To', 'Location', 'Physical Location');
fputcsv($f, $fields, $delimiter); fputcsv($f, $fields, $delimiter);
//move back to beginning of file //move back to beginning of file
@@ -898,22 +900,22 @@ if (isset($_GET['download_assets_csv_template'])) {
if (isset($_POST['export_assets_csv'])) { if (isset($_POST['export_assets_csv'])) {
enforceUserPermission('module_support'); enforceUserPermission('module_support');
validateCSRFToken($_POST['csrf_token']); validateCSRFToken($_POST['csrf_token']);
$client_name = 'All'; // default
if (isset($_POST['client_id'])) { if (isset($_POST['client_id'])) {
$client_id = intval($_POST['client_id']); $client_id = intval($_POST['client_id']);
$client_query = "AND asset_client_id = $client_id"; $client_query = "AND asset_client_id = $client_id";
$client_row = mysqli_fetch_array(mysqli_query($mysqli,"SELECT client_name FROM clients WHERE client_id = $client_id"));
$client_name = $client_row['client_name'];
} else { } else {
$client_query = ''; $client_query = '';
} }
//get records from database // Get records from database
$sql = mysqli_query($mysqli,"SELECT * FROM assets LEFT JOIN contacts ON asset_contact_id = contact_id LEFT JOIN locations ON asset_location_id = location_id LEFT JOIN asset_interfaces ON interface_asset_id = asset_id AND interface_primary = 1 LEFT JOIN clients ON asset_client_id = client_id WHERE asset_archived_at IS NULL $client_query ORDER BY asset_name ASC"); $sql = mysqli_query($mysqli,"SELECT * FROM assets LEFT JOIN contacts ON asset_contact_id = contact_id LEFT JOIN locations ON asset_location_id = location_id LEFT JOIN asset_interfaces ON interface_asset_id = asset_id AND interface_primary = 1 LEFT JOIN clients ON asset_client_id = client_id WHERE asset_archived_at IS NULL $client_query ORDER BY asset_name ASC");
$row = mysqli_fetch_array($sql);
$client_name = $row['client_name'];
$num_rows = mysqli_num_rows($sql); $num_rows = mysqli_num_rows($sql);
if ($num_rows > 0) { if ($num_rows > 0) {
@@ -924,12 +926,12 @@ if (isset($_POST['export_assets_csv'])) {
$f = fopen('php://memory', 'w'); $f = fopen('php://memory', 'w');
//set column headers //set column headers
$fields = array('Name', 'Description', 'Type', 'Make', 'Model', 'Serial Number', 'Operating System', 'Purchase Date', 'Warranty Expire', 'Install Date', 'Assigned To', 'Location', 'Notes'); $fields = array('Name', 'Description', 'Type', 'Make', 'Model', 'Serial Number', 'Operating System', 'Purchase Date', 'Warranty Expire', 'Install Date', 'Assigned To', 'Location', 'Physical Location', 'Notes');
fputcsv($f, $fields, $delimiter); fputcsv($f, $fields, $delimiter);
//output each row of the data, format line as csv and write to file pointer //output each row of the data, format line as csv and write to file pointer
while($row = mysqli_fetch_array($sql)) { while ($row = mysqli_fetch_array($sql)) {
$lineData = array($row['asset_name'], $row['asset_description'], $row['asset_type'], $row['asset_make'], $row['asset_model'], $row['asset_serial'], $row['asset_os'], $row['asset_purchase_date'], $row['asset_warranty_expire'], $row['asset_install_date'], $row['contact_name'], $row['location_name'], $row['asset_notes']); $lineData = array($row['asset_name'], $row['asset_description'], $row['asset_type'], $row['asset_make'], $row['asset_model'], $row['asset_serial'], $row['asset_os'], $row['asset_purchase_date'], $row['asset_warranty_expire'], $row['asset_install_date'], $row['contact_name'], $row['location_name'], $row['asset_physical_location'], $row['asset_notes']);
fputcsv($f, $lineData, $delimiter); fputcsv($f, $lineData, $delimiter);
} }
@@ -1188,7 +1190,6 @@ if (isset($_GET['delete_asset_interface'])) {
if (isset($_POST["import_client_asset_interfaces_csv"])) { if (isset($_POST["import_client_asset_interfaces_csv"])) {
enforceUserPermission('module_support', 2); enforceUserPermission('module_support', 2);
validateCSRFToken($_POST['csrf_token']); validateCSRFToken($_POST['csrf_token']);
$asset_id = intval($_POST['asset_id']); $asset_id = intval($_POST['asset_id']);
@@ -1338,7 +1339,6 @@ if (isset($_GET['download_client_asset_interfaces_csv_template'])) {
if (isset($_POST['export_client_asset_interfaces_csv'])) { if (isset($_POST['export_client_asset_interfaces_csv'])) {
enforceUserPermission('module_support'); enforceUserPermission('module_support');
validateCSRFToken($_POST['csrf_token']); validateCSRFToken($_POST['csrf_token']);
$asset_id = intval($_POST['asset_id']); $asset_id = intval($_POST['asset_id']);

View File

@@ -64,8 +64,41 @@ if (isset($_POST['edit_certificate'])) {
$expire = "'" . $expire . "'"; $expire = "'" . $expire . "'";
} }
// Get current certificate info
$original_certificate_info = mysqli_fetch_assoc(mysqli_query($mysqli,"
SELECT
certificates.*,
domains.domain_name
FROM certificates
LEFT JOIN domains ON certificate_domain_id = domain_id
WHERE certificate_id = $certificate_id
"));
// Update certificate
mysqli_query($mysqli,"UPDATE certificates SET certificate_name = '$name', certificate_description = '$description', certificate_domain = '$domain', certificate_issued_by = '$issued_by', certificate_expire = $expire, certificate_public_key = '$public_key', certificate_notes = '$notes', certificate_domain_id = '$domain_id' WHERE certificate_id = $certificate_id"); mysqli_query($mysqli,"UPDATE certificates SET certificate_name = '$name', certificate_description = '$description', certificate_domain = '$domain', certificate_issued_by = '$issued_by', certificate_expire = $expire, certificate_public_key = '$public_key', certificate_notes = '$notes', certificate_domain_id = '$domain_id' WHERE certificate_id = $certificate_id");
// Fetch the updated info
$new_certificate_info = mysqli_fetch_assoc(mysqli_query($mysqli,"
SELECT
certificates.*,
domains.domain_name
FROM certificates
LEFT JOIN domains ON certificate_domain_id = domain_id
WHERE certificate_id = $certificate_id
"));
// Compare/log changes between old/new info
$ignored_columns = ["certificate_public_key", "certificate_updated_at", "certificate_accessed_at", "certificate_domain_id"];
foreach ($original_certificate_info as $column => $old_value) {
$new_value = $new_certificate_info[$column];
if ($old_value != $new_value && !in_array($column, $ignored_columns)) {
$column = sanitizeInput($column);
$old_value = sanitizeInput($old_value);
$new_value = sanitizeInput($new_value);
mysqli_query($mysqli,"INSERT INTO certificate_history SET certificate_history_column = '$column', certificate_history_old_value = '$old_value', certificate_history_new_value = '$new_value', certificate_history_certificate_id = $certificate_id");
}
}
// Logging // Logging
logAction("Certificate", "Edit", "$session_name edited certificate $name", $client_id, $certificate_id); logAction("Certificate", "Edit", "$session_name edited certificate $name", $client_id, $certificate_id);

View File

@@ -12,7 +12,7 @@ if (isset($_POST['add_login'])) {
require_once 'post/user/credential_model.php'; require_once 'post/user/credential_model.php';
mysqli_query($mysqli,"INSERT INTO logins SET login_name = '$name', login_description = '$description', login_uri = '$uri', login_uri_2 = '$uri_2', login_username = '$username', login_password = '$password', login_otp_secret = '$otp_secret', login_note = '$note', login_important = $important, login_contact_id = $contact_id, login_vendor_id = $vendor_id, login_asset_id = $asset_id, login_software_id = $software_id, login_client_id = $client_id"); mysqli_query($mysqli,"INSERT INTO logins SET login_name = '$name', login_description = '$description', login_uri = '$uri', login_uri_2 = '$uri_2', login_username = '$username', login_password = '$password', login_otp_secret = '$otp_secret', login_note = '$note', login_important = $important, login_contact_id = $contact_id, login_asset_id = $asset_id, login_client_id = $client_id");
$login_id = mysqli_insert_id($mysqli); $login_id = mysqli_insert_id($mysqli);
@@ -50,7 +50,7 @@ if (isset($_POST['edit_login'])) {
} }
// Update the login entry with the new details // Update the login entry with the new details
mysqli_query($mysqli,"UPDATE logins SET login_name = '$name', login_description = '$description', login_uri = '$uri', login_uri_2 = '$uri_2', login_username = '$username', login_password = '$password', login_otp_secret = '$otp_secret', login_note = '$note', login_important = $important, login_contact_id = $contact_id, login_vendor_id = $vendor_id, login_asset_id = $asset_id, login_software_id = $software_id WHERE login_id = $login_id"); mysqli_query($mysqli,"UPDATE logins SET login_name = '$name', login_description = '$description', login_uri = '$uri', login_uri_2 = '$uri_2', login_username = '$username', login_password = '$password', login_otp_secret = '$otp_secret', login_note = '$note', login_important = $important, login_contact_id = $contact_id, login_asset_id = $asset_id WHERE login_id = $login_id");
// Tags // Tags
// Delete existing tags // Delete existing tags

View File

@@ -66,7 +66,7 @@ if (isset($_POST['add_software'])) {
$expire = "'" . $expire . "'"; $expire = "'" . $expire . "'";
} }
$notes = sanitizeInput($_POST['notes']); $notes = sanitizeInput($_POST['notes']);
$vendor = sanitizeInput($_POST['vendor'] ?? 0); $vendor = intval($_POST['vendor'] ?? 0);
mysqli_query($mysqli,"INSERT INTO software SET software_name = '$name', software_version = '$version', software_description = '$description', software_type = '$type', software_key = '$key', software_license_type = '$license_type', software_seats = $seats, software_purchase_reference = '$purchase_reference', software_purchase = $purchase, software_expire = $expire, software_notes = '$notes', software_vendor_id = $vendor, software_client_id = $client_id"); mysqli_query($mysqli,"INSERT INTO software SET software_name = '$name', software_version = '$version', software_description = '$description', software_type = '$type', software_key = '$key', software_license_type = '$license_type', software_seats = $seats, software_purchase_reference = '$purchase_reference', software_purchase = $purchase, software_expire = $expire, software_notes = '$notes', software_vendor_id = $vendor, software_client_id = $client_id");
@@ -127,7 +127,7 @@ if (isset($_POST['edit_software'])) {
$expire = "'" . $expire . "'"; $expire = "'" . $expire . "'";
} }
$notes = sanitizeInput($_POST['notes']); $notes = sanitizeInput($_POST['notes']);
$vendor = sanitizeInput($_POST['vendor'] ?? 0); $vendor = intval($_POST['vendor'] ?? 0);
mysqli_query($mysqli,"UPDATE software SET software_name = '$name', software_version = '$version', software_description = '$description', software_type = '$type', software_key = '$key', software_license_type = '$license_type', software_seats = $seats, software_purchase_reference = '$purchase_reference', software_purchase = $purchase, software_expire = $expire, software_notes = '$notes', software_vendor_id = $vendor WHERE software_id = $software_id"); mysqli_query($mysqli,"UPDATE software SET software_name = '$name', software_version = '$version', software_description = '$description', software_type = '$type', software_key = '$key', software_license_type = '$license_type', software_seats = $seats, software_purchase_reference = '$purchase_reference', software_purchase = $purchase, software_expire = $expire, software_notes = '$notes', software_vendor_id = $vendor WHERE software_id = $software_id");

View File

@@ -18,8 +18,14 @@ if (isset($_GET['client_id'])) {
// Perms // Perms
enforceUserPermission('module_sales'); enforceUserPermission('module_sales');
//Rebuild URL // Status Filter
$url_query_strings_sort = http_build_query($get_copy); if (isset($_GET['status']) && $_GET['status'] == "inactive") {
$status_filter = "inactive";
$status_query = "AND recurring_status = 0";
} else {
$status_filter = "active";
$status_query = "AND recurring_status = 1";
}
$sql = mysqli_query( $sql = mysqli_query(
$mysqli, $mysqli,
@@ -29,6 +35,7 @@ $sql = mysqli_query(
LEFT JOIN recurring_payments ON recurring_payment_recurring_invoice_id = recurring_id LEFT JOIN recurring_payments ON recurring_payment_recurring_invoice_id = recurring_id
WHERE (CONCAT(recurring_prefix,recurring_number) LIKE '%$q%' OR recurring_frequency LIKE '%$q%' OR recurring_scope LIKE '%$q%' OR client_name LIKE '%$q%' OR category_name LIKE '%$q%') WHERE (CONCAT(recurring_prefix,recurring_number) LIKE '%$q%' OR recurring_frequency LIKE '%$q%' OR recurring_scope LIKE '%$q%' OR client_name LIKE '%$q%' OR category_name LIKE '%$q%')
AND DATE(recurring_created_at) BETWEEN '$dtf' AND '$dtt' AND DATE(recurring_created_at) BETWEEN '$dtf' AND '$dtt'
$status_query
$client_query $client_query
ORDER BY $sort $order LIMIT $record_from, $record_to"); ORDER BY $sort $order LIMIT $record_from, $record_to");
@@ -49,6 +56,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<?php if ($client_url) { ?> <?php if ($client_url) { ?>
<input type="hidden" name="client_id" value="<?php echo $client_id; ?>"> <input type="hidden" name="client_id" value="<?php echo $client_id; ?>">
<?php } ?> <?php } ?>
<input type="hidden" name="status" value="<?php echo $status_filter; ?>">
<div class="row"> <div class="row">
<div class="col-sm-4"> <div class="col-sm-4">
<div class="input-group"> <div class="input-group">
@@ -60,7 +68,11 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</div> </div>
</div> </div>
<div class="col-sm-8"> <div class="col-sm-8">
<div class="btn-group float-right"> <div class="btn-toolbar float-right">
<div class="btn-group mr-2">
<a href="?<?php echo $client_url; ?>status=active" class="btn btn-<?php if ($status_filter == "active"){ echo "primary"; } else { echo "default"; } ?>"><i class="fa fa-fw fa-check mr-2"></i>Active</a>
<a href="?<?php echo $client_url; ?>status=inactive" class="btn btn-<?php if ($status_filter == "inactive"){ echo "primary"; } else { echo "default"; } ?>"><i class="fa fa-fw fa-ban mr-2"></i>Inactive</a>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -66,7 +66,7 @@ enforceUserPermission('module_financial');
?> ?>
<tr> <tr>
<td><a href="client_invoices.php?client_id=<?php echo $client_id; ?>"><?php echo $client_name; ?></a></td> <td><a href="invoices.php?client_id=<?php echo $client_id; ?>"><?php echo $client_name; ?></a></td>
<td class="text-right"><?php echo numfmt_format_currency($currency_format, $balance, $session_company_currency); ?></td> <td class="text-right"><?php echo numfmt_format_currency($currency_format, $balance, $session_company_currency); ?></td>
</tr> </tr>
<?php <?php

View File

@@ -60,8 +60,42 @@ while ($row = mysqli_fetch_array($sql_certificates)) {
echo "$public_key\n\n"; echo "$public_key\n\n";
$expire = "'" . $expire . "'"; $expire = "'" . $expire . "'";
// Get current certificate info
$original_certificate_info = mysqli_fetch_assoc(mysqli_query($mysqli,"
SELECT
certificates.*,
domains.domain_name
FROM certificates
LEFT JOIN domains ON certificate_domain_id = domain_id
WHERE certificate_id = $certificate_id
"));
// Update
mysqli_query($mysqli,"UPDATE certificates SET certificate_issued_by = '$issued_by', certificate_expire = $expire, certificate_public_key = '$public_key' WHERE certificate_id = $certificate_id"); mysqli_query($mysqli,"UPDATE certificates SET certificate_issued_by = '$issued_by', certificate_expire = $expire, certificate_public_key = '$public_key' WHERE certificate_id = $certificate_id");
// Fetch the updated info
$new_certificate_info = mysqli_fetch_assoc(mysqli_query($mysqli,"
SELECT
certificates.*,
domains.domain_name
FROM certificates
LEFT JOIN domains ON certificate_domain_id = domain_id
WHERE certificate_id = $certificate_id
"));
// Compare/log changes between old/new info
$ignored_columns = ["certificate_public_key", "certificate_updated_at", "certificate_accessed_at", "certificate_domain_id"];
foreach ($original_certificate_info as $column => $old_value) {
$new_value = $new_certificate_info[$column];
if ($old_value != $new_value && !in_array($column, $ignored_columns)) {
$column = sanitizeInput($column);
$old_value = sanitizeInput($old_value);
$new_value = sanitizeInput($new_value);
mysqli_query($mysqli,"INSERT INTO certificate_history SET certificate_history_column = '$column', certificate_history_old_value = '$old_value', certificate_history_new_value = '$new_value', certificate_history_certificate_id = $certificate_id");
}
}
} else { } else {
logApp("Cron-Certificate-Refresher", "error", "Cron Certificate Refresh - error updating Error updating $domain."); logApp("Cron-Certificate-Refresher", "error", "Cron Certificate Refresh - error updating Error updating $domain.");
error_log("Certificate Cron Error - Error updating $domain"); error_log("Certificate Cron Error - Error updating $domain");