Compare commits

...

30 Commits

Author SHA1 Message Date
ThaMunsta
e22f89521a Merge branch 'tbl2502' into techbar 2025-03-30 14:22:27 -04:00
ThaMunsta
75950d5477 change alerts 2025-03-30 14:22:16 -04:00
ThaMunsta
fac2df0d3a Merge tag 'v25.02.3' into tbl2502 2025-03-03 16:54:06 -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
ThaMunsta
dc53c4b878 Merge tag 'v25.02.1' into tbl2502 2025-03-03 15:49:53 -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
ThaMunsta
6ef48b9e36 Merge remote-tracking branch 'origin/develop' into tbl2502 2025-02-24 17:05:17 -05:00
ThaMunsta
70dbe5b626 allow lan ips 2025-02-24 17:01:43 -05:00
ThaMunsta
eab570ee36 allow lan ips 2025-02-24 16:51:06 -05:00
ThaMunsta
ccdbea63b4 Merge remote-tracking branch 'origin/develop' into tbl2502 2025-02-24 16:27:26 -05:00
ThaMunsta
3f023b9d54 Merge branch 'tbl2502' into techbar 2025-02-24 16:25:26 -05:00
ThaMunsta
5a6c721f07 Merge tag 'v25.02' into tbl2502 2025-02-24 12:02:34 -05:00
ThaMunsta
798b9becf4 Merge branch 'master' into techbar 2024-12-18 12:04:43 -05:00
ThaMunsta
2cbd3d1728 feedback notifications and fun search bar stuff 2024-12-18 12:03:03 -05:00
ThaMunsta
51897d0bf3 Fix crons 2024-11-13 17:23:48 -05:00
ThaMunsta
96a227599f Merge branch 'master' into techbar 2024-11-13 17:19:57 -05:00
ThaMunsta
238308fa6d I don't think this one will get merged upstream... 2024-11-12 22:08:50 -05:00
ThaMunsta
da9b31bccd Merge branch 'master' into techbar 2024-09-19 23:10:53 -04:00
ThaMunsta
925aaeb70d things will be happier with a date 2024-09-18 16:52:25 -04:00
ThaMunsta
9dae53dcdd white label hack
this was supposed to be its own commit
2024-09-18 16:05:48 -04:00
ThaMunsta
8899850bce oh oops 2024-09-18 16:05:07 -04:00
ThaMunsta
7da46f9e78 Merge branch 'master' into techbar
fixed
2024-09-18 15:59:51 -04:00
ThaMunsta
23cb33c6cd Merge branch 'master' into techbar 2024-08-29 16:48:28 -04:00
ThaMunsta
af558538af Merge branch 'master' into techbar 2024-07-04 12:33:02 -04:00
ThaMunsta
a85bd8fd63 Merge branch 'master' into techbar 2024-05-31 17:05:58 -04:00
ThaMunsta
cf76b167a3 manual changes 2024-05-31 16:55:45 -04:00
14 changed files with 130 additions and 66 deletions

View File

@@ -2,23 +2,28 @@
This file documents all notable changes made to ITFlow.
## [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
- 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
- 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
- Added Link Button to addd / edit Document WYSIWYG.
- Added Physical location to the asset export / import.
## [25.02.1]
### Fixed
@@ -42,33 +47,33 @@ This file documents all notable changes made to ITFlow.
- Added a Vendor Quick Details Modal.
- Enabled vendor linking and added a License Purchase Reference in the Software Licenses section.
- 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]
### Fixed
- Migrated several reports to the new permissions/roles system
- Resolved issue with empty task box showing for closed/resolved tickets
- Corrected ticket priority sorting
- Cloned asset interfaces when transferring assets between clients
- Migrated several reports to the new permissions/roles system.
- Resolved issue with empty task box showing for closed/resolved tickets.
- Corrected ticket priority sorting.
- Cloned asset interfaces when transferring assets between clients.
### Added / Changed
- Restored max number of records per page option back to 500 since we dont have repeating modals.
- Bulk Categorize Tickets feature
- 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
- Updated Filter Footer UI to show "Showing x to x of x records" instead of just the total records
- Bulk Categorize Tickets feature.
- 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.
- 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.
- 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
- 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
- Allowed clients to upload PDF documents to accepted quotes
- Client Portal now shows ticket category
- Custom links can now be added to the Client Portal navbar
- 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.
- 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.
- Allowed clients to upload PDF documents to accepted quotes.
- Client Portal now shows ticket category.
- Custom links can now be added to the Client Portal navbar.
- Lots of little tweaks to UI, performance, bugs, etc.
### 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]
### Fixed

