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
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]
### Fixed

View File

@ -364,6 +364,16 @@ ob_start();
</div>
<?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">
<label>Purchase Date</label>
<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']);
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>$ticket_contact_name</td>";
echo "<td>$ticket_status</td>";

View File

@ -721,7 +721,6 @@ if (isset($_GET['query'])) {
} else {
$asset_serial_display = $asset_serial;
}
$asset_mac = nullable_htmlentities($row['asset_mac']);
$asset_uri = nullable_htmlentities($row['asset_uri']);
$asset_status = nullable_htmlentities($row['asset_status']);
$asset_created_at = nullable_htmlentities($row['asset_created_at']);
@ -746,9 +745,9 @@ if (isset($_GET['query'])) {
?>
<tr>
<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)){ ?>
<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 } ?>
</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.
*/
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">
<label>Support Phone</label>
<label>Support Phone / <span class="text-secondary">Extension</span></label>
<div class="form-row">
<div class="col-8">
<div class="col-9">
<div class="form-group">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-phone"></i></span>
</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 class="col-4">
<input type="text" class="form-control" name="extension" placeholder="Prompts" maxlength="200">
<div class="col-3">
<div class="form-group">
<input type="text" class="form-control" name="extension" placeholder="ext." maxlength="200">
</div>
</div>
</div>

View File

@ -21,7 +21,7 @@
<span class="input-group-text"><i class="fa fa-fw fa-envelope"></i></span>
</div>
<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
$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">
<option value="0">Not Assigned</option>
<?php
$sql_users_select = mysqli_query($mysqli, "SELECT users.user_id, user_name FROM users
LEFT JOIN user_settings on users.user_id = user_settings.user_id
AND user_type = 1
$sql_users_select = mysqli_query($mysqli, "SELECT user_id, user_name FROM users
WHERE user_type = 1
AND user_status = 1
AND user_archived_at IS NULL
ORDER BY user_name DESC"

View File

@ -7,84 +7,68 @@
defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
if (isset($_GET['download_database'])) {
validateCSRFToken($_GET['csrf_token']);
// Get All Table Names From the Database
$tables = array();
$sql = "SHOW TABLES";
$result = mysqli_query($mysqli, $sql);
global $mysqli, $database;
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];
}
$sqlScript = "";
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
$query = "SHOW CREATE TABLE $table";
$result = mysqli_query($mysqli, $query);
$row = mysqli_fetch_row($result);
echo "\n-- ----------------------------\n";
echo "-- Table structure for `$table`\n";
echo "-- ----------------------------\n";
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));
$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 "INSERT INTO `$table` (" . implode(", ", $columns) . ") VALUES (" . implode(", ", $values) . ");\n";
}
echo "\n";
}
$sqlScript .= "\n";
}
if (!empty($sqlScript)) {
$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");
//FINAL STEP: Re-enable foreign key checks
echo "\nSET foreign_key_checks = 1;\n";
logAction("Database", "Download", "$session_name downloaded the database.");
$_SESSION['alert_message'] = "Database downloaded";
exit;
}
if (isset($_POST['backup_master_key'])) {

View File

@ -203,6 +203,10 @@ if (isset($_POST['edit_ticket'])) {
$additional_asset_id = intval($additional_asset);
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
@ -494,6 +498,41 @@ if (isset($_GET['delete_ticket_watcher'])) {
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'])) {
enforceUserPermission('module_support', 2);

View File

@ -1058,6 +1058,11 @@ if (isset($_GET['ticket_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; ?>
</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>
<?php