Merge pull request #1247 from itflow-org/develop

Merge Develop into Master for v25.11.1 release
This commit is contained in:
Johnny 2025-11-17 12:22:24 -05:00 committed by GitHub
commit c7ef3627ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
60 changed files with 1642 additions and 394 deletions

View File

@ -2,7 +2,37 @@
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:
- **Outdated CRON Scripts**: The following scripts are removed.

View File

@ -4059,9 +4059,7 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) {
`contract_template_created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
`contract_template_updated_at` DATETIME NULL ON UPDATE CURRENT_TIMESTAMP,
`contract_template_archived_at` DATETIME NULL DEFAULT NULL,
`company_id` INT(11) NOT NULL
`contract_template_archived_at` DATETIME NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");
@ -4115,10 +4113,31 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) {
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.3.7'");
}
// if (CURRENT_DATABASE_VERSION == '2.3.7') {
// // Insert queries here required to update to DB version 2.3.8
if (CURRENT_DATABASE_VERSION == '2.3.7') {
mysqli_query($mysqli, "
CREATE TABLE `asset_tags` (
`asset_tag_asset_id` INT(11) NOT NULL,
`asset_tag_tag_id` INT(11) NOT NULL,
PRIMARY KEY (`asset_tag_asset_id`, `asset_tag_tag_id`),
CONSTRAINT `fk_asset`
FOREIGN KEY (`asset_tag_asset_id`)
REFERENCES `assets`(`asset_id`)
ON DELETE CASCADE,
CONSTRAINT `fk_tag`
FOREIGN KEY (`asset_tag_tag_id`)
REFERENCES `tags`(`tag_id`)
ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
");
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.3.8'");
}
// if (CURRENT_DATABASE_VERSION == '2.3.8') {
// // Insert queries here required to update to DB version 2.3.9
// // Then, update the database to the next sequential version
// mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.3.8'");
// mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.3.9'");
// }
} else {

View File

@ -18,25 +18,37 @@
<li class="nav-item">
<a href="/admin/users.php" class="nav-link <?php if (basename($_SERVER["PHP_SELF"]) == "users.php") {echo "active";} ?>">
<i class="nav-icon fas fa-users"></i>
<p>Users</p>
<p>
Users
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/admin/modals/user/user_add.php"></span>
</p>
</a>
</li>
<li class="nav-item">
<a href="/admin/roles.php" class="nav-link <?php if (basename($_SERVER["PHP_SELF"]) == "roles.php") {echo "active";} ?>">
<i class="nav-icon fas fa-user-shield"></i>
<p>Roles</p>
<p>
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/admin/modals/role/role_add.php"></span>
Roles
</p>
</a>
</li>
<li class="nav-item">
<a href="/admin/modules.php" class="nav-link <?php if (basename($_SERVER["PHP_SELF"]) == "modules.php") {echo "active";} ?>">
<i class="nav-icon fas fa-puzzle-piece"></i>
<p>Modules</p>
<p>
Modules
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/admin/modals/module/module_add.php"></span>
</p>
</a>
</li>
<li class="nav-item">
<a href="/admin/api_keys.php" class="nav-link <?php if (basename($_SERVER["PHP_SELF"]) == "api_keys.php") {echo "active";} ?>">
<i class="nav-icon fas fa-key"></i>
<p>API Keys</p>
<p>
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/admin/modals/api/api_key_add.php"></span>
API Keys
</p>
</a>
</li>
<li class="nav-header">TAGS & CATEGORIES</li>
@ -44,26 +56,38 @@
<li class="nav-item">
<a href="/admin/tag.php" class="nav-link <?php echo (basename($_SERVER['PHP_SELF']) == 'tag.php' ? 'active' : ''); ?>">
<i class="nav-icon fas fa-tags"></i>
<p>Tags</p>
<p>
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/admin/modals/tag/tag_add.php"></span>
Tags
</p>
</a>
</li>
<li class="nav-item">
<a href="/admin/category.php" class="nav-link <?php echo (basename($_SERVER['PHP_SELF']) == 'category.php' ? 'active' : ''); ?>">
<i class="nav-icon fas fa-list-ul"></i>
<p>Categories</p>
<p>
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/admin/modals/category/category_add.php"></span>
Categories
</p>
</a>
</li>
<?php if ($config_module_enable_accounting) { ?>
<li class="nav-item">
<a href="/admin/tax.php" class="nav-link <?php echo (basename($_SERVER['PHP_SELF']) == 'tax.php' ? 'active' : ''); ?>">
<i class="nav-icon fas fa-balance-scale"></i>
<p>Taxes</p>
<p>
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/admin/modals/tax/tax_add.php"></span>
Taxes
</p>
</a>
</li>
<li class="nav-item">
<a href="/admin/payment_method.php" class="nav-link <?php echo (basename($_SERVER['PHP_SELF']) == 'payment_method.php' ? 'active' : ''); ?>">
<i class="nav-icon fas fa-hand-holding-usd"></i>
<p>Payment Methods</p>
<p>
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/admin/modals/payment_method/payment_method_add.php"></span>
Payment Methods
</p>
</a>
</li>
<li class="nav-item">
@ -96,54 +120,80 @@
<li class="nav-item">
<a href="/admin/ticket_status.php" class="nav-link <?php echo (basename($_SERVER['PHP_SELF']) == 'ticket_status.php' ? 'active' : ''); ?>">
<i class="nav-icon fas fa-info-circle"></i>
<p>Ticket Statuses</p>
<p>
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/admin/modals/ticket_status/ticket_status_add.php"></span>
Ticket Statuses
</p>
</a>
</li>
<?php } ?>
<li class="nav-item">
<a href="/admin/custom_link.php" class="nav-link <?php echo (basename($_SERVER['PHP_SELF']) == 'custom_link.php' ? 'active' : ''); ?>">
<i class="nav-icon fas fa-external-link-alt"></i>
<p>Custom Links</p>
<p>
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/admin/modals/custom_link/custom_link_add.php"></span>
Custom Links
</p>
</a>
</li>
<?php if ($config_module_enable_itdoc) { ?>
<li class="nav-header">TEMPLATES</li>
<!-- 2025-11-16 JQ - Hide Contracts not yet ready
<li class="nav-item">
<a href="/admin/contract_templates.php" class="nav-link <?php echo (basename($_SERVER['PHP_SELF']) == 'contract_templates.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>
<p>Contract Templates</p>
<p>
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/admin/modals/contract_template/contract_template_add.php" data-modal-size="lg"></span>
Contract Templates
</p>
</a>
</li>
-->
<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' : ''); ?>">
<i class="nav-icon fas fa-project-diagram"></i>
<p>Project Templates</p>
<p>
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/admin/modals/project_template/project_template_add.php"></span>
Project Templates
</p>
</a>
</li>
<li class="nav-item">
<a href="/admin/ticket_template.php" class="nav-link <?php echo (in_array(basename($_SERVER['PHP_SELF']), ['ticket_template.php', 'ticket_template_details.php']) ? 'active' : ''); ?>">
<i class="nav-icon fas fa-life-ring"></i>
<p>Ticket Templates</p>
<p>
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/admin/modals/ticket_template/ticket_template_add.php" data-modal-size="lg"></span>
Ticket Templates
</p>
</a>
</li>
<li class="nav-item">
<a href="/admin/vendor_template.php" class="nav-link <?php echo (basename($_SERVER['PHP_SELF']) == 'vendor_template.php' ? 'active' : ''); ?>">
<i class="nav-icon fas fa-building"></i>
<p>Vendor Templates</p>
<p>
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/admin/modals/vendor_template/vendor_template_add.php"></span>
Vendor Templates
</p>
</a>
</li>
<li class="nav-item">
<a href="/admin/software_template.php" class="nav-link <?php echo (basename($_SERVER['PHP_SELF']) == 'software_template.php' ? 'active' : ''); ?>">
<i class="nav-icon fas fa-rocket"></i>
<p>License Templates</p>
<p>
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/admin/modals/software_template/software_template_add.php"></span>
License Templates
</p>
</a>
</li>
<li class="nav-item">
<a href="/admin/document_template.php" class="nav-link <?php echo (in_array(basename($_SERVER['PHP_SELF']), ['document_template.php', 'document_template_details.php']) ? 'active' : ''); ?>">
<i class="nav-icon fas fa-file"></i>
<p>Document Templates</p>
<p>
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/admin/modals/document_template/document_template_add.php" data-modal-size="lg"></span>
Document Templates
</p>
</a>
</li>
<?php } ?>

View File

@ -2,7 +2,9 @@
require_once '../../../includes/modal_header.php';
$category = nullable_htmlentities($_GET['category']);
$category = nullable_htmlentities($_GET['category'] ?? '');
$category_types_array = ['Expense', 'Income', 'Referral', 'Ticket'];
?>
@ -13,10 +15,30 @@ $category = nullable_htmlentities($_GET['category']);
</button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="type" value="<?php echo ($category); ?>">
<div class="modal-body">
<?php if ($category) { ?>
<input type="hidden" name="type" value="<?= $category ?>">
<?php } else { ?>
<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-tag"></i></span>
</div>
<select class="form-control select2" name="type" required>
<option value="">- Select Type -</option>
<?php foreach ($category_types_array as $type_select) { ?>
<option><?= $type_select ?></option>
<?php } ?>
</select>
</div>
</div>
<?php } ?>
<div class="form-group">
<label>Name <strong class="text-danger">*</strong></label>
<div class="input-group">
@ -39,7 +61,7 @@ $category = nullable_htmlentities($_GET['category']);
</div>
<div class="modal-footer">
<button type="submit" name="add_category" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Create</button>
<button type="submit" name="add_category" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Create Category</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
</div>
</form>

View File

@ -1,15 +1,18 @@
<?php
require_once '../../../includes/modal_header.php';
ob_start();
$contract_types_array = ['Fully Managed', 'Partialy Managed', 'Break/Fix'];
$update_frequency_array = ['Manual', 'Annually', '2 Year', '3 Year', '5 Year', '7 Year'];
$renewal_frequency_array = ['Manual', 'Annually', '2 Year', '3 Year', '5 Year', '7 Year'];
ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-file-contract mr-2"></i>New Contract Template</h5>
<button type="button" class="close text-white" data-dismiss="modal"><span>&times;</span></button>
</div>
<!-- Tabs Navigation -->
<ul class="modal-header nav nav-pills nav-justified">
<li class="nav-item">
@ -25,37 +28,62 @@ $update_frequency_array = ['Manual', 'Annually', '2 Year', '3 Year', '5 Year', '
<a class="nav-link" id="details-tab" data-toggle="tab" href="#details" role="tab">Details</a>
</li>
</ul>
<form action="post.php" method="post" autocomplete="off">
<div class="modal-body">
<div class="tab-content" id="contractTemplateTabContent">
<!-- General Info Tab -->
<div class="tab-pane fade show active" id="general" role="tabpanel">
<div class="form-group">
<label>Template Name <strong class="text-danger">*</strong></label>
<input type="text" class="form-control" name="contract_template_name" placeholder="Contract Template Name" maxlength="200" required autofocus>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-file-contract"></i></span>
</div>
<input type="text" class="form-control" name="name" placeholder="Contract Template Name" maxlength="200" required autofocus>
</div>
</div>
<div class="form-group">
<label>Template Description <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-align-left"></i></span>
</div>
<input type="text" class="form-control" name="description"
placeholder="Contract Template Description" maxlength="200" required>
</div>
</div>
<div class="form-group">
<label>Contract Type <strong class="text-danger">*</strong></label>
<select class="form-control select2" name="contract_template_type" required>
<option value="">- Select Type -</option>
<?php foreach ($contract_types_array as $type) { ?>
<option><?php echo $type; ?></option>
<?php } ?>
</select>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-list"></i></span>
</div>
<select class="form-control select2" name="type" required>
<option value="">- Select Type -</option>
<?php foreach ($contract_types_array as $type) { ?>
<option><?= $type ?></option>
<?php } ?>
</select>
</div>
</div>
<div class="form-group">
<label>Update Frequency</label>
<select class="form-control select2" name="contract_template_update_frequency">
<option value="">- Select Frequency -</option>
<?php foreach ($update_frequency_array as $freq) { ?>
<option><?php echo $freq; ?></option>
<?php } ?>
</select>
<label>Renewal Frequency</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-sync-alt"></i></span>
</div>
<select class="form-control select2" name="renewal_frequency">
<option value="">- Select Frequency -</option>
<?php foreach ($renewal_frequency_array as $renewal_frequency) { ?>
<option><?= $renewal_frequency ?></option>
<?php } ?>
</select>
</div>
</div>
</div>
@ -64,33 +92,63 @@ $update_frequency_array = ['Manual', 'Annually', '2 Year', '3 Year', '5 Year', '
<div class="form-row">
<div class="form-group col-md-6">
<label>Low Priority Response (hrs)</label>
<input type="number" class="form-control" name="sla_low_response_time" placeholder="e.g., 24">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-clock"></i></span>
</div>
<input type="number" class="form-control" name="sla_low_response_time" placeholder="e.g., 24">
</div>
</div>
<div class="form-group col-md-6">
<label>Low Priority Resolution (hrs)</label>
<input type="number" class="form-control" name="sla_low_resolution_time" placeholder="e.g., 48">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-hourglass-half"></i></span>
</div>
<input type="number" class="form-control" name="sla_low_resolution_time" placeholder="e.g., 48">
</div>
</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<label>Medium Priority Response (hrs)</label>
<input type="number" class="form-control" name="sla_medium_response_time" placeholder="e.g., 12">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-clock"></i></span>
</div>
<input type="number" class="form-control" name="sla_medium_response_time" placeholder="e.g., 12">
</div>
</div>
<div class="form-group col-md-6">
<label>Medium Priority Resolution (hrs)</label>
<input type="number" class="form-control" name="sla_medium_resolution_time" placeholder="e.g., 24">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-hourglass-half"></i></span>
</div>
<input type="number" class="form-control" name="sla_medium_resolution_time" placeholder="e.g., 24">
</div>
</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<label>High Priority Response (hrs)</label>
<input type="number" class="form-control" name="sla_high_response_time" placeholder="e.g., 1">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-bolt"></i></span>
</div>
<input type="number" class="form-control" name="sla_high_response_time" placeholder="e.g., 1">
</div>
</div>
<div class="form-group col-md-6">
<label>High Priority Resolution (hrs)</label>
<input type="number" class="form-control" name="sla_high_resolution_time" placeholder="e.g., 4">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-stopwatch"></i></span>
</div>
<input type="number" class="form-control" name="sla_high_resolution_time" placeholder="e.g., 4">
</div>
</div>
</div>
</div>
@ -99,29 +157,49 @@ $update_frequency_array = ['Manual', 'Annually', '2 Year', '3 Year', '5 Year', '
<div class="tab-pane fade" id="rates" role="tabpanel">
<div class="form-group">
<label>Standard Hourly Rate</label>
<input type="text" class="form-control" name="contract_template_hourly_rate" placeholder="e.g., 100">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-dollar-sign"></i></span>
</div>
<input type="text" class="form-control" name="rate_standard" placeholder="e.g., 100">
</div>
</div>
<div class="form-group">
<label>After Hours Hourly Rate</label>
<input type="text" class="form-control" name="contract_template_after_hours_hourly_rate" placeholder="e.g., 150">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-moon"></i></span>
</div>
<input type="text" class="form-control" name="rate_after_hours" placeholder="e.g., 150">
</div>
</div>
<div class="form-group">
<label>Support Hours</label>
<input type="text" class="form-control" name="contract_template_support_hours" placeholder="e.g., Mon-Fri 9am-5pm">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-calendar"></i></span>
</div>
<input type="text" class="form-control" name="support_hours" placeholder="e.g., Mon-Fri 9am-5pm">
</div>
</div>
<div class="form-group">
<label>Net Terms</label>
<input type="text" class="form-control" name="contract_template_net_terms" placeholder="e.g., Net 30">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-file-invoice-dollar"></i></span>
</div>
<input type="text" class="form-control" name="net_terms" placeholder="e.g., Net 30">
</div>
</div>
</div>
<!-- Details Tab -->
<div class="tab-pane fade" id="details" role="tabpanel">
<div class="form-group">
<textarea class="form-control tinymce" rows="6" name="contract_template_details" placeholder="Enter Contract Details"></textarea>
<textarea class="form-control tinymce" rows="6" name="details" placeholder="Enter Contract Details"></textarea>
</div>
</div>
@ -129,8 +207,12 @@ $update_frequency_array = ['Manual', 'Annually', '2 Year', '3 Year', '5 Year', '
</div>
<div class="modal-footer">
<button type="submit" name="add_contract_template" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Create Template</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
<button type="submit" name="add_contract_template" class="btn btn-primary text-bold">
<i class="fa fa-check mr-2"></i>Create Template
</button>
<button type="button" class="btn btn-light" data-dismiss="modal">
<i class="fa fa-times mr-2"></i>Cancel
</button>
</div>
</form>

View File

@ -0,0 +1,265 @@
<?php
require_once '../../../includes/modal_header.php';
$contract_template_id = intval($_GET['id']);
$contract_types_array = ['Fully Managed', 'Partialy Managed', 'Break/Fix'];
$update_frequency_array = ['Manual', 'Annually', '2 Year', '3 Year', '5 Year', '7 Year'];
// Fetch existing template
$sql = mysqli_query($mysqli, "SELECT * FROM contract_templates WHERE contract_template_id = $contract_template_id LIMIT 1");
$row = mysqli_fetch_array($sql);
// Assign locals
$name = nullable_htmlentities($row['contract_template_name']);
$description = nullable_htmlentities($row['contract_template_description']);
$type = nullable_htmlentities($row['contract_template_type']);
$renewal_frequency = nullable_htmlentities($row['contract_template_renewal_frequency']);
$sla_low_resp = intval($row['contract_template_sla_low_response_time']);
$sla_med_resp = intval($row['contract_template_sla_medium_response_time']);
$sla_high_resp = intval($row['contract_template_sla_high_response_time']);
$sla_low_res = intval($row['contract_template_sla_low_resolution_time']);
$sla_med_res = intval($row['contract_template_sla_medium_resolution_time']);
$sla_high_res = intval($row['contract_template_sla_high_resolution_time']);
$hourly_rate = intval($row['contract_template_rate_standard']);
$after_hours = intval($row['contract_template_rate_after_hours']);
$support_hours = nullable_htmlentities($row['contract_template_support_hours']);
$net_terms = intval($row['contract_template_net_terms']);
$details = nullable_htmlentities($row['contract_template_details']);
ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-file-contract mr-2"></i>Edit Contract Template</h5>
<button type="button" class="close text-white" data-dismiss="modal"><span>&times;</span></button>
</div>
<!-- Tabs Navigation -->
<ul class="modal-header nav nav-pills nav-justified">
<li class="nav-item">
<a class="nav-link active" id="general-tab" data-toggle="tab" href="#general" role="tab">General Info</a>
</li>
<li class="nav-item">
<a class="nav-link" id="sla-tab" data-toggle="tab" href="#sla" role="tab">SLA</a>
</li>
<li class="nav-item">
<a class="nav-link" id="rates-tab" data-toggle="tab" href="#rates" role="tab">Rates & Support</a>
</li>
<li class="nav-item">
<a class="nav-link" id="details-tab" data-toggle="tab" href="#details" role="tab">Details</a>
</li>
</ul>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="contract_template_id" value="<?php echo $contract_template_id; ?>">
<div class="modal-body">
<div class="tab-content" id="contractTemplateTabContent">
<!-- General Info Tab -->
<div class="tab-pane fade show active" id="general" role="tabpanel">
<div class="form-group">
<label>Template Name <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-file-contract"></i></span>
</div>
<input type="text" class="form-control" name="name"
placeholder="Contract Template Name" maxlength="200" required autofocus
value="<?= $name ?>">
</div>
</div>
<div class="form-group">
<label>Template Description <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-align-left"></i></span>
</div>
<input type="text" class="form-control" name="description"
placeholder="Contract Template Description" maxlength="200" required
value="<?= $description ?>">
</div>
</div>
<div class="form-group">
<label>Contract 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-list"></i></span>
</div>
<select class="form-control select2" name="type" required>
<option value="">- Select Type -</option>
<?php foreach ($contract_types_array as $type_select) { ?>
<option <?php if ($type == $type_select) { echo "selected"; } ?>><?= $type_select ?></option>
<?php } ?>
</select>
</div>
</div>
<div class="form-group">
<label>Renewal Frequency</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-sync-alt"></i></span>
</div>
<select class="form-control select2" name="renewal_frequency">
<option value="">- Select Frequency -</option>
<?php foreach ($renewal_frequency_array as $renewal_frequency_select) { ?>
<option <?php if ($renewal_frequency == $renewal_frequency_select) { echo "selected"; } ?>><?= $renewal_frequency_select ?></option>
<?php } ?>
</select>
</div>
</div>
</div>
<!-- SLA Tab -->
<div class="tab-pane fade" id="sla" role="tabpanel">
<div class="form-row">
<div class="form-group col-md-6">
<label>Low Priority Response (hrs)</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-clock"></i></span>
</div>
<input type="number" class="form-control" name="sla_low_response_time" placeholder="e.g., 24"
value="<?= $sla_low_resp ?>">
</div>
</div>
<div class="form-group col-md-6">
<label>Low Priority Resolution (hrs)</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-hourglass-half"></i></span>
</div>
<input type="number" class="form-control" name="sla_low_resolution_time" placeholder="e.g., 48"
value="<?= $sla_low_res ?>">
</div>
</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<label>Medium Priority Response (hrs)</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-clock"></i></span>
</div>
<input type="number" class="form-control" name="sla_medium_response_time" placeholder="e.g., 12"
value="<?= $sla_med_resp ?>">
</div>
</div>
<div class="form-group col-md-6">
<label>Medium Priority Resolution (hrs)</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-hourglass-half"></i></span>
</div>
<input type="number" class="form-control" name="sla_medium_resolution_time" placeholder="e.g., 24"
value="<?= $sla_med_res ?>">
</div>
</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<label>High Priority Response (hrs)</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-bolt"></i></span>
</div>
<input type="number" class="form-control" name="sla_high_response_time" placeholder="e.g., 1"
value="<?= $sla_high_resp ?>">
</div>
</div>
<div class="form-group col-md-6">
<label>High Priority Resolution (hrs)</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-stopwatch"></i></span>
</div>
<input type="number" class="form-control" name="sla_high_resolution_time" placeholder="e.g., 4"
value="<?= $sla_high_res ?>">
</div>
</div>
</div>
</div>
<!-- Rates & Support Tab -->
<div class="tab-pane fade" id="rates" role="tabpanel">
<div class="form-group">
<label>Standard Hourly Rate</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-dollar-sign"></i></span>
</div>
<input type="text" class="form-control" name="rate_standard" placeholder="e.g., 100"
value="<?= $rate_standard ?>">
</div>
</div>
<div class="form-group">
<label>After Hours Hourly Rate</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-moon"></i></span>
</div>
<input type="text" class="form-control" name="rate_after_hours" placeholder="e.g., 150"
value="<?= $rate_after_hours ?>">
</div>
</div>
<div class="form-group">
<label>Support Hours</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-calendar"></i></span>
</div>
<input type="text" class="form-control" name="support_hours" placeholder="e.g., Mon-Fri 9am-5pm"
value="<?= $support_hours ?>">
</div>
</div>
<div class="form-group">
<label>Net Terms</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-file-invoice-dollar"></i></span>
</div>
<input type="text" class="form-control" name="net_terms" placeholder="e.g., Net 30"
value="<?= $net_terms ?>">
</div>
</div>
</div>
<!-- Details Tab -->
<div class="tab-pane fade" id="details" role="tabpanel">
<div class="form-group">
<label>Contract Details</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-align-left"></i></span>
</div>
<textarea class="form-control tinymce" rows="6" name="details"
placeholder="Enter Contract Details"><?= $details ?></textarea>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" name="edit_contract_template" 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>
</div>
</form>
<?php
require_once '../../../includes/modal_footer.php';
?>

View File

@ -22,7 +22,7 @@ ob_start();
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token'] ?>">
<input type="hidden" name="payment_method_id" value="<?= $payment_method_id ?>">
<div class="modal-body">
<div class="form-group">

View File

@ -15,6 +15,8 @@ if (isset($_GET['type'])) {
$type_display = "Contact";
} elseif ($type === 4) {
$type_display = "Credential";
} elseif ($type === 5) {
$type_display = "Asset";
}
}
ob_start();
@ -58,6 +60,7 @@ ob_start();
<option value="2">Location Tag</option>
<option value="3">Contact Tag</option>
<option value="4">Credential Tag</option>
<option value="5">Asset Tag</option>
</select>
</div>
</div>
@ -86,7 +89,7 @@ ob_start();
</div>
<div class="modal-footer">
<button type="submit" name="add_tag" class="btn btn-primary text-bold"><i class="fas fa-check mr-2"></i>Create</button>
<button type="submit" name="add_tag" class="btn btn-primary text-bold"><i class="fas fa-check mr-2"></i>Create Tag</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fas fa-times mr-2"></i>Cancel</button>
</div>
</form>

View File

@ -12,11 +12,24 @@ $tag_type = intval($row['tag_type']);
$tag_color = nullable_htmlentities($row['tag_color']);
$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();
?>
<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">
<span>&times;</span>
</button>
@ -35,22 +48,6 @@ ob_start();
</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>
</select>
</div>
</div>
<div class="form-group">
<label>Color <strong class="text-danger">*</strong></label>
<div class="input-group">
@ -73,7 +70,7 @@ ob_start();
</div>
<div class="modal-footer">
<button type="submit" name="edit_tag" class="btn btn-primary text-bold"><i class="fas fa-check mr-2"></i>Save</button>
<button type="submit" name="edit_tag" class="btn btn-primary text-bold"><i class="fas fa-check mr-2"></i>Save changes</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fas fa-times mr-2"></i>Cancel</button>
</div>
</form>

View File

@ -1,75 +1,79 @@
<div class="modal" id="addTicketTemplateModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-life-ring mr-2"></i>Creating Ticket Template</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
<?php
require_once '../../../includes/modal_header.php';
ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-life-ring mr-2"></i>New Ticket Template</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<form action="post.php" method="post" autocomplete="off">
<div class="modal-body">
<div class="form-group">
<label>Template Name <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-life-ring"></i></span>
</div>
<input type="text" class="form-control" name="name" placeholder="Template name" maxlength="200" required autofocus>
</div>
<form action="post.php" method="post" autocomplete="off">
<div class="modal-body">
</div>
<div class="form-group">
<label>Template Name <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-life-ring"></i></span>
</div>
<input type="text" class="form-control" name="name" placeholder="Template name" maxlength="200" required autofocus>
</div>
</div>
<div class="form-group">
<label>Subject</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-angle-right"></i></span>
</div>
<input type="text" class="form-control" name="subject" placeholder="Subject" maxlength="500">
</div>
</div>
<div class="form-group">
<textarea class="form-control tinymceTicket" name="details"></textarea>
</div>
<div class="form-group">
<label>Description</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-angle-right"></i></span>
</div>
<input type="text" class="form-control" name="description" placeholder="Short description">
</div>
</div>
<div class="form-group">
<label>Add it to a Project Template?</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-project-diagram"></i></span>
</div>
<select class="form-control select2" name="project_template">
<option value="0">- No -</option>
<?php
$sql_project_templates = mysqli_query($mysqli, "SELECT * FROM project_templates WHERE project_template_archived_at IS NULL ORDER BY project_template_name ASC");
while ($row = mysqli_fetch_array($sql_project_templates)) {
$project_template_id_select = intval($row['project_template_id']);
$project_template_name_select = nullable_htmlentities($row['project_template_name']); ?>
<option value="<?php echo $project_template_id_select; ?>"><?php echo $project_template_name_select; ?></option>
<?php } ?>
</select>
</div>
</div>
<div class="form-group">
<label>Subject</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-angle-right"></i></span>
</div>
<div class="modal-footer">
<button type="submit" name="add_ticket_template" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Create</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
<input type="text" class="form-control" name="subject" placeholder="Subject" maxlength="500">
</div>
</div>
<div class="form-group">
<textarea class="form-control tinymceTicket" name="details"></textarea>
</div>
<div class="form-group">
<label>Description</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-angle-right"></i></span>
</div>
</form>
<input type="text" class="form-control" name="description" placeholder="Short description">
</div>
</div>
<div class="form-group">
<label>Add it to a Project Template?</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-project-diagram"></i></span>
</div>
<select class="form-control select2" name="project_template">
<option value="0">- No -</option>
<?php
$sql_project_templates = mysqli_query($mysqli, "SELECT * FROM project_templates WHERE project_template_archived_at IS NULL ORDER BY project_template_name ASC");
while ($row = mysqli_fetch_array($sql_project_templates)) {
$project_template_id_select = intval($row['project_template_id']);
$project_template_name_select = nullable_htmlentities($row['project_template_name']); ?>
<option value="<?php echo $project_template_id_select; ?>"><?php echo $project_template_name_select; ?></option>
<?php } ?>
</select>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" name="add_ticket_template" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Create Template</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
</div>
</form>
<?php
require_once '../../../includes/modal_footer.php';

View File

@ -106,12 +106,14 @@ $num_rows = mysqli_num_rows($sql);
<i class="fas fa-fw fa-edit mr-2"></i>Edit
</a>
<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'] ?><!--">-->
<!-- <i class="fas fa-fw fa-thumbs-down mr-2"></i>Disable-->
<!-- </a>-->
<!-- <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'] ?><!--">-->
<!-- <i class="fas fa-fw fa-trash mr-2"></i>Delete-->
<!-- </a>-->
<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-trash mr-2"></i><strong>Delete Provider and</strong>
<ul class="text-xs">
<li>Related Recurring Payments</li>
<li>Related Saved cards</li>
<li>Client Provider Relations</li>
</ul>
</a>
</div>
</div>
</td>

View File

@ -9,11 +9,12 @@ defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
if (isset($_POST['add_contract_template'])) {
// Sanitize text inputs
$name = sanitizeInput($_POST['contract_template_name']);
$type = sanitizeInput($_POST['contract_template_type']);
$update_frequency = sanitizeInput($_POST['contract_template_update_frequency']);
$support_hours = sanitizeInput($_POST['contract_template_support_hours']);
$details = mysql_escape_string($mysqli, $_POST['contract_template_details']);
$name = sanitizeInput($_POST['name']);
$description = sanitizeInput($_POST['description']);
$type = sanitizeInput($_POST['type']);
$renewal_frequency = sanitizeInput($_POST['renewal_frequency']);
$support_hours = sanitizeInput($_POST['support_hours']);
$details = mysqli_escape_string($mysqli, $_POST['details']);
// Numeric fields cast to integer
$sla_low_resp = intval($_POST['sla_low_response_time']);
@ -22,9 +23,9 @@ if (isset($_POST['add_contract_template'])) {
$sla_low_res = intval($_POST['sla_low_resolution_time']);
$sla_med_res = intval($_POST['sla_medium_resolution_time']);
$sla_high_res = intval($_POST['sla_high_resolution_time']);
$hourly_rate = intval($_POST['contract_template_hourly_rate']);
$after_hours_rate = intval($_POST['contract_template_after_hours_hourly_rate']);
$net_terms = intval($_POST['contract_template_net_terms']);
$rate_standard = intval($_POST['rate_standard']);
$rate_after_hours = intval($_POST['hourly_rate_after_hours']);
$net_terms = intval($_POST['net_terms']);
// Insert into database (numbers not quoted)
mysqli_query($mysqli, "
@ -33,19 +34,17 @@ if (isset($_POST['add_contract_template'])) {
contract_template_description = '$description',
contract_template_details = '$details',
contract_template_type = '$type',
contract_template_update_frequency = '$update_frequency',
sla_low_response_time = $sla_low_resp,
sla_medium_response_time = $sla_med_resp,
sla_high_response_time = $sla_high_resp,
sla_low_resolution_time = $sla_low_res,
sla_medium_resolution_time = $sla_med_res,
sla_high_resolution_time = $sla_high_res,
contract_template_hourly_rate = $hourly_rate,
contract_template_after_hours_hourly_rate = $after_hours_rate,
contract_template_renewal_frequency = '$renewal_frequency',
contract_template_sla_low_response_time = $sla_low_resp,
contract_template_sla_medium_response_time = $sla_med_resp,
contract_template_sla_high_response_time = $sla_high_resp,
contract_template_sla_low_resolution_time = $sla_low_res,
contract_template_sla_medium_resolution_time = $sla_med_res,
contract_template_sla_high_resolution_time = $sla_high_res,
contract_template_rate_standard = $rate_standard,
contract_template_rate_after_hours = $rate_after_hours,
contract_template_support_hours = '$support_hours',
contract_template_net_terms = $net_terms,
contract_template_created_by = $session_user_id,
contract_template_created_at = NOW()
contract_template_net_terms = $net_terms
");
$contract_template_id = mysqli_insert_id($mysqli);
@ -60,4 +59,99 @@ if (isset($_POST['add_contract_template'])) {
redirect();
}
if (isset($_POST['edit_contract_template'])) {
$contract_template_id = intval($_POST['contract_template_id']);
$name = sanitizeInput($_POST['name']);
$description = sanitizeInput($_POST['description']);
$type = sanitizeInput($_POST['type']);
$renewal_frequency= sanitizeInput($_POST['renewal_frequency']);
$support_hours = sanitizeInput($_POST['support_hours']);
$details = mysqli_escape_string($mysqli, $_POST['details']);
$sla_low_resp = intval($_POST['sla_low_response_time']);
$sla_med_resp = intval($_POST['sla_medium_response_time']);
$sla_high_resp = intval($_POST['sla_high_response_time']);
$sla_low_res = intval($_POST['sla_low_resolution_time']);
$sla_med_res = intval($_POST['sla_medium_resolution_time']);
$sla_high_res = intval($_POST['sla_high_resolution_time']);
$rate_standard = intval($_POST['rate_standard']);
$rate_after_hours = intval($_POST['rate_after_hours']);
$net_terms = intval($_POST['net_terms']);
mysqli_query($mysqli, "
UPDATE contract_templates SET
contract_template_name = '$name',
contract_template_description = '$description',
contract_template_details = '$details',
contract_template_type = '$type',
contract_template_renewal_frequency = '$renewal_frequency',
contract_template_sla_low_response_time = $sla_low_resp,
contract_template_sla_medium_response_time = $sla_med_resp,
contract_template_sla_high_response_time = $sla_high_resp,
contract_template_sla_low_resolution_time = $sla_low_res,
contract_template_sla_medium_resolution_time = $sla_med_res,
contract_template_sla_high_resolution_time = $sla_high_res,
contract_template_rate_standard = $rate_standard,
contract_template_rate_after_hours = $rate_after_hours,
contract_template_support_hours = '$support_hours',
contract_template_net_terms = $net_terms
WHERE contract_template_id = $contract_template_id
");
// Log action
logAction("Contract Template", "Update", "$session_name updated contract template $name", 0, $contract_template_id);
// Flash + redirect
flash_alert("Contract Template <strong>$name</strong> updated");
redirect();
}
if (isset($_GET['archive_contract_template'])) {
$contract_template_id = intval($_GET['archive_contract_template']);
$name = getFieldById('contract_templates', $contract_template_id, 'contract_template_name');
mysqli_query($mysqli, "
UPDATE contract_templates SET contract_template_archived_at = NOW()
WHERE contract_template_id = $contract_template_id
LIMIT 1
");
logAction("Contract Template", "Archive", "$session_name archived contract template $name", 0, $contract_template_id);
flash_alert("Contract Template <strong>$name</strong> archived", "danger");
redirect();
}
if (isset($_GET['restore_contract_template'])) {
$contract_template_id = intval($_GET['restore_contract_template']);
$name = getFieldById('contract_templates', $contract_template_id, 'contract_template_name');
mysqli_query($mysqli, "
UPDATE contract_templates SET contract_template_archived_at = NULL
WHERE contract_template_id = $contract_template_id
LIMIT 1
");
logAction("Contract Template", "Restore", "$session_name restored contract template $name", 0, $contract_template_id);
flash_alert("Contract Template <strong>$name</strong> restored");
redirect();
}
if (isset($_GET['delete_contract_template'])) {
$contract_template_id = intval($_GET['delete_contract_template']);
$name = getFieldById('contract_templates', $contract_template_id, 'contract_template_name');
mysqli_query($mysqli, "
DELETE FROM contract_templates
WHERE contract_template_id = $contract_template_id
LIMIT 1
");
logAction("Contract Template", "Delete", "$session_name deleted contract template $name", 0, $contract_template_id);
flash_alert("Contract Template <strong>$name</strong> deleted", "danger");
redirect();
}
?>

View File

@ -101,6 +101,11 @@ if (isset($_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'));
// Delete provider

View File

@ -42,7 +42,7 @@ if (isset($_GET['delete_saved_payment'])) {
try {
// Initialize stripe
require_once 'plugins/stripe-php/init.php';
require_once '../plugins/stripe-php/init.php';
$stripe = new \Stripe\StripeClient($private_key);
// 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");
// SQL Cascade delete will Remove All Associated Auto Payment Methods on recurring invoices in the recurring payments table.

View File

@ -150,7 +150,7 @@ if (isset($_POST['test_email_smtp'])) {
$mail = addToMailQueue($data);
if ($mail === true) {
flash_alert("Test email queued! <a class='text-bold text-light' href='admin_mail_queue.php'>Check Admin > Mail queue</a>");
flash_alert("Test email queued! <a class='text-bold text-light' href='mail_queue.php'>Check Admin > Mail queue</a>");
} else {
flash_alert("Failed to add test mail to queue", 'error');
}
@ -160,27 +160,128 @@ if (isset($_POST['test_email_smtp'])) {
}
if (isset($_POST['test_email_imap'])) {
validateCSRFToken($_POST['csrf_token']);
// Setup your IMAP connection parameters
$hostname = "{" . $config_imap_host . ":" . $config_imap_port . "/" . $config_imap_encryption . "/novalidate-cert}INBOX";
$username = $config_imap_username;
$password = $config_imap_password;
$host = $config_imap_host;
$port = (int) $config_imap_port;
$encryption = strtolower(trim($config_imap_encryption)); // e.g. "ssl", "tls", "none"
$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 {
$inbox = @imap_open($hostname, $username, $password);
$errno = 0;
$errstr = '';
if ($inbox) {
imap_close($inbox);
// 10-second timeout, adjust as needed
$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");
} else {
throw new Exception(imap_last_error());
if (!$errorLine) {
$errorLine = 'Unknown IMAP authentication error';
}
throw new Exception($errorLine);
}
} 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();
}

View File

@ -28,7 +28,7 @@ if (isset($_POST['edit_tag'])) {
$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);

View File

@ -6,10 +6,31 @@ $order = "ASC";
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(
$mysqli,
"SELECT SQL_CALC_FOUND_ROWS * FROM tags
WHERE tag_name LIKE '%$q%'
AND tag_type = $type_filter
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-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">
<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>
@ -30,7 +51,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<div class="col-sm-4 mb-2">
<form autocomplete="off">
<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">
<button class="btn btn-primary"><i class="fa fa-search"></i></button>
</div>
@ -38,6 +59,45 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</form>
</div>
<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>
@ -51,11 +111,6 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
Name <?php if ($sort == 'tag_name') { echo $order_icon; } ?>
</a>
</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>
</tr>
</thead>
@ -65,18 +120,6 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
while ($row = mysqli_fetch_array($sql)) {
$tag_id = intval($row['tag_id']);
$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";
} else {
$tag_type_display = "Unknown Tag";
}
$tag_color = nullable_htmlentities($row['tag_color']);
$tag_icon = nullable_htmlentities($row['tag_icon']);
@ -88,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>
</a>
</td>
<td><?php echo $tag_type_display; ?></td>
<td>
<div class="dropdown dropleft text-center">
<button class="btn btn-secondary btn-sm" type="button" data-toggle="dropdown">
@ -117,8 +159,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</tbody>
</table>
</div>
<?php require_once "../includes/filter_footer.php";
?>
<?php require_once "../includes/filter_footer.php"; ?>
</div>
</div>

View File

@ -27,7 +27,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<div class="card-header py-2">
<h3 class="card-title mt-2"><i class="fas fa-fw fa-life-ring mr-2"></i>Ticket Templates</h3>
<div class="card-tools">
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#addTicketTemplateModal"><i class="fas fa-plus mr-2"></i>New Ticket Template</button>
<button type="button" class="btn btn-primary ajax-modal" data-modal-url="modals/ticket_template/ticket_template_add.php" data-modal-size="lg"><i class="fas fa-plus mr-2"></i>New Ticket Template</button>
</div>
</div>
<div class="card-body">
@ -120,5 +120,4 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</div>
<?php
require_once "modals/ticket_template/ticket_template_add.php";
require_once "../includes/footer.php";

View File

@ -156,7 +156,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
?>
<tr>
<td class="text-center">
<a href="#"
<a href="#" title="UserID: <?= $user_id ?>"
<?php if ($user_id !== $session_user_id) { // Prevent modifying self ?>
class="ajax-modal"
data-modal-url="modals/user/user_edit.php?id=<?= $user_id ?>"

View File

@ -140,7 +140,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</a>
<?php if ($session_user_role == 3) { ?>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger text-bold confirm-link" href="post.php?delete_vendor=<?php echo $vendor_template_id; ?>">
<a class="dropdown-item text-danger text-bold confirm-link" href="post.php?delete_vendor_template=<?= $vendor_template_id ?>">
<i class="fas fa-fw fa-trash mr-2"></i>Delete
</a>
<?php } ?>

View File

@ -118,6 +118,28 @@ if (isset($_GET['asset_id'])) {
);
$document_count = mysqli_num_rows($sql_related_documents);
// Tags - many to many relationship
$asset_tag_name_display_array = array();
$asset_tag_id_array = array();
$sql_asset_tags = mysqli_query($mysqli, "SELECT * FROM asset_tags LEFT JOIN tags ON asset_tag_tag_id = tag_id WHERE asset_tag_asset_id = $asset_id ORDER BY tag_name ASC");
while ($row = mysqli_fetch_array($sql_asset_tags)) {
$asset_tag_id = intval($row['tag_id']);
$asset_tag_name = nullable_htmlentities($row['tag_name']);
$asset_tag_color = nullable_htmlentities($row['tag_color']);
if (empty($asset_tag_color)) {
$asset_tag_color = "dark";
}
$asset_tag_icon = nullable_htmlentities($row['tag_icon']);
if (empty($asset_tag_icon)) {
$asset_tag_icon = "tag";
}
$asset_tag_id_array[] = $asset_tag_id;
$asset_tag_name_display_array[] = "<a href='client_assets.php?client_id=$client_id&q=$asset_tag_name'><span class='badge text-light p-1 mr-1' style='background-color: $asset_tag_color;'><i class='fa fa-fw fa-$asset_tag_icon mr-2'></i>$asset_tag_name</span></a>";
}
$asset_tags_display = implode('', $asset_tag_name_display_array);
// Network Interfaces
$sql_related_interfaces = mysqli_query($mysqli, "
SELECT
@ -257,8 +279,13 @@ if (isset($_GET['asset_id'])) {
<?php } ?>
</div>
<div class="card-body">
<?php if ($asset_tags_display) { ?>
<div>
<?= $asset_tags_display ?>
</div>
<?php } ?>
<?php if ($asset_type) { ?>
<div><i class="fa fa-fw fa-tag text-secondary mr-3"></i><?= $asset_type; ?></div>
<div class="mt-1"><i class="fa fa-fw fa-tag text-secondary mr-3"></i><?= $asset_type; ?></div>
<?php }
if ($asset_make) { ?>
<div class="mt-2"><i class="fa fa-fw fa-circle text-secondary mr-3"></i><?= "$asset_make $asset_model"; ?></div>

View File

@ -78,6 +78,18 @@ if ($client_url && isset($_GET['location']) && !empty($_GET['location'])) {
$location_filter = 0;
}
// Tags Filter
if (isset($_GET['tags']) && is_array($_GET['tags']) && !empty($_GET['tags'])) {
// Sanitize each element of the tags array
$sanitizedTags = array_map('intval', $_GET['tags']);
// Convert the sanitized tags into a comma-separated string
$tag_filter = implode(",", $sanitizedTags);
$tag_query = "AND tag_id IN ($tag_filter)";
} else {
$tag_filter = 0;
$tag_query = '';
}
//Get Asset Counts
$row = mysqli_fetch_assoc(mysqli_query($mysqli, "
SELECT
@ -93,9 +105,13 @@ $row = mysqli_fetch_assoc(mysqli_query($mysqli, "
LEFT JOIN contacts ON asset_contact_id = contact_id
LEFT JOIN locations ON asset_location_id = location_id
LEFT JOIN asset_interfaces ON interface_asset_id = asset_id AND interface_primary = 1
LEFT JOIN asset_tags ON asset_tag_asset_id = asset_id
LEFT JOIN tags ON tag_id = asset_tag_tag_id
WHERE $archive_query
$tag_query
$access_permission_query
$client_query
GROUP BY asset_id
) AS filtered_assets;
"));
@ -127,13 +143,16 @@ $sql = mysqli_query(
LEFT JOIN contacts ON asset_contact_id = contact_id
LEFT JOIN locations ON asset_location_id = location_id
LEFT JOIN asset_interfaces ON interface_asset_id = asset_id AND interface_primary = 1
LEFT JOIN asset_tags ON asset_tag_asset_id = asset_id
LEFT JOIN tags ON tag_id = asset_tag_tag_id
WHERE $archive_query
AND (asset_name LIKE '%$q%' OR asset_description LIKE '%$q%' OR asset_type LIKE '%$q%' OR interface_ip LIKE '%$q%' OR interface_ipv6 LIKE '%$q%' OR interface_mac LIKE '%$q%' OR asset_make LIKE '%$q%' OR asset_model LIKE '%$q%' OR asset_serial LIKE '%$q%' OR asset_os LIKE '%$q%' OR contact_name LIKE '%$q%' OR location_name LIKE '%$q%' OR client_name LIKE '%$q%')
$tag_query
AND (asset_name LIKE '%$q%' OR asset_description LIKE '%$q%' OR asset_type LIKE '%$q%' OR interface_ip LIKE '%$q%' OR interface_ipv6 LIKE '%$q%' OR interface_mac LIKE '%$q%' OR asset_make LIKE '%$q%' OR asset_model LIKE '%$q%' OR asset_serial LIKE '%$q%' OR asset_os LIKE '%$q%' OR contact_name LIKE '%$q%' OR location_name LIKE '%$q%' OR client_name LIKE '%$q%' OR tag_name LIKE '%$q%')
AND ($type_query)
$access_permission_query
$location_query
$client_query
GROUP BY asset_id
ORDER BY $sort $order LIMIT $record_from, $record_to"
);
@ -270,7 +289,32 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</div>
</div>
<?php } ?>
<div class="col-md-3">
<div class="col-md-2">
<div class="input-group mb-3 mb-md-0">
<select onchange="this.form.submit()" class="form-control select2" name="tags[]" data-placeholder="- Select Tags -" multiple>
<?php
$sql_tags_filter = mysqli_query($mysqli, "
SELECT tag_id, tag_name
FROM tags
LEFT JOIN asset_tags ON asset_tag_tag_id = tag_id
LEFT JOIN assets ON asset_tag_asset_id = asset_id
WHERE tag_type = 5
$client_query OR tag_id IN ($tag_filter)
GROUP BY tag_id
HAVING COUNT(asset_tag_asset_id) > 0 OR tag_id IN ($tag_filter)
");
while ($row = mysqli_fetch_array($sql_tags_filter)) {
$tag_id = intval($row['tag_id']);
$tag_name = nullable_htmlentities($row['tag_name']); ?>
<option value="<?php echo $tag_id ?>" <?php if (isset($_GET['tags']) && in_array($tag_id, $_GET['tags'])) { echo 'selected'; } ?>> <?php echo $tag_name ?> </option>
<?php } ?>
</select>
</div>
</div>
<div class="col-md-2">
<div class="form-group">
<select onchange="this.form.submit()" class="form-control select2" name="show_column[]" data-placeholder="- Show Additional Columns -" multiple>
<option
@ -291,7 +335,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</select>
</div>
</div>
<div class="col-md-3">
<div class="col-md-2">
<div class="btn-group float-right">
<a href="?<?php echo $client_url; ?>&archived=<?php if($archived == 1){ echo 0; } else { echo 1; } ?>"
class="btn btn-<?php if($archived == 1){ echo "primary"; } else { echo "default"; } ?>">
@ -314,7 +358,14 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
data-bulk="true">
<i class="fas fa-fw fa-map-marker-alt mr-2"></i>Assign Location
</a>
<div class="dropdown-divider"></div>
<?php } ?>
<a class="dropdown-item ajax-modal" href="#"
data-modal-url="modals/asset/asset_bulk_assign_tags.php"
data-bulk="true">
<i class="fas fa-fw fa-tags mr-2"></i>Assign Tags
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item ajax-modal" href="#"
data-modal-url="modals/asset/asset_bulk_assign_physical_location.php"
data-bulk="true">
@ -576,20 +627,48 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
$sql_credentials = mysqli_query($mysqli, "SELECT * FROM credentials WHERE credential_asset_id = $asset_id");
$credential_count = mysqli_num_rows($sql_credentials);
// Tags
$asset_tag_name_display_array = array();
$asset_tag_id_array = array();
$sql_asset_tags = mysqli_query($mysqli, "SELECT * FROM asset_tags LEFT JOIN tags ON asset_tag_tag_id = tag_id WHERE asset_tag_asset_id = $asset_id ORDER BY tag_name ASC");
while ($row = mysqli_fetch_array($sql_asset_tags)) {
$asset_tag_id = intval($row['tag_id']);
$asset_tag_name = nullable_htmlentities($row['tag_name']);
$asset_tag_color = nullable_htmlentities($row['tag_color']);
if (empty($asset_tag_color)) {
$asset_tag_color = "dark";
}
$asset_tag_icon = nullable_htmlentities($row['tag_icon']);
if (empty($asset_tag_icon)) {
$asset_tag_icon = "tag";
}
$asset_tag_id_array[] = $asset_tag_id;
$asset_tag_name_display_array[] = "<a href='assets.php?$client_url tags[]=$asset_tag_id'><span class='badge text-light p-1 mr-1' style='background-color: $asset_tag_color;'><i class='fa fa-fw fa-$asset_tag_icon mr-2'></i>$asset_tag_name</span></a>";
}
$asset_tags_display = implode('', $asset_tag_name_display_array);
?>
<tr>
<td class="pr-0 bg-light">
<div class="form-check">
<input class="form-check-input bulk-select" type="checkbox" name="asset_ids[]" value="<?php echo $asset_id ?>">
<input class="form-check-input bulk-select" type="checkbox" name="asset_ids[]" value="<?= $asset_id ?>">
</div>
</td>
<td>
<a class="text-dark" href="asset_details.php?client_id=<?php echo $client_id; ?>&asset_id=<?php echo $asset_id; ?>">
<a class="text-dark" href="asset_details.php?client_id=<?= $client_id ?>&asset_id=<?= $asset_id ?>">
<div class="media">
<i class="fa fa-fw fa-2x fa-<?php echo $device_icon; ?> mr-3 mt-1"></i>
<i class="fa fa-fw fa-2x fa-<?= $device_icon ?> mr-3 mt-1"></i>
<div class="media-body">
<div><?php echo $asset_name; ?></div>
<div><small class="text-secondary"><?php echo $asset_description; ?></small></div>
<div><?= $asset_name ?></div>
<div><small class="text-secondary"><?= $asset_description ?></small></div>
<?php
if ($asset_tags_display) { ?>
<div class="mt-1">
<?= $asset_tags_display ?>
</div>
<?php } ?>
</div>
</div>
</a>

View File

@ -64,7 +64,14 @@ if (isset($_GET['contact_id'])) {
}
// Related Assets Query - 1 to 1 relationship
$sql_related_assets = mysqli_query($mysqli, "SELECT * FROM assets LEFT JOIN asset_interfaces ON interface_asset_id = asset_id AND interface_primary = 1 WHERE asset_contact_id = $contact_id ORDER BY asset_name DESC");
$sql_related_assets = mysqli_query($mysqli, "SELECT * FROM assets
LEFT JOIN asset_interfaces ON interface_asset_id = asset_id AND interface_primary = 1
LEFT JOIN asset_tags ON asset_tag_asset_id = asset_id
LEFT JOIN tags ON tag_id = asset_tag_tag_id
WHERE asset_contact_id = $contact_id
GROUP BY asset_id
ORDER BY asset_name ASC"
);
$asset_count = mysqli_num_rows($sql_related_assets);
// Linked Software Licenses
@ -90,7 +97,7 @@ if (isset($_GET['contact_id'])) {
LEFT JOIN tags ON tags.tag_id = credential_tags.tag_id
WHERE credential_contact_id = $contact_id
GROUP BY credentials.credential_id
ORDER BY credential_name DESC
ORDER BY credential_name ASC
");
$credential_count = mysqli_num_rows($sql_related_credentials);
@ -401,6 +408,28 @@ if (isset($_GET['contact_id'])) {
$asset_created_at = nullable_htmlentities($row['asset_created_at']);
$device_icon = getAssetIcon($asset_type);
// Tags
$asset_tag_name_display_array = array();
$asset_tag_id_array = array();
$sql_asset_tags = mysqli_query($mysqli, "SELECT * FROM asset_tags LEFT JOIN tags ON asset_tag_tag_id = tag_id WHERE asset_tag_asset_id = $asset_id ORDER BY tag_name ASC");
while ($row = mysqli_fetch_array($sql_asset_tags)) {
$asset_tag_id = intval($row['tag_id']);
$asset_tag_name = nullable_htmlentities($row['tag_name']);
$asset_tag_color = nullable_htmlentities($row['tag_color']);
if (empty($asset_tag_color)) {
$asset_tag_color = "dark";
}
$asset_tag_icon = nullable_htmlentities($row['tag_icon']);
if (empty($asset_tag_icon)) {
$asset_tag_icon = "tag";
}
$asset_tag_id_array[] = $asset_tag_id;
$asset_tag_name_display_array[] = "<a href='assets.php?$client_url tags[]=$asset_tag_id'><span class='badge text-light p-1 mr-1' style='background-color: $asset_tag_color;'><i class='fa fa-fw fa-$asset_tag_icon mr-2'></i>$asset_tag_name</span></a>";
}
$asset_tags_display = implode('', $asset_tag_name_display_array);
?>
<tr>
<th>
@ -413,6 +442,12 @@ if (isset($_GET['contact_id'])) {
<div class="mt-0">
<small class="text-muted"><?php echo $asset_description; ?></small>
</div>
<?php
if ($asset_tags_display) { ?>
<div class="mt-1">
<?= $asset_tags_display ?>
</div>
<?php } ?>
</th>
<td><?php echo $asset_type; ?></td>
<td>

View File

@ -729,13 +729,22 @@ if ($user_config_dashboard_technical_enable == 1) {
$client_name = nullable_htmlentities($row['client_name']);
$contact_id = intval($row['ticket_contact_id']);
$contact_name = nullable_htmlentities($row['contact_name']);
if ($client_id) {
$has_client = "&client_id=$client_id";
} else {
$has_client = "";
}
$ticket_priority_color = $ticket_priority == "High" ? "danger" : ($ticket_priority == "Medium" ? "warning" : "info");
$contact_display = empty($contact_name) ? "-" : "<a href='contact_details.php?client_id=$client_id&contact_id=$contact_id'>$contact_name</a>";
?>
<tr class="<?php echo empty($ticket_updated_at) ? 'text-bold' : ''; ?>">
<td><a class="text-dark" href="ticket.php?client_id=<?= $client_id ?>&ticket_id=<?php echo $ticket_id; ?>"><?php echo "$ticket_prefix$ticket_number"; ?></a></td>
<td><a href="ticket.php?client_id=<?= $client_id ?>&ticket_id=<?php echo $ticket_id; ?>"><?php echo $ticket_subject; ?></a></td>
<td>
<a class="text-dark"
href="ticket.php?ticket_id=<?= "$ticket_id$has_client" ?>"><?= "$ticket_prefix$ticket_number" ?>
</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><?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>

View File

@ -53,6 +53,7 @@ $num_software = $row['num'];
<i class="nav-icon fas fa-address-book"></i>
<p>
Contacts
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/contact/contact_add.php"></span>
<?php
if ($num_contacts > 0) { ?>
<span class="right badge text-light"><?php echo $num_contacts; ?></span>
@ -65,6 +66,7 @@ $num_software = $row['num'];
<i class="nav-icon fas fa-map-marker-alt"></i>
<p>
Locations
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/location/location_add.php"></span>
<?php
if ($num_locations > 0) { ?>
<span class="right badge text-light"><?php echo $num_locations; ?></span>
@ -77,6 +79,7 @@ $num_software = $row['num'];
<i class="nav-icon fas fa-desktop"></i>
<p>
Assets
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/asset/asset_add.php"></span>
<?php
if ($num_assets > 0) { ?>
<span class="right badge text-light"><?php echo $num_assets; ?></span>
@ -89,6 +92,7 @@ $num_software = $row['num'];
<i class="nav-icon fas fa-cube"></i>
<p>
Licenses
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/software/software_add.php"></span>
<?php
if ($num_software > 0) { ?>
<span class="right badge text-light"><?php echo $num_software; ?></span>
@ -101,6 +105,7 @@ $num_software = $row['num'];
<i class="nav-icon fas fa-key"></i>
<p>
Credentials
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/credential/credential_add.php"></span>
<?php
if ($num_credentials > 0) { ?>
<span class="right badge text-light"><?php echo $num_credentials; ?></span>
@ -113,6 +118,7 @@ $num_software = $row['num'];
<i class="nav-icon fas fa-network-wired"></i>
<p>
Networks
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/network/network_add.php"></span>
<?php
if ($num_networks > 0) { ?>
<span class="right badge text-light"><?php echo $num_networks; ?></span>
@ -125,6 +131,7 @@ $num_software = $row['num'];
<i class="nav-icon fas fa-lock"></i>
<p>
Certificates
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/certificate/certificate_add.php"></span>
<?php
if ($num_certificates > 0) { ?>
<span class="right badge text-light"><?php echo $num_certificates; ?></span>
@ -137,6 +144,7 @@ $num_software = $row['num'];
<i class="nav-icon fas fa-globe"></i>
<p>
Domains
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/domain/domain_add.php"></span>
<?php
if ($num_domains > 0) { ?>
<span class="right badge text-light"><?php echo $num_domains; ?></span>
@ -149,6 +157,7 @@ $num_software = $row['num'];
<i class="nav-icon fas fa-stream"></i>
<p>
Services
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/service/service_add.php"></span>
<?php
if ($num_services > 0) { ?>
<span class="right badge text-light"><?php echo $num_services; ?></span>

View File

@ -30,6 +30,7 @@
<i class="nav-icon fas fa-address-book"></i>
<p>
Contacts
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/contact/contact_add.php?client_id=<?= $client_id ?>"></span>
<?php
if ($num_contacts > 0) { ?>
<span class="right badge text-light"><?php echo $num_contacts; ?></span>
@ -43,6 +44,7 @@
<i class="nav-icon fas fa-map-marker-alt"></i>
<p>
Locations
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/location/location_add.php?client_id=<?= $client_id ?>"></span>
<?php
if ($num_locations > 0) { ?>
<span class="right badge text-light"><?php echo $num_locations; ?></span>
@ -59,6 +61,7 @@
<i class="nav-icon fas fa-life-ring"></i>
<p>
Tickets
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/ticket/ticket_add_v2.php?client_id=<?= $client_id ?>" data-modal-size="lg"></span>
<?php
if ($num_active_tickets > 0) { ?>
<span class="right badge <?php if ($num_active_tickets > 0) { ?> badge-danger <?php } ?> text-light"><?php echo $num_active_tickets; ?></span>
@ -73,6 +76,7 @@
<i class="nav-icon fas fa-redo-alt"></i>
<p>
Recurring Tickets
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/recurring_ticket/recurring_ticket_add.php?client_id=<?= $client_id ?>" data-modal-size="lg"></span>
<?php
if ($num_recurring_tickets) { ?>
<span class="right badge"><?php echo $num_recurring_tickets; ?></span>
@ -87,6 +91,7 @@
<i class="nav-icon fas fa-project-diagram"></i>
<p>
Projects
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/project/project_add.php?client_id=<?= $client_id ?>"></span>
<?php if ($num_active_projects) { ?>
<span class="right badge text-light" data-toggle="tooltip" title="Open Projects"><?php echo $num_active_projects; ?></span>
<?php } ?>
@ -101,6 +106,7 @@
<i class="nav-icon fas fa-building"></i>
<p>
Vendors
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/vendor/vendor_add.php?client_id=<?= $client_id ?>"></span>
<?php
if ($num_vendors > 0) { ?>
<span class="right badge text-light"><?php echo $num_vendors; ?></span>
@ -132,6 +138,7 @@
<i class="nav-icon fas fa-desktop"></i>
<p>
Assets
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/asset/asset_add.php?client_id=<?= $client_id ?>"></span>
<?php
if ($num_assets > 0) { ?>
<span class="right badge text-light"><?php echo $num_assets; ?></span>
@ -145,6 +152,7 @@
<i class="nav-icon fas fa-cube"></i>
<p>
Licenses
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/software/software_add.php?client_id=<?= $client_id ?>"></span>
<?php
if ($num_software > 0) { ?>
<span class="right badge <?php if ($num_software_expiring > 0) { ?> badge-warning text-dark <?php } ?> <?php if ($num_software_expired > 0) { ?> badge-danger <?php } ?> text-white"><?php echo $num_software; ?></span>
@ -159,6 +167,7 @@
<i class="nav-icon fas fa-key"></i>
<p>
Credentials
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/credential/credential_add.php?client_id=<?= $client_id ?>"></span>
<?php
if ($num_credentials > 0) { ?>
<span class="right badge text-light"><?php echo $num_credentials; ?></span>
@ -173,6 +182,7 @@
<i class="nav-icon fas fa-network-wired"></i>
<p>
Networks
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/network/network_add.php?client_id=<?= $client_id ?>"></span>
<?php
if ($num_networks > 0) { ?>
<span class="right badge text-light"><?php echo $num_networks; ?></span>
@ -186,6 +196,7 @@
<i class="nav-icon fas fa-server"></i>
<p>
Racks
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/rack/rack_add.php?client_id=<?= $client_id ?>"></span>
<?php
if ($num_racks > 0) { ?>
<span class="right badge text-light"><?php echo $num_racks; ?></span>
@ -199,7 +210,7 @@
<i class="nav-icon fas fa-lock"></i>
<p>
Certificates
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/certificate/certificate_add.php?client_id=<?= $client_id ?>"></span>
<?php
if ($num_certificates > 0) { ?>
<span class="right badge <?php if ($num_certificates_expiring > 0) { ?> badge-warning text-dark <?php } ?> <?php if ($num_certificates_expired > 0) { ?> badge-danger <?php } ?> text-white"><?php echo $num_certificates; ?></span>
@ -213,7 +224,7 @@
<i class="nav-icon fas fa-globe"></i>
<p>
Domains
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/domain/domain_add.php?client_id=<?= $client_id ?>"></span>
<?php
if ($num_domains > 0) { ?>
<span class="right badge <?php if (isset($num_domains_expiring)) { ?> badge-warning text-dark<?php } ?> <?php if (isset($num_domains_expired)) { ?> badge-danger <?php } ?> text-white"><?php echo $num_domains; ?></span>
@ -227,6 +238,7 @@
<i class="nav-icon fas fa-stream"></i>
<p>
Services
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/service/service_add.php?client_id=<?= $client_id ?>"></span>
<?php
if ($num_services > 0) { ?>
<span class="right badge text-light"><?php echo $num_services; ?></span>
@ -240,6 +252,7 @@
<i class="nav-icon fas fa-folder"></i>
<p>
Documents
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/document/document_add.php?client_id=<?= $client_id ?>" data-modal-size="lg"></span>
<?php
if ($num_documents > 0) { ?>
<span class="right badge text-light"><?php echo $num_documents; ?></span>
@ -256,6 +269,7 @@
<i class="nav-icon fas fa-paperclip"></i>
<p>
Files
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/file/file_upload.php?client_id=<?= $client_id ?>"></span>
<?php
if ($num_files > 0) { ?>
<span class="right badge text-light"><?php echo $num_files; ?></span>
@ -277,6 +291,7 @@
<i class="nav-icon fas fa-file-invoice"></i>
<p>
Invoices
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/invoice/invoice_add.php?client_id=<?= $client_id ?>"></span>
<?php
if ($num_invoices > 0) { ?>
<span class="right badge <?php if ($num_invoices_open > 0) { ?> badge-danger <?php } ?> text-light"><?php echo $num_invoices; ?></span>
@ -290,6 +305,7 @@
<i class="nav-icon fas fa-redo-alt"></i>
<p>
Recurring Invoices
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/recurring_invoice/recurring_invoice_add.php?client_id=<?= $client_id ?>"></span>
<?php
if ($num_recurring_invoices) { ?>
<span class="right badge"><?php echo $num_recurring_invoices; ?></span>
@ -303,6 +319,7 @@
<i class="nav-icon fas fa-comment-dollar"></i>
<p>
Quotes
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/quote/quote_add.php?client_id=<?= $client_id ?>"></span>
<?php
if ($num_quotes > 0) { ?>
<span class="right badge text-light"><?php echo $num_quotes; ?></span>
@ -333,6 +350,7 @@
<i class="nav-icon fas fa-route"></i>
<p>
Trips
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/trip/trip_add.php?client_id=<?= $client_id ?>"></span>
<?php
if ($num_trips > 0) { ?>
<span class="right badge text-light"><?php echo $num_trips; ?></span>

View File

@ -29,8 +29,8 @@ if (isset($_GET['client_id'])) {
$sql = mysqli_query(
$mysqli,
"SELECT * FROM clients
LEFT JOIN locations ON clients.client_id = locations.location_client_id AND location_primary = 1
LEFT JOIN contacts ON clients.client_id = contacts.contact_client_id AND contact_primary = 1
LEFT JOIN locations ON client_id = location_client_id AND location_primary = 1
LEFT JOIN contacts ON client_id = contact_client_id AND contact_primary = 1
WHERE client_id = $client_id"
);

View File

@ -23,10 +23,11 @@
<a href="/agent/clients.php" class="nav-link <?php if (basename($_SERVER["PHP_SELF"]) == "clients.php") { echo "active"; } ?>">
<i class="nav-icon fas fa-users"></i>
<p>
Clients
<?php if ($num_active_clients) { ?>
<span class="right badge text-light" data-toggle="tooltip" title="Active Clients"><?php echo $num_active_clients; ?></span>
<?php } ?>
Clients
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/client/client_add.php"></span>
<?php if ($num_active_clients) { ?>
<span class="right badge text-light" data-toggle="tooltip" title="Active Clients"><?php echo $num_active_clients; ?></span>
<?php } ?>
</p>
</a>
</li>
@ -40,6 +41,7 @@
<i class="nav-icon fas fa-life-ring"></i>
<p>
Tickets
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/ticket/ticket_add_v2.php" data-modal-size="lg"></span>
<?php if ($num_active_tickets) { ?>
<span class="right badge text-light" data-toggle="tooltip" title="Open Tickets"><?php echo $num_active_tickets; ?></span>
<?php } ?>
@ -51,6 +53,7 @@
<i class="nav-icon fas fa-redo-alt"></i>
<p>
Recurring Tickets
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/recurring_ticket/recurring_ticket_add.php" data-modal-size="lg"></span>
<?php if ($num_recurring_tickets) { ?>
<span class="right badge text-light" data-toggle="tooltip" title="Active Recurring Tickets"><?php echo $num_recurring_tickets; ?></span>
<?php } ?>
@ -62,6 +65,7 @@
<i class="nav-icon fas fa-project-diagram"></i>
<p>
Projects
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/project/project_add.php"></span>
<?php if ($num_active_projects) { ?>
<span class="right badge text-light" data-toggle="tooltip" title="Open Projects"><?php echo $num_active_projects; ?></span>
<?php } ?>
@ -84,6 +88,7 @@
<i class="nav-icon fas fa-comment-dollar"></i>
<p>
Quotes
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/quote/quote_add.php"></span>
<?php if ($num_open_quotes) { ?>
<span class="right badge text-light" data-toggle="tooltip" title="Active Quotes"><?php echo $num_open_quotes; ?></span>
<?php } ?>
@ -95,6 +100,7 @@
<i class="nav-icon fas fa-file-invoice"></i>
<p>
Invoices
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/invoice/invoice_add.php"></span>
<?php if ($num_open_invoices) { ?>
<span class="right badge text-light" data-toggle="tooltip" title="Open Invoices"><?php echo $num_open_invoices; ?></span>
<?php } ?>
@ -106,6 +112,7 @@
<i class="nav-icon fas fa-redo-alt"></i>
<p>
Recurring Invoices
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/recurring_invoice/recurring_invoice_add.php"></span>
<?php if ($num_recurring_invoices) { ?>
<span class="right badge text-light" data-toggle="tooltip" title="Active Recurring Invoices"><?php echo $num_recurring_invoices; ?></span>
<?php } ?>
@ -116,12 +123,14 @@
<a href="/agent/revenues.php" class="nav-link <?php if (basename($_SERVER["PHP_SELF"]) == "revenues.php") { echo "active"; } ?>">
<i class="nav-icon fas fa-hand-holding-usd"></i>
<p>Revenues</p>
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/revenue/revenue_add.php" data-modal-size="lg"></span>
</a>
</li>
<li class="nav-item">
<a href="/agent/products.php" class="nav-link <?php if (basename($_SERVER["PHP_SELF"]) == "products.php") { echo "active"; } ?>">
<i class="nav-icon fas fa-box-open"></i>
<p>Products</p>
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/product/product_add.php"></span>
</a>
</li>
<?php } ?>
@ -137,12 +146,14 @@
<a href="/agent/vendors.php" class="nav-link <?php if (basename($_SERVER["PHP_SELF"]) == "vendors.php") { echo "active"; } ?>">
<i class="nav-icon fas fa-building"></i>
<p>Vendors</p>
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/vendor/vendor_add.php"></span>
</a>
</li>
<li class="nav-item">
<a href="/agent/expenses.php" class="nav-link <?php if (basename($_SERVER["PHP_SELF"]) == "expenses.php") { echo "active"; } ?>">
<i class="nav-icon fas fa-shopping-cart"></i>
<p>Expenses</p>
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/expense/expense_add.php"></span>
</a>
</li>
<li class="nav-item">
@ -150,6 +161,7 @@
<i class="nav-icon fas fa-redo-alt"></i>
<p>
Recurring Expenses
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/recurring_expense/recurring_expense_add.php"></span>
<?php if ($num_recurring_expenses) { ?>
<span class="right badge text-light" data-toggle="tooltip" title="Recurring Expenses"><?php echo $num_recurring_expenses; ?></span>
<?php } ?>
@ -160,18 +172,21 @@
<a href="/agent/accounts.php" class="nav-link <?php if (basename($_SERVER["PHP_SELF"]) == "accounts.php") { echo "active"; } ?>">
<i class="nav-icon fas fa-piggy-bank"></i>
<p>Accounts</p>
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/account/account_add.php"></span>
</a>
</li>
<li class="nav-item">
<a href="/agent/transfers.php" class="nav-link <?php if (basename($_SERVER["PHP_SELF"]) == "transfers.php") { echo "active"; } ?>">
<i class="nav-icon fas fa-exchange-alt"></i>
<p>Transfers</p>
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/transfer/transfer_add.php"></span>
</a>
</li>
<li class="nav-item">
<a href="/agent/trips.php" class="nav-link <?php if (basename($_SERVER["PHP_SELF"]) == "trips.php") { echo "active"; } ?>">
<i class="nav-icon fas fa-route"></i>
<p>Trips</p>
<span href="#" class="fas fa-plus-circle right ajax-modal" data-modal-url="/agent/modals/trip/trip_add.php"></span>
</a>
</li>
<?php } ?>

View File

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

View File

@ -344,8 +344,6 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
$recurring_invoice_display = "-";
}
$now = time();
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);
// 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>
@ -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
</a>
<div class="dropdown-divider"></div>
<?php if ($invoice_status !== 'Partial' && $config_stripe_enable && $stripe_id && $stripe_pm) { ?>
<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']; ?>">
<i class="fa fa-fw fa-credit-card mr-2"></i>Pay via saved card
</a>
<?php if (mysqli_num_rows($sql_saved_payment_methods) > 0 && ($invoice_status === 'Sent' || $invoice_status === 'Viewed')) { ?>
<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>
<div class="dropdown-divider"></div>
<?php } ?>
<?php } ?>

View File

@ -27,6 +27,8 @@ if ($os_sql && mysqli_num_rows($os_sql) > 0) {
$json_os = json_encode($os_arr);
}
$sql_tags_select = mysqli_query($mysqli, "SELECT tag_id, tag_name FROM tags WHERE tag_type = 5 ORDER BY tag_name ASC");
ob_start();
?>
@ -465,6 +467,33 @@ ob_start();
<textarea class="form-control" rows="8" placeholder="Enter some notes" name="notes"></textarea>
</div>
<div class="form-group">
<label>Tags</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-tags"></i></span>
</div>
<select class="form-control select2" name="tags[]" data-placeholder="Add some tags" multiple>
<?php
while ($row = mysqli_fetch_array($sql_tags_select)) {
$tag_id = intval($row['tag_id']);
$tag_name = nullable_htmlentities($row['tag_name']);
?>
<option value="<?= $tag_id ?>"><?= $tag_name ?></option>
<?php } ?>
</select>
<div class="input-group-append">
<button class="btn btn-secondary ajax-modal" type="button"
data-modal-url="../admin/modals/tag/tag_add.php?type=5">
<i class="fas fa-plus"></i>
</button>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,61 @@
<?php
require_once '../../../includes/modal_header.php';
$selected_ids = array_map('intval', $_GET['selected_ids'] ?? []);
$count = count($selected_ids);
ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-tags mr-2"></i>Assign Tags for <strong><?= $count ?></strong> Assets</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<?php foreach ($selected_ids as $id) { ?><input type="hidden" name="asset_ids[]" value="<?= $id ?>"><?php } ?>
<div class="modal-body">
<input type="hidden" name="remove_tags" value="0">
<div class="form-group form-check">
<input type="checkbox" class="form-check-input" name="remove_tags" value="1">
<label class="form-check-label text-danger">Remove Existing Tags</label>
</div>
<div class="form-group">
<label>Tags</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-tags"></i></span>
</div>
<select class="form-control select2" name="tags[]" data-placeholder="Add some tags" multiple>
<?php
$sql_tags_select = mysqli_query($mysqli, "SELECT * FROM tags WHERE tag_type = 5 ORDER BY tag_name ASC");
while ($row = mysqli_fetch_array($sql_tags_select)) {
$tag_id_select = intval($row['tag_id']);
$tag_name_select = nullable_htmlentities($row['tag_name']);
?>
<option value="<?php echo $tag_id_select; ?>"><?php echo $tag_name_select; ?></option>
<?php } ?>
</select>
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" name="bulk_assign_asset_tags" class="btn btn-primary text-bold"><i class="fas fa-check mr-2"></i>Assign Tags</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
</div>
</form>
<?php
require_once '../../../includes/modal_footer.php';

View File

@ -70,6 +70,28 @@ if ($location_archived_at) {
$location_name_display = $location_name;
}
// Tags - many to many relationship
$asset_tag_name_display_array = array();
$asset_tag_id_array = array();
$sql_asset_tags = mysqli_query($mysqli, "SELECT * FROM asset_tags LEFT JOIN tags ON asset_tag_tag_id = tag_id WHERE asset_tag_asset_id = $asset_id ORDER BY tag_name ASC");
while ($row = mysqli_fetch_array($sql_asset_tags)) {
$asset_tag_id = intval($row['tag_id']);
$asset_tag_name = nullable_htmlentities($row['tag_name']);
$asset_tag_color = nullable_htmlentities($row['tag_color']);
if (empty($asset_tag_color)) {
$asset_tag_color = "dark";
}
$asset_tag_icon = nullable_htmlentities($row['tag_icon']);
if (empty($asset_tag_icon)) {
$asset_tag_icon = "tag";
}
$asset_tag_id_array[] = $asset_tag_id;
$asset_tag_name_display_array[] = "<a href='client_assets.php?client_id=$client_id&q=$asset_tag_name'><span class='badge text-light p-1 mr-1' style='background-color: $asset_tag_color;'><i class='fa fa-fw fa-$asset_tag_icon mr-2'></i>$asset_tag_name</span></a>";
}
$asset_tags_display = implode('', $asset_tag_name_display_array);
// Network Interfaces
$sql_related_interfaces = mysqli_query($mysqli, "
SELECT
@ -262,8 +284,13 @@ ob_start();
<?php } ?>
</div>
<div class="card-body">
<?php if ($asset_tags_display) { ?>
<div>
<?= $asset_tags_display ?>
</div>
<?php } ?>
<?php if ($asset_type) { ?>
<div><i class="fa fa-fw fa-tag text-secondary mr-2"></i><?php echo $asset_type; ?></div>
<div class="mt-1"><i class="fa fa-fw fa-tag text-secondary mr-2"></i><?php echo $asset_type; ?></div>
<?php }
if ($asset_make) { ?>
<div class="mt-2"><i class="fa fa-fw fa-circle text-secondary mr-2"></i><?php echo "$asset_make $asset_model"; ?></div>

View File

@ -50,7 +50,14 @@ $sql_asset_history = mysqli_query($mysqli, "SELECT * FROM asset_history
DESC LIMIT 10"
);
// Generate the HTML form content using output buffering.
// Tags
$asset_tag_id_array = array();
$sql_asset_tags = mysqli_query($mysqli, "SELECT asset_tag_tag_id FROM asset_tags WHERE asset_tag_asset_id = $asset_id");
while ($row = mysqli_fetch_array($sql_asset_tags)) {
$asset_tag_tag_id = intval($row['asset_tag_tag_id']);
$asset_tag_id_array[] = $asset_tag_tag_id;
}
ob_start();
?>
@ -462,6 +469,33 @@ ob_start();
<textarea class="form-control" rows="8" placeholder="Enter some notes" name="notes"><?= $asset_notes ?></textarea>
</div>
<div class="form-group">
<label>Tags</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-tags"></i></span>
</div>
<select class="form-control select2" name="tags[]" data-placeholder="Add some tags" multiple>
<?php
$sql_tags_select = mysqli_query($mysqli, "SELECT * FROM tags WHERE tag_type = 5 ORDER BY tag_name ASC");
while ($row = mysqli_fetch_array($sql_tags_select)) {
$tag_id_select = intval($row['tag_id']);
$tag_name_select = nullable_htmlentities($row['tag_name']);
?>
<option value="<?= $tag_id_select ?>" <?php if (in_array($tag_id_select, $asset_tag_id_array)) { echo "selected"; } ?>><?php echo $tag_name_select; ?></option>
<?php } ?>
</select>
<div class="input-group-append">
<button class="btn btn-secondary ajax-modal" type="button"
data-modal-url="../admin/modals/tag/tag_add.php?type=5">
<i class="fas fa-plus"></i>
</button>
</div>
</div>
</div>
<p class="text-muted text-right">Asset ID: <?= $asset_id ?></p>
</div>

View File

@ -51,7 +51,14 @@ $auth_method = nullable_htmlentities($row['user_auth_method']);
$contact_client_id = intval($row['contact_client_id']);
// Related Assets Query - 1 to 1 relationship
$sql_related_assets = mysqli_query($mysqli, "SELECT * FROM assets LEFT JOIN asset_interfaces ON interface_asset_id = asset_id AND interface_primary = 1 WHERE asset_contact_id = $contact_id ORDER BY asset_name DESC");
$sql_related_assets = mysqli_query($mysqli, "SELECT * FROM assets
LEFT JOIN asset_interfaces ON interface_asset_id = asset_id AND interface_primary = 1
LEFT JOIN asset_tags ON asset_tag_asset_id = asset_id
LEFT JOIN tags ON tag_id = asset_tag_tag_id
WHERE asset_contact_id = $contact_id
GROUP BY asset_id
ORDER BY asset_name ASC"
);
$asset_count = mysqli_num_rows($sql_related_assets);
// Linked Software Licenses
@ -77,7 +84,7 @@ $sql_related_credentials = mysqli_query($mysqli, "
LEFT JOIN tags ON tags.tag_id = credential_tags.tag_id
WHERE credential_contact_id = $contact_id
GROUP BY credentials.credential_id
ORDER BY credential_name DESC
ORDER BY credential_name ASC
");
$credential_count = mysqli_num_rows($sql_related_credentials);
@ -376,6 +383,27 @@ ob_start();
$asset_notes = nullable_htmlentities($row['asset_notes']);
$asset_created_at = nullable_htmlentities($row['asset_created_at']);
$device_icon = getAssetIcon($asset_type);
// Tags
$asset_tag_name_display_array = array();
$asset_tag_id_array = array();
$sql_asset_tags = mysqli_query($mysqli, "SELECT * FROM asset_tags LEFT JOIN tags ON asset_tag_tag_id = tag_id WHERE asset_tag_asset_id = $asset_id ORDER BY tag_name ASC");
while ($row = mysqli_fetch_array($sql_asset_tags)) {
$asset_tag_id = intval($row['tag_id']);
$asset_tag_name = nullable_htmlentities($row['tag_name']);
$asset_tag_color = nullable_htmlentities($row['tag_color']);
if (empty($asset_tag_color)) {
$asset_tag_color = "dark";
}
$asset_tag_icon = nullable_htmlentities($row['tag_icon']);
if (empty($asset_tag_icon)) {
$asset_tag_icon = "tag";
}
$asset_tag_id_array[] = $asset_tag_id;
$asset_tag_name_display_array[] = "<a href='assets.php?$client_url tags[]=$asset_tag_id'><span class='badge text-light p-1 mr-1' style='background-color: $asset_tag_color;'><i class='fa fa-fw fa-$asset_tag_icon mr-2'></i>$asset_tag_name</span></a>";
}
$asset_tags_display = implode('', $asset_tag_name_display_array);
?>
<tr>
@ -389,6 +417,12 @@ ob_start();
<div class="mt-0">
<small class="text-muted"><?= $asset_description ?></small>
</div>
<?php
if ($asset_tags_display) { ?>
<div class="mt-1">
<?= $asset_tags_display ?>
</div>
<?php } ?>
</th>
<td><?= $asset_type ?></td>
<td>

View File

@ -9,9 +9,12 @@ if ($type == 'product') {
$type_icon = "fa-wrench";
}
$product_types_array = ['product', 'service'];
ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fas fa-fw <?= $type_icon ?> mr-2"></i>New <strong><?= ucwords($type); ?></strong></h5>
<button type="button" class="close text-white" data-dismiss="modal">
@ -19,7 +22,6 @@ ob_start();
</button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="type" value="<?= $type ?>">
<div class="modal-body">
@ -33,6 +35,27 @@ ob_start();
</div>
</div>
<?php if ($type) { ?>
<input type="hidden" name="type" value="<?= $type ?>">
<?php } else { ?>
<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-tag"></i></span>
</div>
<select class="form-control select2" name="type" required>
<option value="">- Select Type -</option>
<?php foreach ($product_types_array as $type_select) { ?>
<option><?= $type_select ?></option>
<?php } ?>
</select>
</div>
</div>
<?php } ?>
<div class="form-group">
<label>Category <strong class="text-danger">*</strong></label>
<div class="input-group">

View File

@ -3,6 +3,7 @@
require_once '../../../includes/modal_header.php';
$ticket_id = intval($_GET['ticket_id']);
$client_id = intval(getFieldById('tickets', $ticket_id, 'ticket_client_id'));
ob_start();

View File

@ -38,33 +38,35 @@ while ($row = mysqli_fetch_array($sql_additional_assets)) {
ob_start();
?>
<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">
<span>&times;</span>
</button>
</div>
<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">
<?php if ($client_id) { ?>
<ul class="nav nav-pills nav-justified mb-3">
<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 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>
</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>
<a class="nav-link" data-toggle="pill" href="#pills-assignment"><i class="fa fa-fw fa-desktop mr-2"></i>Assignment</a>
</li>
</ul>
<hr>
<?php } ?>
<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">
<label>Subject <strong class="text-danger">*</strong></label>
@ -182,7 +184,9 @@ ob_start();
</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">
<label>Contact</label>
@ -236,7 +240,7 @@ ob_start();
</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">
<label>Asset</label>
@ -372,7 +376,7 @@ ob_start();
while ($row = mysqli_fetch_array($sql_projects)) {
$project_id_select = intval($row['project_id']);
$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 } ?>
</select>
@ -380,13 +384,14 @@ ob_start();
</div>
</div>
<?php } // End client_id check ?>
</div>
</div>
<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>
</div>

View File

@ -20,6 +20,14 @@ if (isset($_POST['add_asset'])) {
$asset_id = mysqli_insert_id($mysqli);
// Add Tags
if (isset($_POST['tags'])) {
foreach($_POST['tags'] as $tag) {
$tag = intval($tag);
mysqli_query($mysqli, "INSERT INTO asset_tags SET asset_tag_asset_id = $asset_id, asset_tag_tag_id = $tag");
}
}
// Add Photo
if (isset($_FILES['file']['tmp_name'])) {
if ($new_file_name = checkFileUpload($_FILES['file'], array('jpg', 'jpeg', 'gif', 'png', 'webp'))) {
@ -108,6 +116,18 @@ if (isset($_POST['edit_asset'])) {
mysqli_query($mysqli,"UPDATE assets SET asset_photo = '$new_file_name' WHERE asset_id = $asset_id");
}
// Tags
// Delete existing tags
mysqli_query($mysqli, "DELETE FROM asset_tags WHERE asset_tag_asset_id = $asset_id");
// Add new tags
if (isset($_POST['tags'])) {
foreach($_POST['tags'] as $tag) {
$tag = intval($tag);
mysqli_query($mysqli, "INSERT INTO asset_tags SET asset_tag_asset_id = $asset_id, asset_tag_tag_id = $tag");
}
}
logAction("Asset", "Edit", "$session_name edited asset $name", $client_id, $asset_id);
flash_alert("Asset <strong>$name</strong> edited");
@ -188,6 +208,50 @@ if (isset($_GET['delete_asset'])) {
}
if (isset($_POST['bulk_assign_asset_tags'])) {
enforceUserPermission('module_client', 2);
if (isset($_POST['asset_ids'])) {
$count = count($_POST['asset_ids']);
foreach($_POST['asset_ids'] as $asset_id) {
$asset_id = intval($asset_id);
$sql = mysqli_query($mysqli,"SELECT asset_name, asset_client_id FROM assets WHERE asset_id = $asset_id");
$row = mysqli_fetch_array($sql);
$asset_name = sanitizeInput($row['asset_name']);
$client_id = intval($row['asset_client_id']);
if($_POST['remove_tags']) {
mysqli_query($mysqli, "DELETE FROM asset_tags WHERE asset_tag_asset_id = $asset_id");
}
if (isset($_POST['tags'])) {
foreach($_POST['tags'] as $tag) {
$tag = intval($tag);
$sql = mysqli_query($mysqli,"SELECT * FROM asset_tags WHERE asset_tag_asset_id = $asset_id AND asset_tag_tag_id = $tag");
if (mysqli_num_rows($sql) == 0) {
mysqli_query($mysqli, "INSERT INTO asset_tags SET asset_tag_asset_id = $asset_id, asset_tag_tag_id = $tag");
}
}
}
logAction("Asset", "Edit", "$session_name added tags to asset $asset_name", $client_id, $asset_id);
}
logAction("Asset", "Bulk Edit", "$session_name added tags for $asset_count assets", $client_id);
flash_alert("Assigned tags for <strong>$count</strong> assets");
}
redirect();
}
if (isset($_POST['bulk_assign_asset_location'])) {
validateCSRFToken($_POST['csrf_token']);

View File

@ -1390,22 +1390,25 @@ if (isset($_POST["export_client_pdf"])) {
<th>Description</th>
<th>Username</th>
<th>Password</th>
<th>TOTP</th>
<th>URI</th>
</tr>
</thead>
<tbody>";
while ($row = mysqli_fetch_array($sql_credentials)) {
$credential_name = nullable_htmlentities($row["credential_name"]);
$credential_description = nullable_htmlentities($row["credential_description"]);
$credential_description = getFallback(nullable_htmlentities($row["credential_description"]));
$credential_username = nullable_htmlentities(decryptCredentialEntry($row["credential_username"]));
$credential_password = nullable_htmlentities(decryptCredentialEntry($row["credential_password"]));
$credential_uri = nullable_htmlentities($row["credential_uri"]);
$credential_totp_secret = getFallback(nullable_htmlentities($row['credential_otp_secret']));
$credential_uri = getFallback(nullable_htmlentities($row["credential_uri"]));
$html .= "
<tr>
<td>$credential_name</td>
<td>$credential_description</td>
<td>$credential_username</td>
<td>$credential_password</td>
<td>$credential_totp_secret</td>
<td>$credential_uri</td>
</tr>";
}

View File

@ -497,7 +497,7 @@ if (isset($_GET['email_quote'])) {
logAction("Quote", "Email", "$session_name emailed quote $quote_prefix$quote_number to $contact_email", $client_id, $quote_id);
flash_alert("Quote has been queued successfully! <a class='text-bold text-light' href='admin_mail_queue.php'>See Mail Queue</a>");
flash_alert("Quote sent!");
//Don't change the status to sent if the status is anything but draft
if ($quote_status == 'Draft') {

View File

@ -1814,14 +1814,19 @@ if (isset($_POST['merge_ticket'])) {
// NEW PARENT ticket details
// Get merge into ticket id (as it may differ from the number)
$sql = mysqli_query($mysqli, "SELECT ticket_id FROM tickets WHERE ticket_number = $merge_into_ticket_number");
$sql = mysqli_query($mysqli, "SELECT ticket_id, ticket_client_id FROM tickets WHERE ticket_number = $merge_into_ticket_number");
if (mysqli_num_rows($sql) == 0) {
flash_alert("Cannot merge into that ticket.", 'error');
redirect();
}
$merge_row = mysqli_fetch_array($sql);
$merge_into_ticket_id = intval($merge_row['ticket_id']);
$client_id = intval($merge_row['ticket_client_id']);
if ($client_id) {
$has_client = "&client_id=$client_id";
} else {
$has_client = "";
}
// Sanity check
if ($ticket_number == $merge_into_ticket_number) {
flash_alert("Cannot merge into the same ticket.", 'error');
@ -1853,7 +1858,7 @@ if (isset($_POST['merge_ticket'])) {
flash_alert("Ticket merged into $ticket_prefix$merge_into_ticket_number");
redirect();
redirect("ticket.php?ticket_id=$merge_into_ticket_id$has_client");
}

View File

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

View File

@ -108,6 +108,11 @@
$contact_id = intval($row['contact_id']);
$contact_name = nullable_htmlentities($row['contact_name']);
$contact_email = nullable_htmlentities($row['contact_email']);
if ($client_id) {
$has_client = "&client_id=$client_id";
} else {
$has_client = "";
}
if ($ticket_priority == "High") {
$ticket_priority_color = "danger";
@ -197,14 +202,14 @@
<!-- Ticket Number -->
<td>
<a href="ticket.php?client_id=<?= $client_id ?>&ticket_id=<?= $ticket_id ?>">
<a href="ticket.php?ticket_id=<?= "$ticket_id$has_client" ?>">
<span class="badge badge-pill badge-secondary p-3"><?php echo "$ticket_prefix$ticket_number"; ?></span>
</a>
</td>
<!-- Ticket Subject -->
<td>
<a href="ticket.php?client_id=<?= $client_id ?>&ticket_id=<?= $ticket_id ?>"><?= $ticket_subject ?></a>
<a href="ticket.php?ticket_id=<?= "$ticket_id$has_client" ?>"><?= $ticket_subject ?></a>
<?php if($task_count && $completed_task_count > 0) { ?>
<div class="progress mt-2" style="height: 20px;">

View File

@ -49,7 +49,7 @@ if (isset($_POST['add_ticket'])) {
$details = removeEmoji($details);
$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
$data = [
@ -113,7 +113,7 @@ if (isset($_POST['add_ticket_comment'])) {
$tech_name = sanitizeInput($tech_details['user_name']);
$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 = [
[
@ -440,7 +440,7 @@ if (isset($_GET['add_payment_by_provider'])) {
$sql = mysqli_query($mysqli,"SELECT * FROM invoices
LEFT JOIN clients ON invoice_client_id = client_id
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);
$invoice_number = intval($row['invoice_number']);

20
db.sql
View File

@ -275,6 +275,23 @@ CREATE TABLE `asset_notes` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `asset_tags`
--
DROP TABLE IF EXISTS `asset_tags`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `asset_tags` (
`asset_tag_asset_id` int(11) NOT NULL,
`asset_tag_tag_id` int(11) NOT NULL,
PRIMARY KEY (`asset_tag_asset_id`,`asset_tag_tag_id`),
KEY `fk_tag` (`asset_tag_tag_id`),
CONSTRAINT `fk_asset` FOREIGN KEY (`asset_tag_asset_id`) REFERENCES `assets` (`asset_id`) ON DELETE CASCADE,
CONSTRAINT `fk_tag` FOREIGN KEY (`asset_tag_tag_id`) REFERENCES `tags` (`tag_id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `assets`
--
@ -800,7 +817,6 @@ CREATE TABLE `contract_templates` (
`contract_template_created_at` datetime DEFAULT current_timestamp(),
`contract_template_updated_at` datetime DEFAULT NULL ON UPDATE current_timestamp(),
`contract_template_archived_at` datetime DEFAULT NULL,
`company_id` int(11) NOT NULL,
PRIMARY KEY (`contract_template_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
@ -2897,4 +2913,4 @@ CREATE TABLE `vendors` (
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2025-11-07 16:47:40
-- Dump completed on 2025-11-11 19:57:21

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

View File

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

12
plugins/composer.lock generated
View File

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

View File

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

View File

@ -19,7 +19,7 @@ return array(
'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'),
'Psr\\Container\\' => array($vendorDir . '/psr/container/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\\Contracts\\' => array($vendorDir . '/illuminate/contracts'),
'Doctrine\\Inflector\\' => array($vendorDir . '/doctrine/inflector/src'),

View File

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

View File

@ -929,17 +929,17 @@
},
{
"name": "symfony/http-foundation",
"version": "v7.3.3",
"version_normalized": "7.3.3.0",
"version": "v7.3.7",
"version_normalized": "7.3.7.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
"reference": "7475561ec27020196c49bb7c4f178d33d7d3dc00"
"reference": "db488a62f98f7a81d5746f05eea63a74e55bb7c4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/7475561ec27020196c49bb7c4f178d33d7d3dc00",
"reference": "7475561ec27020196c49bb7c4f178d33d7d3dc00",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/db488a62f98f7a81d5746f05eea63a74e55bb7c4",
"reference": "db488a62f98f7a81d5746f05eea63a74e55bb7c4",
"shasum": ""
},
"require": {
@ -963,7 +963,7 @@
"symfony/mime": "^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",
"installation-source": "dist",
"autoload": {
@ -991,7 +991,7 @@
"description": "Defines an object-oriented layer for the HTTP specification",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/http-foundation/tree/v7.3.3"
"source": "https://github.com/symfony/http-foundation/tree/v7.3.7"
},
"funding": [
{

View File

@ -5,7 +5,7 @@
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => '981fb9585d0c76e8b9c31812d58dfdd5b56d6454',
'reference' => '612041635d962d37f2f400ba1974bec5456ccd1e',
'name' => '__root__',
'dev' => true,
),
@ -16,7 +16,7 @@
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => '981fb9585d0c76e8b9c31812d58dfdd5b56d6454',
'reference' => '612041635d962d37f2f400ba1974bec5456ccd1e',
'dev_requirement' => false,
),
'carbonphp/carbon-doctrine-types' => array(
@ -158,12 +158,12 @@
'dev_requirement' => false,
),
'symfony/http-foundation' => array(
'pretty_version' => 'v7.3.3',
'version' => '7.3.3.0',
'pretty_version' => 'v7.3.7',
'version' => '7.3.7.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/http-foundation',
'aliases' => array(),
'reference' => '7475561ec27020196c49bb7c4f178d33d7d3dc00',
'reference' => 'db488a62f98f7a81d5746f05eea63a74e55bb7c4',
'dev_requirement' => false,
),
'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) {
$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 .= '_';
} else {
$filenameFallback .= $char;

View File

@ -300,10 +300,21 @@ class Request
$server['PATH_INFO'] = '';
$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.'#')) {
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, '?#')) {
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 = 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)
// check that it does not contain forbidden characters (see RFC 952 and RFC 2181)
// use preg_replace() instead of preg_match() to prevent DoS attacks with long host names
if ($host && '' !== preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host)) {
// the host can come from the user (HTTP_HOST and depending on the configuration, SERVER_NAME too can come from the user)
if ($host && !self::isHostValid($host)) {
if (!$this->isHostValid) {
return '';
}
@ -1236,15 +1245,22 @@ class Request
static::initializeFormats();
}
$exactFormat = null;
$canonicalFormat = null;
foreach (static::$formats as $format => $mimeTypes) {
if (\in_array($mimeType, (array) $mimeTypes, true)) {
return $format;
if (\in_array($mimeType, $mimeTypes, true)) {
$exactFormat = $format;
}
if (null !== $canonicalMimeType && \in_array($canonicalMimeType, (array) $mimeTypes, true)) {
return $format;
if (null !== $canonicalMimeType && \in_array($canonicalMimeType, $mimeTypes, true)) {
$canonicalFormat = $format;
}
}
if ($format = $exactFormat ?? $canonicalFormat) {
return $format;
}
return null;
}
@ -1259,7 +1275,7 @@ class Request
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));
if ('' === $pathInfo) {
// If substr() returns false then PATH_INFO is set to an empty string
return '/';
if ('' === $pathInfo || '/' !== $pathInfo[0]) {
return '/'.$pathInfo;
}
return $pathInfo;
@ -2101,4 +2116,21 @@ class Request
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
{
$this->cookies[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie;
$this->cookies[$cookie->getDomain() ?? ''][$cookie->getPath()][$cookie->getName()] = $cookie;
$this->headerNames['set-cookie'] = 'Set-Cookie';
}
@ -170,13 +170,13 @@ class ResponseHeaderBag extends HeaderBag
{
$path ??= '/';
unset($this->cookies[$domain][$path][$name]);
unset($this->cookies[$domain ?? ''][$path][$name]);
if (empty($this->cookies[$domain][$path])) {
unset($this->cookies[$domain][$path]);
if (empty($this->cookies[$domain ?? ''][$path])) {
unset($this->cookies[$domain ?? ''][$path]);
if (empty($this->cookies[$domain])) {
unset($this->cookies[$domain]);
if (empty($this->cookies[$domain ?? ''])) {
unset($this->cookies[$domain ?? '']);
}
}

View File

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

View File

@ -219,7 +219,7 @@ class PdoSessionHandler extends AbstractSessionHandler
$table->addColumn($this->timeCol, Types::INTEGER)->setNotnull(true);
break;
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->lifetimeCol, Types::INTEGER)->setUnsigned(true)->setNotnull(true);
$table->addColumn($this->timeCol, Types::INTEGER)->setUnsigned(true)->setNotnull(true);