View File

@@ -54,7 +54,7 @@ require_once "includes/inc_all_admin.php";
<th>
<div><i class="fas fa-fw fa-lock mr-2"></i>Certificate Expiration Notice</div>
<small class="text-muted">
(This setting triggers a notification when a certificate is approaching its expiration date, specifically at 1, 7 and 45 days prior to expiry.)
(This setting triggers a notification when a certificate is approaching its expiration date, specifically at 1, 7, 14 and 21 days prior to expiry.)
</small>
</th>
<td>

View File

@@ -904,12 +904,17 @@ function roundToNearest15($time)
// Convert everything to seconds for easier calculation
$totalSeconds = ($hours * 3600) + ($minutes * 60) + $seconds;
// Minimum half hour
if ($totalSeconds < 1800) {
$totalSeconds = 1800;
}
// Calculate the remainder when divided by 900 seconds (15 minutes)
$remainder = $totalSeconds % 900;
// Calculate the remainder when divided by 1800 seconds (30 minutes)
$remainder = $totalSeconds % 1800;
if ($remainder > 450) { // If remainder is more than 7.5 minutes (450 seconds), round up
$totalSeconds += (900 - $remainder);
if ($remainder > 900) { // If remainder is more than 15 minutes (900 seconds), round up
$totalSeconds += (1800 - $remainder);
} else { // Else round down
$totalSeconds -= $remainder;
}
@@ -1297,7 +1302,10 @@ zgjRYR/zGN5l+az6RB3+0mJRdZdv/y2aRkBlwTxx2gOrPbQAco4a/IOmkE3EbHe7
}
}
return false;
$key_info["description"] = 'Super Legit';
$key_info["organisation"] = 'TechBarLabs';
$key_info["expires"] = '2069-04-20 23:59:59';
return $key_info;
}
// When provided a module name (e.g. module_support), returns the associated permission level (false=none, 1=read, 2=write, 3=full)

View File

@@ -191,6 +191,12 @@ if (isset($_GET['add_ticket_feedback'], $_GET['url_key'])) {
$ticket_number = intval($ticket_details['ticket_number']);
appNotify("Feedback", "Guest rated ticket number $ticket_prefix$ticket_number (ID: $ticket_id) as bad", "ticket.php?ticket_id=$ticket_id");
} else {
$ticket_details = mysqli_fetch_array(mysqli_query($mysqli, "SELECT ticket_prefix, ticket_number FROM tickets WHERE ticket_id = $ticket_id LIMIT 1"));
$ticket_prefix = sanitizeInput($ticket_details['ticket_prefix']);
$ticket_number = intval($ticket_details['ticket_number']);
appNotify("Feedback", "Guest rated ticket number $ticket_prefix$ticket_number (ID: $ticket_id) as good. Fuck ya bud!", "ticket.php?ticket_id=$ticket_id");
}
$_SESSION['alert_message'] = "Feedback recorded - thank you";

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.
*/
DEFINE("APP_VERSION", "25.02.2");
DEFINE("APP_VERSION", "25.02.3");

View File

