Merge pull request #1206 from itflow-org/develop

Develop to Master for 25.03.4 Release
This commit is contained in:
Johnny 2025-04-07 13:31:20 -04:00 committed by GitHub
commit d92f0fc49b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 128 additions and 76 deletions

View File

@ -1,6 +1,19 @@
# Changelog # Changelog
This file documents all notable changes made to ITFlow. This file documents all notable changes made to ITFlow.
## [25.03.4]
### Fixed
- Ability to remove additional assets from the ticket details screen.
- Fix the ability to remove assets from edit ticket not working when only 1 asset exists.
- Fix Database Backup corruption.
- Client Portal - show ticket number instead of ticket id in ticket listing.
- Add Purchase Reference to copy asset.
- Add Link to asset details from the global search.
- Fix Bulk assign ticket only showing contacts instead of ITFlow users.
## [25.03.3] ## [25.03.3]
### Fixed ### Fixed

View File

@ -364,6 +364,16 @@ ob_start();
</div> </div>
<?php if ($asset_type !== 'Virtual Machine') { ?> <?php if ($asset_type !== 'Virtual Machine') { ?>
<div class="form-group">
<label>Purchase Reference</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-shopping-cart"></i></span>
</div>
<input type="text" class="form-control" name="purchase_reference" placeholder="eg. Invoice, PO Number" >
</div>
</div>
<div class="form-group"> <div class="form-group">
<label>Purchase Date</label> <label>Purchase Date</label>
<div class="input-group"> <div class="input-group">

View File

@ -62,7 +62,7 @@ $all_tickets = mysqli_query($mysqli, "SELECT ticket_id, ticket_prefix, ticket_nu
$ticket_contact_name = nullable_htmlentities($row['contact_name']); $ticket_contact_name = nullable_htmlentities($row['contact_name']);
echo "<tr>"; echo "<tr>";
echo "<td> <a href='ticket.php?id=$ticket_id'> $ticket_prefix$ticket_id</a></td>"; echo "<td> <a href='ticket.php?id=$ticket_id'> $ticket_prefix$ticket_number</a></td>";
echo "<td> <a href='ticket.php?id=$ticket_id'> $ticket_subject</a></td>"; echo "<td> <a href='ticket.php?id=$ticket_id'> $ticket_subject</a></td>";
echo "<td>$ticket_contact_name</td>"; echo "<td>$ticket_contact_name</td>";
echo "<td>$ticket_status</td>"; echo "<td>$ticket_status</td>";

View File

@ -721,7 +721,6 @@ if (isset($_GET['query'])) {
} else { } else {
$asset_serial_display = $asset_serial; $asset_serial_display = $asset_serial;
} }
$asset_mac = nullable_htmlentities($row['asset_mac']);
$asset_uri = nullable_htmlentities($row['asset_uri']); $asset_uri = nullable_htmlentities($row['asset_uri']);
$asset_status = nullable_htmlentities($row['asset_status']); $asset_status = nullable_htmlentities($row['asset_status']);
$asset_created_at = nullable_htmlentities($row['asset_created_at']); $asset_created_at = nullable_htmlentities($row['asset_created_at']);
@ -746,9 +745,9 @@ if (isset($_GET['query'])) {
?> ?>
<tr> <tr>
<td> <td>
<i class="fa fa-fw text-secondary fa-<?php echo $device_icon; ?> mr-2"></i><?php echo $asset_name; ?> <i class="fa fa-fw text-secondary fa-<?php echo $device_icon; ?> mr-2"></i><a href="asset_details.php?client_id=<?php echo $client_id; ?>&asset_id=<?php echo $asset_id; ?>"><?php echo $asset_name; ?></a>
<?php if(!empty($asset_uri)){ ?> <?php if(!empty($asset_uri)){ ?>
<a href="<?php echo $asset_uri; ?>" target="_blank"><i class="fas fa-fw fa-external-link-alt ml-2"></i></a> <a href="<?php echo $asset_uri; ?>" target="_blank"><i class="fas fa-fw fa-external-link-alt ml-2"></i></a>
<?php } ?> <?php } ?>
</td> </td>
<td><?php echo $asset_type; ?></td> <td><?php echo $asset_type; ?></td>

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.03.3"); DEFINE("APP_VERSION", "25.03.4");

View File

@ -75,20 +75,23 @@
<div class="tab-pane fade" id="pills-support"> <div class="tab-pane fade" id="pills-support">
<label>Support Phone</label> <label>Support Phone / <span class="text-secondary">Extension</span></label>
<div class="form-row"> <div class="form-row">
<div class="col-8"> <div class="col-9">
<div class="form-group"> <div class="form-group">
<div class="input-group"> <div class="input-group">
<div class="input-group-prepend"> <div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-phone"></i></span> <span class="input-group-text"><i class="fa fa-fw fa-phone"></i></span>
</div> </div>
<input type="text" class="form-control" name="phone" placeholder="Phone Number" maxlength="200"> <input type="tel" class="form-control col-2" name="phone_country_code" placeholder="+" maxlength="4">
<input type="tel" class="form-control" name="phone" placeholder="Phone Number" maxlength="200">
</div> </div>
</div> </div>
</div> </div>
<div class="col-4"> <div class="col-3">
<input type="text" class="form-control" name="extension" placeholder="Prompts" maxlength="200"> <div class="form-group">
<input type="text" class="form-control" name="extension" placeholder="ext." maxlength="200">
</div>
</div> </div>
</div> </div>

View File

@ -21,7 +21,7 @@
<span class="input-group-text"><i class="fa fa-fw fa-envelope"></i></span> <span class="input-group-text"><i class="fa fa-fw fa-envelope"></i></span>
</div> </div>
<select class="form-control select2" data-tags="true" name="watcher_email"> <select class="form-control select2" data-tags="true" name="watcher_email">
<option value="">-Select a contact-</option> <option value="">- Select a contact or enter an email -</option>
<?php <?php
$sql_client_contacts_select = mysqli_query($mysqli, "SELECT contact_id, contact_name, contact_email FROM contacts WHERE contact_client_id = $client_id AND contact_email <> '' ORDER BY contact_name ASC"); $sql_client_contacts_select = mysqli_query($mysqli, "SELECT contact_id, contact_name, contact_email FROM contacts WHERE contact_client_id = $client_id AND contact_email <> '' ORDER BY contact_name ASC");

View File

@ -18,9 +18,8 @@
<select class="form-control select2" name="assign_to"> <select class="form-control select2" name="assign_to">
<option value="0">Not Assigned</option> <option value="0">Not Assigned</option>
<?php <?php
$sql_users_select = mysqli_query($mysqli, "SELECT users.user_id, user_name FROM users $sql_users_select = mysqli_query($mysqli, "SELECT user_id, user_name FROM users
LEFT JOIN user_settings on users.user_id = user_settings.user_id WHERE user_type = 1
AND user_type = 1
AND user_status = 1 AND user_status = 1
AND user_archived_at IS NULL AND user_archived_at IS NULL
ORDER BY user_name DESC" ORDER BY user_name DESC"

View File

@ -7,84 +7,68 @@
defined('FROM_POST_HANDLER') || die("Direct file access is not allowed"); defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
if (isset($_GET['download_database'])) { if (isset($_GET['download_database'])) {
validateCSRFToken($_GET['csrf_token']); validateCSRFToken($_GET['csrf_token']);
// Get All Table Names From the Database global $mysqli, $database;
$tables = array();
$sql = "SHOW TABLES";
$result = mysqli_query($mysqli, $sql);
while ($row = mysqli_fetch_row($result)) { $backupFileName = date('Y-m-d_H-i-s') . '_backup.sql';
header('Content-Type: application/sql');
header('Content-Disposition: attachment; filename="' . $backupFileName . '"');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Pragma: no-cache');
header('Expires: 0');
if (ob_get_level()) ob_end_clean();
flush();
// Start of dump file — charset declaration
echo "-- UTF-8 + Foreign Key Safe Dump\n";
echo "SET NAMES 'utf8mb4';\n";
echo "SET foreign_key_checks = 0;\n\n";
// Get all tables
$tables = [];
$res = $mysqli->query("SHOW TABLES");
while ($row = $res->fetch_row()) {
$tables[] = $row[0]; $tables[] = $row[0];
} }
$sqlScript = "";
foreach ($tables as $table) { foreach ($tables as $table) {
// Table structure
$createRes = $mysqli->query("SHOW CREATE TABLE `$table`");
$createRow = $createRes->fetch_assoc();
$createSQL = array_values($createRow)[1];
// Prepare SQLscript for creating table structure echo "\n-- ----------------------------\n";
$query = "SHOW CREATE TABLE $table"; echo "-- Table structure for `$table`\n";
$result = mysqli_query($mysqli, $query); echo "-- ----------------------------\n";
$row = mysqli_fetch_row($result); echo "DROP TABLE IF EXISTS `$table`;\n";
echo $createSQL . ";\n\n";
$sqlScript .= "\n\n" . $row[1] . ";\n\n"; // Table data
$dataRes = $mysqli->query("SELECT * FROM `$table`");
if ($dataRes->num_rows > 0) {
echo "-- Dumping data for table `$table`\n";
while ($row = $dataRes->fetch_assoc()) {
$columns = array_map(fn($col) => '`' . $mysqli->real_escape_string($col) . '`', array_keys($row));
$values = array_map(function ($val) use ($mysqli) {
if (is_null($val)) return "NULL";
return "'" . $mysqli->real_escape_string($val) . "'";
}, array_values($row));
echo "INSERT INTO `$table` (" . implode(", ", $columns) . ") VALUES (" . implode(", ", $values) . ");\n";
$query = "SELECT * FROM $table";
$result = mysqli_query($mysqli, $query);
$columnCount = mysqli_num_fields($result);
// Prepare SQLscript for dumping data for each table
for ($i = 0; $i < $columnCount; $i ++) {
while ($row = mysqli_fetch_row($result)) {
$sqlScript .= "INSERT INTO $table VALUES(";
for ($j = 0; $j < $columnCount; $j ++) {
if (isset($row[$j])) {
$sqlScript .= '"' . $row[$j] . '"';
} else {
$sqlScript .= '""';
}
if ($j < ($columnCount - 1)) {
$sqlScript .= ',';
}
}
$sqlScript .= ");\n";
} }
echo "\n";
} }
$sqlScript .= "\n";
} }
if (!empty($sqlScript)) { //FINAL STEP: Re-enable foreign key checks
echo "\nSET foreign_key_checks = 1;\n";
$company_name = $session_company_name;
// Save the SQL script to a backup file
$backup_file_name = date('Y-m-d') . '_ITFlow_backup.sql';
$fileHandler = fopen($backup_file_name, 'w+');
$number_of_lines = fwrite($fileHandler, $sqlScript);
fclose($fileHandler);
// Download the SQL backup file to the browser
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename=' . basename($backup_file_name));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($backup_file_name));
ob_clean();
flush();
readfile($backup_file_name);
exec('rm ' . $backup_file_name);
}
// Logging
logAction("Database", "Download", "$session_name downloaded the database");
logAction("Database", "Download", "$session_name downloaded the database.");
$_SESSION['alert_message'] = "Database downloaded"; $_SESSION['alert_message'] = "Database downloaded";
exit;
} }
if (isset($_POST['backup_master_key'])) { if (isset($_POST['backup_master_key'])) {

View File

@ -203,6 +203,10 @@ if (isset($_POST['edit_ticket'])) {
$additional_asset_id = intval($additional_asset); $additional_asset_id = intval($additional_asset);
mysqli_query($mysqli, "INSERT INTO ticket_assets SET ticket_id = $ticket_id, asset_id = $additional_asset_id"); mysqli_query($mysqli, "INSERT INTO ticket_assets SET ticket_id = $ticket_id, asset_id = $additional_asset_id");
} }
} else {
// If no additional assets are provided, delete them all
// This handles cases where the assets input might be cleared or not set at all.
mysqli_query($mysqli, "DELETE FROM ticket_assets WHERE ticket_id = $ticket_id");
} }
// Get contact/ticket details after update for logging / email purposes // Get contact/ticket details after update for logging / email purposes
@ -494,6 +498,41 @@ if (isset($_GET['delete_ticket_watcher'])) {
header("Location: " . $_SERVER["HTTP_REFERER"]); header("Location: " . $_SERVER["HTTP_REFERER"]);
} }
if (isset($_GET['delete_ticket_additional_asset'])) {
enforceUserPermission('module_support', 2);
$asset_id = intval($_GET['delete_ticket_additional_asset']);
$ticket_id = intval($_GET['ticket_id']);
// Get ticket / asset details for logging
$sql = mysqli_query($mysqli, "SELECT asset_name, ticket_prefix, ticket_number, ticket_status_name, ticket_client_id FROM assets
JOIN tickets ON ticket_id = $ticket_id
JOIN ticket_statuses ON ticket_status = ticket_status_id
WHERE asset_id = $asset_id"
);
$row = mysqli_fetch_array($sql);
$ticket_prefix = sanitizeInput($row['ticket_prefix']);
$ticket_number = intval($row['ticket_number']);
$ticket_status_name = sanitizeInput($row['ticket_status_name']);
$asset_name = sanitizeInput($row['asset_name']);
$client_id = intval($row['ticket_client_id']);
mysqli_query($mysqli, "DELETE FROM ticket_assets WHERE ticket_id = $ticket_id AND asset_id = $asset_id");
// History
mysqli_query($mysqli, "INSERT INTO ticket_history SET ticket_history_status = '$ticket_status_name', ticket_history_description = '$session_name removed additional asset $asset_name', ticket_history_ticket_id = $ticket_id");
// Logging
logAction("Ticket", "Edit", "$session_name removed asset $asset_name from ticket $ticket_prefix$ticket_number", $client_id, $ticket_id);
$_SESSION['alert_type'] = "error";
$_SESSION['alert_message'] = "Removed asset <strong>$asset_name</strong> from ticket.";
header("Location: " . $_SERVER["HTTP_REFERER"]);
}
if (isset($_POST['edit_ticket_asset'])) { if (isset($_POST['edit_ticket_asset'])) {
enforceUserPermission('module_support', 2); enforceUserPermission('module_support', 2);

View File

@ -1058,6 +1058,11 @@ if (isset($_GET['ticket_id'])) {
data-ajax-id="<?php echo $additional_asset_id; ?>"> data-ajax-id="<?php echo $additional_asset_id; ?>">
<i class="fa fa-fw fa-<?php echo $additional_asset_icon; ?> text-secondary mr-2"></i><?php echo $additional_asset_name; ?> <i class="fa fa-fw fa-<?php echo $additional_asset_icon; ?> text-secondary mr-2"></i><?php echo $additional_asset_name; ?>
</a> </a>
<?php if (empty($ticket_closed_at)) { ?>
<a class="confirm-link float-right" href="post.php?delete_ticket_additional_asset=<?php echo $additional_asset_id; ?>&ticket_id=<?php echo $ticket_id; ?>" title="Remove asset from ticket">
<i class="fas fa-fw fa-trash-alt text-secondary"></i>
</a>
<?php } ?>
</div> </div>
<?php <?php