Merge pull request #1242 from itflow-org/develop

Develop to Master
This commit is contained in:
Johnny 2025-10-08 17:39:41 -04:00 committed by GitHub
commit 0d5bfdafdf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
59 changed files with 1922 additions and 278 deletions

9
.gitignore vendored
View File

@ -34,5 +34,14 @@ client/custom/*
!client/custom/readme.php
guest/custom/*
!guest/custom/readme.php
cron/custom/*
!cron/custom/readme.php
scripts/custom/*
!scripts/custom/readme.php
setup/custom/*
!setup/custom/readme.php
api/v1/custom/*
!api/v1/custom/readme.php
.zed

View File

@ -2,6 +2,30 @@
This file documents all notable changes made to ITFlow.
## [25.10.1]
- Deprecation Notice: `/scripts/cron_mail_queue.php` , `/scripts/cron_ticket_email_parser.php` , `/scripts/cron.php` `/scripts/cron_domain_refresher.php`, `/scripts/cron_certificate_refresher.php` are being phased out. Please transition to `/cron/mail_queue.php` , `/cron/ticket_email_parser.php`, `/cron/cron.php`, `/cron/domain_refresher.php`, `/cron/certificate_refresher.php` These older scripts will be removed in the November 25.11 release—update accordingly. 25.10.1 installs have the script already configured.
### Fixes
- Fix regression missing custom Favicon.
- Update SMTP and IMAP provider to allow for empty strings, empty means disabled.
- Fix Client portal Microsoft SSO Logins.
- Fix regression in Vendor Templates.
- Fix refression in some broken links from user to agent.
- Fix Project edit.
- Prevent open redirects upon agent login.
- Fix regression on switching to Webklex IMAP to allow for no SSL/TLS in IMAP.
- Fix Setup Redirect not behaving properly when setup hasnt been performed.
- Added Server Document Root Var to several includes, headers, footers files to allow includes from deeper directory strutures such as the new custom directories.
- Fix edit contact in contact details.
- Add .htaccess to /cron/.
### Added / Changed
- Support for HTML Signatures.
- Add Edit Project Functionality in a ticket.
- Added more custom locations: /cron/custom/, /scripts/custom/, /api/v1/custom/, /setup/custom/.
- Copied `/scripts/cron.php` `/scripts/cron_domain_refresher.php`, `/scripts/cron_certificate_refresher.php` to `/cron/cron.php`, `/cron/domain_refresher.php`, `/cron/certificate_refresher.php`. See Above!
- Signatures is now handled in post ticket reply on Public Comments only.
## [25.10]
### Breaking Changes

View File

@ -4027,10 +4027,16 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) {
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.3.5'");
}
// if (CURRENT_DATABASE_VERSION == '2.3.4') {
// // Insert queries here required to update to DB version 2.3.4
if (CURRENT_DATABASE_VERSION == '2.3.5') {
mysqli_query($mysqli, "ALTER TABLE `settings` CHANGE `config_smtp_provider` `config_smtp_provider` VARCHAR(200) DEFAULT NULL");
mysqli_query($mysqli, "ALTER TABLE `settings` CHANGE `config_imap_provider` `config_imap_provider` VARCHAR(200) DEFAULT NULL");
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.3.6'");
}
// if (CURRENT_DATABASE_VERSION == '2.3.5') {
// // Insert queries here required to update to DB version 2.3.5
// // Then, update the database to the next sequential version
// mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.3.5'");
// mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.3.6'");
// }
} else {

View File

@ -1,16 +1,16 @@
<?php
require_once "../config.php";
require_once "../functions.php";
require_once "../includes/check_login.php";
require_once "../includes/page_title.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/config.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/functions.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/check_login.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/page_title.php';
if (!isset($session_is_admin) || !$session_is_admin) {
exit(WORDING_ROLECHECK_FAILED . "<br>Tell your admin: Your role does not have admin access.");
}
require_once "../includes/header.php";
require_once "../includes/top_nav.php";
require_once "includes/side_nav.php";
require_once "../includes/inc_wrapper.php";
require_once "../includes/inc_alert_feedback.php";
require_once "../includes/filter_header.php";
require_once "../includes/app_version.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/top_nav.php';
require_once 'includes/side_nav.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/inc_wrapper.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/inc_alert_feedback.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/filter_header.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/app_version.php';

View File

@ -1,6 +1,6 @@
<!-- Main Sidebar Container -->
<aside class="main-sidebar sidebar-dark-<?php echo nullable_htmlentities($config_theme); ?> d-print-none">
<a class="brand-link pb-1 mt-1" href="../agent/<?php echo $config_start_page ?>">
<a class="brand-link pb-1 mt-1" href="/agent/<?php echo $config_start_page ?>">
<p class="h6">
<i class="nav-icon fas fa-arrow-left ml-3 mr-2"></i>
<span class="brand-text">

View File

@ -6,7 +6,7 @@ if (isset($_POST['edit_mail_smtp_settings'])) {
validateCSRFToken($_POST['csrf_token']);
$config_smtp_provider = sanitizeInput($_POST['config_smtp_provider'] ?? 'standard_smtp');
$config_smtp_provider = sanitizeInput($_POST['config_smtp_provider']);
$config_smtp_host = sanitizeInput($_POST['config_smtp_host']);
$config_smtp_port = intval($_POST['config_smtp_port'] ?? 0);
$config_smtp_encryption = sanitizeInput($_POST['config_smtp_encryption']);
@ -22,7 +22,7 @@ if (isset($_POST['edit_mail_smtp_settings'])) {
mysqli_query($mysqli, "
UPDATE settings SET
config_smtp_provider = " . ($config_smtp_provider === 'none' ? "NULL" : "'$config_smtp_provider'") . ",
config_smtp_provider = '$config_smtp_provider',
config_smtp_host = '$config_smtp_host',
config_smtp_port = $config_smtp_port,
config_smtp_encryption = '$config_smtp_encryption',
@ -48,7 +48,7 @@ if (isset($_POST['edit_mail_imap_settings'])) {
validateCSRFToken($_POST['csrf_token']);
$config_imap_provider = sanitizeInput($_POST['config_imap_provider'] ?? 'standard_imap');
$config_imap_provider = sanitizeInput($_POST['config_imap_provider']);
$config_imap_host = sanitizeInput($_POST['config_imap_host']);
$config_imap_port = intval($_POST['config_imap_port'] ?? 0);
$config_imap_encryption = sanitizeInput($_POST['config_imap_encryption']);
@ -64,7 +64,7 @@ if (isset($_POST['edit_mail_imap_settings'])) {
mysqli_query($mysqli, "
UPDATE settings SET
config_imap_provider = " . ($config_imap_provider === 'none' ? "NULL" : "'$config_imap_provider'") . ",
config_imap_provider = '$config_imap_provider',
config_imap_host = '$config_imap_host',
config_imap_port = $config_imap_port,
config_imap_encryption = '$config_imap_encryption',

View File

@ -5,8 +5,8 @@
defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
// Import shared code from user-side tickets/tasks as we reuse functions
require_once '../user/post/ticket.php';
require_once '../user/post/task.php';
require_once '../agent/post/ticket.php';
require_once '../agent/post/task.php';
if (isset($_POST['add_ticket_template'])) {

View File

@ -5,7 +5,7 @@
defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
// Import shared code from user-side vendor management as we reuse functions
require_once '../user/post/vendor.php';
require_once '../agent/post/vendor.php';
if (isset($_POST['add_vendor_template'])) {

View File

@ -18,10 +18,10 @@ require_once "includes/inc_all_admin.php";
<span class="input-group-text"><i class="fa fa-fw fa-cloud"></i></span>
</div>
<select class="form-control" name="config_smtp_provider" id="config_smtp_provider">
<option value="none" <?php if(($config_smtp_provider ?? '')==='none' || ($config_smtp_provider ?? '')==='') echo 'selected'; ?>>None (Disabled)</option>
<option value="standard_smtp" <?php if(($config_smtp_provider ?? 'standard_smtp')==='standard_smtp') echo 'selected'; ?>>Standard SMTP (Username/Password)</option>
<option value="google_oauth" <?php if(($config_smtp_provider ?? '')==='google_oauth') echo 'selected'; ?>>Google Workspace (OAuth)</option>
<option value="microsoft_oauth" <?php if(($config_smtp_provider ?? '')==='microsoft_oauth') echo 'selected'; ?>>Microsoft 365 (OAuth)</option>
<option value="" <?php if(empty($config_smtp_provider)) { echo 'selected'; } ?>>None (Disabled)</option>
<option value="standard_smtp" <?php if($config_smtp_provider === 'standard_smtp') { echo 'selected'; } ?>>Standard SMTP (Username/Password)</option>
<option value="google_oauth" <?php if($config_smtp_provider === 'google_oauth') { echo 'selected'; } ?>>Google Workspace (OAuth)</option>
<option value="microsoft_oauth" <?php if($config_smtp_provider === 'microsoft_oauth') { echo 'selected'; } ?>>Microsoft 365 (OAuth)</option>
</select>
</div>
<small class="text-secondary d-block mt-1" id="smtp_provider_hint">
@ -116,10 +116,10 @@ require_once "includes/inc_all_admin.php";
<span class="input-group-text"><i class="fa fa-fw fa-cloud"></i></span>
</div>
<select class="form-control" name="config_imap_provider" id="config_imap_provider">
<option value="none" <?php if($config_imap_provider ==='') echo 'selected'; ?>>None (Disabled)</option>
<option value="standard_imap" <?php if(($config_imap_provider ?? 'standard_imap')==='standard_imap') echo 'selected'; ?>>Standard IMAP (Username/Password)</option>
<option value="google_oauth" <?php if(($config_imap_provider ?? '')==='google_oauth') echo 'selected'; ?>>Google Workspace (OAuth)</option>
<option value="microsoft_oauth" <?php if(($config_imap_provider ?? '')==='microsoft_oauth') echo 'selected'; ?>>Microsoft 365 (OAuth)</option>
<option value="" <?php if(empty($config_imap_provider)) { echo 'selected'; } ?>>None (Disabled)</option>
<option value="standard_imap" <?php if($config_imap_provider === 'standard_imap') { echo 'selected'; } ?>>Standard IMAP (Username/Password)</option>
<option value="google_oauth" <?php if($config_imap_provider === 'google_oauth') { echo 'selected'; } ?>>Google Workspace (OAuth)</option>
<option value="microsoft_oauth" <?php if($config_imap_provider === 'microsoft_oauth') { echo 'selected'; } ?>>Microsoft 365 (OAuth)</option>
</select>
</div>
<small class="text-secondary d-block mt-1" id="imap_provider_hint">

View File

@ -56,8 +56,6 @@ $git_log = shell_exec("git log $repo_branch..origin/$repo_branch --pretty=format
<p class="text-center font-weight-bold">Ignore this warning at your own risk.</p>
</div>
<a class="btn btn-primary btn-lg my-4 confirm-link" href="post.php?no"><i class="fas fa-fw fa-4x fa-download mb-1"></i><h5>TEST</h5></a>
<a class="btn btn-primary btn-lg my-4 confirm-link" href="post.php?update"><i class="fas fa-fw fa-4x fa-download mb-1"></i><h5>Update App</h5></a>
<a class="btn btn-danger btn-lg confirm-link" href="post.php?update&force_update=1"><i class="fas fa-fw fa-4x fa-hammer mb-1"></i><h5>FORCE Update App</h5></a>

View File

@ -1,19 +1,19 @@
<?php
// Configuration & core
require_once "../config.php";
require_once "../functions.php";
require_once "../includes/check_login.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/config.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/functions.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/check_login.php';
// Page setup
require_once "../includes/page_title.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/page_title.php';
// Layout UI
require_once "../includes/header.php";
require_once "../includes/top_nav.php";
require_once "includes/get_side_nav_counts.php";
require_once "includes/side_nav.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/top_nav.php';
require_once 'includes/get_side_nav_counts.php';
require_once 'includes/side_nav.php';
// Wrapper & alerts
require_once "../includes/inc_wrapper.php";
require_once "../includes/inc_alert_feedback.php";
require_once "../includes/filter_header.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/inc_wrapper.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/inc_alert_feedback.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/filter_header.php';

View File

@ -1,9 +1,9 @@
<?php
require_once "../config.php";
require_once "../functions.php";
require_once "../includes/check_login.php";
require_once "../includes/page_title.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/config.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/functions.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/check_login.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/page_title.php';
// Perms
enforceUserPermission('module_client');
@ -35,7 +35,7 @@ if (isset($_GET['client_id'])) {
);
if (mysqli_num_rows($sql) == 0) {
require_once "../includes/header.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php';
echo "<center><h1 class='text-secondary mt-5'>Nothing to see here</h1></center>";
} else {
@ -304,10 +304,10 @@ if (isset($_GET['client_id'])) {
}
}
require_once "../includes/header.php";
require_once "../includes/top_nav.php";
require_once "includes/client_side_nav.php";
require_once "../includes/inc_wrapper.php";
require_once "../includes/inc_alert_feedback.php";
require_once "includes/inc_client_top_head.php";
require_once "../includes/filter_header.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/top_nav.php';
require_once 'includes/client_side_nav.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/inc_wrapper.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/inc_alert_feedback.php';
require_once 'includes/inc_client_top_head.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/filter_header.php';

View File

@ -1,12 +1,12 @@
<?php
require_once "../config.php";
require_once "../functions.php";
require_once "../includes/check_login.php";
require_once "../includes/page_title.php";
require_once "../includes/header.php";
require_once "../includes/top_nav.php";
require_once "includes/client_overview_side_nav.php";
require_once "../includes/inc_wrapper.php";
require_once "../includes/inc_alert_feedback.php";
require_once "../includes/filter_header.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/config.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/functions.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/check_login.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/page_title.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/top_nav.php';
require_once 'includes/client_overview_side_nav.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/inc_wrapper.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/inc_alert_feedback.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/filter_header.php';

View File

@ -845,8 +845,8 @@ ob_start();
<a href="contact_details.php?client_id=<?php echo $client_id; ?>&contact_id=<?php echo $contact_id; ?>" class="btn btn-primary text-bold">
<span class="text-white"><i class="fas fa-info-circle mr-2"></i>More Details</span>
</a>
<a href="#" class="btn btn-secondary"
data-toggle="ajax-modal" data-ajax-url="ajax/ajax_contact_edit.php" data-ajax-id="<?php echo $contact_id; ?>">
<a href="#" class="btn btn-secondary ajax-modal"
data-modal-url="modals/contact/contact_edit.php?id=<?= $contact_id ?>">
<span class="text-white"><i class="fas fa-edit mr-2"></i>Edit</span>
</a>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Close</button>

View File

@ -0,0 +1,65 @@
<?php
require_once '../../../includes/modal_header.php';
$ticket_id = intval($_GET['id']);
$sql = mysqli_query($mysqli, "SELECT * FROM tickets LEFT JOIN clients ON client_id = ticket_client_id WHERE ticket_id = $ticket_id LIMIT 1");
$row = mysqli_fetch_array($sql);
$client_id = intval($row['ticket_client_id']);
$client_name = nullable_htmlentities($row['client_name']);
$ticket_prefix = nullable_htmlentities($row['ticket_prefix']);
$ticket_number = intval($row['ticket_number']);
$ticket_project_id = intval($row['ticket_project_id']);
// Select box arrays
$sql_projects = mysqli_query($mysqli, "SELECT project_id, project_name FROM projects WHERE (project_client_id = $client_id OR project_client_id = 0) AND project_completed_at IS NULL AND project_archived_at IS NULL ORDER BY project_name ASC");
// Generate the HTML form content using output buffering.
ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fas fa-fw fa-project-diagram mr-2"></i>Project: <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; ?>">
<div class="modal-body">
<div class="form-group">
<label>Project</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">
<option value="0">- None -</option>
<?php
while ($row = mysqli_fetch_array($sql_projects)) {
$project_id = intval($row['project_id']);
$project_name = nullable_htmlentities($row['project_name']); ?>
<option <?php if ($ticket_project_id == $project_id) { echo "selected"; } ?>
value="<?= $project_id ?>"><?= $project_name ?>
</option>
<?php } ?>
</select>
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" name="edit_ticket_project" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Save</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

@ -26,7 +26,7 @@ $module = str_ireplace('_details', '', $module);
// Dynamically load admin-related module POST logic
// Load all module POST logic
// Loads everything in post/user/
// Loads everything in post
// Eventually, it would be nice to only specifically load what we need like we do for admins
foreach (glob("post/*.php") as $user_module) {

View File

@ -40,9 +40,9 @@ if (isset($_POST['add_client'])) {
$client_id = mysqli_insert_id($mysqli);
if (!file_exists("../uploads/clients/$client_id")) {
mkdir("../uploads/clients/$client_id");
file_put_contents("../uploads/clients/$client_id/index.php", "");
if (!file_exists($_SERVER['DOCUMENT_ROOT'] . "/uploads/clients/$client_id")) {
mkdir($_SERVER['DOCUMENT_ROOT'] . "/uploads/clients/$client_id");
file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/uploads/clients/$client_id/index.php", "");
}
// Create Referral if it doesn't exist

View File

@ -416,6 +416,28 @@ if (isset($_POST['edit_ticket_contact'])) {
}
if (isset($_POST['edit_ticket_project'])) {
enforceUserPermission('module_support', 2);
$ticket_id = intval($_POST['ticket_id']);
$project_id = intval($_POST['project']);
$project_name = sanitizeInput(getFieldById('projects', $project_id, 'project_name'));
$client_id = intval(getFieldById('tickets', $ticket_id, 'ticket_client_id'));
$ticket_prefix = sanitizeInput(getFieldById('tickets', $ticket_id, 'ticket_prefix'));
$ticket_number = sanitizeInput(getFieldById('tickets', $ticket_id, 'ticket_number'));
mysqli_query($mysqli, "UPDATE tickets SET ticket_project_id = $project_id WHERE ticket_id = $ticket_id");
logAction("Ticket", "Edit", "$session_name set ticket $ticket_prefix$ticket_number project to $project_name", $client_id, $ticket_id);
flash_alert("Project changed to <strong>$project_name</strong> for Ticket <strong>$ticket_prefix$ticket_number</strong>");
redirect();
}
if (isset($_POST['add_ticket_watcher'])) {
enforceUserPermission('module_support', 2);
@ -1545,7 +1567,7 @@ if (isset($_POST['add_ticket_reply'])) {
enforceUserPermission('module_support', 2);
$ticket_id = intval($_POST['ticket_id']);
$ticket_reply = mysqli_real_escape_string($mysqli, $_POST['ticket_reply']);
$ticket_reply = $_POST['ticket_reply']; // Reply is SQL escaped below
$ticket_status = intval($_POST['status']);
$client_id = intval($_POST['client_id']);
@ -1566,6 +1588,12 @@ if (isset($_POST['add_ticket_reply'])) {
} else {
$ticket_reply_type = 'Internal';
}
// Add Signature to the end of the ticket reply if not Internal and if there is reply
if ($ticket_reply !== '' && $ticket_reply_type !== 'Internal') {
$ticket_reply .= getFieldById('user_settings',$session_user_id,'user_config_signature', 'raw');
}
$ticket_reply = mysqli_escape_string($mysqli, $ticket_reply); // SQL Escape Ticket Reply
// Update Ticket Status & updated at (in case status didn't change)
mysqli_query($mysqli, "UPDATE tickets SET ticket_status = $ticket_status, ticket_updated_at = NOW() WHERE ticket_id = $ticket_id");

View File

@ -1,17 +1,17 @@
<?php
require_once "../../config.php";
require_once "../../functions.php";
require_once "../../includes/check_login.php";
require_once "../../includes/page_title.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/config.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/functions.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/check_login.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/page_title.php';
// Reporting Perms
enforceUserPermission('module_reporting');
require_once "../../includes/header.php";
require_once "../../includes/top_nav.php";
require_once "includes/reports_side_nav.php";
require_once "../../includes/inc_wrapper.php";
require_once "../../includes/inc_alert_feedback.php";
require_once "../../includes/filter_header.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/top_nav.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/agent/reports/includes/reports_side_nav.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/inc_wrapper.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/inc_alert_feedback.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/filter_header.php';
// Set variable default values
$largest_income_month = 0;

View File

@ -620,7 +620,6 @@ if (isset($_GET['ticket_id'])) {
<textarea
class="form-control tinymceTicket" name="ticket_reply"
placeholder="Type a response">
<?php echo nl2br(getFieldById('user_settings',$session_user_id,'user_config_signature','html')); ?>
</textarea>
</div>
@ -1227,9 +1226,9 @@ if (isset($_GET['ticket_id'])) {
<h5 class="card-title"><i class="fas fa-fw fa-project-diagram mr-2 mt-2"></i>Project</h5>
<div class="card-tools">
<?php if (empty($ticket_resolved_at) && lookupUserPermission("module_support") >= 2) { ?>
<a class="btn btn-light text-secondary btn-sm" href="#">
<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>
</a>
</button>
<?php } ?>
</div>
</div>

View File

@ -1,13 +1,12 @@
<?php
require_once "../../config.php";
require_once "../../functions.php";
require_once "../../includes/check_login.php";
require_once "../../includes/page_title.php";
require_once "../../includes/header.php";
require_once "../../includes/top_nav.php";
require_once "includes/user_side_nav.php";
require_once "../../includes/inc_wrapper.php";
require_once "../../includes/inc_alert_feedback.php";
require_once "../../includes/filter_header.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/config.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/functions.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/check_login.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/page_title.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/top_nav.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/agent/user/includes/user_side_nav.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/inc_wrapper.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/inc_alert_feedback.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/filter_header.php';

View File

@ -12,7 +12,7 @@ if (isset($_POST['edit_your_user_details'])) {
$name = sanitizeInput($_POST['name']);
$email = sanitizeInput($_POST['email']);
$signature = sanitizeInput($_POST['signature']);
$signature = mysqli_escape_string($mysqli,$_POST['signature']);
$existing_file_name = sanitizeInput(getFieldById('users', $session_user_id, 'user_avatar'));

View File

@ -58,12 +58,8 @@ require_once "includes/inc_all_user.php";
<div class="form-group">
<label>Signature</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-pen"></i></span>
</div>
<textarea class="form-control" name="signature" rows="4" placeholder="Create a signature automatically appended to tickets, emails etc"><?php echo getFieldById('user_settings',$session_user_id,'user_config_signature','html'); ?></textarea>
</div>
<textarea class="form-control tinymceTicket" name="signature" rows="4" placeholder="Create a signature automatically appended to tickets, emails etc"><?php echo getFieldById('user_settings',$session_user_id,'user_config_signature','html'); ?>
</textarea>
</div>
<button type="submit" name="edit_your_user_details" class="btn btn-primary btn-responsive"><i class="fas fa-check mr-2"></i>Save</button>

8
api/v1/custom/readme.php Normal file
View File

@ -0,0 +1,8 @@
<?php
/*
- Custom Pages -
If you wish to add custom pages to ITFlow, add them to this directory"
Link to Documentation for File Directory Structure and examples
*/