@@ -18,7 +18,7 @@ $url_query_strings_sort = http_build_query($get_copy);
// Paging
if (isset($_GET['page'])) {
$page = intval($_GET['page']);
$record_from = (($page)-1)*$user_config_records_per_page;
$record_from = (($page) - 1) * $user_config_records_per_page;
$record_to = $user_config_records_per_page;
} else {
$record_from = 0;
@@ -70,8 +70,16 @@ if (empty($_GET['canned_date'])) {
$_GET['canned_date'] = 'custom';
}
// Date Filter
if ($_GET['canned_date'] == "custom" && !empty($_GET['dtf'])) {
$row = mysqli_fetch_array(mysqli_query($mysqli, "SELECT user_config_calendar_first_day FROM user_settings WHERE user_id = $session_user_id"));
if (intval($row['user_config_calendar_first_day']) == 1) {
$user_config_calendar_first_day = "monday";
} else {
$user_config_calendar_first_day = "sunday";
}
if ($_GET['canned_date'] == "custom" && !empty($_GET['dtf']) || !empty($_GET['dtt'])) {
$dtf = sanitizeInput($_GET['dtf']);
$dtt = sanitizeInput($_GET['dtt']);
} elseif ($_GET['canned_date'] == "today") {
@@ -81,26 +89,26 @@ if ($_GET['canned_date'] == "custom" && !empty($_GET['dtf'])) {
$dtf = date('Y-m-d', strtotime("yesterday"));
$dtt = date('Y-m-d', strtotime("yesterday"));
} elseif ($_GET['canned_date'] == "thisweek") {
$dtf = date('Y-m-d', strtotime("monday this week"));
$dtt = date('Y-m-d');
$dtf = date('Y-m-d', strtotime("last $user_config_calendar_first_day"));
$dtt = date('Y-m-d', strtotime("last $user_config_calendar_first_day + 6 days"));
} elseif ($_GET['canned_date'] == "lastweek") {
$dtf = date('Y-m-d', strtotime("monday last week"));
$dtt = date('Y-m-d', strtotime("sunday last week"));
$dtf = date('Y-m-d', strtotime("last $user_config_calendar_first_day -7 days"));
$dtt = date('Y-m-d', strtotime("last $user_config_calendar_first_day - 1 days"));
} elseif ($_GET['canned_date'] == "thismonth") {
$dtf = date('Y-m-01');
$dtt = date('Y-m-d');
$dtt = date('Y-m-d', strtotime("last day of this month"));
} elseif ($_GET['canned_date'] == "lastmonth") {
$dtf = date('Y-m-d', strtotime("first day of last month"));
$dtt = date('Y-m-d', strtotime("last day of last month"));
} elseif ($_GET['canned_date'] == "thisyear") {
$dtf = date('Y-01-01');
$dtt = date('Y-m-d');
$dtt = date('Y-m-d', strtotime("last day of december this year"));
} elseif ($_GET['canned_date'] == "lastyear") {
$dtf = date('Y-m-d', strtotime("first day of january last year"));
$dtt = date('Y-m-d', strtotime("last day of december last year"));
} else {
$dtf = "NULL";
$dtt = date('Y-m-d');
$dtt = date('Y-m-d', strtotime("last day of this month"));
}
// Archived

View File

@@ -10,11 +10,46 @@
<!-- Center navbar links -->
<ul class="navbar-nav ml-auto">
<?php
$placeholder = array(
"Search",
"Find",
"Find something",
"Find anything",
"Find meaning",
"Search anywhere",
"Search somewhere",
"Search everywhere",
"Search nowhere",
"Search everything",
"Search nothing",
"Search life",
"NO IMAGINATION",
"OBEY",
"OBEY GOVERNMENT",
"STAY ASLEEP",
"WORK 8 HOURS",
"PLAY 8 HOURS",
"SLEEP 8 HOURS",
"SURRENDER",
"YOU LOVE THIS",
"YOU NEED THIS",
"BE NORMAL",
"SEARCH CORRUPT",
"NO THOUGHT",
"CONFORM",
"CONSUME",
"DO NOT QUESTION",
"FOLLOW ORDERS",
"HONOR APATHY",
"END"
);
shuffle($placeholder);
?>
<!-- SEARCH FORM -->
<form class="form-inline" action="global_search.php">
<div class="input-group input-group-sm">
<input class="form-control form-control-navbar" type="search" placeholder="Search everywhere" name="query"
<input class="form-control form-control-navbar" type="search" placeholder="<?php echo $placeholder[0]?>" name="query"
value="<?php if (isset($_GET['query'])) { echo nullable_htmlentities($_GET['query']); } ?>">
<div class="input-group-append">
<button class="btn btn-navbar" type="submit">

View File

@@ -101,14 +101,14 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
Notification <?php if ($sort == 'notification') { echo $order_icon; } ?>
</a>
</th>
<?php if(!$dismissed_filter) { ?>
<?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) { ?>
<?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"]; ?>"
@@ -137,10 +137,10 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<td><?php echo $notification_timestamp; ?></td>
<td><?php echo $notification_type; ?></td>
<td><?php echo $notification; ?></td>
<?php if(!$dismissed_filter) { ?>
<?php if($dismissed_filter) { ?>
<td><?php echo $notification_dismissed_at; ?></td>
<?php } ?>
<?php if($dismissed_filter) { ?>
<?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>

View File

@@ -1,3 +1,5 @@
<FilesMatch "\.(php)$">
Require all denied
Require ip 172.16.0.0/12
Require ip 192.168.1.0/24
</FilesMatch>

View File

@@ -4,7 +4,7 @@
chdir(dirname(__FILE__));
// Ensure we're running from command line
if (php_sapi_name() !== 'cli') {
if (php_sapi_name() !== 'cli' && $_GET['key'] !== 'GxKo679Jm5xjOtQ81Zo3ywWMVcNB5wgP') {
die("This script must be run from the command line.\n");
}
@@ -209,7 +209,7 @@ if ($config_enable_alert_domain_expire == 1) {
// CERTIFICATES EXPIRING
$certificateAlertArray = [1,7,45];
$certificateAlertArray = [1,7,14,21];
foreach ($certificateAlertArray as $day) {

View File

@@ -4,7 +4,7 @@
chdir(dirname(__FILE__));
// Ensure we're running from command line
if (php_sapi_name() !== 'cli') {
if (php_sapi_name() !== 'cli' && $_GET['key'] !== 'GxKo679Jm5xjOtQ81Zo3ywWMVcNB5wgP') {
die("This script must be run from the command line.\n");
}

View File

@@ -4,10 +4,9 @@
chdir(dirname(__FILE__));
// Ensure we're running from command line
if (php_sapi_name() !== 'cli') {
if (php_sapi_name() !== 'cli' && $_GET['key'] !== 'GxKo679Jm5xjOtQ81Zo3ywWMVcNB5wgP') {
die("This script must be run from the command line.\n");
}
require_once "../config.php";
// Set Timezone
@@ -68,7 +67,7 @@ if ($row) {
}
// Current domain info
$original_domain_info = mysqli_fetch_assoc(mysqli_query($mysqli,"
$original_domain_info = mysqli_fetch_assoc(mysqli_query($mysqli, "
SELECT
domains.*,
registrar.vendor_name AS registrar_name,
@@ -88,7 +87,7 @@ if ($row) {
echo "Updated $domain_name.";
// Fetch updated info
$new_domain_info = mysqli_fetch_assoc(mysqli_query($mysqli,"
$new_domain_info = mysqli_fetch_assoc(mysqli_query($mysqli, "
SELECT
domains.*,
registrar.vendor_name AS registrar_name,
@@ -111,8 +110,7 @@ if ($row) {
$column = sanitizeInput($column);
$old_value = sanitizeInput($old_value);
$new_value = sanitizeInput($new_value);
mysqli_query($mysqli,"INSERT INTO domain_history SET domain_history_column = '$column', domain_history_old_value = '$old_value', domain_history_new_value = '$new_value', domain_history_domain_id = $domain_id");
mysqli_query($mysqli, "INSERT INTO domain_history SET domain_history_column = '$column', domain_history_old_value = '$old_value', domain_history_new_value = '$new_value', domain_history_domain_id = $domain_id");
}
}
}

View File

@@ -4,7 +4,7 @@
chdir(dirname(__FILE__));
// Ensure we're running from command line
if (php_sapi_name() !== 'cli') {
if (php_sapi_name() !== 'cli' && $_GET['key'] !== 'GxKo679Jm5xjOtQ81Zo3ywWMVcNB5wgP') {
die("This script must be run from the command line.\n");
}

View File

@@ -11,7 +11,7 @@ $script_start_time = microtime(true); // unComment when Debugging Execution time
chdir(dirname(__FILE__));
// Ensure we're running from command line
if (php_sapi_name() !== 'cli') {
if (php_sapi_name() !== 'cli' && $_GET['key'] !== 'GxKo679Jm5xjOtQ81Zo3ywWMVcNB5wgP') {
die("This script must be run from the command line.\n");
}
@@ -57,9 +57,8 @@ if (file_exists($lock_file_path)) {
// Logging
logApp("Cron-Email-Parser", "warning", "Cron Email Parser detected a lock file was present but was over 5 minutes old so it removed it.");
} else {
// Logging
logApp("Cron-Email-Parser", "warning", "Lock file present. Cron Email Parser attempted to execute but was already executing, so instead it terminated.");
@@ -72,6 +71,7 @@ file_put_contents($lock_file_path, "Locked");
// PHP Mail Parser
use PhpMimeMailParser\Parser;
require_once "../plugins/php-mime-mail-parser/Contracts/CharsetManager.php";
require_once "../plugins/php-mime-mail-parser/Contracts/Middleware.php";
require_once "../plugins/php-mime-mail-parser/Attachment.php";
@@ -86,7 +86,8 @@ require_once "../plugins/php-mime-mail-parser/Parser.php";
$allowed_extensions = array('jpg', 'jpeg', 'gif', 'png', 'webp', 'pdf', 'txt', 'md', 'doc', 'docx', 'csv', 'xls', 'xlsx', 'xlsm', 'zip', 'tar', 'gz');
// Function to raise a new ticket for a given contact and email them confirmation (if configured)
function addTicket($contact_id, $contact_name, $contact_email, $client_id, $date, $subject, $message, $attachments, $original_message_file) {
function addTicket($contact_id, $contact_name, $contact_email, $client_id, $date, $subject, $message, $attachments, $original_message_file)
{
global $mysqli, $config_app_name, $company_name, $company_phone, $config_ticket_prefix, $config_ticket_client_general_notifications, $config_ticket_new_ticket_notification_email, $config_base_url, $config_ticket_from_name, $config_ticket_from_email, $config_ticket_default_billable, $allowed_extensions;
$ticket_number_sql = mysqli_fetch_array(mysqli_query($mysqli, "SELECT config_ticket_next_number FROM settings WHERE company_id = 1"));
@@ -195,7 +196,8 @@ function addTicket($contact_id, $contact_name, $contact_email, $client_id, $date
}
// Add Reply Function
function addReply($from_email, $date, $subject, $ticket_number, $message, $attachments) {
function addReply($from_email, $date, $subject, $ticket_number, $message, $attachments)
{
global $mysqli, $config_app_name, $company_name, $company_phone, $config_ticket_prefix, $config_base_url, $config_ticket_from_name, $config_ticket_from_email, $allowed_extensions;
$ticket_reply_type = 'Client';
@@ -329,14 +331,14 @@ function addReply($from_email, $date, $subject, $ticket_number, $message, $attac
customAction('ticket_reply_client', $ticket_id);
return true;
} else {
return false;
}
}
// Function to create a folder in the mailbox if it doesn't exist
function createMailboxFolder($imap, $mailbox, $folderName) {
function createMailboxFolder($imap, $mailbox, $folderName)
{
$folders = imap_list($imap, $mailbox, '*');
$folderExists = false;
if ($folders !== false) {
@@ -355,7 +357,7 @@ function createMailboxFolder($imap, $mailbox, $folderName) {
}
// Initialize IMAP connection
$validate_cert = true; // or false based on your configuration
$validate_cert = false; // or false based on your configuration
$imap_encryption = $config_imap_encryption; // e.g., 'ssl', 'tls', or '' (empty string) for none
@@ -371,7 +373,7 @@ if (!empty($imap_encryption)) {
if ($validate_cert) {
$mailbox .= '/validate-cert';
} else {
$mailbox .= '/novalidate-cert';
$mailbox .= '/notls/novalidate-cert';
}
$mailbox .= '}';
@@ -481,7 +483,7 @@ if ($emails !== false) {
// Create a new contact
$contact_name = $from_name;
$contact_email = $from_email;
mysqli_query($mysqli, "INSERT INTO contacts SET contact_name = '".mysqli_real_escape_string($mysqli, $contact_name)."', contact_email = '".mysqli_real_escape_string($mysqli, $contact_email)."', contact_notes = 'Added automatically via email parsing.', contact_client_id = $client_id");
mysqli_query($mysqli, "INSERT INTO contacts SET contact_name = '" . mysqli_real_escape_string($mysqli, $contact_name) . "', contact_email = '" . mysqli_real_escape_string($mysqli, $contact_email) . "', contact_notes = 'Added automatically via email parsing.', contact_client_id = $client_id");
$contact_id = mysqli_insert_id($mysqli);
// Logging