Merge branch 'develop' of https://github.com/itflow-org/itflow into develop

This commit is contained in:
wrongecho
2025-11-18 13:08:17 +00:00
37 changed files with 425 additions and 233 deletions

View File

@@ -2,7 +2,37 @@
This file documents all notable changes made to ITFlow. This file documents all notable changes made to ITFlow.
## [25.11] Changelog ## [25.11.1] Maint Release
### Fixes
- Fix broken edit Payment Method.
- Fix unable to delete Vendor Template.
- Fix Mail Queue link in flash alert for testing email and sending a quote.
- Add Show Category Type select if not defined.
- Add Show Product Type select if not defined.
- Fix add ticket watcher.
- Fix if Client isn't assigned to a ticket dont show client view.
- Fix missing session client id check when paying an invoice from client portal.
- Update Composer Webklex-IMAP library dependency symfony/http-foundation from 7.3.3 to 7.3.7 to fix security related issues.
- Add back delete Payment provider the database will handle cascade deletes to saved cards, recurring payments and client payment provider reference.
- Don't show Client Tickets Breadcrumb if no client is assigned to a ticket.
- Don't Show Contact or Assignment Tab in edit ticket if no Client is Assigned.
- Don't Show add contact, asset, vendor, watcher if not client is assigned to a ticket.
- Don't Show Public Comment & Email if contact email doesn't exist.
- Fixed IMAP Test whicn now uses RAW TCP Connection instead of the depracated php-imap extension.
- Fix Broken Link in Ticket Updates via Client Portal to agent.
### Added / Changed
- [Feature] Added Asset Tags.
- [Feature] Added Quick Add Links to most side bar navs example quickly add a client from sidebar.
- Migrate ticket template add to ajax modal.
- Add TOTP secret to Client Export PDF in Credential section.
- Add UserID on hover in users listing.
- Merge ticket now redirects to the new ticket details page.
- [Feature] Add Pay via saved card under invoice Listings.
- Ticket Related Side Items UI Cleanup to use btn-tool class.
## [25.11] Stable
### Deprecation Notice: ### Deprecation Notice:
- **Outdated CRON Scripts**: The following scripts are removed. - **Outdated CRON Scripts**: The following scripts are removed.

View File