View File

@ -16,18 +16,18 @@ if (!isset($_SESSION)) {
}
if (!isset($_SESSION['client_logged_in']) || !$_SESSION['client_logged_in']) {
header("Location: login.php");
header("Location: /client/login.php");
die;
}
// Check user type
if ($_SESSION['user_type'] !== 2) {
header("Location: login.php");
header("Location: /client/login.php");
exit();
}
// Set Timezone
require_once "../includes/inc_set_timezone.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/inc_set_timezone.php';
// User IP & UA
$session_ip = sanitizeInput(getIP());

View File

@ -21,7 +21,7 @@
</p>
<?php require_once "../includes/inc_confirm_modal.php"; ?>
<?php require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/inc_confirm_modal.php'; ?>
<!-- jQuery -->
<script src="/plugins/jquery/jquery.min.js"></script>

View File

@ -19,8 +19,8 @@ header("X-Frame-Options: DENY"); // Legacy
<meta name="robots" content="noindex">
<!-- Favicon: If Fav Icon exists, else use the default one -->
<?php if (file_exists('../uploads/favicon.ico')) { ?>
<link rel="icon" type="image/x-icon" href="/uploads/favicon.ico">
<?php if(file_exists($_SERVER['DOCUMENT_ROOT'] . '/uploads/favicon.ico')) { ?>
<link rel="icon" href="/uploads/favicon.ico">
<?php } ?>
<!-- Font Awesome -->
@ -43,10 +43,10 @@ header("X-Frame-Options: DENY"); // Legacy
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item <?php if (basename($_SERVER['PHP_SELF']) == "index.php") {echo "active";} ?>">
<a class="nav-link" href="index.php">Home</a>
<a class="nav-link" href="/client/index.php">Home</a>
</li>
<li class="nav-item">
<a class="nav-link <?php if (basename($_SERVER['PHP_SELF']) == "tickets.php" || basename($_SERVER['PHP_SELF']) == "ticket_add.php" || basename($_SERVER['PHP_SELF']) == "ticket.php") {echo "active";} ?>" href="tickets.php">Tickets</a>
<a class="nav-link <?php if (basename($_SERVER['PHP_SELF']) == "tickets.php" || basename($_SERVER['PHP_SELF']) == "ticket_add.php" || basename($_SERVER['PHP_SELF']) == "ticket.php") {echo "active";} ?>" href="/client/tickets.php">Tickets</a>
</li>
<?php if (($session_contact_primary == 1 || $session_contact_is_billing_contact) && $config_module_enable_accounting == 1) { ?>
@ -55,10 +55,10 @@ header("X-Frame-Options: DENY"); // Legacy
Finance
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown1">
<a class="dropdown-item" href="invoices.php">Invoices</a>
<a class="dropdown-item" href="recurring_invoices.php">Recurring Invoices</a>
<a class="dropdown-item" href="quotes.php">Quotes</a>
<a class="dropdown-item" href="saved_payment_methods.php">Saved Payments</a>
<a class="dropdown-item" href="/client/invoices.php">Invoices</a>
<a class="dropdown-item" href="/client/recurring_invoices.php">Recurring Invoices</a>
<a class="dropdown-item" href="/client/quotes.php">Quotes</a>
<a class="dropdown-item" href="/client/saved_payment_methods.php">Saved Payments</a>
</div>
</li>
<?php } ?>
@ -69,12 +69,12 @@ header("X-Frame-Options: DENY"); // Legacy
Technical
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown2">
<a class="dropdown-item" href="contacts.php">Contacts</a>
<a class="dropdown-item" href="assets.php">Assets</a>
<a class="dropdown-item" href="documents.php">Documents</a>
<a class="dropdown-item" href="domains.php">Domains</a>
<a class="dropdown-item" href="certificates.php">Certificates</a>
<a class="dropdown-item" href="ticket_view_all.php">All tickets</a>
<a class="dropdown-item" href="/client/contacts.php">Contacts</a>
<a class="dropdown-item" href="/client/assets.php">Assets</a>
<a class="dropdown-item" href="/client/documents.php">Documents</a>
<a class="dropdown-item" href="/client/domains.php">Domains</a>
<a class="dropdown-item" href="/client/certificates.php">Certificates</a>
<a class="dropdown-item" href="/client/ticket_view_all.php">All tickets</a>
</div>
</li>
<?php } ?>
@ -110,9 +110,9 @@ header("X-Frame-Options: DENY"); // Legacy
<?php echo stripslashes(nullable_htmlentities($session_contact_name)); ?>
</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="profile.php"><i class="fas fa-fw fa-user mr-2"></i>Account</a>
<a class="dropdown-item" href="/client/profile.php"><i class="fas fa-fw fa-user mr-2"></i>Account</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="post.php?logout"><i class="fas fa-fw fa-sign-out-alt mr-2"></i>Sign out</a>
<a class="dropdown-item" href="/client/post.php?logout"><i class="fas fa-fw fa-sign-out-alt mr-2"></i>Sign out</a>
</div>
</li>
</ul>
@ -128,7 +128,7 @@ header("X-Frame-Options: DENY"); // Legacy
<div class="row mb-3">
<div class="col-md-1 text-center">
<?php if (!empty($session_contact_photo)) { ?>
<img src="<?php echo "../uploads/clients/$session_client_id/$session_contact_photo"; ?>" alt="..." height="50" width="50" class="img-circle img-responsive">
<img src="/uploads/clients/<?= $session_client_id ?>/<?= $session_contact_photo ?>" alt="..." height="50" width="50" class="img-circle img-responsive">
<?php } else { ?>
<span class="fa-stack fa-2x rounded-left">

View File

@ -4,9 +4,9 @@
* Includes for all pages (except login)
*/
require_once '../config.php';
require_once '../includes/load_global_settings.php';
require_once '../functions.php';
require_once 'check_login.php';
require_once 'functions.php';
require_once "header.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/config.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/load_global_settings.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/functions.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/client/includes/check_login.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/client/functions.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/client/includes/header.php';

View File

@ -57,7 +57,17 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['login'])) {
} else {
$sql = mysqli_query($mysqli, "SELECT * FROM users LEFT JOIN contacts ON user_id = contact_user_id LEFT JOIN clients ON contact_client_id = client_id WHERE user_email = '$email' AND client_archived_at IS NULL AND user_archived_at IS NULL AND user_type = 2 AND user_status = 1 LIMIT 1");
$sql = mysqli_query($mysqli, "SELECT * FROM users
LEFT JOIN contacts ON user_id = contact_user_id
LEFT JOIN clients ON contact_client_id = client_id
WHERE user_email = '$email'
AND client_archived_at IS NULL
AND user_archived_at IS NULL
AND user_type = 2
AND user_status = 1
LIMIT 1"
);
$row = mysqli_fetch_array($sql);
$client_id = intval($row['contact_client_id']);
$user_id = intval($row['user_id']);

View File

@ -100,7 +100,16 @@ if (isset($_POST['code']) && $_POST['state'] == session_id()) {
$upn = mysqli_real_escape_string($mysqli, $msgraph_response["userPrincipalName"]);
$sql = mysqli_query($mysqli, "SELECT * FROM users LEFT JOIN contacts ON user_id = contact_user_id LEFT JOIN contact_client_id = client_id WHERE user_email = '$upn' AND user_archived_at IS NULL AND client_archived_at IS NULL AND user_type = 2 AND user_status = 1 LIMIT 1");
$sql = mysqli_query($mysqli, "SELECT * FROM users
LEFT JOIN contacts ON user_id = contact_user_id
LEFT JOIN clients ON contact_client_id = client_id
WHERE user_email = '$upn'
AND user_archived_at IS NULL
AND client_archived_at IS NULL
AND user_type = 2
AND user_status = 1
LIMIT 1"
);
$row = mysqli_fetch_array($sql);
$client_id = intval($row['contact_client_id']);
$user_id = intval($row['user_id']);

3
cron/.htaccess Normal file
View File

@ -0,0 +1,3 @@
<FilesMatch "\.(php)$">
Require all denied
</FilesMatch>

View File

@ -0,0 +1,104 @@
<?php
// Set working directory to the directory this cron script lives at.
chdir(dirname(__FILE__));
// Ensure we're running from command line
if (php_sapi_name() !== 'cli') {
die("This script must be run from the command line.\n");
}
require_once "../config.php";
// Set Timezone
require_once "../includes/inc_set_timezone.php";
require_once "../functions.php";
$sql_settings = mysqli_query($mysqli, "SELECT * FROM settings WHERE settings.company_id = 1");
$row = mysqli_fetch_array($sql_settings);
// Company Settings
$config_enable_cron = intval($row['config_enable_cron']);
// Check cron is enabled
if ($config_enable_cron == 0) {
logApp("Cron-Certificate-Refresher", "error", "Cron Certificate Refresh unable to run - cron not enabled in admin settings.");
exit("Cron: is not enabled -- Quitting..");
}
/*
* ###############################################################################################################
* UPDATE CERTIFICATE EXPIRY DATE
* ###############################################################################################################
*/
$sql_certificates = mysqli_query(
$mysqli,
"SELECT * FROM certificates
LEFT JOIN clients ON certificates.certificate_client_id = clients.client_id
WHERE certificate_archived_at IS NULL
AND client_archived_at IS NULL"
);
while ($row = mysqli_fetch_array($sql_certificates)) {
$certificate_id = intval($row['certificate_id']);
$domain = sanitizeInput($row['certificate_domain']);
$certificate = getSSL($domain);
$expire = sanitizeInput($certificate['expire']);
$issued_by = sanitizeInput($certificate['issued_by']);
$public_key = sanitizeInput($certificate['public_key']);
if (!empty($expire)) {
echo "\n$domain\n";
echo "$issued_by\n";
echo "$expire\n";
echo "$public_key\n\n";
$expire = "'" . $expire . "'";
// Get current certificate info
$original_certificate_info = mysqli_fetch_assoc(mysqli_query($mysqli,"
SELECT
certificates.*,
domains.domain_name
FROM certificates
LEFT JOIN domains ON certificate_domain_id = domain_id
WHERE certificate_id = $certificate_id
"));
// Update
mysqli_query($mysqli,"UPDATE certificates SET certificate_issued_by = '$issued_by', certificate_expire = $expire, certificate_public_key = '$public_key' WHERE certificate_id = $certificate_id");
// Fetch the updated info
$new_certificate_info = mysqli_fetch_assoc(mysqli_query($mysqli,"
SELECT
certificates.*,
domains.domain_name
FROM certificates
LEFT JOIN domains ON certificate_domain_id = domain_id
WHERE certificate_id = $certificate_id
"));
// Compare/log changes between old/new info
$ignored_columns = ["certificate_public_key", "certificate_updated_at", "certificate_accessed_at", "certificate_domain_id"];
foreach ($original_certificate_info as $column => $old_value) {
$new_value = $new_certificate_info[$column];
if ($old_value != $new_value && !in_array($column, $ignored_columns)) {
$column = sanitizeInput($column);
$old_value = sanitizeInput($old_value);
$new_value = sanitizeInput($new_value);
mysqli_query($mysqli,"INSERT INTO certificate_history SET certificate_history_column = '$column', certificate_history_old_value = '$old_value', certificate_history_new_value = '$new_value', certificate_history_certificate_id = $certificate_id");
}
}
} else {
logApp("Cron-Certificate-Refresher", "error", "Cron Certificate Refresh - error updating Error updating $domain.");
error_log("Certificate Cron Error - Error updating $domain");
}
}

1238
cron/cron.php Normal file

File diff suppressed because it is too large Load Diff

8
cron/custom/readme.php Normal file
View File

@ -0,0 +1,8 @@
<?php
/*
- Custom Pages -
If you wish to add custom pages to ITFlow, add them to this directory"
Link to Documentation for File Directory Structure and examples
*/

118
cron/domain_refresher.php Normal file
View File

@ -0,0 +1,118 @@
<?php
// Set working directory to the directory this cron script lives at.
chdir(dirname(__FILE__));
// Ensure we're running from command line
if (php_sapi_name() !== 'cli') {
die("This script must be run from the command line.\n");
}
require_once "../config.php";
// Set Timezone
require_once "../includes/inc_set_timezone.php";
require_once "../functions.php";
$sql_settings = mysqli_query($mysqli, "SELECT * FROM settings WHERE settings.company_id = 1");
$row = mysqli_fetch_array($sql_settings);
// Company Settings
$config_enable_cron = intval($row['config_enable_cron']);
// Check cron is enabled
if ($config_enable_cron == 0) {
logApp("Cron-Domain-Refresher", "error", "Cron Domain Refresh unable to run - cron not enabled in admin settings.");
exit("Cron: is not enabled -- Quitting..");
}
/*
* ###############################################################################################################
* REFRESH DATA
* ###############################################################################################################
*/
// REFRESH DOMAIN WHOIS DATA (1 a day/run)
// Get the oldest updated domain (MariaDB shows NULLs first when ordering by default)
$row = mysqli_fetch_array(mysqli_query($mysqli, "SELECT domain_id, domain_name, domain_expire FROM `domains` WHERE domain_archived_at IS NULL ORDER BY domain_updated_at LIMIT 1"));
if ($row) {
// Get current data in database
$domain_id = intval($row['domain_id']);
$domain_name = sanitizeInput($row['domain_name']);
$current_expire = sanitizeInput($row['domain_expire']);
// Touch the record we're refreshing to ensure we don't loop
mysqli_query($mysqli, "UPDATE domains SET domain_updated_at = NOW() WHERE domain_id = $domain_id");
// Lookup fresh info
$expire = getDomainExpirationDate($domain_name);
$records = getDomainRecords($domain_name);
$a = sanitizeInput($records['a']);
$ns = sanitizeInput($records['ns']);
$mx = sanitizeInput($records['mx']);
$txt = sanitizeInput($records['txt']);
$whois = sanitizeInput($records['whois']);
// Handle expiry date
if (strtotime($expire)) {
$expire = "'" . $expire . "'"; // Valid
} elseif (!strtotime($expire) && strtotime($current_expire)) {
// New expiry date is invalid, but old one is OK - reverting back
$expire = "'" . $current_expire . "'";
} else {
// Neither are valid, setting expiry to NULL
$expire = 'NULL';
}
// Current domain info
$original_domain_info = mysqli_fetch_assoc(mysqli_query($mysqli,"
SELECT
domains.*,
registrar.vendor_name AS registrar_name,
dnshost.vendor_name AS dnshost_name,
mailhost.vendor_name AS mailhost_name,
webhost.vendor_name AS webhost_name
FROM domains
LEFT JOIN vendors AS registrar ON domains.domain_registrar = registrar.vendor_id
LEFT JOIN vendors AS dnshost ON domains.domain_dnshost = dnshost.vendor_id
LEFT JOIN vendors AS mailhost ON domains.domain_mailhost = mailhost.vendor_id
LEFT JOIN vendors AS webhost ON domains.domain_webhost = webhost.vendor_id
WHERE domain_id = $domain_id
"));
// Update the domain
mysqli_query($mysqli, "UPDATE domains SET domain_name = '$domain_name', domain_expire = $expire, domain_ip = '$a', domain_name_servers = '$ns', domain_mail_servers = '$mx', domain_txt = '$txt', domain_raw_whois = '$whois' WHERE domain_id = $domain_id");
echo "Updated $domain_name.";
// Fetch updated info
$new_domain_info = mysqli_fetch_assoc(mysqli_query($mysqli,"
SELECT
domains.*,
registrar.vendor_name AS registrar_name,
dnshost.vendor_name AS dnshost_name,
mailhost.vendor_name AS mailhost_name,
webhost.vendor_name AS webhost_name
FROM domains
LEFT JOIN vendors AS registrar ON domains.domain_registrar = registrar.vendor_id
LEFT JOIN vendors AS dnshost ON domains.domain_dnshost = dnshost.vendor_id
LEFT JOIN vendors AS mailhost ON domains.domain_mailhost = mailhost.vendor_id
LEFT JOIN vendors AS webhost ON domains.domain_webhost = webhost.vendor_id
WHERE domain_id = $domain_id
"));
// Compare/log changes
$ignored_columns = ["domain_updated_at", "domain_accessed_at", "domain_registrar", "domain_webhost", "domain_dnshost", "domain_mailhost"];
foreach ($original_domain_info as $column => $old_value) {
$new_value = $new_domain_info[$column];
if ($old_value != $new_value && !in_array($column, $ignored_columns)) {
$column = sanitizeInput($column);
$old_value = sanitizeInput($old_value);
$new_value = sanitizeInput($new_value);
mysqli_query($mysqli,"INSERT INTO domain_history SET domain_history_column = '$column', domain_history_old_value = '$old_value', domain_history_new_value = '$new_value', domain_history_domain_id = $domain_id");
}
}
}

View File

@ -55,7 +55,7 @@ $config_smtp_port = intval($row['config_smtp_port']);
$config_smtp_encryption = $row['config_smtp_encryption'];
// SMTP provider + shared OAuth fields
$config_smtp_provider = $row['config_smtp_provider'] ?? 'standard_smtp'; // 'standard_smtp' | 'google_oauth' | 'microsoft_oauth'
$config_smtp_provider = $row['config_smtp_provider']; // 'standard_smtp' | 'google_oauth' | 'microsoft_oauth'
$config_mail_oauth_client_id = $row['config_mail_oauth_client_id'] ?? '';
$config_mail_oauth_client_secret = $row['config_mail_oauth_client_secret'] ?? '';
$config_mail_oauth_tenant_id = $row['config_mail_oauth_tenant_id'] ?? '';
@ -68,6 +68,11 @@ if ($config_enable_cron == 0) {
exit("Cron: is not enabled -- Quitting..");
}
if (empty($config_smtp_provider)) {
logApp("Cron-Mail-Queue", "info", "SMTP sending skipped: provider not configured.");
exit(0);
}
/** =======================================================================
* Lock file
* ======================================================================= */

View File

@ -468,7 +468,7 @@ $validate_cert = true;
// Defaults from settings (standard IMAP)
$host = $config_imap_host;
$port = (int)$config_imap_port;
$encr = !empty($config_imap_encryption) ? $config_imap_encryption : null; // 'ssl'|'tls'|null
$encr = !empty($config_imap_encryption) ? $config_imap_encryption : 'notls'; // 'ssl'|'tls'|'notls'
$user = $config_imap_username;
$pass = $config_imap_password;
$auth = null; // 'oauth' for OAuth providers

6
db.sql
View File

@ -1981,7 +1981,7 @@ CREATE TABLE `settings` (
`company_id` int(11) NOT NULL,
`config_current_database_version` varchar(10) NOT NULL,
`config_start_page` varchar(200) DEFAULT 'clients.php',
`config_smtp_provider` enum('standard_smtp','google_oauth','microsoft_oauth') DEFAULT NULL,
`config_smtp_provider` varchar(200) DEFAULT NULL,
`config_smtp_host` varchar(200) DEFAULT NULL,
`config_smtp_port` int(5) DEFAULT NULL,
`config_smtp_encryption` varchar(200) DEFAULT NULL,
@ -1989,7 +1989,7 @@ CREATE TABLE `settings` (
`config_smtp_password` varchar(200) DEFAULT NULL,
`config_mail_from_email` varchar(200) DEFAULT NULL,
`config_mail_from_name` varchar(200) DEFAULT NULL,
`config_imap_provider` enum('standard_imap','google_oauth','microsoft_oauth') DEFAULT NULL,
`config_imap_provider` varchar(200) DEFAULT NULL,
`config_mail_oauth_client_id` varchar(255) DEFAULT NULL,
`config_mail_oauth_client_secret` varchar(255) DEFAULT NULL,
`config_mail_oauth_tenant_id` varchar(255) DEFAULT NULL,
@ -2819,4 +2819,4 @@ CREATE TABLE `vendors` (
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2025-09-25 17:24:06
-- Dump completed on 2025-10-02 14:27:24

View File

@ -1607,6 +1607,8 @@ function getFieldById($table, $id, $field, $escape_method = 'sql') {
// Apply the desired escaping method or auto-detect integer type if using SQL escaping
switch ($escape_method) {
case 'raw':
return $value; // Return as-is from the database
case 'html':
return htmlspecialchars($value ?? '', ENT_QUOTES, 'UTF-8'); // Escape for HTML
case 'json':

View File

@ -1,6 +1,6 @@
<?php
require_once 'includes/guest_header.php';
require_once 'includes/inc_all_guest.php';
DEFINE("WORDING_PAYMENT_FAILED", "<br><h2>There was an error verifying your payment. Please contact us for more information before attempting payment again.</h2>");
@ -300,4 +300,4 @@ if (isset($_GET['invoice_id'], $_GET['url_key']) && !isset($_GET['payment_intent
exit(WORDING_PAYMENT_FAILED);
}
require_once 'includes/guest_footer.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php';

View File

@ -1,10 +1,10 @@
<?php
require_once "includes/guest_header.php";
require_once "includes/inc_all_guest.php";
if (!isset($_GET['invoice_id'], $_GET['url_key'])) {
echo "<br><h2>Oops, something went wrong! Please raise a ticket if you believe this is an error.</h2>";
require_once "includes/guest_footer.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php';
exit();
}
@ -25,7 +25,7 @@ $sql = mysqli_query(
if (mysqli_num_rows($sql) !== 1) {
// Invalid invoice/key
echo "<br><h2>Oops, something went wrong! Please raise a ticket if you believe this is an error.</h2>";
require_once "includes/guest_footer.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php';
exit();
}
@ -478,4 +478,4 @@ if ($outstanding_invoices_count > 0) { ?>
<?php } // End previous unpaid invoices
require_once "includes/guest_footer.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php';

View File

@ -4,7 +4,7 @@ header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
require_once "includes/guest_header.php";
require_once "includes/inc_all_guest.php";
//Initialize the HTML Purifier to prevent XSS
@ -39,7 +39,7 @@ $currency_format = numfmt_create($company_locale, NumberFormatter::CURRENCY);
<?php
if (!isset($_GET['id']) || !isset($_GET['key'])) {
echo "<div class='alert alert-danger'>Incorrect URL.</div>";
include "includes/guest_footer.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php';
exit();
}
@ -53,7 +53,7 @@ $row = mysqli_fetch_array($sql);
// Check we got a result
if (mysqli_num_rows($sql) !== 1 || !$row) {
echo "<div class='alert alert-danger' >No item to view. Check with the person that sent you this link to ensure it is correct and has not expired.</div>";
include "includes/guest_footer.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php';
exit();
}
@ -61,7 +61,7 @@ if (mysqli_num_rows($sql) !== 1 || !$row) {
// Check item share is active & hasn't been viewed too many times but allow 0 views as that is consider infinite views
if ($row['item_active'] !== "1" || ($row['item_view_limit'] > 0 && $row['item_views'] >= $row['item_view_limit'])) {
echo "<div class='alert alert-danger'>Item cannot be viewed at this time. Check with the person that sent you this link to ensure it is correct and has not expired.</div>";
include "includes/guest_footer.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php';
exit();
}
@ -123,7 +123,7 @@ if ($item_type == "Document") {
if (mysqli_num_rows($doc_sql) !== 1 || !$doc_row) {
echo "<div class='alert alert-danger'>Error retrieving document to view.</div>";
require_once "includes/guest_footer.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php';
exit();
}
@ -150,7 +150,7 @@ if ($item_type == "Document") {
if (mysqli_num_rows($file_sql) !== 1 || !$file_row) {
echo "<div class='alert alert-danger'>Error retrieving file.</div>";
include "includes/guest_footer.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php';
exit();
}
@ -171,7 +171,7 @@ if ($item_type == "Document") {
$credential_row = mysqli_fetch_array($credential_sql);
if (mysqli_num_rows($credential_sql) !== 1 || !$credential_row) {
echo "<div class='alert alert-danger'>Error retrieving login.</div>";
include "includes/guest_footer.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php';
exit();
}
@ -274,6 +274,4 @@ if ($item_type == "Document") {
</div>
<?php
require_once "includes/guest_footer.php";
?>
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php';

View File

@ -1,11 +1,11 @@
<?php
require_once "includes/guest_header.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/guest/includes/inc_all_guest.php';
if (!isset($_GET['quote_id'], $_GET['url_key'])) {
echo "<br><h2>Oops, something went wrong! Please raise a ticket if you believe this is an error.</h2>";
require_once "includes/guest_footer.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php';
exit();
}
@ -27,7 +27,7 @@ $sql = mysqli_query(
if (mysqli_num_rows($sql) !== 1) {
// Invalid quote/key
echo "<br><h2>Oops, something went wrong! Please raise a ticket if you believe this is an error.</h2>";
require_once "includes/guest_footer.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php';
exit();
}
@ -60,10 +60,6 @@ $contact_mobile_country_code = nullable_htmlentities($row['contact_mobile_countr
$contact_mobile = nullable_htmlentities(formatPhoneNumber($row['contact_mobile'], $contact_mobile_country_code));
$client_website = nullable_htmlentities($row['client_website']);
$client_currency_code = nullable_htmlentities($row['client_currency_code']);
$client_net_terms = intval($row['client_net_terms']);
if ($client_net_terms == 0) {
$client_net_terms = intval($row['config_default_net_terms']);
}
$sql = mysqli_query($mysqli, "SELECT * FROM companies, settings WHERE companies.company_id = settings.company_id AND companies.company_id = 1");
$row = mysqli_fetch_array($sql);
@ -133,7 +129,7 @@ if ($quote_status == "Draft" || $quote_status == "Sent" || $quote_status == "Vie
<div class="row mb-3">
<?php if (file_exists("../uploads/settings/$company_logo")) { ?>
<div class="col-sm-2">
<img class="img-fluid" src="<?php echo "../uploads/settings/$company_logo"; ?>" alt="Company logo">
<img class="img-fluid" src="<?php echo "/uploads/settings/$company_logo"; ?>" alt="Company logo">
</div>
<?php } ?>
<div class="col-sm-6 <?php if (!file_exists("../uploads/settings/$company_logo")) { echo "col-sm-8"; } ?>">
@ -301,4 +297,4 @@ if ($quote_status == "Draft" || $quote_status == "Sent" || $quote_status == "Vie
<?php
require_once "guest_quote_upload_file_modal.php";
require_once "includes/guest_footer.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php';

View File

@ -1,6 +1,6 @@
<?php
require_once "includes/guest_header.php";
require_once "includes/inc_all_guest.php";
//Initialize the HTML Purifier to prevent XSS
require "../plugins/htmlpurifier/HTMLPurifier.standalone.php";
@ -12,12 +12,24 @@ $purifier = new HTMLPurifier($purifier_config);
if (!isset($_GET['ticket_id'], $_GET['url_key'])) {
echo "<br><h2>Oops, something went wrong! Please raise a ticket if you believe this is an error.</h2>";
require_once "includes/guest_footer.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php';
exit();
}
// Company info
$company_sql_row = mysqli_fetch_array(mysqli_query($mysqli, "SELECT company_phone, company_website FROM companies, settings WHERE companies.company_id = settings.company_id AND companies.company_id = 1"));
$company_sql_row = mysqli_fetch_array(mysqli_query($mysqli, "
SELECT
company_phone,
company_phone_country_code,
company_website
FROM
companies,
settings
WHERE
companies.company_id = settings.company_id
AND companies.company_id = 1"
));
$company_phone_country_code = nullable_htmlentities($company_sql_row['company_phone_country_code']);
$company_phone = nullable_htmlentities(formatPhoneNumber($company_sql_row['company_phone'], $company_phone_country_code));
$company_website = nullable_htmlentities($company_sql_row['company_website']);
@ -35,7 +47,7 @@ $ticket_sql = mysqli_query($mysqli,
if (mysqli_num_rows($ticket_sql) !== 1) {
// Invalid invoice/key
echo "<br><h2>Oops, something went wrong! Please raise a ticket if you believe this is an error.</h2>";
require_once "includes/guest_footer.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php';
exit();
}
@ -198,7 +210,7 @@ if ($ticket_row) {
?>
<script src="../js/pretty_content.js"></script>
<script src="/js/pretty_content.js"></script>
<?php } else {
echo "Ticket ID not found!";
@ -209,4 +221,4 @@ if ($ticket_row) {
</div>
<?php
require_once "includes/guest_footer.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php';

View File

@ -1,29 +0,0 @@
</div><!-- /.container-fluid -->
</div>
<!-- /.content -->
</div>
<!-- /.content-wrapper -->
</div>
<!-- ./wrapper -->
<!-- REQUIRED SCRIPTS -->
<?php require_once "../includes/inc_confirm_modal.php"; ?>
<!-- jQuery -->
<script src="/plugins/jquery/jquery.min.js"></script>
<!-- Bootstrap 4 -->
<script src="/plugins/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- AdminLTE App -->
<script src="/plugins/adminlte/js/adminlte.min.js"></script>
<!-- Custom js -->
<script src="/plugins/tempusdominus-bootstrap-4/js/tempusdominus-bootstrap-4.min.js"></script>
<script src="/plugins/moment/moment.min.js"></script>
<script src='/plugins/daterangepicker/daterangepicker.js'></script>
<script src='/plugins/select2/js/select2.min.js'></script>
<script src='/plugins/inputmask/inputmask.min.js'></script>
<script src="/js/app.js"></script>
<script src="/js/pretty_content.js"></script>
<script src="/js/confirm_modal.js"></script>
</body>
</html>

View File

@ -1,27 +1,3 @@
<?php
require_once "../config.php";
require_once "../functions.php";
require_once "../includes/load_global_settings.php";
session_start();
// Set Timezone
require_once "../includes/inc_set_timezone.php";
$ip = sanitizeInput(getIP());
$user_agent = sanitizeInput($_SERVER['HTTP_USER_AGENT']);
$os = sanitizeInput(getOS($user_agent));
$browser = sanitizeInput(getWebBrowser($user_agent));
// Get Company Name
$sql = mysqli_query($mysqli, "SELECT company_name FROM companies WHERE company_id = 1");
$row = mysqli_fetch_array($sql);
$session_company_name = $row['company_name'];
?>
<!DOCTYPE html>
<html lang="en">
<head>
@ -36,8 +12,8 @@ $session_company_name = $row['company_name'];
Favicon
If Fav Icon exists else use the default one
-->
<?php if(file_exists('../uploads/favicon.ico')) { ?>
<link rel="icon" type="image/x-icon" href="/uploads/favicon.ico">
<?php if(file_exists($_SERVER['DOCUMENT_ROOT'] . '/uploads/favicon.ico')) { ?>
<link rel="icon" href="/uploads/favicon.ico">
<?php } ?>
<!-- Font Awesome Icons -->
@ -46,37 +22,15 @@ $session_company_name = $row['company_name'];
<link rel="stylesheet" href="/plugins/adminlte/css/adminlte.min.css">
<!-- Custom Style Sheet -->
<link href="/plugins/tempusdominus-bootstrap-4/css/tempusdominus-bootstrap-4.min.css" rel="stylesheet" type="text/css">
<link href="/plugins/select2/css/select2.min.css" rel="stylesheet" type="text/css">
<link href="/plugins/select2-bootstrap4-theme/select2-bootstrap4.min.css" rel="stylesheet" type="text/css">
<link href='/plugins/daterangepicker/daterangepicker.css' rel='stylesheet' />
<link rel="stylesheet" href="/plugins/tempusdominus-bootstrap-4/css/tempusdominus-bootstrap-4.min.css">
<link rel="stylesheet" href="/plugins/select2/css/select2.min.css">
<link rel="stylesheet" href="/plugins/select2-bootstrap4-theme/select2-bootstrap4.min.css">
<link rel="stylesheet" href='/plugins/daterangepicker/daterangepicker.css'>
<!-- Scripts -->
<script src="/plugins/jquery/jquery.min.js"></script>
<script src="/plugins/toastr/toastr.min.js"></script>
</head>
<body class="layout-top-nav">
<div class="wrapper text-sm">
<!-- Content Wrapper. Contains page content -->
<div class="content-wrapper">
<!-- Main content -->
<div class="content">
<div class="container">
<?php
//Alert Feedback
if (!empty($_SESSION['alert_message'])) {
if (!isset($_SESSION['alert_type'])) {
$_SESSION['alert_type'] = "info";
}
?>
<div class="alert alert-<?php echo $_SESSION['alert_type']; ?>" id="alert">
<?php echo nullable_htmlentities($_SESSION['alert_message']); ?>
<button class='close' data-dismiss='alert'>&times;</button>
</div>
<?php
unset($_SESSION['alert_type']);
unset($_SESSION['alert_message']);
}
?>
<div class="wrapper text-sm">

View File

@ -0,0 +1,32 @@
<?php
// Configuration & core
require_once $_SERVER['DOCUMENT_ROOT'] . '/config.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/functions.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/load_global_settings.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/session_init.php';
// Set Timezone
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/inc_set_timezone.php';
$ip = sanitizeInput(getIP());
$user_agent = sanitizeInput($_SERVER['HTTP_USER_AGENT']);
$os = sanitizeInput(getOS($user_agent));
$browser = sanitizeInput(getWebBrowser($user_agent));
// Get Company Name
$sql = mysqli_query($mysqli, "SELECT company_name FROM companies WHERE company_id = 1");
$row = mysqli_fetch_array($sql);
$session_company_name = $row['company_name'];
// Page setup
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/page_title.php';
// Layout UI
require_once $_SERVER['DOCUMENT_ROOT'] . '/guest/includes/guest_header.php';
// Wrapper & alerts
require_once $_SERVER['DOCUMENT_ROOT'] . '/guest/includes/inc_wrapper.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/inc_alert_feedback.php';
//require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/filter_header.php';

View File

@ -0,0 +1,6 @@
<!-- Content Wrapper. Contains page content -->
<div class="content-wrapper">
<!-- Main content -->
<div class="content">
<div class="container">

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

View File

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

View File

@ -7,6 +7,17 @@ if (basename(dirname($_SERVER['REQUEST_URI'])) === 'admin') { ?>
<p class="text-right font-weight-light">ITFlow <?php echo APP_VERSION ?> &nbsp; · &nbsp; <a target="_blank" href="https://docs.itflow.org">Docs</a> &nbsp; · &nbsp; <a target="_blank" href="https://forum.itflow.org">Forum</a> &nbsp; · &nbsp; <a target="_blank" href="https://services.itflow.org">Services</a></p>
<br>
<?php } ?>
<?php
if (basename(dirname($_SERVER['REQUEST_URI'])) === 'guest') { ?>
<p class="text-center">
<?php
echo nullable_htmlentities($session_company_name);
if (!$config_whitelabel_enabled) {
echo '<br><small class="text-muted">Powered by ITFlow</small>';
}
?>
</p>
<?php } ?>
</div><!-- /.container-fluid -->
</div> <!-- /.content -->

View File

@ -19,9 +19,9 @@ header("X-Frame-Options: DENY");
<title><?= $session_company_name; ?></title>
<!-- Favicon -->
<?php if(file_exists(__DIR__ . '../uploads/favicon.ico')): ?>
<?php if(file_exists($_SERVER['DOCUMENT_ROOT'] . '/uploads/favicon.ico')) { ?>
<link rel="icon" type="image/x-icon" href="/uploads/favicon.ico">
<?php endif; ?>
<?php } ?>
<!-- Font Awesome -->
<link rel="stylesheet" href="/plugins/fontawesome-free/css/all.min.css">

View File

@ -1,8 +1,8 @@
<?php
require_once "../../../config.php";
require_once "../../../functions.php";
require_once "../../../includes/check_login.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/config.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/functions.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/check_login.php';
header('Content-Type: application/json');

View File

@ -773,4 +773,3 @@ $locales_array = [
'zu_ZA' => 'Zulu (South Africa)',
'zu' => 'Zulu',
];
?>

View File

@ -1,28 +1,37 @@
<?php
// App setup is complete?
// Check if the app is set up
if (file_exists("config.php")) {
require_once "config.php";
// Check if setup is enabled (not completed)
if (!isset($config_enable_setup) || $config_enable_setup == 1) {
header("Location: /setup");
exit();
}
// Start the session
require_once "includes/session_init.php";
// If they are an app user, send them to their start page
// If user is an agent
if (isset($_SESSION['logged'])) {
require_once "includes/load_global_settings.php";
header("Location: /agent/$config_start_page");
exit();
// If they're a client, send them to the client area
// If user is a client
} elseif (isset($_SESSION['client_logged_in'])) {
header("Location: /client/");
exit();
// Else, require login
// Not logged in
} else {
header("Location: /login.php");
exit();
}
// Installation needs to be completed
} else {
header("Location: /setup");
// If config.php doesn't exist, redirect to setup
header("Location: /setup");
exit();
}

View File

@ -3,12 +3,22 @@
// Enforce a Content Security Policy for security against cross-site scripting
header("Content-Security-Policy: default-src 'self'");
// Check if the config.php file exists
if (!file_exists('config.php')) {
header("Location: /setup"); //must use header instead of redirect as functions isnt included yet.
// Redirect to the setup page if config.php doesn't exist
header("Location: /setup"); // Must use header as functions aren't included yet
exit();
}
require_once "config.php";
// Check if setup mode is enabled or the variable is missing
if (!isset($config_enable_setup) || $config_enable_setup == 1) {
// Redirect to the setup page
header("Location: /setup");
exit();
}
// Set Timezone
require_once "includes/inc_set_timezone.php";
@ -215,8 +225,13 @@ if (isset($_POST['login'])) {
//}
}
if (isset($_GET['last_visited'])) {
// Redirect to last visited or config home
if (isset($_GET['last_visited']) && (str_starts_with(base64_decode($_GET['last_visited']), '/agent') || str_starts_with(base64_decode($_GET['last_visited']), '/admin'))) {
redirect($_SERVER["REQUEST_SCHEME"] . "://" . $config_base_url . base64_decode($_GET['last_visited']) );
} else {
redirect("agent/$config_start_page");
}

View File

@ -1,8 +1,6 @@
<?php
require_once "../config.php";
require_once "../functions.php";
require_once "../includes/check_login.php";
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/modal_header.php';
header('Content-Type: application/json');
@ -137,7 +135,5 @@ $(document).ready(function () {
</script>
<?php
$content = ob_get_clean();
// Return the title and content as a JSON response
echo json_encode(['content' => $content]);
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/modal_footer.php';

View File

@ -0,0 +1,8 @@
<?php
/*
- Custom Pages -
If you wish to add custom pages to ITFlow, add them to this directory"
Link to Documentation for File Directory Structure and examples
*/

8
setup/custom/readme.php Normal file
View File

@ -0,0 +1,8 @@
<?php
/*
- Custom Pages -
If you wish to add custom pages to ITFlow, add them to this directory"
Link to Documentation for File Directory Structure and examples
*/