@@ -140,6 +140,7 @@
<?php if ($config_module_enable_itdoc) { ?> <?php if ($config_module_enable_itdoc) { ?>
<li class="nav-header">TEMPLATES</li> <li class="nav-header">TEMPLATES</li>
<!-- 2025-11-16 JQ - Hide Contracts not yet ready
<li class="nav-item"> <li class="nav-item">
<a href="/admin/contract_template.php" class="nav-link <?php echo (basename($_SERVER['PHP_SELF']) == 'contract_template.php' ? 'active' : ''); ?>"> <a href="/admin/contract_template.php" class="nav-link <?php echo (basename($_SERVER['PHP_SELF']) == 'contract_template.php' ? 'active' : ''); ?>">
<i class="nav-icon fas fa-file-contract"></i> <i class="nav-icon fas fa-file-contract"></i>
@@ -149,6 +150,7 @@
</p> </p>
</a> </a>
</li> </li>
-->
<li class="nav-item"> <li class="nav-item">
<a href="/admin/project_template.php" class="nav-link <?php echo (in_array(basename($_SERVER['PHP_SELF']), ['project_template.php', 'project_template_details.php']) ? 'active' : ''); ?>"> <a href="/admin/project_template.php" class="nav-link <?php echo (in_array(basename($_SERVER['PHP_SELF']), ['project_template.php', 'project_template_details.php']) ? 'active' : ''); ?>">
<i class="nav-icon fas fa-project-diagram"></i> <i class="nav-icon fas fa-project-diagram"></i>

View File

@@ -12,11 +12,24 @@ $tag_type = intval($row['tag_type']);
$tag_color = nullable_htmlentities($row['tag_color']); $tag_color = nullable_htmlentities($row['tag_color']);
$tag_icon = nullable_htmlentities($row['tag_icon']); $tag_icon = nullable_htmlentities($row['tag_icon']);
// Generate the HTML form content using output buffering. if ($tag_type == 1) {
$tag_type_display = "Client";
} elseif ( $tag_type == 2) {
$tag_type_display = "Location";
} elseif ( $tag_type == 3) {
$tag_type_display = "Contact";
} elseif ( $tag_type == 4) {
$tag_type_display = "Credential";
} elseif ( $tag_type == 5) {
$tag_type_display = "Asset";
} else {
$tag_type_display = "Unknown";
}
ob_start(); ob_start();
?> ?>
<div class="modal-header bg-dark"> <div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fas fa-fw fa-tag mr-2"></i>Editing tag: <strong><?php echo $tag_name; ?></strong></h5> <h5 class="modal-title"><i class="fas fa-fw fa-tag mr-2"></i><?= $tag_type_display ?> Tag: <strong><?php echo $tag_name; ?></strong></h5>
<button type="button" class="close text-white" data-dismiss="modal"> <button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span> <span>&times;</span>
</button> </button>
@@ -35,23 +48,6 @@ ob_start();
</div> </div>
</div> </div>
<div class="form-group">
<label>Type <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-th"></i></span>
</div>
<select class="form-control select2" name="type" required>
<option value="">- Type -</option>
<option value="1" <?php if ($tag_type == 1) { echo "selected"; } ?>>Client Tag</option>
<option value="2" <?php if ($tag_type == 2) { echo "selected"; } ?>>Location Tag</option>
<option value="3" <?php if ($tag_type == 3) { echo "selected"; } ?>>Contact Tag</option>
<option value="4" <?php if ($tag_type == 4) { echo "selected"; } ?>>Credential Tag</option>
<option value="5" <?php if ($tag_type == 5) { echo "selected"; } ?>>Asset Tag</option>
</select>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label>Color <strong class="text-danger">*</strong></label> <label>Color <strong class="text-danger">*</strong></label>
<div class="input-group"> <div class="input-group">

View File

@@ -106,12 +106,14 @@ $num_rows = mysqli_num_rows($sql);
<i class="fas fa-fw fa-edit mr-2"></i>Edit <i class="fas fa-fw fa-edit mr-2"></i>Edit
</a> </a>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<!-- <a class="dropdown-item text-danger confirm-link" href="post.php?disable_payment_provider=--><?php //echo $provider_id; ?><!--&csrf_token=--><?php //echo $_SESSION['csrf_token'] ?><!--">--> <a class="dropdown-item text-danger confirm-link" href="post.php?delete_payment_provider=<?= $provider_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<!-- <i class="fas fa-fw fa-thumbs-down mr-2"></i>Disable--> <i class="fas fa-fw fa-trash mr-2"></i><strong>Delete Provider and</strong>
<!-- </a>--> <ul class="text-xs">
<!-- <a class="dropdown-item text-danger confirm-link" href="post.php?delete_payment_provider=--><?php //echo $provider_id; ?><!--&csrf_token=--><?php //echo $_SESSION['csrf_token'] ?><!--">--> <li>Related Recurring Payments</li>
<!-- <i class="fas fa-fw fa-trash mr-2"></i>Delete--> <li>Related Saved cards</li>
<!-- </a>--> <li>Client Provider Relations</li>
</ul>
</a>
</div> </div>
</div> </div>
</td> </td>

View File

@@ -101,6 +101,11 @@ if (isset($_GET['delete_payment_provider'])) {
$provider_id = intval($_GET['delete_payment_provider']); $provider_id = intval($_GET['delete_payment_provider']);
// When deleted it cascades deletes
// all Recurring paymentes related to payment provider
// Delete all Saved Cards related
// Delete Client Payment Provider Releation
$provider_name = sanitizeInput(getFieldById('payment_providers', $provider_id, 'provider_name')); $provider_name = sanitizeInput(getFieldById('payment_providers', $provider_id, 'provider_name'));
// Delete provider // Delete provider

View File

@@ -42,7 +42,7 @@ if (isset($_GET['delete_saved_payment'])) {
try { try {
// Initialize stripe // Initialize stripe
require_once 'plugins/stripe-php/init.php'; require_once '../plugins/stripe-php/init.php';
$stripe = new \Stripe\StripeClient($private_key); $stripe = new \Stripe\StripeClient($private_key);
// Detach PM // Detach PM
@@ -56,7 +56,7 @@ if (isset($_GET['delete_saved_payment'])) {
} }
// Remove payment method from ITFlow // Remove payment method from ITFlow. This will also cascade delete related recurring payments setup
mysqli_query($mysqli, "DELETE FROM client_saved_payment_methods WHERE saved_payment_id = $saved_payment_id"); mysqli_query($mysqli, "DELETE FROM client_saved_payment_methods WHERE saved_payment_id = $saved_payment_id");
// SQL Cascade delete will Remove All Associated Auto Payment Methods on recurring invoices in the recurring payments table. // SQL Cascade delete will Remove All Associated Auto Payment Methods on recurring invoices in the recurring payments table.

View File

@@ -160,27 +160,128 @@ if (isset($_POST['test_email_smtp'])) {
} }
if (isset($_POST['test_email_imap'])) { if (isset($_POST['test_email_imap'])) {
validateCSRFToken($_POST['csrf_token']); validateCSRFToken($_POST['csrf_token']);
// Setup your IMAP connection parameters $host = $config_imap_host;
$hostname = "{" . $config_imap_host . ":" . $config_imap_port . "/" . $config_imap_encryption . "/novalidate-cert}INBOX"; $port = (int) $config_imap_port;
$username = $config_imap_username; $encryption = strtolower(trim($config_imap_encryption)); // e.g. "ssl", "tls", "none"
$password = $config_imap_password; $username = $config_imap_username;
$password = $config_imap_password;
// Build remote socket (implicit SSL vs plain TCP)
$transport = 'tcp';
if ($encryption === 'ssl') {
$transport = 'ssl';
}
$remote_socket = $transport . '://' . $host . ':' . $port;
// Stream context (you can tighten these if you want strict validation)
$contextOptions = [];
if (in_array($encryption, ['ssl', 'tls'], true)) {
$contextOptions['ssl'] = [
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => true,
];
}
$context = stream_context_create($contextOptions);
try { try {
$inbox = @imap_open($hostname, $username, $password); $errno = 0;
$errstr = '';
if ($inbox) { // 10-second timeout, adjust as needed
imap_close($inbox); $fp = @stream_socket_client(
$remote_socket,
$errno,
$errstr,
10,
STREAM_CLIENT_CONNECT,
$context
);
if (!$fp) {
throw new Exception("Could not connect to IMAP server: [$errno] $errstr");
}
stream_set_timeout($fp, 10);
// Read server greeting (IMAP servers send something like: * OK Dovecot ready)
$greeting = fgets($fp, 1024);
if ($greeting === false || strpos($greeting, '* OK') !== 0) {
fclose($fp);
throw new Exception("Invalid IMAP greeting: " . trim((string) $greeting));
}
// If you really want STARTTLS for "tls" (port 143), you can do it here
if ($encryption === 'tls' && stripos($greeting, 'STARTTLS') !== false) {
// Request STARTTLS
fwrite($fp, "A0001 STARTTLS\r\n");
$line = fgets($fp, 1024);
if ($line === false || stripos($line, 'A0001 OK') !== 0) {
fclose($fp);
throw new Exception("STARTTLS failed: " . trim((string) $line));
}
// Enable crypto on the stream
if (!stream_socket_enable_crypto($fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
fclose($fp);
throw new Exception("Unable to enable TLS encryption on IMAP connection.");
}
}
// --- Do LOGIN command ---
$tag = 'A0002';
// Simple quoting; this may fail with some special chars in username/password.
$loginCmd = sprintf(
"%s LOGIN \"%s\" \"%s\"\r\n",
$tag,
addcslashes($username, "\\\""),
addcslashes($password, "\\\"")
);
fwrite($fp, $loginCmd);
$success = false;
$errorLine = '';
while (!feof($fp)) {
$line = fgets($fp, 2048);
if ($line === false) {
break;
}
// Look for tagged response for our LOGIN
if (strpos($line, $tag . ' ') === 0) {
if (stripos($line, $tag . ' OK') === 0) {
$success = true;
} else {
$errorLine = trim($line);
}
break;
}
}
// Always logout / close
fwrite($fp, "A0003 LOGOUT\r\n");
fclose($fp);
if ($success) {
flash_alert("Connected successfully"); flash_alert("Connected successfully");
} else { } else {
throw new Exception(imap_last_error()); if (!$errorLine) {
$errorLine = 'Unknown IMAP authentication error';
}
throw new Exception($errorLine);
} }
} catch (Exception $e) { } catch (Exception $e) {
flash_alert("<strong>IMAP connection failed:</strong> " . $e->getMessage(), 'error'); flash_alert("<strong>IMAP connection failed:</strong> " . htmlspecialchars($e->getMessage()), 'error');
} }
redirect(); redirect();
} }

View File

@@ -28,7 +28,7 @@ if (isset($_POST['edit_tag'])) {
$tag_id = intval($_POST['tag_id']); $tag_id = intval($_POST['tag_id']);
mysqli_query($mysqli,"UPDATE tags SET tag_name = '$name', tag_type = $type, tag_color = '$color', tag_icon = '$icon' WHERE tag_id = $tag_id"); mysqli_query($mysqli,"UPDATE tags SET tag_name = '$name', tag_color = '$color', tag_icon = '$icon' WHERE tag_id = $tag_id");
logAction("Tag", "Edit", "$session_name edited tag $name", 0, $tag_id); logAction("Tag", "Edit", "$session_name edited tag $name", 0, $tag_id);

View File

@@ -6,10 +6,31 @@ $order = "ASC";
require_once "includes/inc_all_admin.php"; require_once "includes/inc_all_admin.php";
if (isset($_GET['type'])) {
$type_filter = intval($_GET['type']);
} else {
$type_filter = 1;
}
if ($type_filter == 1) {
$tag_type_display = "Client";
} elseif ( $type_filter == 2) {
$tag_type_display = "Location";
} elseif ( $type_filter == 3) {
$tag_type_display = "Contact";
} elseif ( $type_filter == 4) {
$tag_type_display = "Credential";
} elseif ( $type_filter == 5) {
$tag_type_display = "Asset";
} else {
$tag_type_display = "Unknown";
}
$sql = mysqli_query( $sql = mysqli_query(
$mysqli, $mysqli,
"SELECT SQL_CALC_FOUND_ROWS * FROM tags "SELECT SQL_CALC_FOUND_ROWS * FROM tags
WHERE tag_name LIKE '%$q%' WHERE tag_name LIKE '%$q%'
AND tag_type = $type_filter
ORDER BY $sort $order LIMIT $record_from, $record_to" ORDER BY $sort $order LIMIT $record_from, $record_to"
); );
@@ -19,9 +40,9 @@ $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-tags mr-2"></i>Tags</h3> <h3 class="card-title mt-2"><i class="fas fa-fw fa-tags mr-2"></i><?= $tag_type_display ?> Tags</h3>
<div class="card-tools"> <div class="card-tools">
<button type="button" class="btn btn-primary ajax-modal" data-modal-url="modals/tag/tag_add.php"><i class="fas fa-plus mr-2"></i>New Tag</button> <button type="button" class="btn btn-primary ajax-modal" data-modal-url="modals/tag/tag_add.php?type=<?= $type_filter ?>"><i class="fas fa-plus mr-2"></i>New <?= $tag_type_display ?> Tag</button>
</div> </div>
</div> </div>
@@ -30,7 +51,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<div class="col-sm-4 mb-2"> <div class="col-sm-4 mb-2">
<form autocomplete="off"> <form autocomplete="off">
<div class="input-group"> <div class="input-group">
<input type="search" class="form-control" name="q" value="<?php if (isset($q)) { echo stripslashes(nullable_htmlentities($q)); } ?>" placeholder="Search Tags"> <input type="search" class="form-control" name="q" value="<?php if (isset($q)) { echo stripslashes(nullable_htmlentities($q)); } ?>" placeholder="Search <?= $tag_type_display ?> Tags">
<div class="input-group-append"> <div class="input-group-append">
<button class="btn btn-primary"><i class="fa fa-search"></i></button> <button class="btn btn-primary"><i class="fa fa-search"></i></button>
</div> </div>
@@ -38,6 +59,45 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</form> </form>
</div> </div>
<div class="col-sm-8"> <div class="col-sm-8">
<div class="btn-group float-right">
<a href="?type=1"
class="btn <?php if ($type_filter == 1) {
echo 'btn-primary';
} else {
echo 'btn-default';
} ?>">Client</a>
<a href="?type=2"
class="btn <?php if ($type_filter == 2) {
echo 'btn-primary';
} else {
echo 'btn-default';
} ?>">Location</a>
<a href="?type=3"
class="btn <?php if ($type_filter == 3) {
echo 'btn-primary';
} else {
echo 'btn-default';
} ?>">Contact</a>
<a href="?type=4"
class="btn <?php if ($type_filter == 4) {
echo 'btn-primary';
} else {
echo 'btn-default';
} ?>">Credential</a>
<a href="?type=5"
class="btn <?php if ($type_filter == 5) {
echo 'btn-primary';
} else {
echo 'btn-default';
} ?>">Asset</a>
<a href="?<?= $url_query_strings_sort ?>&archived=1"
class="btn <?php if (isset($_GET['archived'])) {
echo 'btn-primary';
} else {
echo 'btn-default';
} ?>"><i
class="fas fa-fw fa-archive mr-2"></i>Archived</a>
</div>
</div> </div>
</div> </div>
@@ -51,11 +111,6 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
Name <?php if ($sort == 'tag_name') { echo $order_icon; } ?> Name <?php if ($sort == 'tag_name') { echo $order_icon; } ?>
</a> </a>
</th> </th>
<th>
<a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=tag_type&order=<?php echo $disp; ?>">
Type <?php if ($sort == 'tag_type') { echo $order_icon; } ?>
</a>
</th>
<th class="text-center">Action</th> <th class="text-center">Action</th>
</tr> </tr>
</thead> </thead>
@@ -65,20 +120,6 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
while ($row = mysqli_fetch_array($sql)) { while ($row = mysqli_fetch_array($sql)) {
$tag_id = intval($row['tag_id']); $tag_id = intval($row['tag_id']);
$tag_name = nullable_htmlentities($row['tag_name']); $tag_name = nullable_htmlentities($row['tag_name']);
$tag_type = intval($row['tag_type']);
if ( $tag_type == 1) {
$tag_type_display = "Client Tag";
} elseif ( $tag_type == 2) {
$tag_type_display = "Location Tag";
} elseif ( $tag_type == 3) {
$tag_type_display = "Contact Tag";
} elseif ( $tag_type == 4) {
$tag_type_display = "Credential Tag";
} elseif ( $tag_type == 5) {
$tag_type_display = "Asset Tag";
} else {
$tag_type_display = "Unknown Tag";
}
$tag_color = nullable_htmlentities($row['tag_color']); $tag_color = nullable_htmlentities($row['tag_color']);
$tag_icon = nullable_htmlentities($row['tag_icon']); $tag_icon = nullable_htmlentities($row['tag_icon']);
@@ -90,7 +131,6 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<span class='badge text-light p-2 mr-1' style="background-color: <?php echo $tag_color; ?>"><i class="fa fa-fw fa-<?php echo $tag_icon; ?> mr-2"></i><?php echo $tag_name; ?></span> <span class='badge text-light p-2 mr-1' style="background-color: <?php echo $tag_color; ?>"><i class="fa fa-fw fa-<?php echo $tag_icon; ?> mr-2"></i><?php echo $tag_name; ?></span>
</a> </a>
</td> </td>
<td><?php echo $tag_type_display; ?></td>
<td> <td>
<div class="dropdown dropleft text-center"> <div class="dropdown dropleft text-center">
<button class="btn btn-secondary btn-sm" type="button" data-toggle="dropdown"> <button class="btn btn-secondary btn-sm" type="button" data-toggle="dropdown">
@@ -119,8 +159,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</tbody> </tbody>
</table> </table>
</div> </div>
<?php require_once "../includes/filter_footer.php"; <?php require_once "../includes/filter_footer.php"; ?>
?>
</div> </div>
</div> </div>

View File

@@ -133,9 +133,6 @@ $network_count = intval($row['network_count']);
//Other Count //Other Count
$other_count = intval($row['other_count']); $other_count = intval($row['other_count']);
//Rebuild URL
$url_query_strings_sort = http_build_query($get_copy);
$sql = mysqli_query( $sql = mysqli_query(
$mysqli, $mysqli,
"SELECT SQL_CALC_FOUND_ROWS * FROM assets "SELECT SQL_CALC_FOUND_ROWS * FROM assets

View File

@@ -744,7 +744,7 @@ if ($user_config_dashboard_technical_enable == 1) {
href="ticket.php?ticket_id=<?= "$ticket_id$has_client" ?>"><?= "$ticket_prefix$ticket_number" ?> href="ticket.php?ticket_id=<?= "$ticket_id$has_client" ?>"><?= "$ticket_prefix$ticket_number" ?>
</a> </a>
</td> </td>
<td><a href="ticket.php?ticket_id=<?= $ticket_id$has_client" ?>"><?= $ticket_subject ?></a></td> <td><a href="ticket.php?ticket_id=<?= "$ticket_id$has_client" ?>"><?= $ticket_subject ?></a></td>
<td><a href="tickets.php?client_id=<?php echo $client_id; ?>"><strong><?php echo $client_name; ?></strong></a></td> <td><a href="tickets.php?client_id=<?php echo $client_id; ?>"><strong><?php echo $client_name; ?></strong></a></td>
<td><?php echo $contact_display; ?></td> <td><?php echo $contact_display; ?></td>
<td><span class='p-2 badge badge-pill badge-<?php echo $ticket_priority_color; ?>'><?php echo $ticket_priority; ?></span></td> <td><span class='p-2 badge badge-pill badge-<?php echo $ticket_priority_color; ?>'><?php echo $ticket_priority; ?></span></td>

View File

@@ -23,9 +23,6 @@ if (!empty($q)) {
$query_snippet = ""; // empty $query_snippet = ""; // empty
} }
//Rebuild URL
$url_query_strings_sort = http_build_query($get_copy);
// Folder ID // Folder ID
$get_folder_id = 0; $get_folder_id = 0;
if (!empty($_GET['folder_id'])) { if (!empty($_GET['folder_id'])) {

View File

@@ -14,9 +14,6 @@ if (!empty($_GET['folder_id'])) {
$folder_id = 0; $folder_id = 0;
} }
//Rebuild URL
$url_query_strings_sort = http_build_query($get_copy);
// Folder ID // Folder ID
$get_folder_id = 0; $get_folder_id = 0;
if (!empty($_GET['folder_id'])) { if (!empty($_GET['folder_id'])) {

View File

@@ -18,8 +18,8 @@ if (isset($_GET['invoice_id'])) {
$mysqli, $mysqli,
"SELECT * FROM invoices "SELECT * FROM invoices
LEFT JOIN clients ON invoice_client_id = client_id LEFT JOIN clients ON invoice_client_id = client_id
LEFT JOIN contacts ON clients.client_id = contacts.contact_client_id AND contact_primary = 1 LEFT JOIN contacts ON client_id = contact_client_id AND contact_primary = 1
LEFT JOIN locations ON clients.client_id = locations.location_client_id AND location_primary = 1 LEFT JOIN locations ON client_id = location_client_id AND location_primary = 1
WHERE invoice_id = $invoice_id WHERE invoice_id = $invoice_id
$access_permission_query $access_permission_query
LIMIT 1" LIMIT 1"

View File

@@ -344,8 +344,6 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
$recurring_invoice_display = "-"; $recurring_invoice_display = "-";
} }
$now = time(); $now = time();
if (($invoice_status == "Sent" || $invoice_status == "Partial" || $invoice_status == "Viewed") && strtotime($invoice_due) + 86400 < $now) { if (($invoice_status == "Sent" || $invoice_status == "Partial" || $invoice_status == "Viewed") && strtotime($invoice_due) + 86400 < $now) {
@@ -356,6 +354,15 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
$invoice_badge_color = getInvoiceBadgeColor($invoice_status); $invoice_badge_color = getInvoiceBadgeColor($invoice_status);
// Saved Payment Methods
$sql_saved_payment_methods = mysqli_query($mysqli, "
SELECT * FROM client_saved_payment_methods
LEFT JOIN payment_providers
ON client_saved_payment_methods.saved_payment_provider_id = payment_providers.payment_provider_id
WHERE saved_payment_client_id = $client_id
AND payment_provider_active = 1;
");
?> ?>
<tr> <tr>
@@ -395,10 +402,8 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<i class="fa fa-fw fa-credit-card mr-2"></i>Add Payment <i class="fa fa-fw fa-credit-card mr-2"></i>Add Payment
</a> </a>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<?php if ($invoice_status !== 'Partial' && $config_stripe_enable && $stripe_id && $stripe_pm) { ?> <?php if (mysqli_num_rows($sql_saved_payment_methods) > 0 && ($invoice_status === 'Sent' || $invoice_status === 'Viewed')) { ?>
<a class="dropdown-item confirm-link" href="post.php?add_payment_stripe&invoice_id=<?php echo $invoice_id; ?>&csrf_token=<?php echo $_SESSION['csrf_token']; ?>"> <a class="dropdown-item ajax-modal" href="#" data-modal-url="modals/payment/payment_saved_method_add.php?id=<?= $invoice_id ?>"><i class="fas fa-fw fa-wallet mr-2"></i>Pay with Saved Card</a>
<i class="fa fa-fw fa-credit-card mr-2"></i>Pay via saved card
</a>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<?php } ?> <?php } ?>
<?php } ?> <?php } ?>

View File

@@ -380,7 +380,7 @@ ob_start();
// Send a POST request to ajax.php as ajax.php with data contact_set_notes=true, contact_id=NUM, notes=NOTES // Send a POST request to ajax.php as ajax.php with data contact_set_notes=true, contact_id=NUM, notes=NOTES
jQuery.post( jQuery.post(
"../ajax.php", "ajax.php",
{ {
asset_set_notes: 'TRUE', asset_set_notes: 'TRUE',
asset_id: asset_id, asset_id: asset_id,

View File

@@ -10,7 +10,7 @@ ob_start();
?> ?>
<div class="modal-header bg-dark"> <div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-clone mr-2"></i>Merge & close <strong><?= $count ?></strong>tickets</h5> <h5 class="modal-title"><i class="fa fa-fw fa-clone mr-2"></i>Merge & close <strong><?= $count ?></strong> tickets</h5>
<button type="button" class="close text-white" data-dismiss="modal"> <button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span> <span>&times;</span>
</button> </button>
@@ -75,3 +75,5 @@ ob_start();
<!-- Ticket merge JS --> <!-- Ticket merge JS -->
<script src="/agent/js/ticket_merge.js"></script> <script src="/agent/js/ticket_merge.js"></script>
<?php require_once '../../../includes/modal_footer.php';

View File

@@ -38,33 +38,35 @@ while ($row = mysqli_fetch_array($sql_additional_assets)) {
ob_start(); ob_start();
?> ?>
<div class="modal-header bg-dark"> <div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-life-ring mr-2"></i>Editing ticket: <strong><?php echo "$ticket_prefix$ticket_number"; ?></strong> - <?php echo $client_name; ?></h5> <h5 class="modal-title"><i class="fa fa-fw fa-life-ring mr-2"></i>Ticket: <strong><?= "$ticket_prefix$ticket_number" ?></strong> - <?= $client_name ?></h5>
<button type="button" class="close text-white" data-dismiss="modal"> <button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span> <span>&times;</span>
</button> </button>
</div> </div>
<form action="post.php" method="post" autocomplete="off"> <form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="ticket_id" value="<?php echo $ticket_id; ?>"> <input type="hidden" name="ticket_id" value="<?= $ticket_id ?>">
<div class="modal-body"> <div class="modal-body">
<?php if ($client_id) { ?>
<ul class="nav nav-pills nav-justified mb-3"> <ul class="nav nav-pills nav-justified mb-3">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link active" data-toggle="pill" href="#pills-details<?php echo $ticket_id; ?>"><i class="fa fa-fw fa-life-ring mr-2"></i>Details</a> <a class="nav-link active" data-toggle="pill" href="#pills-details"><i class="fa fa-fw fa-life-ring mr-2"></i>Details</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="pill" href="#pills-contacts"><i class="fa fa-fw fa-users mr-2"></i>Contact</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" data-toggle="pill" href="#pills-contacts<?php echo $ticket_id; ?>"><i class="fa fa-fw fa-users mr-2"></i>Contact</a> <a class="nav-link" data-toggle="pill" href="#pills-assignment"><i class="fa fa-fw fa-desktop mr-2"></i>Assignment</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="pill" href="#pills-assignment<?php echo $ticket_id; ?>"><i class="fa fa-fw fa-desktop mr-2"></i>Assignment</a>
</li> </li>
</ul> </ul>
<hr> <hr>
<?php } ?>
<div class="tab-content" <?php if (lookupUserPermission('module_support') <= 1) { echo 'inert'; } ?>> <div class="tab-content" <?php if (lookupUserPermission('module_support') <= 1) { echo 'inert'; } ?>>
<div class="tab-pane fade show active" id="pills-details<?php echo $ticket_id; ?>"> <div class="tab-pane fade show active" id="pills-details">
<div class="form-group"> <div class="form-group">
<label>Subject <strong class="text-danger">*</strong></label> <label>Subject <strong class="text-danger">*</strong></label>
@@ -182,7 +184,9 @@ ob_start();
</div> </div>
<div class="tab-pane fade" id="pills-contacts<?php echo $ticket_id; ?>"> <?php if ($client_id) { ?>
<div class="tab-pane fade" id="pills-contacts">
<div class="form-group"> <div class="form-group">
<label>Contact</label> <label>Contact</label>
@@ -236,7 +240,7 @@ ob_start();
</div> </div>
<div class="tab-pane fade" id="pills-assignment<?php echo $ticket_id; ?>"> <div class="tab-pane fade" id="pills-assignment">
<div class="form-group"> <div class="form-group">
<label>Asset</label> <label>Asset</label>
@@ -372,7 +376,7 @@ ob_start();
while ($row = mysqli_fetch_array($sql_projects)) { while ($row = mysqli_fetch_array($sql_projects)) {
$project_id_select = intval($row['project_id']); $project_id_select = intval($row['project_id']);
$project_name_select = nullable_htmlentities($row['project_name']); ?> $project_name_select = nullable_htmlentities($row['project_name']); ?>
<option <?php if ($project_id == $project_id_select) { echo "selected"; } ?> value="<?php echo $project_id_select; ?>"><?php echo $project_name_select; ?></option> <option <?php if ($project_id == $project_id_select) { echo "selected"; } ?> value="<?= $project_id_select ?>"><?= $project_name_select ?></option>
<?php } ?> <?php } ?>
</select> </select>
@@ -380,13 +384,14 @@ ob_start();
</div> </div>
</div> </div>
<?php } // End client_id check ?>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="submit" name="edit_ticket" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Save</button> <button type="submit" name="edit_ticket" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Save changes</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button> <button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
</div> </div>

View File

@@ -38,9 +38,6 @@ if (isset($_GET['account']) & !empty($_GET['account'])) {
$account_filter = ''; $account_filter = '';
} }
//Rebuild URL
$url_query_strings_sort = http_build_query($get_copy);
$sql = mysqli_query( $sql = mysqli_query(
$mysqli, $mysqli,
"SELECT SQL_CALC_FOUND_ROWS * FROM payments "SELECT SQL_CALC_FOUND_ROWS * FROM payments

View File

@@ -9,9 +9,6 @@ require_once "includes/inc_all.php";
// Perms // Perms
enforceUserPermission('module_financial'); enforceUserPermission('module_financial');
//Rebuild URL
$url_query_strings_sort = http_build_query($get_copy);
$sql = mysqli_query( $sql = mysqli_query(
$mysqli, $mysqli,
"SELECT SQL_CALC_FOUND_ROWS * FROM recurring_expenses "SELECT SQL_CALC_FOUND_ROWS * FROM recurring_expenses

View File

@@ -9,9 +9,6 @@ require_once "includes/inc_all.php";
// Perms // Perms
enforceUserPermission('module_financial'); enforceUserPermission('module_financial');
//Rebuild URL
$url_query_strings_sort = http_build_query($get_copy);
$sql = mysqli_query( $sql = mysqli_query(
$mysqli, $mysqli,
"SELECT SQL_CALC_FOUND_ROWS * FROM revenues "SELECT SQL_CALC_FOUND_ROWS * FROM revenues

View File

@@ -39,7 +39,6 @@ if (isset($_GET['ticket_id'])) {
LEFT JOIN ticket_statuses ON ticket_status = ticket_status_id LEFT JOIN ticket_statuses ON ticket_status = ticket_status_id
LEFT JOIN categories ON ticket_category = category_id LEFT JOIN categories ON ticket_category = category_id
WHERE ticket_id = $ticket_id WHERE ticket_id = $ticket_id
LIMIT 1" LIMIT 1"
); );
@@ -358,9 +357,11 @@ if (isset($_GET['ticket_id'])) {
<li class="breadcrumb-item"> <li class="breadcrumb-item">
<a href="tickets.php">All Tickets</a> <a href="tickets.php">All Tickets</a>
</li> </li>
<?php if ($client_url) { ?>
<li class="breadcrumb-item"> <li class="breadcrumb-item">
<a href="tickets.php?client_id=<?php echo $client_id; ?>"><?= $client_name ?> Tickets</a> <a href="tickets.php?client_id=<?php echo $client_id; ?>"><?= $client_name ?> Tickets</a>
</li> </li>
<?php } ?>
<li class="breadcrumb-item active"><?php echo "$ticket_prefix$ticket_number";?></li> <li class="breadcrumb-item active"><?php echo "$ticket_prefix$ticket_number";?></li>
</ol> </ol>
@@ -415,18 +416,13 @@ if (isset($_GET['ticket_id'])) {
<i class="fas fa-fw fa-ellipsis-v"></i> <i class="fas fa-fw fa-ellipsis-v"></i>
</button> </button>
<div class="dropdown-menu"> <div class="dropdown-menu">
<a class="dropdown-item ajax-modal" href="#"
data-modal-size = "lg"
data-modal-url="modals/ticket/ticket_edit.php?id=<?= $ticket_id ?>">
<i class="fas fa-fw fa-edit mr-2"></i>Edit
</a>
<a class="dropdown-item ajax-modal" href="#" data-modal-url="modals/ticket/ticket_summary.php?ticket_id=<?= $ticket_id ?>" data-modal-size="lg"> <a class="dropdown-item ajax-modal" href="#" data-modal-url="modals/ticket/ticket_summary.php?ticket_id=<?= $ticket_id ?>" data-modal-size="lg">
<i class="fas fa-fw fa-lightbulb mr-2"></i>Summarize <i class="fas fa-fw fa-lightbulb mr-2"></i>Summarize
</a> </a>
<a class="dropdown-item ajax-modal" href="#" data-modal-url="modals/ticket/ticket_merge.php?ticket_id=<?= $ticket_id ?>"> <a class="dropdown-item ajax-modal" href="#" data-modal-url="modals/ticket/ticket_merge.php?ticket_id=<?= $ticket_id ?>">
<i class="fas fa-fw fa-clone mr-2"></i>Merge <i class="fas fa-fw fa-clone mr-2"></i>Merge
</a> </a>
<?php if (empty($ticket_closed_at)) { ?> <?php if (empty($ticket_closed_at) && $client_id) { ?>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<a class="dropdown-item ajax-modal" href="#" <a class="dropdown-item ajax-modal" href="#"
data-modal-url="modals/ticket/ticket_contact.php?id=<?= $ticket_id ?>"> data-modal-url="modals/ticket/ticket_contact.php?id=<?= $ticket_id ?>">
@@ -564,9 +560,14 @@ if (isset($_GET['ticket_id'])) {
<div class="card card-dark mb-3"> <div class="card card-dark mb-3">
<div class="card-header bg-dark"> <div class="card-header bg-dark">
<h3 class="card-title"> <h5 class="card-title">
Ticket Details Ticket Details
</h3> </h5>
<?php if (empty($ticket_closed_at)) { ?>
<div class="card-tools">
<button type="button" class="btn btn-tool ajax-modal" data-modal-url="modals/ticket/ticket_edit.php?id=<?= $ticket_id ?>" data-modal-size="lg"><i class="fas fa-edit"></i></button>
</div>
<?php } ?>
</div> </div>
<div class="card-body prettyContent" id="ticketDetails"> <div class="card-body prettyContent" id="ticketDetails">
@@ -599,9 +600,11 @@ if (isset($_GET['ticket_id'])) {
<label class="btn btn-outline-dark active"> <label class="btn btn-outline-dark active">
<input type="radio" name="public_reply_type" value="0" checked>Internal Note <input type="radio" name="public_reply_type" value="0" checked>Internal Note
</label> </label>
<?php if ($contact_email) { ?>
<label class="btn btn-outline-info"> <label class="btn btn-outline-info">
<input type="radio" name="public_reply_type" value="2">Public Comment & Email <input type="radio" name="public_reply_type" value="2">Public Comment & Email
</label> </label>
<?php } ?>
<label class="btn btn-outline-info"> <label class="btn btn-outline-info">
<input type="radio" name="public_reply_type" value="1">Public Comment <input type="radio" name="public_reply_type" value="1">Public Comment
</label> </label>
@@ -807,16 +810,17 @@ if (isset($_GET['ticket_id'])) {
<div class="col-md-3"> <div class="col-md-3">
<!-- Ticket details right card --> <!-- Ticket details right card -->
<div class="card"> <div class="card <?php if(!$ticket_resolved_at) { echo "collapsed-card"; } ?>">
<div class="card-header"> <div class="card-header">
<a class="text-reset text-decoration-none" href="#" data-toggle="collapse" data-target="#ticketDetailsCard"> <h5 class="card-title"><i class="fas fa-fw fa-life-ring mr-2"></i>Ticket Details</h5>
<h5 class="card-title mt-1"><i class="fas fa-fw fa-life-ring mr-2"></i>Ticket Details</h5>
</a>
<div class="card-tools"> <div class="card-tools">
<a class="fa fa-chevron-down" href="#" data-toggle="collapse" data-target="#ticketDetailsCard"></a> <button type="button" class="btn btn-tool" data-card-widget="collapse">
<i class="fas fa-chevron-down"></i>
</button>
</div> </div>
</div> </div>
<div class="card-body collapse <?php echo !empty($ticket_resolved_at) ? 'show' : ''; ?>" id="ticketDetailsCard"> <div class="card-body">
<!-- Created --> <!-- Created -->
<div title="<?php echo $ticket_created_at; ?>"> <div title="<?php echo $ticket_created_at; ?>">
@@ -912,12 +916,12 @@ if (isset($_GET['ticket_id'])) {
<!-- Tasks Card --> <!-- Tasks Card -->
<?php if (empty($ticket_resolved_at) || (!empty($ticket_resolved_at) && $task_count > 0)) { ?> <?php if (empty($ticket_resolved_at) || (!empty($ticket_resolved_at) && $task_count > 0)) { ?>
<div class="card"> <div class="card">
<div class="card-header py-2"> <div class="card-header">
<h5 class="card-title"><i class="fas fa-fw fa-tasks mr-2 mt-2"></i>Tasks</h5> <h5 class="card-title"><i class="fas fa-fw fa-tasks mr-2"></i>Tasks</h5>
<?php if (empty($ticket_resolved_at) && lookupUserPermission("module_support") >= 2) { ?>
<div class="card-tools"> <div class="card-tools">
<?php if (empty($ticket_resolved_at) && lookupUserPermission("module_support") >= 2) { ?>
<div class="dropdown dropleft text-center"> <div class="dropdown dropleft text-center">
<button class="btn btn-light text-secondary btn-sm" type="button" data-toggle="dropdown"> <button class="btn btn-tool" type="button" data-toggle="dropdown">
<i class="fas fa-ellipsis-v"></i> <i class="fas fa-ellipsis-v"></i>
</button> </button>
<div class="dropdown-menu"> <div class="dropdown-menu">
@@ -934,8 +938,8 @@ if (isset($_GET['ticket_id'])) {
</a> </a>
</div> </div>
</div> </div>
<?php } ?>
</div> </div>
<?php } ?>
</div> </div>
<div class="card-body p-0"> <div class="card-body p-0">
@@ -964,7 +968,7 @@ if (isset($_GET['ticket_id'])) {
$task_completion_estimate = intval($row['task_completion_estimate']); $task_completion_estimate = intval($row['task_completion_estimate']);
$task_completed_at = nullable_htmlentities($row['task_completed_at']); $task_completed_at = nullable_htmlentities($row['task_completed_at']);
?> ?>
<tr data-task-id="<?php echo $task_id; ?>"> <tr data-task-id="<?= $task_id ?>">
<td> <td>
<?php if ($task_completed_at) { ?> <?php if ($task_completed_at) { ?>
<i class="far fa-check-square text-success"></i> <i class="far fa-check-square text-success"></i>
@@ -1022,16 +1026,16 @@ if (isset($_GET['ticket_id'])) {
<!-- Contact card --> <!-- Contact card -->
<?php if ($contact_id) { ?> <?php if ($contact_id) { ?>
<div class="card"> <div class="card">
<div class="card-header py-2"> <div class="card-header">
<h5 class="card-title"><i class="fas fa-fw fa-user-check mr-2 mt-2"></i>Contact</h5> <h5 class="card-title"><i class="fas fa-fw fa-user-check mr-2"></i>Contact</h5>
<?php if (empty($ticket_resolved_at) && lookupUserPermission("module_support") >= 2) { ?>
<div class="card-tools"> <div class="card-tools">
<?php if (empty($ticket_resolved_at) && lookupUserPermission("module_support") >= 2) { ?> <a class="btn btn-tool ajax-modal" href="#"
<a class="btn btn-light text-secondary btn-sm ajax-modal" href="#" data-modal-url="modals/ticket/ticket_contact.php?id=<?= $ticket_id ?>">
data-modal-url="modals/ticket/ticket_contact.php?id=<?= $ticket_id ?>"> <i class="fas fa-edit"></i>
<i class="fas fa-edit"></i> </a>
</a>
<?php } ?>
</div> </div>
<?php } ?>
</div> </div>
<div class="card-body"> <div class="card-body">
@@ -1073,23 +1077,19 @@ if (isset($_GET['ticket_id'])) {
<?php } ?> <?php } ?>
<!-- End contact card --> <!-- End contact card -->
<!-- Ticket watchers card --> <!-- Ticket watchers card -->
<?php if (empty($ticket_closed_at) && mysqli_num_rows($sql_ticket_watchers) > 0) { ?> <?php if (empty($ticket_closed_at) && mysqli_num_rows($sql_ticket_watchers) > 0) { ?>
<div class="card"> <div class="card">
<div class="card-header py-2"> <div class="card-header">
<h5 class="card-title"><i class="fas fa-fw fa-eye mr-2 mt-2"></i>Watchers</h5> <h5 class="card-title"><i class="fas fa-fw fa-eye mr-2"></i>Watchers</h5>
<?php if (empty($ticket_resolved_at) && lookupUserPermission("module_support") >= 2) { ?>
<div class="card-tools"> <div class="card-tools">
<?php if (empty($ticket_resolved_at) && lookupUserPermission("module_support") >= 2) { ?> <a class="btn btn-tool ajax-modal" href="#" data-modal-url="modals/ticket/ticket_add_watcher.php?ticket_id=<?= $ticket_id ?>">
<a class="btn btn-light text-secondary btn-sm ajax-modal" href="#" data-modal-url="modals/ticket/ticket_add_watcher.php?ticket_id=<?= $ticket_id ?>"> <i class="fas fa-fw fa-plus"></i>
<i class="fas fa-fw fa-plus"></i> </a>
</a>
<?php } ?>
</div> </div>
<?php } ?>
</div> </div>
<div class="card-body"> <div class="card-body">
@@ -1117,15 +1117,15 @@ if (isset($_GET['ticket_id'])) {
<!-- Asset card --> <!-- Asset card -->
<?php if ($asset_id) { ?> <?php if ($asset_id) { ?>
<div class="card mb-3"> <div class="card mb-3">
<div class="card-header py-2"> <div class="card-header">
<h5 class="card-title"><i class="fas fa-fw fa-desktop mr-2 mt-2"></i>Assets</h5> <h5 class="card-title"><i class="fas fa-fw fa-desktop mr-2"></i>Assets</h5>
<?php if (empty($ticket_resolved_at) && lookupUserPermission("module_support") >= 2) { ?>
<div class="card-tools"> <div class="card-tools">
<?php if (empty($ticket_resolved_at) && lookupUserPermission("module_support") >= 2) { ?> <a class="btn btn-tool ajax-modal" href="#" data-modal-url="modals/ticket/ticket_edit_asset.php?id=<?= $ticket_id ?>">
<a class="btn btn-light text-secondary btn-sm ajax-modal" href="#" data-modal-url="modals/ticket/ticket_edit_asset.php?id=<?= $ticket_id ?>"> <i class="fas fa-fw fa-edit"></i>
<i class="fas fa-fw fa-edit"></i> </a>
</a>
<?php } ?>
</div> </div>
<?php } ?>
</div> </div>
<div class="card-body"> <div class="card-body">
<div> <div>
@@ -1165,15 +1165,15 @@ if (isset($_GET['ticket_id'])) {
<!-- Vendor card --> <!-- Vendor card -->
<?php if ($vendor_id) { ?> <?php if ($vendor_id) { ?>
<div class="card mb-3"> <div class="card mb-3">
<div class="card-header py-2"> <div class="card-header">
<h5 class="card-title"><i class="fas fa-fw fa-building mr-2 mt-2"></i>Vendor</h5> <h5 class="card-title"><i class="fas fa-fw fa-building mr-2"></i>Vendor</h5>
<?php if (empty($ticket_resolved_at) && lookupUserPermission("module_support") >= 2) { ?>
<div class="card-tools"> <div class="card-tools">
<?php if (empty($ticket_resolved_at) && lookupUserPermission("module_support") >= 2) { ?> <a class="btn btn-tool ajax-modal" href="#" data-modal-url="modals/ticket/ticket_edit_vendor.php?ticket_id=<?= $ticket_id ?>">
<a class="btn btn-light text-secondary btn-sm ajax-modal" href="#" data-modal-url="modals/ticket/ticket_edit_vendor.php?ticket_id=<?= $ticket_id ?>"> <i class="fas fa-fw fa-edit"></i>
<i class="fas fa-fw fa-edit"></i> </a>
</a>
<?php } ?>
</div> </div>
<?php } ?>
</div> </div>
<div class="card-body"> <div class="card-body">
@@ -1220,15 +1220,15 @@ if (isset($_GET['ticket_id'])) {
<!-- project card --> <!-- project card -->
<?php if ($project_id) { ?> <?php if ($project_id) { ?>
<div class="card"> <div class="card">
<div class="card-header py-2"> <div class="card-header">
<h5 class="card-title"><i class="fas fa-fw fa-project-diagram mr-2 mt-2"></i>Project</h5> <h5 class="card-title"><i class="fas fa-fw fa-project-diagram mr-2"></i>Project</h5>
<?php if (empty($ticket_resolved_at) && lookupUserPermission("module_support") >= 2) { ?>
<div class="card-tools"> <div class="card-tools">
<?php if (empty($ticket_resolved_at) && lookupUserPermission("module_support") >= 2) { ?> <button type="button" class="btn btn-tool ajax-modal" data-modal-url="modals/ticket/ticket_edit_project.php?id=<?= $ticket_id ?>">
<button type="button" class="btn btn-light text-secondary btn-sm ajax-modal" data-modal-url="modals/ticket/ticket_edit_project.php?id=<?= $ticket_id ?>"> <i class="fas fa-edit"></i>
<i class="fas fa-edit"></i> </button>
</button>
<?php } ?>
</div> </div>
<?php } ?>
</div> </div>
<div class="card-body"> <div class="card-body">
<div> <div>

View File

@@ -28,10 +28,6 @@ if (isset($_GET['account_to']) & !empty($_GET['account_to'])) {
$account_to_filter = ''; $account_to_filter = '';
} }
//Rebuild URL
$url_query_strings_sort = http_build_query($get_copy);
$sql = mysqli_query( $sql = mysqli_query(
$mysqli, $mysqli,
"SELECT SQL_CALC_FOUND_ROWS transfer_created_at, expense_date AS transfer_date, expense_amount AS transfer_amount, expense_account_id AS transfer_account_from, revenue_account_id AS transfer_account_to, transfer_expense_id, transfer_revenue_id , transfer_id, transfer_method, transfer_notes FROM transfers, expenses, revenues "SELECT SQL_CALC_FOUND_ROWS transfer_created_at, expense_date AS transfer_date, expense_amount AS transfer_amount, expense_account_id AS transfer_account_from, revenue_account_id AS transfer_account_to, transfer_expense_id, transfer_revenue_id , transfer_id, transfer_method, transfer_notes FROM transfers, expenses, revenues

View File

@@ -15,9 +15,6 @@ if (isset($_GET['client_id'])) {
$client_url = ''; $client_url = '';
} }
//Rebuild URL
$url_query_strings_sort = http_build_query($get_copy);
$sql = mysqli_query( $sql = mysqli_query(
$mysqli, $mysqli,
"SELECT SQL_CALC_FOUND_ROWS * FROM trips "SELECT SQL_CALC_FOUND_ROWS * FROM trips

View File

@@ -49,7 +49,7 @@ if (isset($_POST['add_ticket'])) {
$details = removeEmoji($details); $details = removeEmoji($details);
$email_subject = "ITFlow - New Ticket - $client_name: $subject"; $email_subject = "ITFlow - New Ticket - $client_name: $subject";
$email_body = "Hello, <br><br>This is a notification that a new ticket has been raised in ITFlow. <br>Client: $client_name<br>Priority: $priority<br>Link: https://$config_base_url/ticket.php?ticket_id=$ticket_id <br><br><b>$subject</b><br>$details"; $email_body = "Hello, <br><br>This is a notification that a new ticket has been raised in ITFlow. <br>Client: $client_name<br>Priority: $priority<br>Link: https://$config_base_url/agent/ticket.php?ticket_id=$ticket_id&client_id=$session_client_id <br><br><b>$subject</b><br>$details";
// Queue Mail // Queue Mail
$data = [ $data = [
@@ -113,7 +113,7 @@ if (isset($_POST['add_ticket_comment'])) {
$tech_name = sanitizeInput($tech_details['user_name']); $tech_name = sanitizeInput($tech_details['user_name']);
$subject = "$config_app_name Ticket updated - [$config_ticket_prefix$ticket_number] $ticket_subject"; $subject = "$config_app_name Ticket updated - [$config_ticket_prefix$ticket_number] $ticket_subject";
$body = "Hello $tech_name,<br><br>A new reply has been added to the below ticket, check ITFlow for full details.<br><br>Client: $client_name<br>Ticket: $config_ticket_prefix$ticket_number<br>Subject: $ticket_subject<br><br>https://$config_base_url/ticket.php?ticket_id=$ticket_id"; $body = "Hello $tech_name,<br><br>A new reply has been added to the below ticket, check ITFlow for full details.<br><br>Client: $client_name<br>Ticket: $config_ticket_prefix$ticket_number<br>Subject: $ticket_subject<br><br>https://$config_base_url/agent/ticket.php?ticket_id=$ticket_id&client_id=$session_client_id";
$data = [ $data = [
[ [
@@ -440,7 +440,7 @@ if (isset($_GET['add_payment_by_provider'])) {
$sql = mysqli_query($mysqli,"SELECT * FROM invoices $sql = mysqli_query($mysqli,"SELECT * FROM invoices
LEFT JOIN clients ON invoice_client_id = client_id LEFT JOIN clients ON invoice_client_id = client_id
LEFT JOIN contacts ON client_id = contact_client_id AND contact_primary = 1 LEFT JOIN contacts ON client_id = contact_client_id AND contact_primary = 1
WHERE invoice_id = $invoice_id" WHERE invoice_id = $invoice_id AND client_id = $session_client_id"
); );
$row = mysqli_fetch_array($sql); $row = mysqli_fetch_array($sql);
$invoice_number = intval($row['invoice_number']); $invoice_number = intval($row['invoice_number']);

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

12
plugins/composer.lock generated
View File

@@ -893,16 +893,16 @@
}, },
{ {
"name": "symfony/http-foundation", "name": "symfony/http-foundation",
"version": "v7.3.3", "version": "v7.3.7",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/http-foundation.git", "url": "https://github.com/symfony/http-foundation.git",
"reference": "7475561ec27020196c49bb7c4f178d33d7d3dc00" "reference": "db488a62f98f7a81d5746f05eea63a74e55bb7c4"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/7475561ec27020196c49bb7c4f178d33d7d3dc00", "url": "https://api.github.com/repos/symfony/http-foundation/zipball/db488a62f98f7a81d5746f05eea63a74e55bb7c4",
"reference": "7475561ec27020196c49bb7c4f178d33d7d3dc00", "reference": "db488a62f98f7a81d5746f05eea63a74e55bb7c4",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -952,7 +952,7 @@
"description": "Defines an object-oriented layer for the HTTP specification", "description": "Defines an object-oriented layer for the HTTP specification",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/http-foundation/tree/v7.3.3" "source": "https://github.com/symfony/http-foundation/tree/v7.3.7"
}, },
"funding": [ "funding": [
{ {
@@ -972,7 +972,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2025-08-20T08:04:18+00:00" "time": "2025-11-08T16:41:12+00:00"
}, },
{ {
"name": "symfony/polyfill-mbstring", "name": "symfony/polyfill-mbstring",

View File

@@ -8,11 +8,11 @@ $baseDir = dirname($vendorDir);
return array( return array(
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'662a729f963d39afe703c9d9b7ab4a8c' => $vendorDir . '/symfony/polyfill-php83/bootstrap.php', '662a729f963d39afe703c9d9b7ab4a8c' => $vendorDir . '/symfony/polyfill-php83/bootstrap.php',
'606a39d89246991a373564698c2d8383' => $vendorDir . '/symfony/polyfill-php85/bootstrap.php',
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php', '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
'606a39d89246991a373564698c2d8383' => $vendorDir . '/symfony/polyfill-php85/bootstrap.php',
'2203a247e6fda86070a5e4e07aed533a' => $vendorDir . '/symfony/clock/Resources/now.php', '2203a247e6fda86070a5e4e07aed533a' => $vendorDir . '/symfony/clock/Resources/now.php',
'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php',
'9d2b9fc6db0f153a0a149fefb182415e' => $vendorDir . '/symfony/polyfill-php84/bootstrap.php', '9d2b9fc6db0f153a0a149fefb182415e' => $vendorDir . '/symfony/polyfill-php84/bootstrap.php',
'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php',
'23f09fe3194f8c2f70923f90d6702129' => $vendorDir . '/illuminate/collections/functions.php', '23f09fe3194f8c2f70923f90d6702129' => $vendorDir . '/illuminate/collections/functions.php',
'60799491728b879e74601d83e38b2cad' => $vendorDir . '/illuminate/collections/helpers.php', '60799491728b879e74601d83e38b2cad' => $vendorDir . '/illuminate/collections/helpers.php',
'f625ee536139dfb962a398b200bdb2bd' => $vendorDir . '/illuminate/support/functions.php', 'f625ee536139dfb962a398b200bdb2bd' => $vendorDir . '/illuminate/support/functions.php',

View File

@@ -19,7 +19,7 @@ return array(
'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'), 'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'),
'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
'Psr\\Clock\\' => array($vendorDir . '/psr/clock/src'), 'Psr\\Clock\\' => array($vendorDir . '/psr/clock/src'),
'Illuminate\\Support\\' => array($vendorDir . '/illuminate/macroable', $vendorDir . '/illuminate/conditionable', $vendorDir . '/illuminate/collections', $vendorDir . '/illuminate/support'), 'Illuminate\\Support\\' => array($vendorDir . '/illuminate/collections', $vendorDir . '/illuminate/conditionable', $vendorDir . '/illuminate/macroable', $vendorDir . '/illuminate/support'),
'Illuminate\\Pagination\\' => array($vendorDir . '/illuminate/pagination'), 'Illuminate\\Pagination\\' => array($vendorDir . '/illuminate/pagination'),
'Illuminate\\Contracts\\' => array($vendorDir . '/illuminate/contracts'), 'Illuminate\\Contracts\\' => array($vendorDir . '/illuminate/contracts'),
'Doctrine\\Inflector\\' => array($vendorDir . '/doctrine/inflector/src'), 'Doctrine\\Inflector\\' => array($vendorDir . '/doctrine/inflector/src'),

View File

@@ -9,11 +9,11 @@ class ComposerStaticInit9b9826e5b5cc7806cd328c4112cca75e
public static $files = array ( public static $files = array (
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'662a729f963d39afe703c9d9b7ab4a8c' => __DIR__ . '/..' . '/symfony/polyfill-php83/bootstrap.php', '662a729f963d39afe703c9d9b7ab4a8c' => __DIR__ . '/..' . '/symfony/polyfill-php83/bootstrap.php',
'606a39d89246991a373564698c2d8383' => __DIR__ . '/..' . '/symfony/polyfill-php85/bootstrap.php',
'6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
'606a39d89246991a373564698c2d8383' => __DIR__ . '/..' . '/symfony/polyfill-php85/bootstrap.php',
'2203a247e6fda86070a5e4e07aed533a' => __DIR__ . '/..' . '/symfony/clock/Resources/now.php', '2203a247e6fda86070a5e4e07aed533a' => __DIR__ . '/..' . '/symfony/clock/Resources/now.php',
'a1105708a18b76903365ca1c4aa61b02' => __DIR__ . '/..' . '/symfony/translation/Resources/functions.php',
'9d2b9fc6db0f153a0a149fefb182415e' => __DIR__ . '/..' . '/symfony/polyfill-php84/bootstrap.php', '9d2b9fc6db0f153a0a149fefb182415e' => __DIR__ . '/..' . '/symfony/polyfill-php84/bootstrap.php',
'a1105708a18b76903365ca1c4aa61b02' => __DIR__ . '/..' . '/symfony/translation/Resources/functions.php',
'23f09fe3194f8c2f70923f90d6702129' => __DIR__ . '/..' . '/illuminate/collections/functions.php', '23f09fe3194f8c2f70923f90d6702129' => __DIR__ . '/..' . '/illuminate/collections/functions.php',
'60799491728b879e74601d83e38b2cad' => __DIR__ . '/..' . '/illuminate/collections/helpers.php', '60799491728b879e74601d83e38b2cad' => __DIR__ . '/..' . '/illuminate/collections/helpers.php',
'f625ee536139dfb962a398b200bdb2bd' => __DIR__ . '/..' . '/illuminate/support/functions.php', 'f625ee536139dfb962a398b200bdb2bd' => __DIR__ . '/..' . '/illuminate/support/functions.php',
@@ -118,9 +118,9 @@ class ComposerStaticInit9b9826e5b5cc7806cd328c4112cca75e
), ),
'Illuminate\\Support\\' => 'Illuminate\\Support\\' =>
array ( array (
0 => __DIR__ . '/..' . '/illuminate/macroable', 0 => __DIR__ . '/..' . '/illuminate/collections',
1 => __DIR__ . '/..' . '/illuminate/conditionable', 1 => __DIR__ . '/..' . '/illuminate/conditionable',
2 => __DIR__ . '/..' . '/illuminate/collections', 2 => __DIR__ . '/..' . '/illuminate/macroable',
3 => __DIR__ . '/..' . '/illuminate/support', 3 => __DIR__ . '/..' . '/illuminate/support',
), ),
'Illuminate\\Pagination\\' => 'Illuminate\\Pagination\\' =>

View File

@@ -929,17 +929,17 @@
}, },
{ {
"name": "symfony/http-foundation", "name": "symfony/http-foundation",
"version": "v7.3.3", "version": "v7.3.7",
"version_normalized": "7.3.3.0", "version_normalized": "7.3.7.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/http-foundation.git", "url": "https://github.com/symfony/http-foundation.git",
"reference": "7475561ec27020196c49bb7c4f178d33d7d3dc00" "reference": "db488a62f98f7a81d5746f05eea63a74e55bb7c4"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/7475561ec27020196c49bb7c4f178d33d7d3dc00", "url": "https://api.github.com/repos/symfony/http-foundation/zipball/db488a62f98f7a81d5746f05eea63a74e55bb7c4",
"reference": "7475561ec27020196c49bb7c4f178d33d7d3dc00", "reference": "db488a62f98f7a81d5746f05eea63a74e55bb7c4",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -963,7 +963,7 @@
"symfony/mime": "^6.4|^7.0", "symfony/mime": "^6.4|^7.0",
"symfony/rate-limiter": "^6.4|^7.0" "symfony/rate-limiter": "^6.4|^7.0"
}, },
"time": "2025-08-20T08:04:18+00:00", "time": "2025-11-08T16:41:12+00:00",
"type": "library", "type": "library",
"installation-source": "dist", "installation-source": "dist",
"autoload": { "autoload": {
@@ -991,7 +991,7 @@
"description": "Defines an object-oriented layer for the HTTP specification", "description": "Defines an object-oriented layer for the HTTP specification",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/http-foundation/tree/v7.3.3" "source": "https://github.com/symfony/http-foundation/tree/v7.3.7"
}, },
"funding": [ "funding": [
{ {

View File

@@ -5,7 +5,7 @@
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),
'reference' => '981fb9585d0c76e8b9c31812d58dfdd5b56d6454', 'reference' => '612041635d962d37f2f400ba1974bec5456ccd1e',
'name' => '__root__', 'name' => '__root__',
'dev' => true, 'dev' => true,
), ),
@@ -16,7 +16,7 @@
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),
'reference' => '981fb9585d0c76e8b9c31812d58dfdd5b56d6454', 'reference' => '612041635d962d37f2f400ba1974bec5456ccd1e',
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'carbonphp/carbon-doctrine-types' => array( 'carbonphp/carbon-doctrine-types' => array(
@@ -158,12 +158,12 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/http-foundation' => array( 'symfony/http-foundation' => array(
'pretty_version' => 'v7.3.3', 'pretty_version' => 'v7.3.7',
'version' => '7.3.3.0', 'version' => '7.3.7.0',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/http-foundation', 'install_path' => __DIR__ . '/../symfony/http-foundation',
'aliases' => array(), 'aliases' => array(),
'reference' => '7475561ec27020196c49bb7c4f178d33d7d3dc00', 'reference' => 'db488a62f98f7a81d5746f05eea63a74e55bb7c4',
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/polyfill-mbstring' => array( 'symfony/polyfill-mbstring' => array(

View File

@@ -164,7 +164,7 @@ class BinaryFileResponse extends Response
for ($i = 0, $filenameLength = mb_strlen($filename, $encoding); $i < $filenameLength; ++$i) { for ($i = 0, $filenameLength = mb_strlen($filename, $encoding); $i < $filenameLength; ++$i) {
$char = mb_substr($filename, $i, 1, $encoding); $char = mb_substr($filename, $i, 1, $encoding);
if ('%' === $char || \ord($char) < 32 || \ord($char) > 126) { if ('%' === $char || \ord($char[0]) < 32 || \ord($char[0]) > 126) {
$filenameFallback .= '_'; $filenameFallback .= '_';
} else { } else {
$filenameFallback .= $char; $filenameFallback .= $char;

View File

@@ -300,10 +300,21 @@ class Request
$server['PATH_INFO'] = ''; $server['PATH_INFO'] = '';
$server['REQUEST_METHOD'] = strtoupper($method); $server['REQUEST_METHOD'] = strtoupper($method);
if (($i = strcspn($uri, ':/?#')) && ':' === ($uri[$i] ?? null) && (strspn($uri, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-.') !== $i || strcspn($uri, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'))) {
throw new BadRequestException('Invalid URI: Scheme is malformed.');
}
if (false === $components = parse_url(\strlen($uri) !== strcspn($uri, '?#') ? $uri : $uri.'#')) { if (false === $components = parse_url(\strlen($uri) !== strcspn($uri, '?#') ? $uri : $uri.'#')) {
throw new BadRequestException('Invalid URI.'); throw new BadRequestException('Invalid URI.');
} }
$part = ($components['user'] ?? '').':'.($components['pass'] ?? '');
if (':' !== $part && \strlen($part) !== strcspn($part, '[]')) {
throw new BadRequestException('Invalid URI: Userinfo is malformed.');
}
if (($part = $components['host'] ?? '') && !self::isHostValid($part)) {
throw new BadRequestException('Invalid URI: Host is malformed.');
}
if (false !== ($i = strpos($uri, '\\')) && $i < strcspn($uri, '?#')) { if (false !== ($i = strpos($uri, '\\')) && $i < strcspn($uri, '?#')) {
throw new BadRequestException('Invalid URI: A URI cannot contain a backslash.'); throw new BadRequestException('Invalid URI: A URI cannot contain a backslash.');
} }
@@ -1091,10 +1102,8 @@ class Request
// host is lowercase as per RFC 952/2181 // host is lowercase as per RFC 952/2181
$host = strtolower(preg_replace('/:\d+$/', '', trim($host))); $host = strtolower(preg_replace('/:\d+$/', '', trim($host)));
// as the host can come from the user (HTTP_HOST and depending on the configuration, SERVER_NAME too can come from the user) // the host can come from the user (HTTP_HOST and depending on the configuration, SERVER_NAME too can come from the user)
// check that it does not contain forbidden characters (see RFC 952 and RFC 2181) if ($host && !self::isHostValid($host)) {
// use preg_replace() instead of preg_match() to prevent DoS attacks with long host names
if ($host && '' !== preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host)) {
if (!$this->isHostValid) { if (!$this->isHostValid) {
return ''; return '';
} }
@@ -1236,15 +1245,22 @@ class Request
static::initializeFormats(); static::initializeFormats();
} }
$exactFormat = null;
$canonicalFormat = null;
foreach (static::$formats as $format => $mimeTypes) { foreach (static::$formats as $format => $mimeTypes) {
if (\in_array($mimeType, (array) $mimeTypes, true)) { if (\in_array($mimeType, $mimeTypes, true)) {
return $format; $exactFormat = $format;
} }
if (null !== $canonicalMimeType && \in_array($canonicalMimeType, (array) $mimeTypes, true)) { if (null !== $canonicalMimeType && \in_array($canonicalMimeType, $mimeTypes, true)) {
return $format; $canonicalFormat = $format;
} }
} }
if ($format = $exactFormat ?? $canonicalFormat) {
return $format;
}
return null; return null;
} }
@@ -1259,7 +1275,7 @@ class Request
static::initializeFormats(); static::initializeFormats();
} }
static::$formats[$format] = \is_array($mimeTypes) ? $mimeTypes : [$mimeTypes]; static::$formats[$format ?? ''] = (array) $mimeTypes;
} }
/** /**
@@ -1892,9 +1908,8 @@ class Request
} }
$pathInfo = substr($requestUri, \strlen($baseUrl)); $pathInfo = substr($requestUri, \strlen($baseUrl));
if ('' === $pathInfo) { if ('' === $pathInfo || '/' !== $pathInfo[0]) {
// If substr() returns false then PATH_INFO is set to an empty string return '/'.$pathInfo;
return '/';
} }
return $pathInfo; return $pathInfo;
@@ -2101,4 +2116,21 @@ class Request
return $this->isIisRewrite; return $this->isIisRewrite;
} }
/**
* See https://url.spec.whatwg.org/.
*/
private static function isHostValid(string $host): bool
{
if ('[' === $host[0]) {
return ']' === $host[-1] && filter_var(substr($host, 1, -1), \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6);
}
if (preg_match('/\.[0-9]++\.?$/D', $host)) {
return null !== filter_var($host, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4 | \FILTER_NULL_ON_FAILURE);
}
// use preg_replace() instead of preg_match() to prevent DoS attacks with long host names
return '' === preg_replace('/[-a-zA-Z0-9_]++\.?/', '', $host);
}
} }

View File

@@ -159,7 +159,7 @@ class ResponseHeaderBag extends HeaderBag
public function setCookie(Cookie $cookie): void public function setCookie(Cookie $cookie): void
{ {
$this->cookies[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie; $this->cookies[$cookie->getDomain() ?? ''][$cookie->getPath()][$cookie->getName()] = $cookie;
$this->headerNames['set-cookie'] = 'Set-Cookie'; $this->headerNames['set-cookie'] = 'Set-Cookie';
} }
@@ -170,13 +170,13 @@ class ResponseHeaderBag extends HeaderBag
{ {
$path ??= '/'; $path ??= '/';
unset($this->cookies[$domain][$path][$name]); unset($this->cookies[$domain ?? ''][$path][$name]);
if (empty($this->cookies[$domain][$path])) { if (empty($this->cookies[$domain ?? ''][$path])) {
unset($this->cookies[$domain][$path]); unset($this->cookies[$domain ?? ''][$path]);
if (empty($this->cookies[$domain])) { if (empty($this->cookies[$domain ?? ''])) {
unset($this->cookies[$domain]); unset($this->cookies[$domain ?? '']);
} }
} }

View File

@@ -132,14 +132,12 @@ class ServerEvent implements \IteratorAggregate
} }
yield $head; yield $head;
if ($this->data) { if (is_iterable($this->data)) {
if (is_iterable($this->data)) { foreach ($this->data as $data) {
foreach ($this->data as $data) { yield \sprintf('data: %s', $data)."\n";
yield \sprintf('data: %s', $data)."\n";
}
} else {
yield \sprintf('data: %s', $this->data)."\n";
} }
} elseif ('' !== $this->data) {
yield \sprintf('data: %s', $this->data)."\n";
} }
yield "\n"; yield "\n";

View File

@@ -219,7 +219,7 @@ class PdoSessionHandler extends AbstractSessionHandler
$table->addColumn($this->timeCol, Types::INTEGER)->setNotnull(true); $table->addColumn($this->timeCol, Types::INTEGER)->setNotnull(true);
break; break;
case 'sqlsrv': case 'sqlsrv':
$table->addColumn($this->idCol, Types::TEXT)->setLength(128)->setNotnull(true); $table->addColumn($this->idCol, Types::STRING)->setLength(128)->setNotnull(true);
$table->addColumn($this->dataCol, Types::BLOB)->setNotnull(true); $table->addColumn($this->dataCol, Types::BLOB)->setNotnull(true);
$table->addColumn($this->lifetimeCol, Types::INTEGER)->setUnsigned(true)->setNotnull(true); $table->addColumn($this->lifetimeCol, Types::INTEGER)->setUnsigned(true)->setNotnull(true);
$table->addColumn($this->timeCol, Types::INTEGER)->setUnsigned(true)->setNotnull(true); $table->addColumn($this->timeCol, Types::INTEGER)->setUnsigned(true)->setNotnull(true);