Merge branch 'master' into techbar

This commit is contained in:
ThaMunsta
2024-08-29 16:48:28 -04:00
154 changed files with 2723 additions and 1571 deletions

View File

@@ -12,4 +12,4 @@ jobs:
- uses: actions/checkout@v2
- name: Check PHP syntax errors
uses: overtrue/phplint@4.1.0
uses: overtrue/phplint@9.4.1

View File

@@ -1,5 +1,6 @@
<?php
$key = randomString(156);
$decryptPW = randomString(160);
?>
<div class="modal" id="addApiKeyModal" tabindex="-1">
<div class="modal-dialog">
@@ -13,64 +14,103 @@ $key = randomString(156);
<form action="post.php" method="post" autocomplete="off">
<div class="modal-body bg-white">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token'] ?>">
<input type="hidden" name="key" value="<?php echo $key ?>">
<div class="form-group">
<label>API Key <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-key"></i></span>
</div>
<input type="text" class="form-control" value="<?php echo $key ?>" required disabled>
<div class="input-group-append">
<button class="btn btn-default clipboardjs" type="button" data-clipboard-text="<?php echo $key; ?>"><i class="fa fa-fw fa-copy"></i></button>
</div>
</div>
</div>
<ul class="nav nav-pills nav-justified mb-3">
<li class="nav-item">
<a class="nav-link active" data-toggle="pill" href="#pills-api-details">Details</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="pill" href="#pills-api-keys">Keys</a>
</li>
</ul>
<hr>
<div class="form-group">
<label>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-sticky-note"></i></span>
<div class="tab-content">
<div class="tab-pane fade show active" id="pills-api-details">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token'] ?>">
<input type="hidden" name="key" value="<?php echo $key ?>">
<input type="hidden" name="password" value="<?php echo $decryptPW ?>">
<div class="form-group">
<label>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-sticky-note"></i></span>
</div>
<input type="text" class="form-control" name="name" placeholder="Key Name" required autofocus>
</div>
</div>
<div class="form-group">
<label>Expiration Date <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-calendar"></i></span>
</div>
<input type="date" class="form-control" name="expire" min="<?php echo date('Y-m-d')?>" max="2999-12-31" required>
</div>
</div>
<div class="form-group">
<label>Client Access <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-user"></i></span>
</div>
<select class="form-control select2" name="client" required>
<option value="0"> ALL CLIENTS </option>
<?php
$sql = mysqli_query($mysqli, "SELECT * FROM clients ORDER BY client_name ASC");
while ($row = mysqli_fetch_array($sql)) {
$client_id = intval($row['client_id']);
$client_name = nullable_htmlentities($row['client_name']); ?>
<option value="<?php echo $client_id; ?>"><?php echo "$client_name (Client ID: $client_id)"; ?></option>
<?php } ?>
</select>
</div>
</div>
<input type="text" class="form-control" name="name" placeholder="Key Name" required autofocus>
</div>
<div class="tab-pane fade" id="pills-api-keys">
<div class="form-group">
<label>API Key <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-key"></i></span>
</div>
<input type="text" class="form-control" value="<?php echo $key ?>" required disabled>
<div class="input-group-append">
<button class="btn btn-default clipboardjs" type="button" data-clipboard-text="<?php echo $key; ?>"><i class="fa fa-fw fa-copy"></i></button>
</div>
</div>
</div>
<div class="form-group">
<label>Login credential decryption password <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-unlock-alt"></i></span>
</div>
<input type="text" class="form-control" value="<?php echo $decryptPW ?>" required disabled>
<div class="input-group-append">
<button class="btn btn-default clipboardjs" type="button" data-clipboard-text="<?php echo $decryptPW; ?>"><i class="fa fa-fw fa-copy"></i></button>
</div>
</div>
</div>
<br>
<div class="form-group">
<label>I have made a copy of the key(s)<strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<input type="checkbox" name="ack" value="1" required>
</div>
</div>
</div>
</div>
</div>
<div class="form-group">
<label>Expiration Date <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-calendar"></i></span>
</div>
<input type="date" class="form-control" name="expire" min="<?php echo date('Y-m-d')?>" max="2999-12-31" required>
</div>
</div>
<div class="form-group">
<label>Client Access <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-user"></i></span>
</div>
<select class="form-control select2" name="client" required>
<option value="0"> ALL CLIENTS </option>
<?php
$sql = mysqli_query($mysqli, "SELECT * FROM clients ORDER BY client_name ASC");
while ($row = mysqli_fetch_array($sql)) {
$client_id = intval($row['client_id']);
$client_name = nullable_htmlentities($row['client_name']); ?>
<option value="<?php echo $client_id; ?>"><?php echo "$client_name (Client ID: $client_id)"; ?></option>
<?php } ?>
</select>
</div>
</div>
</div>
<div class="modal-footer bg-white">
<button type="submit" name="add_api_key" 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="fas fa-times mr-2"></i>Cancel</button>

View File

@@ -186,7 +186,7 @@ $loadedModules = get_loaded_extensions();
//Get Server Info / Service versions
$phpVersion = phpversion();
$mysqlVersion = $mysqli->server_version;
$databaseInfo = mysqli_get_server_info($mysqli) . " / " . $mysqli->server_version;
$operatingSystem = php_uname();
$webServer = $_SERVER['SERVER_SOFTWARE'];
$errorLog = ini_get('error_log') ?: "Debian/Ubuntu default is usually /var/log/apache2/error.log";
@@ -204,7 +204,7 @@ $updates = fetchUpdates();
<?php
echo "PHP version: " . $phpVersion . "<br>";
echo "MySQL Version: " . $mysqlVersion . "<br>";
echo "Database Version: " . $databaseInfo . "<br>";
echo "Operating System: " . $operatingSystem . "<br>";
echo "Web Server: " . $webServer . "<br>";
echo "Apache/PHP Error Log: " . $errorLog

View File

@@ -16,6 +16,16 @@ if (isset($_GET['user']) & !empty($_GET['user'])) {
$user = '';
}
// Client Filter
if (isset($_GET['client']) & !empty($_GET['client'])) {
$client_query = 'AND (log_client_id = ' . intval($_GET['client']) . ')';
$client = intval($_GET['client']);
} else {
// Default - any
$client_query = '';
$client = '';
}
// Log Type Filter
if (isset($_GET['type']) & !empty($_GET['type'])) {
$log_type_query = "AND (log_type = '" . sanitizeInput($_GET['type']) . "')";
@@ -42,14 +52,15 @@ $url_query_strings_sort = http_build_query($get_copy);
$sql = mysqli_query(
$mysqli,
"SELECT SQL_CALC_FOUND_ROWS * FROM logs
LEFT JOIN users ON log_user_id = user_id
LEFT JOIN clients ON log_client_id = client_id
WHERE (log_type LIKE '%$q%' OR log_action LIKE '%$q%' OR log_description LIKE '%$q%' OR log_ip LIKE '%$q%' OR log_user_agent LIKE '%$q%' OR user_name LIKE '%$q%' OR client_name LIKE '%$q%')
AND DATE(log_created_at) BETWEEN '$dtf' AND '$dtt'
$user_query
$log_type_query
$log_action_query
ORDER BY $sort $order LIMIT $record_from, $record_to"
LEFT JOIN users ON log_user_id = user_id
LEFT JOIN clients ON log_client_id = client_id
WHERE (log_type LIKE '%$q%' OR log_action LIKE '%$q%' OR log_description LIKE '%$q%' OR log_ip LIKE '%$q%' OR log_user_agent LIKE '%$q%' OR user_name LIKE '%$q%' OR client_name LIKE '%$q%')
AND DATE(log_created_at) BETWEEN '$dtf' AND '$dtt'
$user_query
$client_query
$log_type_query
$log_action_query
ORDER BY $sort $order LIMIT $record_from, $record_to"
);
$num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
@@ -72,6 +83,27 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</div>
</div>
</div>
<div class="col-sm-2">
<div class="form-group">
<select class="form-control select2" name="client" onchange="this.form.submit()">
<option value="" <?php if ($client == "") { echo "selected"; } ?>>- All Clients -</option>
<?php
$sql_clients_filter = mysqli_query($mysqli, "SELECT * FROM clients ORDER BY client_name ASC");
while ($row = mysqli_fetch_array($sql_clients_filter)) {
$client_id = intval($row['client_id']);
$client_name = nullable_htmlentities($row['client_name']);
?>
<option <?php if ($client == $client_id) { echo "selected"; } ?> value="<?php echo $client_id; ?>"><?php echo $client_name; ?></option>
<?php
}
?>
</select>
</div>
</div>
<div class="col-sm-2">
<div class="form-group">
<select class="form-control select2" name="user" onchange="this.form.submit()">
@@ -170,13 +202,14 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<tr>
<th><a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=log_created_at&order=<?php echo $disp; ?>">Timestamp</a></th>
<th><a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=user_name&order=<?php echo $disp; ?>">User</a></th>
<?php if(empty($client)) { ?>
<th><a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=client_name&order=<?php echo $disp; ?>">Client</a></th>
<?php } ?>
<th><a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=log_type&order=<?php echo $disp; ?>">Type</a></th>
<th><a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=log_action&order=<?php echo $disp; ?>">Action</a></th>
<th><a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=log_description&order=<?php echo $disp; ?>">Description</a></th>
<th><a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=log_ip&order=<?php echo $disp; ?>">IP Address</a></th>
<th><a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=log_user_agent&order=<?php echo $disp; ?>">User Agent</a></th>
<th><a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=log_entity_id&order=<?php echo $disp; ?>">Entity ID</a></th>
</tr>
</thead>
<tbody>
@@ -213,13 +246,14 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<tr>
<td><?php echo $log_created_at; ?></td>
<td><?php echo $user_name_display; ?></td>
<?php if(empty($client)) { ?>
<td><?php echo $client_name_display; ?></td>
<?php } ?>
<td><?php echo $log_type; ?></td>
<td><?php echo $log_action; ?></td>
<td><?php echo $log_description; ?></td>
<td><?php echo $log_ip; ?></td>
<td><?php echo "$log_user_os<br>$log_user_browser"; ?></td>
<td><?php echo $log_entity_id; ?></td>
</tr>
<?php

View File

@@ -48,9 +48,12 @@ $git_log = shell_exec("git log $repo_branch..origin/$repo_branch --pretty=format
<small class="text-secondary">Current DB Version: <?php echo CURRENT_DATABASE_VERSION; ?></small>
<br>
<small class="text-secondary">Latest DB Version: <?php echo LATEST_DATABASE_VERSION; ?></small>
<br>
<small class="text-secondary">Branch: <?php echo $repo_branch; ?></small>
<?php } else { ?>
<p class="text-secondary">Current Database Version:<br><strong class="text-dark"><?php echo CURRENT_DATABASE_VERSION; ?></strong></p>
<p class="text-secondary">Current App Version:<br><strong class="text-dark"><?php echo $current_version; ?></strong></p>
<p class="text-secondary">Branch:<br><strong class="text-dark"><?php echo $repo_branch; ?></strong></p>
<p class="text-muted">You are up to date!<br>Everything is going to be alright</p>
<i class="far fa-3x text-dark fa-smile-wink"></i><br>
<?php }

View File

@@ -176,7 +176,7 @@ if (isset($_POST['asset_set_notes'])) {
mysqli_query($mysqli, "UPDATE assets SET asset_notes = '$notes' WHERE asset_id = $asset_id");
// Logging
mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Assets', log_action = 'Modify', log_description = '$session_name modified asset notes', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_user_id = $session_user_id");
mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Asset', log_action = 'Modify', log_description = '$session_name modified asset notes', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_user_id = $session_user_id");
}

View File

@@ -0,0 +1,30 @@
<?php
require_once '../validate_api_key.php';
require_once '../require_post_method.php';
// Parse info
require_once 'credential_model.php';
// Default
$insert_id = false;
if (!empty($api_key_decrypt_password) && !empty($name) && !(empty($password))) {
// Add credential
$insert_sql = mysqli_query($mysqli,"INSERT INTO logins SET login_name = '$name', login_description = '$description', login_uri = '$uri', login_uri_2 = '$uri_2', login_username = '$username', login_password = '$password', login_otp_secret = '$otp_secret', login_note = '$note', login_important = $important, login_contact_id = $contact_id, login_vendor_id = $vendor_id, login_asset_id = $asset_id, login_software_id = $software_id, login_client_id = $client_id");
// Check insert & get insert ID
if ($insert_sql) {
$insert_id = mysqli_insert_id($mysqli);
//Logging
mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Credential', log_action = 'Create', log_description = '$name via API ($api_key_name)', log_ip = '$ip', log_user_agent = '$user_agent', log_client_id = $client_id");
mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'API', log_action = 'Success', log_description = 'Created credential $name via API ($api_key_name)', log_ip = '$ip', log_user_agent = '$user_agent', log_client_id = $client_id");
}
}
// Output
require_once '../create_output.php';

View File

@@ -0,0 +1,120 @@
<?php
// Variable assignment from POST (or: blank/from DB is updating)
$api_key_decrypt_password = '';
if (isset($_POST['api_key_decrypt_password'])) {
$api_key_decrypt_password = $_POST['api_key_decrypt_password']; // No sanitization
}
if (isset($_POST['login_name'])) {
$name = sanitizeInput($_POST['login_name']);
} elseif (isset($credential_row) && isset($credential_row['login_name'])) {
$name = $credential_row['login_name'];
} else {
$name = '';
}
if (isset($_POST['login_description'])) {
$description = sanitizeInput($_POST['login_description']);
} elseif (isset($credential_row) && isset($credential_row['login_description'])) {
$description = $credential_row['login_description'];
} else {
$description = '';
}
if (isset($_POST['login_uri'])) {
$uri = sanitizeInput($_POST['login_uri']);
} elseif (isset($credential_row) && isset($credential_row['login_uri'])) {
$uri = $credential_row['login_uri'];
} else {
$uri = '';
}
if (isset($_POST['login_uri_2'])) {
$uri_2 = sanitizeInput($_POST['login_uri_2']);
} elseif (isset($credential_row) && isset($credential_row['login_uri_2'])) {
$uri_2 = $credential_row['login_uri_2'];
} else {
$uri_2 = '';
}
if (isset($_POST['login_username'])) {
$username = $_POST['login_username'];
$username = apiEncryptLoginEntry($username, $api_key_decrypt_hash, $api_key_decrypt_password);
} elseif (isset($credential_row) && isset($credential_row['login_username'])) {
$username = $credential_row['login_username'];
} else {
$username = '';
}
if (isset($_POST['login_password'])) {
$password = $_POST['login_password'];
$password = apiEncryptLoginEntry($password, $api_key_decrypt_hash, $api_key_decrypt_password);
$password_changed = true;
} elseif (isset($credential_row) && isset($credential_row['login_password'])) {
$password = $credential_row['login_password'];
$password_changed = false;
} else {
$password = '';
$password_changed = false;
}
if (isset($_POST['login_otp_secret'])) {
$otp_secret = sanitizeInput($_POST['login_otp_secret']);
} elseif (isset($credential_row) && isset($credential_row['login_otp_secret'])) {
$otp_secret = $credential_row['login_otp_secret'];
} else {
$otp_secret = '';
}
if (isset($_POST['login_note'])) {
$note = sanitizeInput($_POST['login_note']);
} elseif (isset($credential_row) && isset($credential_row['login_note'])) {
$note = $credential_row['login_note'];
} else {
$note = '';
}
if (isset($_POST['login_important'])) {
$important = intval($_POST['login_important']);
} elseif (isset($credential_row) && isset($credential_row['login_important'])) {
$important = $credential_row['login_important'];
} else {
$important = '';
}
if (isset($_POST['login_contact_id'])) {
$contact_id = intval($_POST['login_contact_id']);
} elseif (isset($credential_row) && isset($credential_row['login_contact_id'])) {
$contact_id = $credential_row['login_contact_id'];
} else {
$contact_id = '';
}
if (isset($_POST['login_vendor_id'])) {
$vendor_id = intval($_POST['login_vendor_id']);
} elseif (isset($credential_row) && isset($credential_row['login_vendor_id'])) {
$vendor_id = $credential_row['login_vendor_id'];
} else {
$vendor_id = '';
}
if (isset($_POST['login_asset_id'])) {
$asset_id = intval($_POST['login_asset_id']);
} elseif (isset($credential_row) && isset($credential_row['login_asset_id'])) {
$asset_id = $credential_row['login_asset_id'];
} else {
$asset_id = '';
}
if (isset($_POST['login_software_id'])) {
$software_id = intval($_POST['login_software_id']);
} elseif (isset($credential_row) && isset($credential_row['login_software_id'])) {
$software_id = $credential_row['login_software_id'];
} else {
$software_id = '';
}

View File

@@ -0,0 +1,59 @@
<?php
require_once '../validate_api_key.php';
require_once '../require_get_method.php';
// Defaults
$sql = false;
$api_key_decrypt_password = '';
if (isset($_GET['api_key_decrypt_password'])) {
$api_key_decrypt_password = $_GET['api_key_decrypt_password']; // No sanitization
}
// Specific credential/login via ID (single)
if (isset($_GET['login_id']) && !empty($api_key_decrypt_password)) {
$id = intval($_GET['login_id']);
$sql = mysqli_query($mysqli, "SELECT * FROM logins WHERE login_id = '$id' AND login_client_id LIKE '$client_id' LIMIT 1");
} elseif (!empty($api_key_decrypt_password)) {
// All credentials ("logins")
$sql = mysqli_query($mysqli, "SELECT * FROM logins WHERE login_client_id LIKE '$client_id' ORDER BY login_id LIMIT $limit OFFSET $offset");
}
// Output - Not using the standard API read_output.php
// Usually we just output what is in the database, but credentials need to be decrypted first.
if ($sql && mysqli_num_rows($sql) > 0) {
$return_arr['success'] = "True";
$return_arr['count'] = mysqli_num_rows($sql);
$row = array();
while ($row = mysqli_fetch_array($sql)) {
$row['login_username'] = apiDecryptLoginEntry($row['login_username'], $api_key_decrypt_hash, $api_key_decrypt_password);
$row['login_password'] = apiDecryptLoginEntry($row['login_password'], $api_key_decrypt_hash, $api_key_decrypt_password);
$return_arr['data'][] = $row;
}
echo json_encode($return_arr);
exit();
}
else {
$return_arr['success'] = "False";
$return_arr['message'] = "No resource (for this client and company) with the specified parameter(s).";
// Log any database/schema related errors to the PHP Error log
if (mysqli_error($mysqli)) {
error_log("API Database Error: " . mysqli_error($mysqli));
}
echo json_encode($return_arr);
exit();
}

View File

@@ -0,0 +1,38 @@
<?php
require_once '../validate_api_key.php';
require_once '../require_post_method.php';
// Parse ID
$login_id = intval($_POST['login_id']);
// Default
$update_count = false;
if (!empty($_POST['api_key_decrypt_password']) && !empty($login_id)) {
$credential_row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT * FROM logins WHERE login_id = '$login_id' AND login_client_id = $client_id LIMIT 1"));
// Variable assignment from POST - assigning the current database value if a value is not provided
require_once 'credential_model.php';
$update_sql = mysqli_query($mysqli,"UPDATE logins SET login_name = '$name', login_description = '$description', login_uri = '$uri', login_uri_2 = '$uri_2', login_username = '$username', login_password = '$password', login_otp_secret = '$otp_secret', login_note = '$note', login_important = $important, login_contact_id = $contact_id, login_vendor_id = $vendor_id, login_asset_id = $asset_id, login_software_id = $software_id, login_client_id = $client_id WHERE login_id = '$login_id' AND login_client_id = $client_id LIMIT 1");
// Check insert & get insert ID
if ($update_sql) {
$update_count = mysqli_affected_rows($mysqli);
if ($password_changed) {
mysqli_query($mysqli, "UPDATE logins SET login_password_changed_at = NOW() WHERE login_id = $login_id LIMIT 1");
}
//Logging
mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Credential', log_action = 'Update', log_description = '$name via API ($api_key_name)', log_ip = '$ip', log_user_agent = '$user_agent', log_client_id = $client_id");
mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'API', log_action = 'Success', log_description = 'Updated credential $name via API ($api_key_name)', log_ip = '$ip', log_user_agent = '$user_agent', log_client_id = $client_id");
}
}
// Output
require_once '../update_output.php';

View File

@@ -88,6 +88,7 @@ if (isset($api_key)) {
// Set client ID, company ID & key name
$row = mysqli_fetch_array($sql);
$api_key_name = htmlentities($row['api_key_name']);
$api_key_decrypt_hash = $row['api_key_decrypt_hash']; // No sanitization
$client_id = intval($row['api_key_client_id']);
// Set limit & offset for queries

View File

@@ -314,6 +314,18 @@
</div>
</div>
<?php if ($_GET['type'] !== 'virtual') { ?>
<div class="form-group">
<label>Purchase Date</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-shopping-cart"></i></span>
</div>
<input type="date" class="form-control" name="purchase_date" max="2999-12-31">
</div>
</div>
<?php } ?>
<div class="form-group">
<label>Install Date</label>
<div class="input-group">
@@ -325,15 +337,6 @@
</div>
<?php if ($_GET['type'] !== 'virtual') { ?>
<div class="form-group">
<label>Purchase Date</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-shopping-cart"></i></span>
</div>
<input type="date" class="form-control" name="purchase_date" max="2999-12-31">
</div>
</div>
<div class="form-group">
<label>Warranty Expire</label>
<div class="input-group">

View File

@@ -29,6 +29,9 @@
<li class="nav-item">
<a class="nav-link" data-toggle="pill" href="#pills-notes<?php echo $asset_id; ?>">Notes</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="pill" href="#pills-history<?php echo $asset_id; ?>">History</a>
</li>
</ul>
<hr>
@@ -333,6 +336,18 @@
</div>
</div>
<?php if ($asset_type !== 'Virtual Machine') { ?>
<div class="form-group">
<label>Purchase Date</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-shopping-cart"></i></span>
</div>
<input type="date" class="form-control" name="purchase_date" max="2999-12-31" value="<?php echo $asset_purchase_date; ?>">
</div>
</div>
<?php } ?>
<div class="form-group">
<label>Install Date</label>
<div class="input-group">
@@ -344,16 +359,6 @@
</div>
<?php if ($asset_type !== 'Virtual Machine') { ?>
<div class="form-group">
<label>Purchase Date</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-shopping-cart"></i></span>
</div>
<input type="date" class="form-control" name="purchase_date" max="2999-12-31" value="<?php echo $asset_purchase_date; ?>">
</div>
</div>
<div class="form-group">
<label>Warranty Expire</label>
<div class="input-group">
@@ -386,6 +391,32 @@
</div>
<div class="tab-pane fade" id="pills-history<?php echo $asset_id; ?>">
<?php $sql_history = mysqli_query($mysqli, "SELECT * FROM logs WHERE log_type = 'asset' and log_entity_id = $asset_id and log_client_id = $client_id"); ?>
<div class="form-group">
<label>Asset History</label>
<ul>
<?php
while ($row = mysqli_fetch_array($sql_history)) {
$log_action = nullable_htmlentities(($row['log_action']));
$log_description = nullable_htmlentities(($row['log_description']));
$log_created_at = nullable_htmlentities(($row['log_created_at']));
echo "<li>$log_created_at - $log_action: $log_description</li>";
}
?>
</ul>
</div>
</div>
</div>
</div>
<div class="modal-footer bg-white">

View File

@@ -0,0 +1,46 @@
<div class="modal" id="transferAssetModal<?php echo $asset_id; ?>" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content bg-dark">
<div class="modal-header">
<h5 class="modal-title"><i class="fa fa-fw fa-<?php echo $device_icon; ?> mr-2"></i>Transfer asset: <strong><?php echo $asset_name; ?></strong> to a different client</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="current_asset_id" value="<?php echo $asset_id; ?>">
<div class="modal-body bg-white">
<div class="form-group">
<label>New Client <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-users"></i></span>
</div>
<?php $clients_sql = mysqli_query($mysqli, "SELECT client_id, client_name FROM clients WHERE client_id != $client_id AND client_archived_at IS NULL"); ?>
<select class="form-control select2" name="new_client_id" required>
<?php
while ($row = mysqli_fetch_array($clients_sql)) {
$id = intval($row["client_id"]);
$name = nullable_htmlentities($row["client_name"]);
echo "<option value='$id'>$name</option>";
}
?>
</select>
</div>
</div>
<div class="alert alert-dark" role="alert">
<i>The current asset will be archived and content copied to a new asset.</i>
</div>
</div>
<div class="modal-footer bg-white">
<button type="submit" name="change_client_asset" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Transfer</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
</div>
</form>
</div>
</div>
</div>

View File

@@ -240,7 +240,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
if ($_GET['type'] !== 'network' && $_GET['type'] !== 'other') { ?>
<th><a class="text-secondary" href="?<?php echo $url_query_strings_sort; ?>&sort=asset_os&order=<?php echo $disp; ?>">OS</a></th>
<?php } ?>
<th><a class="text-secondary" href="?<?php echo $url_query_strings_sort; ?>&sort=asset_ip&order=<?php echo $disp; ?>">IP</a></th>
<th><a class="text-secondary" href="?<?php echo $url_query_strings_sort; ?>&sort=interface_ip&order=<?php echo $disp; ?>">IP</a></th>
<th><a class="text-secondary" href="?<?php echo $url_query_strings_sort; ?>&sort=asset_install_date&order=<?php echo $disp; ?>">Install Date</a></th>
<?php if ($_GET['type'] !== 'network' && $_GET['type'] !== 'servers' && $_GET['type'] !== 'other') { ?>
<th><a class="text-secondary" href="?<?php echo $url_query_strings_sort; ?>&sort=contact_name&order=<?php echo $disp; ?>">Assigned To</a></th>
@@ -466,6 +466,9 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<i class="fas fa-fw fa-redo mr-2"></i>Unarchive
</a>
<?php } else { ?>
<a class="dropdown-item" href="#" data-toggle="modal" data-target="#transferAssetModal<?php echo $asset_id; ?>">
<i class="fas fa-fw fa-arrow-right mr-2"></i>Transfer
</a>
<a class="dropdown-item text-danger confirm-link" href="post.php?archive_asset=<?php echo $asset_id; ?>">
<i class="fas fa-fw fa-archive mr-2"></i>Archive
</a>
@@ -489,6 +492,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
require "client_asset_copy_modal.php";
require "client_asset_transfer_modal.php";
}

View File

@@ -1,147 +0,0 @@
<?php
require_once "inc_all_client.php";
$sql = mysqli_query($mysqli, "SELECT * FROM contacts
WHERE contact_client_id = $client_id
AND contact_archived_at IS NULL
AND contact_email != ''
ORDER BY contact_primary DESC,
contact_important DESC"
);
?>
<div class="card">
<div class="card-header">
<h3 class="card-title mt-2 mb-2"><i class="fa fa-fw fa-envelope-open mr-2"></i>Bulk Mail to Special Contacts</h3>
<div class="card-tools">
<button id="bulkActionButton" hidden class="btn btn-primary" type="submit" form='bulkActions' name="send_bulk_mail_now">
<i class="fas fa-fw fa-paper-plane mr-2"></i>Send Now (<span id="selectedCount">0</span>)
</button>
</div>
</div>
<div class="card-body">
<form id="bulkActions" action="post.php" method="post">
<div class="row">
<div class="col">
<h5>Email Message</h5>
<hr>
<div class="form-group">
<select type="text" class="form-control select2" name="mail_from">
<option value="<?php echo nullable_htmlentities($config_mail_from_email); ?>">
<?php echo nullable_htmlentities("$config_mail_from_name - $config_mail_from_email"); ?></option>
<option value="<?php echo nullable_htmlentities($config_invoice_from_email); ?>">
<?php echo nullable_htmlentities("$config_invoice_from_name - $config_invoice_from_email"); ?></option>
<option value="<?php echo nullable_htmlentities($config_quote_from_email); ?>">
<?php echo nullable_htmlentities("$config_quote_from_name - $config_quote_from_email"); ?></option>
<option value="<?php echo nullable_htmlentities($config_ticket_from_email); ?>">
<?php echo nullable_htmlentities("$config_ticket_from_name - $config_ticket_from_email"); ?></option>
</select>
</div>
<div class="form-group">
<input type="text" class="form-control" name="mail_from_name" placeholder="From Name"
value="<?php echo nullable_htmlentities($config_mail_from_name); ?>" required>
</div>
<div class="form-group">
<input type="text" class="form-control" name="subject" placeholder="Subject" required>
</div>
<div class="form-group">
<textarea class="form-control tinymce" name="body"
placeholder="Type an email in here"></textarea>
</div>
<div class="form-group">
<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="datetime-local" class="form-control" name="queued_at">
</div>
</div>
</div>
<div class="col">
<h5>Select Contacts</h5>
<hr>
<div class="card">
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<td>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="selectAllCheckbox"
onclick="checkAll(this)">
</div>
</td>
<th>Name</th>
<th>Title</th>
<th>Email</th>
</tr>
</thead>
<tbody>
<?php
while ($row = mysqli_fetch_array($sql)) {
$contact_id = intval($row['contact_id']);
$contact_name = nullable_htmlentities($row['contact_name']);
$contact_title = nullable_htmlentities($row['contact_title']);
if (empty($contact_title)) {
$contact_title_display = "-";
} else {
$contact_title_display = "$contact_title";
}
$contact_email = nullable_htmlentities($row['contact_email']);
$contact_primary = intval($row['contact_primary']);
$contact_important = intval($row['contact_important']);
$contact_billing = intval($row['contact_billing']);
$contact_technical = intval($row['contact_technical']);
?>
<tr>
<td class="pr-0 bg-light">
<div class="form-check">
<input type="checkbox" class="form-check-input bulk-select" name="contact_ids[]"
value="<?php echo $contact_id; ?>">
</div>
</td>
<td>
<a href="client_contact_details.php?client_id=<?php echo $client_id; ?>&contact_id=<?php echo $contact_id; ?>"
target="_blank">
<?php echo $contact_name; ?>
</a>
</td>
<td><?php echo $contact_title_display; ?></td>
<td><?php echo $contact_email; ?></td>
</tr>
<?php } ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</form>
<script src="js/bulk_actions.js"></script>
<?php
require_once "footer.php";

View File

@@ -0,0 +1,57 @@
<div class="modal" id="bulkSendEmailModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content bg-dark">
<div class="modal-header">
<h5 class="modal-title"><i class="fa fa-fw fa-envelope-open mr-2"></i>Bulk Send Email</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<div class="modal-body bg-white">
<div class="form-group">
<select type="text" class="form-control select2" name="mail_from">
<option value="<?php echo nullable_htmlentities($config_mail_from_email); ?>">
<?php echo nullable_htmlentities("$config_mail_from_name - $config_mail_from_email"); ?></option>
<option value="<?php echo nullable_htmlentities($config_invoice_from_email); ?>">
<?php echo nullable_htmlentities("$config_invoice_from_name - $config_invoice_from_email"); ?></option>
<option value="<?php echo nullable_htmlentities($config_quote_from_email); ?>">
<?php echo nullable_htmlentities("$config_quote_from_name - $config_quote_from_email"); ?></option>
<option value="<?php echo nullable_htmlentities($config_ticket_from_email); ?>">
<?php echo nullable_htmlentities("$config_ticket_from_name - $config_ticket_from_email"); ?></option>
</select>
</div>
<div class="form-group">
<input type="text" class="form-control" name="mail_from_name" placeholder="From Name"
value="<?php echo nullable_htmlentities($config_mail_from_name); ?>">
</div>
<div class="form-group">
<input type="text" class="form-control" name="subject" placeholder="Subject">
</div>
<div class="form-group">
<textarea class="form-control tinymce" name="body"
placeholder="Type an email in here"></textarea>
</div>
<div class="form-group">
<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="datetime-local" class="form-control" name="queued_at">
</div>
</div>
</div>
<div class="modal-footer bg-white">
<button type="submit" name="send_bulk_mail_now" class="btn btn-primary text-bold"><i class="fas fa-paper-plane mr-2"></i>Send</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
</div>
</div>
</div>
</div>

View File

@@ -21,7 +21,7 @@
<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 $contact_id; ?>"><i class="fa fa-fw fa-user mr-2"></i>Details</a>
<a class="nav-link active" data-toggle="pill" href="#pills-details<?php echo $contact_id; ?>"><i class="fa fa-fw fa-id-badge mr-2"></i>Details</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="pill" href="#pills-photo<?php echo $contact_id; ?>"><i class="fa fa-fw fa-image mr-2"></i>Photo</a>

View File

@@ -54,7 +54,7 @@ $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="fa fa-fw fa-users mr-2"></i>Contacts</h3>
<h3 class="card-title mt-2"><i class="fa fa-fw fa-address-book mr-2"></i>Contacts</h3>
<div class="card-tools">
<div class="btn-group">
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#addContactModal">
@@ -156,6 +156,10 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<a class="dropdown-item" href="#" data-toggle="modal" data-target="#bulkAssignTagsModal">
<i class="fas fa-fw fa-tags mr-2"></i>Assign Tags
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#" data-toggle="modal" data-target="#bulkSendEmailModal">
<i class="fas fa-fw fa-paper-plane mr-2"></i>Send Email
</a>
<?php if ($archived) { ?>
<div class="dropdown-divider"></div>
<button class="dropdown-item text-info"
@@ -411,6 +415,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<?php require_once "client_contact_bulk_edit_department_modal.php"; ?>
<?php require_once "client_contact_bulk_edit_role_modal.php"; ?>
<?php require_once "client_contact_bulk_assign_tags_modal.php"; ?>
<?php require_once "client_contact_bulk_email_modal.php"; ?>
</form>
<?php require_once "pagination.php";
?>

View File

@@ -217,6 +217,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<th>
<a class="text-secondary" href="?<?php echo $url_query_strings_sort; ?>&sort=document_updated_at&order=<?php echo $disp; ?>">Last Update</a>
</th>
<th></th>
<th class="text-center">
Action
</th>
@@ -235,6 +236,31 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
$document_updated_at = date("m/d/Y",strtotime($row['document_updated_at']));
$document_folder_id = intval($row['document_folder_id']);
// Check if shared
$sql_shared = mysqli_query(
$mysqli,
"SELECT * FROM shared_items
WHERE item_client_id = $client_id
AND item_active = 1
AND item_views != item_view_limit
AND item_expire_at > NOW()
AND item_type = 'Document'
AND item_related_id = $document_id
LIMIT 1"
);
$row = mysqli_fetch_array($sql_shared);
$item_id = intval($row['item_id']);
$item_active = nullable_htmlentities($row['item_active']);
$item_key = nullable_htmlentities($row['item_key']);
$item_type = nullable_htmlentities($row['item_type']);
$item_related_id = intval($row['item_related_id']);
$item_note = nullable_htmlentities($row['item_note']);
$item_views = nullable_htmlentities($row['item_views']);
$item_view_limit = nullable_htmlentities($row['item_view_limit']);
$item_created_at = nullable_htmlentities($row['item_created_at']);
$item_expire_at = nullable_htmlentities($row['item_expire_at']);
$item_expire_at_human = timeAgo($row['item_expire_at']);
?>
<tr>
@@ -252,6 +278,13 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<div class="text-secondary mt-1"><?php echo $document_created_by_name; ?>
</td>
<td><?php echo $document_updated_at; ?></td>
<td>
<?php if($item_id) { ?>
<div title="Expires <?php echo $item_expire_at_human; ?>">
<i class="fas fa-fw fa-link"></i> Shared
</div>
<?php } ?>
</td>
<td>
<div class="dropdown dropleft text-center">
<button class="btn btn-secondary btn-sm" type="button" data-toggle="dropdown">

View File

@@ -44,7 +44,7 @@
<label>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-user"></i></span>
<span class="input-group-text"><i class="fa fa-fw fa-id-badge"></i></span>
</div>
<input type="text" class="form-control" name="name" placeholder="Name or Company"
value="<?php echo $client_name; ?>" required>

View File

@@ -24,7 +24,6 @@
$sql_assets_select = mysqli_query($mysqli, "SELECT * FROM assets
WHERE asset_client_id = $client_id
AND asset_archived_at IS NULL
$exclude_condition
ORDER BY asset_name ASC"
);
while ($row = mysqli_fetch_array($sql_assets_select)) {
@@ -65,7 +64,7 @@
<?php
}
?>
</div>
<div class="modal-footer bg-white">
<button type="submit" name="link_asset_to_file" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Link</button>

View File

@@ -70,10 +70,10 @@ $num_of_files = mysqli_num_rows($sql);
?>
<div class="card card-dark">
<div class="card-header py-2">
<h3 class="card-title mt-2"><i class="fa fa-fw fa-paperclip mr-2"></i>Files</h3>
<div class="card-tools">
<div class="btn-group">
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#uploadFilesModal">
@@ -88,7 +88,7 @@ $num_of_files = mysqli_num_rows($sql);
</div>
</div>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-3 border-right mb-3">
@@ -156,7 +156,7 @@ $num_of_files = mysqli_num_rows($sql);
</div>
<div class="col-md-9">
<form autocomplete="off">
<input type="hidden" name="client_id" value="<?php echo $client_id; ?>">
<input type="hidden" name="view" value="<?php echo $view; ?>">
@@ -174,7 +174,7 @@ $num_of_files = mysqli_num_rows($sql);
<div class="btn-group float-right">
<a href="?<?php echo $url_query_strings_sort; ?>&view=0" class="btn <?php if($view == 0){ echo "btn-primary"; } else { echo "btn-outline-secondary"; } ?>"><i class="fas fa-list-ul"></i></a>
<a href="?<?php echo $url_query_strings_sort; ?>&view=1" class="btn <?php if($view == 1){ echo "btn-primary"; } else { echo "btn-outline-secondary"; } ?>"><i class="fas fa-th-large"></i></a>
<div class="dropdown ml-2" id="bulkActionButton" hidden>
<button class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown">
<i class="fas fa-fw fa-layer-group mr-2"></i>Bulk Action (<span id="selectedCount">0</span>)
@@ -190,7 +190,7 @@ $num_of_files = mysqli_num_rows($sql);
</div>
</div>
</form>
<hr>
<?php
@@ -225,7 +225,10 @@ $num_of_files = mysqli_num_rows($sql);
<small><?php echo $file_name; ?></small>
<a href="#" data-toggle="modal" data-target="#deleteFileModal" onclick="populateFileDeleteModal(<?php echo "$file_id , '$file_name'" ?>)" class="text-white float-right mr-1"><i class="fa fa-times"></i></a>
<?php if ($session_user_role == 3) { ?>
<a href="#" data-toggle="modal" data-target="#deleteFileModal" onclick="populateFileDeleteModal(<?php echo "$file_id , '$file_name'" ?>)" class="text-white float-right mr-1"><i class="fa fa-times"></i></a>
<?php } ?>
</div>
</div>
</div>
@@ -243,7 +246,7 @@ $num_of_files = mysqli_num_rows($sql);
<div class="table-responsive-sm">
<table class="table border">
<thead class="thead-light <?php if ($num_rows[0] == 0) { echo "d-none"; } ?>">
<tr>
<td class="bg-light pr-0">
@@ -253,10 +256,11 @@ $num_of_files = mysqli_num_rows($sql);
</td>
<th><a class="text-secondary" href="?<?php echo $url_query_strings_sort; ?>&sort=file_name&order=<?php echo $disp; ?>">Name</a></th>
<th><a class="text-secondary" href="?<?php echo $url_query_strings_sort; ?>&sort=file_created_at&order=<?php echo $disp; ?>">Uploaded</a></th>
<th></th>
<th class="text-center">Action</th>
</tr>
</thead>
<tbody>
<?php
@@ -290,6 +294,32 @@ $num_of_files = mysqli_num_rows($sql);
$file_icon = "file";
}
$file_created_at = nullable_htmlentities($row['file_created_at']);
// Check if shared
$sql_shared = mysqli_query(
$mysqli,
"SELECT * FROM shared_items
WHERE item_client_id = $client_id
AND item_active = 1
AND item_views != item_view_limit
AND item_expire_at > NOW()
AND item_type = 'File'
AND item_related_id = $file_id
LIMIT 1"
);
$row = mysqli_fetch_array($sql_shared);
$item_id = intval($row['item_id']);
$item_active = nullable_htmlentities($row['item_active']);
$item_key = nullable_htmlentities($row['item_key']);
$item_type = nullable_htmlentities($row['item_type']);
$item_related_id = intval($row['item_related_id']);
$item_note = nullable_htmlentities($row['item_note']);
$item_views = nullable_htmlentities($row['item_views']);
$item_view_limit = nullable_htmlentities($row['item_view_limit']);
$item_created_at = nullable_htmlentities($row['item_created_at']);
$item_expire_at = nullable_htmlentities($row['item_expire_at']);
$item_expire_at_human = timeAgo($row['item_expire_at']);
?>
<tr>
@@ -313,6 +343,13 @@ $num_of_files = mysqli_num_rows($sql);
</a>
</td>
<td><?php echo $file_created_at; ?></td>
<td>
<?php if($item_id) { ?>
<div title="Expires <?php echo $item_expire_at_human; ?>">
<i class="fas fa-fw fa-link"></i> Shared
</div>
<?php } ?>
</td>
<td>
<div class="dropdown dropleft text-center">
<button class="btn btn-secondary btn-sm" type="button" data-toggle="dropdown">
@@ -338,10 +375,12 @@ $num_of_files = mysqli_num_rows($sql);
<a class="dropdown-item text-danger confirm-link" href="post.php?archive_file=<?php echo $file_id; ?>">
<i class="fas fa-fw fa-archive mr-2"></i>Archive
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger text-bold" href="#" data-toggle="modal" data-target="#deleteFileModal" onclick="populateFileDeleteModal(<?php echo "$file_id , '$file_name'" ?>)">
<i class="fas fa-fw fa-trash mr-2"></i>Delete
</a>
<?php if ($session_user_role == 3) { ?>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger text-bold" href="#" data-toggle="modal" data-target="#deleteFileModal" onclick="populateFileDeleteModal(<?php echo "$file_id , '$file_name'" ?>)">
<i class="fas fa-fw fa-trash mr-2"></i>Delete
</a>
<?php } ?>
</div>
</div>
</td>

28
client_import_modal.php Normal file
View File

@@ -0,0 +1,28 @@
<div class="modal" id="importClientModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content bg-dark">
<div class="modal-header">
<h5 class="modal-title"><i class="fa fa-fw fa-users mr-2"></i>Import Clients</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<form action="post.php" method="post" enctype="multipart/form-data" autocomplete="off">
<input type="hidden" name="client_id" value="<?php echo $client_id; ?>">
<div class="modal-body bg-white">
<p><strong>Format csv file with headings & data:</strong><br>Client Name, Industry, Referral, Website, Location Name, Location Phone, Location Address, City, State, Postal Code, Country, Contact Name, Title, Contact Phone, Extension, Contact Mobile, Contact Email, Hourly Rate, Currency, Payment Terms, Tax ID, Abbreviation</p>
<hr>
<div class="form-group my-4">
<input type="file" class="form-control-file" name="file" accept=".csv">
</div>
<hr>
<div>Download: <a class="text-bold" href="post.php?download_clients_csv_template">sample csv template</a></div>
</div>
<div class="modal-footer bg-white">
<button type="submit" name="import_clients_csv" class="btn btn-primary text-strong"><i class="fas fa-upload mr-2"></i>Import</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
</div>
</form>
</div>
</div>
</div>

View File

@@ -7,16 +7,32 @@ $order = "ASC";
require_once "inc_all_client.php";
// Location Filter
if (isset($_GET['location']) & !empty($_GET['location'])) {
$location_query = 'AND (a.asset_location_id = ' . intval($_GET['location']) . ')';
$location_query_innerjoin = 'INNER JOIN assets a on a.asset_id = l.login_asset_id ';
$location = intval($_GET['location']);
} else {
// Default - any
$location_query_innerjoin = '';
$location_query = '';
$location = '';
}
//Rebuild URL
$url_query_strings_sort = http_build_query($get_copy);
$sql = mysqli_query(
$mysqli,
"SELECT SQL_CALC_FOUND_ROWS * FROM logins
WHERE login_client_id = $client_id
AND login_$archive_query
AND (login_name LIKE '%$q%' OR login_description LIKE '%$q%' OR login_uri LIKE '%$q%')
ORDER BY login_important DESC, $sort $order LIMIT $record_from, $record_to"
"SELECT SQL_CALC_FOUND_ROWS *
FROM logins l
$location_query_innerjoin
WHERE l.login_client_id = $client_id
AND l.login_$archive_query
AND (l.login_name LIKE '%$q%' OR l.login_description LIKE '%$q%' OR l.login_uri LIKE '%$q%')
$location_query
ORDER BY l.login_important DESC, $sort $order LIMIT $record_from, $record_to"
);
$num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
@@ -28,7 +44,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<h3 class="card-title mt-2"><i class="fa fa-fw fa-key mr-2"></i>Credentials</h3>
<div class="card-tools">
<div class="btn-group">
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#addLoginModal">
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#addLoginModal" <?php if (!isset($_COOKIE['user_encryption_session_key'])) { echo "disabled"; } ?>>
<i class="fas fa-plus mr-2"></i>New Credential
</button>
<button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown"></button>
@@ -58,7 +74,27 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</div>
</div>
<div class="col-md-8">
<div class="col-md-2">
<div class="input-group">
<select class="form-control select2" name="location" onchange="this.form.submit()">
<option value="" <?php if ($location == "") { echo "selected"; } ?>>- All Asset Locations -</option>
<?php
$sql_locations_filter = mysqli_query($mysqli, "SELECT * FROM locations WHERE location_client_id = $client_id AND location_archived_at IS NULL ORDER BY location_name ASC");
while ($row = mysqli_fetch_array($sql_locations_filter)) {
$location_id = intval($row['location_id']);
$location_name = nullable_htmlentities($row['location_name']);
?>
<option <?php if ($location == $location_id) { echo "selected"; } ?> value="<?php echo $location_id; ?>"><?php echo $location_name; ?></option>
<?php
}
?>
</select>
</div>
</div>
<div class="col-md-6">
<div class="btn-group float-right">
<a href="?client_id=<?php echo $client_id; ?>&archived=<?php if($archived == 1){ echo 0; } else { echo 1; } ?>"
class="btn btn-<?php if($archived == 1){ echo "primary"; } else { echo "default"; } ?>">
@@ -109,6 +145,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<th>Password / Key</th>
<th>OTP</th>
<th><a class="text-secondary" href="?<?php echo $url_query_strings_sort; ?>&sort=login_uri&order=<?php echo $disp; ?>">URI</a></th>
<th></th>
<th class="text-center">Action</th>
</tr>
</thead>
@@ -149,6 +186,32 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
$login_asset_id = intval($row['login_asset_id']);
$login_software_id = intval($row['login_software_id']);
// Check if shared
$sql_shared = mysqli_query(
$mysqli,
"SELECT * FROM shared_items
WHERE item_client_id = $client_id
AND item_active = 1
AND item_views != item_view_limit
AND item_expire_at > NOW()
AND item_type = 'Login'
AND item_related_id = $login_id
LIMIT 1"
);
$row = mysqli_fetch_array($sql_shared);
$item_id = intval($row['item_id']);
$item_active = nullable_htmlentities($row['item_active']);
$item_key = nullable_htmlentities($row['item_key']);
$item_type = nullable_htmlentities($row['item_type']);
$item_related_id = intval($row['item_related_id']);
$item_note = nullable_htmlentities($row['item_note']);
$item_views = nullable_htmlentities($row['item_views']);
$item_view_limit = nullable_htmlentities($row['item_view_limit']);
$item_created_at = nullable_htmlentities($row['item_created_at']);
$item_expire_at = nullable_htmlentities($row['item_expire_at']);
$item_expire_at_human = timeAgo($row['item_expire_at']);
?>
<tr class="<?php if (!empty($login_important)) { echo "text-bold"; } ?>">
<td class="pr-0">
@@ -173,6 +236,13 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</td>
<td><?php echo $otp_display; ?></td>
<td><?php echo $login_uri_display; ?></td>
<td>
<?php if($item_id) { ?>
<div title="Expires <?php echo $item_expire_at_human; ?>">
<i class="fas fa-fw fa-link"></i> Shared
</div>
<?php } ?>
</td>
<td class="text-center">
<div class="btn-group">
<?php if ( !empty($login_uri) || !empty($login_uri_2) ) { ?>

View File

@@ -1,114 +0,0 @@
<?php
// Default Column Sortby Filter
$sort = "log_id";
$order = "DESC";
require_once "inc_all_client.php";
//Rebuild URL
$url_query_strings_sort = http_build_query($get_copy);
$sql = mysqli_query(
$mysqli,
"SELECT SQL_CALC_FOUND_ROWS * FROM logs
LEFT JOIN users ON log_user_id = user_id
WHERE (log_type LIKE '%$q%' OR log_action LIKE '%$q%' OR log_description LIKE '%$q%' OR log_ip LIKE '%$q%' OR log_user_agent LIKE '%$q%' OR user_name LIKE '%$q%')
AND log_client_id = $client_id
ORDER BY $sort $order LIMIT $record_from, $record_to"
);
$num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
?>
<div class="card card-dark">
<div class="card-header py-3">
<h3 class="card-title"><i class="fa fa-fw fa-history mr-2"></i>Audit Logs</h3>
</div>
<div class="card-body">
<form autocomplete="off">
<input type="hidden" name="client_id" value="<?php echo $client_id; ?>">
<div class="row">
<div class="col-md-4">
<div class="input-group mb-3 mb-md-0">
<input type="search" class="form-control" name="q" value="<?php if (isset($q)) { echo stripslashes(nullable_htmlentities($q)); } ?>" placeholder="Search Logs">
<div class="input-group-append">
<button class="btn btn-dark"><i class="fa fa-search"></i></button>
</div>
</div>
</div>
<div class="col-md-8">
</div>
</div>
</form>
<hr>
<div class="table-responsive-sm border">
<table class="table table-hover">
<thead class="text-dark <?php if ($num_rows[0] == 0) { echo "d-none"; } ?>">
<tr>
<th><a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=log_created_at&order=<?php echo $disp; ?>">Timestamp</a></th>
<th><a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=user_name&order=<?php echo $disp; ?>">User</a></th>
<th><a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=log_type&order=<?php echo $disp; ?>">Type</a></th>
<th><a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=log_action&order=<?php echo $disp; ?>">Action</a></th>
<th><a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=log_description&order=<?php echo $disp; ?>">Description</a></th>
<th><a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=log_ip&order=<?php echo $disp; ?>">IP Address</a></th>
<th><a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=log_user_agent&order=<?php echo $disp; ?>">User Agent</a></th>
<th><a class="text-dark" href="?<?php echo $url_query_strings_sort; ?>&sort=log_entity_id&order=<?php echo $disp; ?>">Entity ID</a></th>
</tr>
</thead>
<tbody>
<?php
while ($row = mysqli_fetch_array($sql)) {
$log_id = intval($row['log_id']);
$log_type = nullable_htmlentities($row['log_type']);
$log_action = nullable_htmlentities($row['log_action']);
$log_description = nullable_htmlentities($row['log_description']);
$log_ip = nullable_htmlentities($row['log_ip']);
$log_user_agent = nullable_htmlentities($row['log_user_agent']);
$log_user_os = getOS($log_user_agent);
$log_user_browser = getWebBrowser($log_user_agent);
$log_created_at = nullable_htmlentities($row['log_created_at']);
$user_id = intval($row['user_id']);
$user_name = nullable_htmlentities($row['user_name']);
if (empty($user_name)) {
$user_name_display = "-";
} else {
$user_name_display = $user_name;
}
$log_entity_id = intval($row['log_entity_id']);
?>
<tr>
<td><?php echo $log_created_at; ?></td>
<td><?php echo $user_name_display; ?></td>
<td><?php echo $log_type; ?></td>
<td><?php echo $log_action; ?></td>
<td><?php echo $log_description; ?></td>
<td><?php echo $log_ip; ?></td>
<td><?php echo "$log_user_os<br>$log_user_browser"; ?></td>
<td><?php echo $log_entity_id; ?></td>
</tr>
<?php
}
?>
</tbody>
</table>
</div>
<?php require_once "pagination.php";
?>
</div>
</div>
<?php
require_once "footer.php";

View File

@@ -7,7 +7,7 @@ $sql_recent_activities = mysqli_query(
$mysqli,
"SELECT * FROM logs
WHERE log_client_id = $client_id
ORDER BY log_created_at DESC LIMIT 10"
ORDER BY log_created_at DESC LIMIT 5"
);
$sql_important_contacts = mysqli_query(
@@ -16,7 +16,7 @@ $sql_important_contacts = mysqli_query(
WHERE contact_client_id = $client_id
AND (contact_important = 1 OR contact_billing = 1 OR contact_technical = 1 OR contact_primary = 1)
AND contact_archived_at IS NULL
ORDER BY contact_primary DESC, contact_name DESC"
ORDER BY contact_primary DESC, contact_name DESC LIMIT 5"
);
$sql_recent_tickets = mysqli_query(
@@ -33,6 +33,16 @@ $sql_recent_logins = mysqli_query(
ORDER BY login_updated_at DESC LIMIT 5"
);
$sql_shared_items = mysqli_query(
$mysqli,
"SELECT * FROM shared_items
WHERE item_client_id = $client_id
AND item_active = 1
AND item_views != item_view_limit
AND item_expire_at > NOW()
ORDER BY item_created_at DESC LIMIT 5"
);
/*
* EXPIRING/ACTION ITEMS
*/
@@ -44,7 +54,7 @@ $sql_stale_tickets = mysqli_query(
WHERE ticket_client_id = $client_id
AND ticket_updated_at < CURRENT_DATE - INTERVAL 3 DAY
AND ticket_closed_at IS NULL
ORDER BY ticket_updated_at DESC"
ORDER BY ticket_updated_at DESC LIMIT 5"
);
// Get Domains Expiring
@@ -55,7 +65,7 @@ $sql_domains_expiring = mysqli_query(
AND domain_expire IS NOT NULL
AND domain_archived_at IS NULL
AND domain_expire < CURRENT_DATE + INTERVAL 90 DAY
ORDER BY domain_expire DESC"
ORDER BY domain_expire DESC LIMIT 5"
);
// Get Licenses Expiring
@@ -66,7 +76,7 @@ $sql_licenses_expiring = mysqli_query(
AND software_expire IS NOT NULL
AND software_archived_at IS NULL
AND software_expire < CURRENT_DATE + INTERVAL 90 DAY
ORDER BY software_expire DESC"
ORDER BY software_expire DESC LIMIT 5"
);
// Get Asset Warranties Expiring
@@ -77,7 +87,7 @@ $sql_asset_warranties_expiring = mysqli_query(
AND asset_warranty_expire IS NOT NULL
AND asset_archived_at IS NULL
AND asset_warranty_expire < CURRENT_DATE + INTERVAL 90 DAY
ORDER BY asset_warranty_expire DESC"
ORDER BY asset_warranty_expire DESC LIMIT 5"
);
// Get Assets Retiring
@@ -88,7 +98,7 @@ $sql_asset_retire = mysqli_query(
AND asset_install_date IS NOT NULL
AND asset_archived_at IS NULL
AND asset_install_date + INTERVAL 7 YEAR < CURRENT_DATE + INTERVAL 90 DAY
ORDER BY asset_install_date DESC"
ORDER BY asset_install_date DESC LIMIT 5"
);
?>
@@ -97,7 +107,7 @@ $sql_asset_retire = mysqli_query(
<!-- Notes -->
<div class="col-md-12">
<div class="col-md-4">
<div class="card card-dark mb-3">
<div class="card-header">
@@ -159,6 +169,71 @@ $sql_asset_retire = mysqli_query(
<?php } ?>
<?php if (mysqli_num_rows($sql_shared_items) > 0) { ?>
<div class="col-md-4">
<div class="card card-dark mb-3">
<div class="card-header">
<h5 class="card-title"><i class="fa fa-fw fa-share-square mr-2"></i>Shared Items</h5>
</div>
<div class="card-body p-2">
<table class="table table-borderless table-sm">
<tbody>
<?php
while ($row = mysqli_fetch_array($sql_shared_items)) {
$item_id = intval($row['item_id']);
$item_active = nullable_htmlentities($row['item_active']);
$item_key = nullable_htmlentities($row['item_key']);
$item_type = nullable_htmlentities($row['item_type']);
$item_related_id = intval($row['item_related_id']);
$item_note = nullable_htmlentities($row['item_note']);
$item_views = nullable_htmlentities($row['item_views']);
$item_view_limit = nullable_htmlentities($row['item_view_limit']);
$item_created_at = nullable_htmlentities($row['item_created_at']);
$item_expire_at = nullable_htmlentities($row['item_expire_at']);
$item_expire_at_human = timeAgo($row['item_expire_at']);
if ($item_type == 'Login') {
$share_item_sql = mysqli_query($mysqli, "SELECT login_name FROM logins WHERE login_id = $item_related_id AND login_client_id = $client_id");
$share_item = mysqli_fetch_array($share_item_sql);
$item_name = nullable_htmlentities($share_item['login_name']);
$item_icon = "fas fa-key";
} elseif ($item_type == 'Document') {
$share_item_sql = mysqli_query($mysqli, "SELECT document_name FROM documents WHERE document_id = $item_related_id AND document_client_id = $client_id");
$share_item = mysqli_fetch_array($share_item_sql);
$item_name = nullable_htmlentities($share_item['document_name']);
$item_icon = "fas fa-folder";
} elseif ($item_type == 'File') {
$share_item_sql = mysqli_query($mysqli, "SELECT file_name FROM files WHERE file_id = $item_related_id AND file_client_id = $client_id");
$share_item = mysqli_fetch_array($share_item_sql);
$item_name = nullable_htmlentities($share_item['file_name']);
$item_icon = "fas fa-paperclip";
}
?>
<tr>
<td title="<?php echo $item_type; ?>"><i class="<?php echo $item_icon; ?> mr-2 text-secondary"></i><?php echo $item_name; ?></td>
<td>Views: <?php echo "$item_views / $item_view_limit" ?></td>
<td title="Expires at <?php echo $item_expire_at; ?>">Expires <?php echo $item_expire_at_human ?></td>
<td title="Deactivate Link">
<a class="text-danger confirm-link" href="post.php?deactivate_shared_item=<?php echo $item_id; ?>">
<i class="fas fa-fw fa-calendar-times mr-2"></i>
</a>
</td>
</tr>
<?php } ?>
</tbody>
</table>
</div>
</div>
</div>
<?php } ?>
<?php
if (mysqli_num_rows($sql_domains_expiring) > 0
|| mysqli_num_rows($sql_asset_warranties_expiring) > 0
@@ -303,7 +378,7 @@ $sql_asset_retire = mysqli_query(
<!-- Recent Activities -->
<div class="col-md-12">
<div class="col-md-4">
<div class="card card-dark mb-3">
<div class="card-header">
@@ -332,6 +407,11 @@ $sql_asset_retire = mysqli_query(
</tbody>
</table>
</div>
<?php if ($session_user_role == 3) { ?>
<div class="card-footer">
<a href="admin_logs.php?client=<?php echo $client_id; ?>">See More...</a>
</div>
<?php } ?>
</div>
</div>

View File

@@ -90,7 +90,7 @@
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-sort-numeric-up-alt"></i></span>
</div>
<input type="number" class="form-control" name="units" placeholder="Number of Units" min="1" max="44" required>
<input type="number" class="form-control" name="units" placeholder="Number of Units" min="1" max="70" required>
</div>
</div>

View File

@@ -91,7 +91,7 @@
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-sort-numeric-up-alt"></i></span>
</div>
<input type="number" class="form-control" name="units" placeholder="Number of Units" min="1" max="44" value="<?php echo $rack_units; ?>" required>
<input type="number" class="form-control" name="units" placeholder="Number of Units" min="1" max="70" value="<?php echo $rack_units; ?>" required>
</div>
</div>

View File

@@ -1,125 +0,0 @@
<?php
// Default Column Sortby Filter
$sort = "item_created_at";
$order = "DESC";
require_once "inc_all_client.php";
//Rebuild URL
$url_query_strings_sort = http_build_query($get_copy);
$sql = mysqli_query(
$mysqli,
"SELECT SQL_CALC_FOUND_ROWS * FROM shared_items
WHERE item_client_id = $client_id
AND item_active = '1'
AND item_views != item_view_limit
AND item_expire_at > NOW()
AND (item_note LIKE '%$q%') ORDER BY $sort $order LIMIT $record_from, $record_to"
);
$num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
?>
<div class="card card-dark">
<div class="card-header py-3">
<h3 class="card-title"><i class="fa fa-fw fa-share mr-2"></i>Shared Items (Links)</h3>
</div>
<div class="card-body">
<form autocomplete="off">
<input type="hidden" name="client_id" value="<?php echo $client_id; ?>">
<div class="row">
<div class="col-md-4">
<div class="input-group mb-3 mb-md-0">
<input type="search" class="form-control" name="q" value="<?php if (isset($q)) { echo stripslashes(nullable_htmlentities($q)); } ?>" placeholder="Search Shared Items">
<div class="input-group-append">
<button class="btn btn-dark"><i class="fa fa-search"></i></button>
</div>
</div>
</div>
</div>
</form>
<hr>
<div class="table-responsive-sm">
<table class="table table-striped table-borderless table-hover">
<thead class="text-dark <?php if ($num_rows[0] == 0) { echo "d-none"; } ?>">
<tr>
<th>Item Name</th>
<th>Item Type</th>
<th>Share Note</th>
<th>Views</th>
<th>Expires</th>
<th class="text-center">Action</th>
</tr>
</thead>
<tbody>
<?php
while ($row = mysqli_fetch_array($sql)) {
$item_id = intval($row['item_id']);
$item_active = nullable_htmlentities($row['item_active']);
$item_key = nullable_htmlentities($row['item_key']);
$item_type = nullable_htmlentities($row['item_type']);
$item_related_id = intval($row['item_related_id']);
$item_note = nullable_htmlentities($row['item_note']);
$item_views = nullable_htmlentities($row['item_views']);
$item_view_limit = nullable_htmlentities($row['item_view_limit']);
$item_created_at = nullable_htmlentities($row['item_created_at']);
$item_expire_at = nullable_htmlentities($row['item_expire_at']);
if ($item_type == 'Login') {
$share_item_sql = mysqli_query($mysqli, "SELECT login_name FROM logins WHERE login_id = '$item_related_id' AND login_client_id = '$client_id'");
$share_item = mysqli_fetch_array($share_item_sql);
$item_name = nullable_htmlentities($share_item['login_name']);
} elseif ($item_type == 'Document') {
$share_item_sql = mysqli_query($mysqli, "SELECT document_name FROM documents WHERE document_id = '$item_related_id' AND document_client_id = '$client_id'");
$share_item = mysqli_fetch_array($share_item_sql);
$item_name = nullable_htmlentities($share_item['document_name']);
} elseif ($item_type == 'File') {
$share_item_sql = mysqli_query($mysqli, "SELECT file_name FROM files WHERE file_id = '$item_related_id' AND file_client_id = '$client_id'");
$share_item = mysqli_fetch_array($share_item_sql);
$item_name = nullable_htmlentities($share_item['file_name']);
}
?>
<tr>
<td><?php echo $item_name; ?></td>
<td><?php echo $item_type ?></td>
<td><?php echo $item_note ?></td>
<td><?php echo "$item_views / $item_view_limit" ?></td>
<td><?php echo $item_expire_at ?></td>
<td>
<?php if ($session_user_role == 3) { ?>
<div class="dropdown dropleft text-center">
<button class="btn btn-secondary btn-sm" type="button" data-toggle="dropdown">
<i class="fas fa-ellipsis-h"></i>
</button>
<div class="dropdown-menu">
<a class="dropdown-item text-danger confirm-link" href="post.php?deactivate_shared_item=<?php echo $item_id; ?>">
<i class="fas fa-fw fa-times mr-2"></i>Deactivate
</a>
</div>
</div>
<?php } ?>
</td>
</tr>
<?php } ?>
</tbody>
</table>
</div>
<?php require_once "pagination.php";
?>
</div>
</div>
<?php
require_once "footer.php";

View File

@@ -2,7 +2,13 @@
<aside class="main-sidebar sidebar-dark-<?php echo nullable_htmlentities($config_theme); ?> d-print-none">
<a class="brand-link pb-1 mt-1" href="clients.php">
<p class="h5"><i class="nav-icon fas fa-arrow-left ml-3 mr-2"></i> Back | <strong><?php if($client_abbreviation) { echo $client_abbreviation; } else { echo shortenClient($client_name); } ?></strong></p>
<p class="h5">
<i class="nav-icon fas fa-arrow-left ml-3 mr-2"></i>
Back |
<strong>
<?php if($client_abbreviation) { echo $client_abbreviation; } else { echo shortenClient($client_name); } ?>
</strong>
</p>
</a>
<!-- Sidebar -->
@@ -22,7 +28,7 @@
<li class="nav-item">
<a href="client_contacts.php?client_id=<?php echo $client_id; ?>" class="nav-link <?php if (basename($_SERVER["PHP_SELF"]) == "client_contacts.php" || basename($_SERVER["PHP_SELF"]) == "client_contact_details.php") { echo "active"; } ?>">
<i class="nav-icon fas fa-users"></i>
<i class="nav-icon fas fa-address-book"></i>
<p>
Contacts
<?php
@@ -183,7 +189,7 @@
<?php
if ($num_certificates > 0) { ?>
<span class="right badge <?php if ($num_certs_expiring > 0) { ?> badge-warning <?php } ?> text-light"><?php echo $num_certificates; ?></span>
<span class="right badge <?php if ($num_certs_expiring > 0) { ?> badge-warning text-dark <?php } ?> text-light"><?php echo $num_certificates; ?></span>
<?php } ?>
</p>
</a>
@@ -197,7 +203,7 @@
<?php
if ($num_domains > 0) { ?>
<span class="right badge <?php if ($num_domains_expiring > 0) { ?> badge-warning <?php } ?> text-light"><?php echo $num_domains; ?></span>
<span class="right badge <?php if ($num_domains_expiring > 0) { ?> badge-warning text-dark<?php } ?> <?php if ($num_domains_expired > 0) { ?> badge-danger <?php } ?> text-white"><?php echo $num_domains; ?></span>
<?php } ?>
</p>
</a>
@@ -302,15 +308,6 @@
</a>
</li>
<li class="nav-item">
<a href="client_statement.php?client_id=<?php echo $client_id; ?>" class="nav-link <?php if (basename($_SERVER["PHP_SELF"]) == "client_statement.php") { echo "active"; } ?>">
<i class="nav-icon fas fa-file-invoice-dollar"></i>
<p>
Statement
</p>
</a>
</li>
<li class="nav-item">
<a href="client_trips.php?client_id=<?php echo $client_id; ?>" class="nav-link <?php if (basename($_SERVER["PHP_SELF"]) == "client_trips.php") { echo "active"; } ?>">
<i class="nav-icon fas fa-route"></i>
@@ -326,41 +323,6 @@
<?php } ?>
<li class="nav-header mt-3">MISC</li>
<li class="nav-item">
<a href="client_bulk_mail.php?client_id=<?php echo $client_id; ?>" class="nav-link <?php if (basename($_SERVER["PHP_SELF"]) == "client_bulk_mail.php") { echo "active"; } ?>">
<i class="nav-icon fas fa-envelope-open"></i>
<p>Bulk Mail</p>
</a>
</li>
<li class="nav-item">
<a href="client_shared_items.php?client_id=<?php echo $client_id; ?>" class="nav-link <?php if (basename($_SERVER["PHP_SELF"]) == "client_shared_items.php") { echo "active"; } ?>">
<i class="nav-icon fas fa-share"></i>
<p>
Shared Links
<?php
if ($num_shared_links > 0) { ?>
<span class="right badge text-light"><?php echo $num_shared_links; ?></span>
<?php } ?>
</p>
</a>
</li>
<li class="nav-item">
<a href="client_logs.php?client_id=<?php echo $client_id; ?>" class="nav-link <?php if (basename($_SERVER["PHP_SELF"]) == "client_logs.php") { echo "active"; } ?>">
<i class="nav-icon fas fa-history"></i>
<p>
Audit Logs
<?php
if ($num_logs > 0) { ?>
<span class="right badge text-light"><?php echo $num_logs; ?></span>
<?php } ?>
</p>
</a>
</li>
</ul>
</nav>
<!-- /.sidebar-menu -->

View File

@@ -154,7 +154,7 @@
<?php
$sql_assets_select = mysqli_query($mysqli, "SELECT * FROM assets LEFT JOIN contacts ON asset_contact_id = contact_id WHERE (asset_archived_at > '$software_created_at' OR asset_archived_at IS NULL) AND asset_client_id = $client_id ORDER BY asset_archived_at ASC, asset_name ASC");
$sql_assets_select = mysqli_query($mysqli, "SELECT * FROM assets LEFT JOIN contacts ON asset_contact_id = contact_id WHERE asset_archived_at IS NULL AND asset_client_id = $client_id ORDER BY asset_archived_at ASC, asset_name ASC");
while ($row = mysqli_fetch_array($sql_assets_select)) {
$asset_id_select = intval($row['asset_id']);

View File

@@ -1,189 +0,0 @@
<?php
require_once "inc_all_client.php";
if (isset($_GET['client_id'])) {
$client_id = intval($_GET['client_id']);
$sql_client_details = "
SELECT
client_name,
client_type,
client_website,
client_net_terms
FROM
clients
WHERE
client_id = $client_id";
$result_client_details = mysqli_query($mysqli, $sql_client_details);
$row_client_details = mysqli_fetch_assoc($result_client_details);
$client_name = nullable_htmlentities($row_client_details['client_name']);
$client_type = nullable_htmlentities($row_client_details['client_type']);
$client_website = nullable_htmlentities($row_client_details['client_website']);
$client_net_terms = intval($row_client_details['client_net_terms']);
$sql_client_unpaid_invoices = "
SELECT
invoice_id,
invoice_number,
invoice_prefix,
invoice_date,
invoice_due,
invoice_amount
FROM
invoices
WHERE
invoice_client_id = $client_id
AND invoice_status NOT LIKE 'Draft'
AND invoice_status NOT LIKE 'Cancelled'
AND invoice_status NOT LIKE 'Paid'";
$result_client_unpaid_invoices = mysqli_query($mysqli, $sql_client_unpaid_invoices);
$currency_code = getSettingValue($mysqli, "company_currency");
?>
<ol class="breadcrumb d-print-none">
<li class="breadcrumb-item">
<a href="clients.php">Clients</a>
</li>
<li class="breadcrumb-item">
<a href="client_invoices.php?client_id=<?php echo $client_id; ?>"><?php echo $client_name; ?></a>
</li>
</ol>
<div class="card card-dark">
<div class="card-header py-2">
<h3 class="card-title mt-2"><i class="fas fa-fw fa-balance-scale mr-2"></i>Statement for <?php echo $client_name ?></h3>
<div class="card-tools">
<button type="button" class="btn btn-primary d-print-none" onclick="window.print();"><i class="fas fa-fw fa-print mr-2"></i>Print</button>
</div>
</div>
<div class="card-body p-0">
<div>
<div class="table-responsive-sm">
<table class="table table-sm">
<!-- Past Due Payments -->
<thead class="text-dark">
<tr>
<th>Invoice Number</th>
<th>Date</th>
<th>Due Date</th>
<th>Amount</th>
<th>Balance</th>
</tr>
</thead>
<tbody>
<?php
while ($row = mysqli_fetch_assoc($result_client_unpaid_invoices)) {
$invoice_number = intval($row['invoice_number']);
$invoice_id = intval($row['invoice_id']);
$invoice_prefix = nullable_htmlentities($row['invoice_prefix']);
$invoice_date = nullable_htmlentities($row['invoice_date']);
$invoice_amount = floatval($row['invoice_amount']);
$invoice_amount_formatted = numfmt_format_currency($currency_format, $invoice_amount, $currency_code);
$invoice_url = "invoice.php?invoice_id=$invoice_id";
$invoice_due = nullable_htmlentities($row['invoice_due']);
$invoice_balance = floatval(calculateInvoiceBalance($mysqli, $invoice_id));
$invoice_balance_formatted = numfmt_format_currency($currency_format, $invoice_balance, $currency_code);
?>
<tr>
<td>
<a href="<?php echo $invoice_url; ?>" target="_blank"><?php echo $invoice_prefix . $invoice_number; ?></a>
</td>
<td>
<?php echo $invoice_date; ?>
</td>
<td>
<?php echo $invoice_due; ?>
</td>
<td>
<?php echo $invoice_amount_formatted; ?>
</td>
<td>
<?php echo $invoice_balance_formatted; ?>
</td>
<?php
}
?>
</tbody>
</table>
</div>
<div class="table-responsive-sm">
<!-- Previous Payments -->
<table class="table table-sm">
<thead class="text-dark">
<tr>
<th>Payment Reference</th>
<th>Payment Date</th>
<th>Payment Amount</th>
<th>Invoice Number</th>
</tr>
</thead>
<tbody>
<?php
$sql_client_payments = "
SELECT
payments.payment_date,
payments.payment_amount,
payments.payment_reference,
invoices.invoice_number,
invoices.invoice_prefix,
invoices.invoice_client_id
FROM
payments
LEFT JOIN
invoices ON payments.payment_invoice_id = invoices.invoice_id
WHERE
invoice_client_id = $client_id
AND payment_archived_at IS NULL
ORDER BY
payment_date DESC";
$result_client_payments = mysqli_query($mysqli, $sql_client_payments);
while ($row = mysqli_fetch_assoc($result_client_payments)) {
$payment_date = nullable_htmlentities($row['payment_date']);
$payment_amount = floatval($row['payment_amount']);
$payment_reference = nullable_htmlentities($row['payment_reference']);
$invoice_number = nullable_htmlentities($row['invoice_prefix'].$row['invoice_number']);
$payment_amount_formatted = numfmt_format_currency($currency_format, $payment_amount, $currency_code);
?>
<tr>
<td>
<?php echo $payment_reference; ?>
</td>
<td>
<?php echo $payment_date; ?>
</td>
<td>
<?php echo $payment_amount_formatted; ?>
</td>
<td>
<?php echo $invoice_number; ?>
</td>
<?php
}
?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<?php require_once "footer.php";
}
?>

View File

@@ -9,11 +9,11 @@ require_once "inc_all_client.php";
if (isset($_GET['status']) && ($_GET['status']) == 'Closed') {
$status = 'Closed';
$ticket_status_snippet = "ticket_closed_at IS NOT NULL";
$ticket_status_snippet = "ticket_resolved_at IS NOT NULL";
} else {
// Default - Show open tickets
$status = 'Open';
$ticket_status_snippet = "ticket_closed_at IS NULL";
$ticket_status_snippet = "ticket_resolved_at IS NULL";
}
if (isset($_GET['billable']) && ($_GET['billable']) == '1') {
@@ -49,12 +49,12 @@ $sql = mysqli_query(
$num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
//Get Total tickets open
$sql_total_tickets_open = mysqli_query($mysqli, "SELECT COUNT(ticket_id) AS total_tickets_open FROM tickets WHERE ticket_client_id = $client_id AND ticket_closed_at IS NULL");
$sql_total_tickets_open = mysqli_query($mysqli, "SELECT COUNT(ticket_id) AS total_tickets_open FROM tickets WHERE ticket_client_id = $client_id AND ticket_resolved_at IS NULL");
$row = mysqli_fetch_array($sql_total_tickets_open);
$total_tickets_open = intval($row['total_tickets_open']);
//Get Total tickets closed
$sql_total_tickets_closed = mysqli_query($mysqli, "SELECT COUNT(ticket_id) AS total_tickets_closed FROM tickets WHERE ticket_client_id = $client_id AND ticket_closed_at IS NOT NULL");
$sql_total_tickets_closed = mysqli_query($mysqli, "SELECT COUNT(ticket_id) AS total_tickets_closed FROM tickets WHERE ticket_client_id = $client_id AND ticket_resolved_at IS NOT NULL");
$row = mysqli_fetch_array($sql_total_tickets_closed);
$total_tickets_closed = intval($row['total_tickets_closed']);
@@ -215,7 +215,7 @@ $total_tickets_closed = intval($row['total_tickets_closed']);
?>
<tr class="<?php if(empty($ticket_reply_created_at)) { echo "text-bold"; }?> <?php if ($ticket_reply_type == "Client") { echo "table-warning"; } ?>">
<tr class="<?php if(empty($ticket_reply_created_at)) { echo "text-bold"; }?> <?php if (empty($ticket_closed_at) && $ticket_reply_type == "Client") { echo "table-warning"; } ?>">
<!-- Ticket Number -->
<td>
@@ -263,15 +263,15 @@ $total_tickets_closed = intval($row['total_tickets_closed']);
<!-- Ticket Last Response -->
<td>
<div title="<?php echo $ticket_reply_created_at; ?>"><?php echo $ticket_reply_created_at_time_ago; ?></div>
<div><?php echo $ticket_reply_by_display; ?></div>
<?php if (!empty($ticket_reply_created_at)) { ?>
<div title="<?php echo $ticket_reply_created_at; ?>"><?php echo $ticket_reply_created_at_time_ago; ?></div>
<div><?php echo $ticket_reply_by_display; ?></div>
<?php } ?>
</td>
<!-- Ticket Created At -->
<td>
<td title="<?php echo $ticket_created_at; ?>">
<?php echo $ticket_created_at_time_ago; ?>
<br>
<small class="text-secondary"><?php echo $ticket_created_at; ?></small>
</td>
</tr>

View File

@@ -101,6 +101,10 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</button>
<button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown"></button>
<div class="dropdown-menu">
<a class="dropdown-item text-dark" href="#" data-toggle="modal" data-target="#importClientModal">
<i class="fa fa-fw fa-upload mr-2"></i>Import
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-dark" href="#" data-toggle="modal" data-target="#exportClientModal">
<i class="fa fa-fw fa-download mr-2"></i>Export
</a>
@@ -351,7 +355,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
?>
<tr>
<td>
<a class="font-weight-bold" href="client_overview.php?client_id=<?php echo $client_id; ?>"><?php echo $client_name; ?></a>
<a data-toggle="tooltip" data-placement="right" title="Client ID: <?php echo $client_id; ?>" class="font-weight-bold" href="client_overview.php?client_id=<?php echo $client_id; ?>"><?php echo $client_name; ?></a>
<?php
if (!empty($client_type)) {
@@ -466,6 +470,8 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<?php
require_once "client_add_modal.php";
require_once "client_import_modal.php";
require_once "client_export_modal.php";
require_once "footer.php";

View File

@@ -51,7 +51,6 @@ $config_ticket_prefix = sanitizeInput($row['config_ticket_prefix']);
$config_ticket_from_name = sanitizeInput($row['config_ticket_from_name']);
$config_ticket_from_email = sanitizeInput($row['config_ticket_from_email']);
$config_ticket_client_general_notifications = intval($row['config_ticket_client_general_notifications']);
$config_ticket_autoclose = intval($row['config_ticket_autoclose']);
$config_ticket_autoclose_hours = intval($row['config_ticket_autoclose_hours']);
$config_ticket_new_ticket_notification_email = sanitizeInput($row['config_ticket_new_ticket_notification_email']);
@@ -380,86 +379,32 @@ if (mysqli_num_rows($sql_scheduled_tickets) > 0) {
//mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Cron', log_action = 'Task', log_description = 'Cron created sent out recurring tickets'");
// AUTO CLOSE TICKET - CLOSE
// Automatically silently closes tickets 22 hrs after the last chase
// TICKET RESOLUTION/CLOSURE PROCESS
// Changes tickets status from 'Resolved' >> 'Closed' after a defined interval
// Check to make sure auto-close is enabled
if ($config_ticket_autoclose == 1) {
$sql_tickets_to_chase = mysqli_query(
$mysqli,
"SELECT * FROM tickets
WHERE ticket_status = 4
AND ticket_updated_at < NOW() - INTERVAL $config_ticket_autoclose_hours HOUR"
);
$sql_resolved_tickets_to_close = mysqli_query(
$mysqli,
"SELECT * FROM tickets
WHERE ticket_status = 4
AND ticket_updated_at < NOW() - INTERVAL $config_ticket_autoclose_hours HOUR"
);
while ($row = mysqli_fetch_array($sql_tickets_to_chase)) {
while ($row = mysqli_fetch_array($sql_resolved_tickets_to_close)) {
$ticket_id = $row['ticket_id'];
$ticket_prefix = sanitizeInput($row['ticket_prefix']);
$ticket_number = intval($row['ticket_number']);
$ticket_subject = sanitizeInput($row['ticket_subject']);
$ticket_status = sanitizeInput($row['ticket_status']);
$ticket_assigned_to = sanitizeInput($row['ticket_assigned_to']);
$client_id = intval($row['ticket_client_id']);
$ticket_id = $row['ticket_id'];
$ticket_prefix = sanitizeInput($row['ticket_prefix']);
$ticket_number = intval($row['ticket_number']);
$ticket_subject = sanitizeInput($row['ticket_subject']);
$ticket_status = sanitizeInput($row['ticket_status']);
$ticket_assigned_to = sanitizeInput($row['ticket_assigned_to']);
$client_id = intval($row['ticket_client_id']);
mysqli_query($mysqli,"UPDATE tickets SET ticket_status = 5, ticket_closed_at = NOW(), ticket_closed_by = $ticket_assigned_to WHERE ticket_id = $ticket_id");
mysqli_query($mysqli,"UPDATE tickets SET ticket_status = 5, ticket_closed_at = NOW(), ticket_closed_by = $ticket_assigned_to WHERE ticket_id = $ticket_id");
//Logging
mysqli_query($mysqli,"INSERT INTO logs SET log_type = 'Ticket', log_action = 'Closed', log_description = '$ticket_prefix$ticket_number auto closed', log_entity_id = $ticket_id");
//Logging
mysqli_query($mysqli,"INSERT INTO logs SET log_type = 'Ticket', log_action = 'Closed', log_description = '$ticket_prefix$ticket_number auto closed', log_entity_id = $ticket_id");
}
// AUTO CLOSE TICKETS - CHASE
// Automatically sends a chaser email after approx 48 hrs/2 days
$sql_tickets_to_chase = mysqli_query(
$mysqli,
"SELECT contact_name, contact_email, ticket_id, ticket_prefix, ticket_number, ticket_subject, ticket_status, ticket_client_id FROM tickets
LEFT JOIN clients ON ticket_client_id = client_id
LEFT JOIN contacts ON ticket_contact_id = contact_id
WHERE ticket_status = 4
AND ticket_updated_at < NOW() - INTERVAL 48 HOUR"
);
while ($row = mysqli_fetch_array($sql_tickets_to_chase)) {
$contact_name = sanitizeInput($row['contact_name']);
$contact_email = sanitizeInput($row['contact_email']);
$ticket_id = intval($row['ticket_id']);
$ticket_prefix = sanitizeInput($row['ticket_prefix']);
$ticket_number = intval($row['ticket_number']);
$ticket_subject = sanitizeInput($row['ticket_subject']);
$ticket_status = sanitizeInput( getTicketStatusName($row['ticket_status']));
$client_id = intval($row['ticket_client_id']);
$sql_ticket_reply = mysqli_query($mysqli, "SELECT ticket_reply FROM ticket_replies WHERE ticket_reply_type = 'Public' AND ticket_reply_ticket_id = $ticket_id ORDER BY ticket_reply_created_at DESC LIMIT 1");
$ticket_reply_row = mysqli_fetch_array($sql_ticket_reply);
$ticket_reply = $ticket_reply_row['ticket_reply'];
$subject = "Ticket pending closure - [$ticket_prefix$ticket_number] - $ticket_subject";
$body = "<i style=\'color: #808080\'>##- Please type your reply above this line -##</i><br><br>Hello, $contact_name<br><br>This is an automatic friendly reminder that your ticket regarding \"$ticket_subject\" will be closed, unless you respond.<br><br>--------------------------------<br>$ticket_reply--------------------------------<br><br>If your issue is resolved, you can ignore this email - the ticket will automatically close. If you need further assistance, please respond to this email. <br><br>Ticket: $ticket_prefix$ticket_number<br>Subject: $ticket_subject<br>Status: $ticket_status <br>Portal: https://$config_base_url/portal/ticket.php?id=$ticket_id<br><br>--<br>$company_name - Support<br>$config_ticket_from_email<br>$company_phone";
$data = [
[
'from' => $config_ticket_from_email,
'from_name' => $config_ticket_from_name,
'recipient' => $contact_email,
'recipient_name' => $contact_name,
'subject' => $subject,
'body' => $body
]
];
$mail = addToMailQueue($mysqli, $data);
if ($mail !== true) {
mysqli_query($mysqli,"INSERT INTO notifications SET notification_type = 'Mail', notification = 'Failed to send email to $contact_email'");
mysqli_query($mysqli,"INSERT INTO logs SET log_type = 'Mail', log_action = 'Error', log_description = 'Failed to send email to $contact_email regarding $subject. $mail'");
}
mysqli_query($mysqli,"INSERT INTO logs SET log_type = 'Ticket Reply', log_action = 'Create', log_description = 'Auto close chaser email sent to $contact_email for ticket $ticket_prefix$ticket_number - $ticket_subject', log_client_id = $client_id");
}
//TODO: Add client notifs if $config_ticket_client_general_notifications is on
}
if ($config_send_invoice_reminders == 1) {
@@ -518,7 +463,7 @@ if ($config_send_invoice_reminders == 1) {
mysqli_query($mysqli, "INSERT INTO notifications SET notification_type = 'Invoice Overdue', notification = 'Invoice $invoice_prefix$invoice_number for $client_name in the amount of $invoice_amount is overdue by $day days', notification_action = 'invoice.php?invoice_id=$invoice_id', notification_client_id = $client_id, notification_entity_id = $invoice_id");
$subject = "$company_name Overdue Invoice $invoice_prefix$invoice_number";
$subject = "Overdue Invoice $invoice_prefix$invoice_number";
$body = "Hello $contact_name,<br><br>Our records indicate that we have not yet received payment for the invoice $invoice_prefix$invoice_number. We kindly request that you submit your payment as soon as possible. If you have any questions or concerns, please do not hesitate to contact us at $company_email or $company_phone.
<br><br>
Kindly review the invoice details mentioned below.<br><br>Invoice: $invoice_prefix$invoice_number<br>Issue Date: $invoice_date<br>Total: " . numfmt_format_currency($currency_format, $invoice_amount, $invoice_currency_code) . "<br>Due Date: $invoice_due<br>Over Due By: $day Days<br><br><br>To view your invoice, please click <a href=\'https://$config_base_url/guest_view_invoice.php?invoice_id=$invoice_id&url_key=$invoice_url_key\'>here</a>.<br><br><br>--<br>$company_name - Billing<br>$config_invoice_from_email<br>$company_phone";
@@ -639,7 +584,7 @@ while ($row = mysqli_fetch_array($sql_recurring)) {
$contact_name = sanitizeInput($row['contact_name']);
$contact_email = sanitizeInput($row['contact_email']);
$subject = "$company_name Invoice $invoice_prefix$invoice_number";
$subject = "Invoice $invoice_prefix$invoice_number";
$body = "Hello $contact_name,<br><br>An invoice regarding \"$invoice_scope\" has been generated. Please view the details below.<br><br>Invoice: $invoice_prefix$invoice_number<br>Issue Date: $invoice_date<br>Total: " . numfmt_format_currency($currency_format, $invoice_amount, $recurring_currency_code) . "<br>Due Date: $invoice_due<br><br><br>To view your invoice, please click <a href=\'https://$config_base_url/guest_view_invoice.php?invoice_id=$new_invoice_id&url_key=$invoice_url_key\'>here</a>.<br><br><br>--<br>$company_name - Billing<br>$config_invoice_from_email<br>$company_phone";
$mail = addToMailQueue($mysqli, [

View File

@@ -22,6 +22,7 @@ require_once "get_settings.php";
$config_ticket_prefix = sanitizeInput($config_ticket_prefix);
$config_ticket_from_name = sanitizeInput($config_ticket_from_name);
$config_ticket_email_parse_unknown_senders = intval($row['config_ticket_email_parse_unknown_senders']);
// Get company name & phone & timezone
$sql = mysqli_query($mysqli, "SELECT * FROM companies, settings WHERE companies.company_id = settings.company_id AND companies.company_id = 1");
@@ -94,7 +95,10 @@ function addTicket($contact_id, $contact_name, $contact_email, $client_id, $date
$contact_email_esc = mysqli_real_escape_string($mysqli, $contact_email);
$client_id_esc = intval($client_id);
mysqli_query($mysqli, "INSERT INTO tickets SET ticket_prefix = '$ticket_prefix_esc', ticket_number = $ticket_number, ticket_subject = '$subject_esc', ticket_details = '$message_esc', ticket_priority = 'Low', ticket_status = 1, ticket_created_by = 0, ticket_contact_id = $contact_id, ticket_client_id = $client_id_esc");
//Generate a unique URL key for clients to access
$url_key = randomString(156);
mysqli_query($mysqli, "INSERT INTO tickets SET ticket_prefix = '$ticket_prefix_esc', ticket_number = $ticket_number, ticket_subject = '$subject_esc', ticket_details = '$message_esc', ticket_priority = 'Low', ticket_status = 1, ticket_created_by = 0, ticket_contact_id = $contact_id, ticket_url_key = '$url_key', ticket_client_id = $client_id_esc");
$id = mysqli_insert_id($mysqli);
echo "Created new ticket.<br>";
@@ -132,7 +136,7 @@ function addTicket($contact_id, $contact_name, $contact_email, $client_id, $date
}
$data = [];
if ($config_ticket_client_general_notifications == 1) {
if ($config_ticket_client_general_notifications == 1 && $client_id != 0) {
$subject_email = "Ticket created - [$config_ticket_prefix$ticket_number] - $subject";
$body = "<i style='color: #808080'>##- Please type your reply above this line -##</i><br><br>Hello $contact_name,<br><br>Thank you for your email. A ticket regarding \"$subject\" has been automatically created for you.<br><br>Ticket: $config_ticket_prefix$ticket_number<br>Subject: $subject<br>Status: New<br>https://$config_base_url/portal/ticket.php?id=$id<br><br>--<br>$company_name - Support<br>$config_ticket_from_email<br>$company_phone";
@@ -212,7 +216,7 @@ function addReply($from_email, $date, $subject, $ticket_number, $message, $attac
mysqli_query($mysqli, "INSERT INTO notifications SET notification_type = 'Ticket', notification = 'Email parser: $from_email attempted to re-open ticket $config_ticket_prefix_esc$ticket_number_esc (ID $ticket_id_esc) - check inbox manually to see email', notification_action = 'ticket.php?ticket_id=$ticket_id_esc', notification_client_id = $client_id_esc");
$email_subject = "Action required: This ticket is already closed";
$email_body = "Hi there, <br><br>You've tried to reply to a ticket that is closed - we won't see your response. <br><br>Please raise a new ticket by sending a fresh e-mail to our support address below. <br><br>--<br>$company_name - Support<br>$config_ticket_from_email<br>$company_phone";
$email_body = "Hi there, <br><br>You've tried to reply to a ticket that is closed - we won't see your response. <br><br>Please raise a new ticket by sending a new e-mail to our support address below. <br><br>--<br>$company_name - Support<br>$config_ticket_from_email<br>$company_phone";
$data = [
[
@@ -300,7 +304,7 @@ function addReply($from_email, $date, $subject, $ticket_number, $message, $attac
}
}
mysqli_query($mysqli, "UPDATE tickets SET ticket_status = 2 WHERE ticket_id = $ticket_id AND ticket_client_id = $client_id LIMIT 1");
mysqli_query($mysqli, "UPDATE tickets SET ticket_status = 2, ticket_resolved_at = NULL WHERE ticket_id = $ticket_id AND ticket_client_id = $client_id LIMIT 1");
echo "Updated existing ticket.<br>";
mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Ticket', log_action = 'Update', log_description = 'Email parser: Client contact $from_email_esc updated ticket $config_ticket_prefix$ticket_number_esc ($subject)', log_client_id = $client_id");
@@ -311,12 +315,71 @@ function addReply($from_email, $date, $subject, $ticket_number, $message, $attac
}
}
// Prepare connection string with encryption (TLS/SSL/<blank>)
$imap_mailbox = "$config_imap_host:$config_imap_port/imap/$config_imap_encryption";
// Function to create a folder in the mailbox if it doesn't exist
function createMailboxFolder($client, $folderName) {
try {
// Attempt to get the folder
$folder = $client->getFolder($folderName);
// If the folder doesn't exist, create it
if (!$folder) {
$client->createFolder($folderName);
echo "Folder '$folderName' created successfully.";
// Disconnect and reconnect to ensure the server registers the new folder
$client->disconnect();
sleep(1); // Pause before reconnecting
$client->connect();
} else {
echo "Folder '$folderName' already exists.";
}
// Re-fetch the folder after reconnecting
return $client->getFolder($folderName);
} catch (Exception $e) {
echo "Error creating folder '$folderName': " . $e->getMessage();
return null;
}
}
// Function to subscribe to a folder in the mailbox
function subscribeMailboxFolder($client, $folder) {
if ($folder) {
try {
// Subscribe to the folder
$folder->subscribe();
echo "Folder '{$folder->name}' subscribed successfully.";
} catch (Exception $e) {
echo "Error subscribing to folder '{$folder->name}': " . $e->getMessage();
}
} else {
echo "Cannot subscribe to folder because it does not exist.";
}
}
// Initialize the client manager and create the client
$clientManager = new ClientManager();
$client = $clientManager->make([
'host' => $config_imap_host,
'port' => $config_imap_port,
'encryption' => $config_imap_encryption,
'validate_cert' => true,
'username' => $config_imap_username,
'password' => $config_imap_password,
'protocol' => 'imap'
]);
// Connect to the IMAP server
$client->connect();
// Create the "ITFlow" mailbox folder if it doesn't exist
$folder = createMailboxFolder($client, 'ITFlow');
// Subscribe to the "ITFlow" mailbox folder
subscribeMailboxFolder($client, $folder);
// Possible names for the inbox folder
$inboxNames = ['Inbox', 'INBOX', 'inbox'];
@@ -407,6 +470,12 @@ if ($messages->count() > 0) {
if (addTicket($contact_id, $contact_name, $contact_email, $client_id, $date, $subject, $message_body, $message->getAttachments(), $original_message_file)) {
$email_processed = true;
}
} elseif ($config_ticket_email_parse_unknown_senders) {
// Parse even if the sender is unknown
if (addTicket(0, 'Guest', $from_email, 0, $date, $subject, $message_body, $message->getAttachments(), $original_message_file)) {
$email_processed = true;
}
}
}
}

View File

@@ -571,7 +571,7 @@ if ($user_config_dashboard_technical_enable == 1) {
<div class="row">
<div class="col-lg-4 col-6">
<!-- small box -->
<a class="small-box bg-secondary" href="clients.php?date_from=<?php echo $year; ?>-01-01&date_to=<?php echo $year; ?>-12-31">
<a class="small-box bg-secondary" href="clients.php?dtf=<?php echo $year; ?>-01-01&dtt=<?php echo $year; ?>-12-31">
<div class="inner">
<h3><?php echo $clients_added; ?></h3>
<p>New Clients</p>

View File

@@ -2084,10 +2084,57 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) {
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '1.4.2'");
}
// if (CURRENT_DATABASE_VERSION == '1.4.2') {
// // Insert queries here required to update to DB version 1.4.3
if (CURRENT_DATABASE_VERSION == '1.4.2') {
mysqli_query($mysqli, "ALTER TABLE `settings` ADD `config_ticket_email_parse_unknown_senders` INT(1) NOT NULL DEFAULT '0' AFTER `config_ticket_email_parse`");
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '1.4.3'");
}
if (CURRENT_DATABASE_VERSION == '1.4.3') {
// Add ticket URL key column
mysqli_query($mysqli, "ALTER TABLE `tickets` ADD `ticket_url_key` VARCHAR(200) DEFAULT NULL AFTER `ticket_feedback`");
// Populate pre-existing columns for open tickets
$sql_tickets_1 = mysqli_query($mysqli, "SELECT ticket_id FROM tickets WHERE tickets.ticket_closed_at IS NULL");
foreach ($sql_tickets_1 as $row) {
$ticket_id = intval($row['ticket_id']);
$url_key = randomString(156);
mysqli_query($mysqli, "UPDATE tickets SET ticket_url_key = '$url_key' WHERE ticket_id = '$ticket_id'");
}
// Add ticket resolved at column
mysqli_query($mysqli, "ALTER TABLE `tickets` ADD `ticket_resolved_at` DATETIME DEFAULT NULL AFTER `ticket_updated_at`");
// Populate pre-existing columns for closed tickets
$sql_tickets_2 = mysqli_query($mysqli, "SELECT ticket_id, ticket_updated_at, ticket_closed_at FROM tickets WHERE tickets.ticket_closed_at IS NOT NULL");
foreach ($sql_tickets_2 as $row) {
$ticket_id = intval($row['ticket_id']);
$ticket_updated_at = sanitizeInput($row['ticket_updated_at']); // To keep old updated_at time
$ticket_closed_at = sanitizeInput($row['ticket_closed_at']);
mysqli_query($mysqli, "UPDATE tickets SET ticket_resolved_at = '$ticket_closed_at', ticket_updated_at = '$ticket_updated_at' WHERE ticket_id = '$ticket_id'");
}
// Change ticket status 'Auto close' to 'Resolved'
mysqli_query($mysqli, "UPDATE `ticket_statuses` SET `ticket_status_name` = 'Resolved' WHERE `ticket_statuses`.`ticket_status_id` = 4");
// Auto-close is no longer optional
mysqli_query($mysqli, "ALTER TABLE `settings` DROP `config_ticket_autoclose`");
mysqli_query($mysqli, "UPDATE `settings` SET `config_ticket_autoclose_hours` = '72'");
// DB Version
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '1.4.4'");
}
if (CURRENT_DATABASE_VERSION == '1.4.4') {
mysqli_query($mysqli, "ALTER TABLE `api_keys` ADD `api_key_decrypt_hash` VARCHAR(200) NOT NULL AFTER `api_key_secret`");
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '1.4.5'");
}
// if (CURRENT_DATABASE_VERSION == '1.4.5') {
// // Insert queries here required to update to DB version 1.4.6
// // Then, update the database to the next sequential version
// mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '1.4.3'");
// mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '1.4.6'");
// }
} else {

View File

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

5
db.sql
View File

@@ -66,6 +66,7 @@ CREATE TABLE `api_keys` (
`api_key_id` int(11) NOT NULL AUTO_INCREMENT,
`api_key_name` varchar(255) NOT NULL,
`api_key_secret` varchar(255) NOT NULL,
`api_key_decrypt_hash` varchar(255) NULL,
`api_key_created_at` datetime NOT NULL DEFAULT current_timestamp(),
`api_key_expire` date NOT NULL,
`api_key_client_id` int(11) NOT NULL DEFAULT 0,
@@ -1467,8 +1468,8 @@ CREATE TABLE `settings` (
`config_ticket_from_name` varchar(200) DEFAULT NULL,
`config_ticket_from_email` varchar(200) DEFAULT NULL,
`config_ticket_email_parse` tinyint(1) NOT NULL DEFAULT 0,
`config_ticket_email_parse_unknown_senders` tinyint(1) NOT NULL DEFAULT 0,
`config_ticket_client_general_notifications` tinyint(1) NOT NULL DEFAULT 1,
`config_ticket_autoclose` tinyint(1) NOT NULL DEFAULT 0,
`config_ticket_autoclose_hours` int(5) NOT NULL DEFAULT 72,
`config_ticket_new_ticket_notification_email` varchar(200) DEFAULT NULL,
`config_ticket_default_billable` tinyint(1) NOT NULL DEFAULT 0,
@@ -1845,8 +1846,10 @@ CREATE TABLE `tickets` (
`ticket_onsite` tinyint(1) NOT NULL DEFAULT 0,
`ticket_vendor_ticket_number` varchar(255) DEFAULT NULL,
`ticket_feedback` varchar(200) DEFAULT NULL,
`ticket_url_key` varchar(200) DEFAULT NULL,
`ticket_created_at` datetime NOT NULL DEFAULT current_timestamp(),
`ticket_updated_at` datetime DEFAULT NULL ON UPDATE current_timestamp(),
`ticket_resolved_at` datetime DEFAULT NULL,
`ticket_archived_at` datetime DEFAULT NULL,
`ticket_closed_at` datetime DEFAULT NULL,
`ticket_created_by` int(11) NOT NULL,

View File

@@ -15,7 +15,6 @@
<script src="plugins/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- Custom js-->
<script src="js/header_timers.js"></script>
<script src="plugins/moment/moment.min.js"></script>
<script src="plugins/chart.js/Chart.min.js"></script>
<script src="plugins/tempusdominus-bootstrap-4/js/tempusdominus-bootstrap-4.min.js"></script>

View File

@@ -271,7 +271,7 @@ function setupFirstUserSpecificKey($user_password, $site_encryption_master_key)
}
/*
* For additional users / password changes
* For additional users / password changes (and now the API)
* New Users: Requires the admin setting up their account have a Specific/Session key configured
* Password Changes: Will use the current info in the session.
*/
@@ -282,7 +282,7 @@ function encryptUserSpecificKey($user_password)
// Get the session info.
$user_encryption_session_ciphertext = $_SESSION['user_encryption_session_ciphertext'];
$user_encryption_session_iv = $_SESSION['user_encryption_session_iv'];
$user_encryption_session_iv = $_SESSION['user_encryption_session_iv'];
$user_encryption_session_key = $_COOKIE['user_encryption_session_key'];
// Decrypt the session key to get the master key
@@ -297,7 +297,7 @@ function encryptUserSpecificKey($user_password)
return $salt . $iv . $ciphertext;
}
// Given a ciphertext (incl. IV) and the user's password, returns the site master key
// Given a ciphertext (incl. IV) and the user's (or API key) password, returns the site master key
// Ran at login, to facilitate generateUserSessionKey
function decryptUserSpecificKey($user_encryption_ciphertext, $user_password)
{
@@ -380,6 +380,32 @@ function encryptLoginEntry($login_password_cleartext)
return $iv . $ciphertext;
}
function apiDecryptLoginEntry($login_ciphertext, $api_key_decrypt_hash, #[\SensitiveParameter]$api_key_decrypt_password)
{
// Split the login entry (username/password) into IV and Ciphertext
$login_iv = substr($login_ciphertext, 0, 16);
$login_ciphertext = $salt = substr($login_ciphertext, 16);
// Decrypt the api hash to get the master key
$site_encryption_master_key = decryptUserSpecificKey($api_key_decrypt_hash, $api_key_decrypt_password);
// Decrypt the login password using the master key
return openssl_decrypt($login_ciphertext, 'aes-128-cbc', $site_encryption_master_key, 0, $login_iv);
}
function apiEncryptLoginEntry(#[\SensitiveParameter]$credential_cleartext, $api_key_decrypt_hash, #[\SensitiveParameter]$api_key_decrypt_password)
{
$iv = randomString();
// Decrypt the api hash to get the master key
$site_encryption_master_key = decryptUserSpecificKey($api_key_decrypt_hash, $api_key_decrypt_password);
// Encrypt the credential using the master key
$ciphertext = openssl_encrypt($credential_cleartext, 'aes-128-cbc', $site_encryption_master_key, 0, $iv);
return $iv . $ciphertext;
}
// Get domain general info (whois + NS/A/MX records)
function getDomainRecords($name)
{
@@ -738,6 +764,10 @@ function sanitizeForEmail($data)
function timeAgo($datetime)
{
if (is_null($datetime)) {
return "-";
}
$time = strtotime($datetime);
$difference = $time - time(); // Changed to handle future dates
@@ -1193,36 +1223,6 @@ function fetchUpdates() {
}
// Get domain expiration date -- Remove in the future Replace with PHP function
function getDomainExpirationDateOLD($name)
{
// Only run if we think the domain is valid
if (!filter_var($name, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) {
return "NULL";
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://lookup.itflow.org:8080/$name");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$response = json_decode(curl_exec($ch), 1);
if ($response) {
if (is_array($response['expiration_date'])) {
$expiry = new DateTime($response['expiration_date'][1]);
} elseif (isset($response['expiration_date'])) {
$expiry = new DateTime($response['expiration_date']);
} else {
return "NULL";
}
return $expiry->format('Y-m-d');
}
// Default return
return "NULL";
}
function getDomainExpirationDate($domain) {
// Execute the whois command
$result = shell_exec("whois " . escapeshellarg($domain));

View File

@@ -68,12 +68,13 @@ $config_ticket_next_number = intval($row['config_ticket_next_number']);
$config_ticket_from_name = $row['config_ticket_from_name'];
$config_ticket_from_email = $row['config_ticket_from_email'];
$config_ticket_email_parse = intval($row['config_ticket_email_parse']);
$config_ticket_email_parse_unknown_senders = intval($row['config_ticket_email_parse_unknown_senders']);
$config_ticket_client_general_notifications = intval($row['config_ticket_client_general_notifications']);
$config_ticket_autoclose = intval($row['config_ticket_autoclose']);
$config_ticket_autoclose_hours = intval($row['config_ticket_autoclose_hours']);
$config_ticket_new_ticket_notification_email = $row['config_ticket_new_ticket_notification_email'];
$config_ticket_default_billable = intval($row['config_ticket_default_billable']);
// Cron
$config_enable_cron = intval($row['config_enable_cron']);
$config_cron_key = $row['config_cron_key'];

View File

@@ -55,3 +55,83 @@ if (isset($_GET['decline_quote'], $_GET['url_key'])) {
}
if (isset($_GET['reopen_ticket'], $_GET['url_key'])) {
$ticket_id = intval($_GET['ticket_id']);
$url_key = sanitizeInput($_GET['url_key']);
$sql = mysqli_query($mysqli, "SELECT * FROM tickets WHERE ticket_id = $ticket_id AND ticket_url_key = '$url_key' AND ticket_resolved_at IS NOT NULL and ticket_closed_at IS NULL");
if (mysqli_num_rows($sql) == 1) {
// Update the ticket
mysqli_query($mysqli, "UPDATE tickets SET ticket_status = 2, ticket_resolved_at = NULL WHERE ticket_id = $ticket_id AND ticket_url_key = '$url_key'");
// Add reply
mysqli_query($mysqli, "INSERT INTO ticket_replies SET ticket_reply = 'Ticket reopened by client (guest URL).', ticket_reply_type = 'Internal', ticket_reply_by = 0, ticket_reply_ticket_id = $ticket_id");
//Logging
mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Ticket', log_action = 'Replied', log_description = '$ticket_id reopened by client (guest)', log_ip = '$session_ip', log_user_agent = '$session_user_agent'");
$_SESSION['alert_message'] = "Ticket reopened";
header("Location: " . $_SERVER["HTTP_REFERER"]);
} else {
echo "Invalid!!";
}
}
if (isset($_GET['close_ticket'], $_GET['url_key'])) {
$ticket_id = intval($_GET['ticket_id']);
$url_key = sanitizeInput($_GET['url_key']);
$sql = mysqli_query($mysqli, "SELECT * FROM tickets WHERE ticket_id = $ticket_id AND ticket_url_key = '$url_key' AND ticket_resolved_at IS NOT NULL and ticket_closed_at IS NULL");
if (mysqli_num_rows($sql) == 1) {
// Update the ticket
mysqli_query($mysqli, "UPDATE tickets SET ticket_status = 5, ticket_closed_at = NOW() WHERE ticket_id = $ticket_id AND ticket_url_key = '$url_key'");
// Add reply
mysqli_query($mysqli, "INSERT INTO ticket_replies SET ticket_reply = 'Ticket closed by client (guest URL).', ticket_reply_type = 'Internal', ticket_reply_by = 0, ticket_reply_ticket_id = $ticket_id");
//Logging
mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Ticket', log_action = 'Replied', log_description = '$ticket_id closed by client (guest)', log_ip = '$session_ip', log_user_agent = '$session_user_agent'");
$_SESSION['alert_message'] = "Ticket closed";
header("Location: " . $_SERVER["HTTP_REFERER"]);
} else {
echo "Invalid!!";
}
}
if (isset($_GET['add_ticket_feedback'], $_GET['url_key'])) {
$ticket_id = intval($_GET['ticket_id']);
$url_key = sanitizeInput($_GET['url_key']);
$feedback = sanitizeInput($_GET['feedback']);
$sql = mysqli_query($mysqli, "SELECT * FROM tickets WHERE ticket_id = $ticket_id AND ticket_url_key = '$url_key' AND ticket_closed_at IS NOT NULL");
if (mysqli_num_rows($sql) == 1) {
// Add feedback
mysqli_query($mysqli, "UPDATE tickets SET ticket_feedback = '$feedback' WHERE ticket_id = $ticket_id AND ticket_url_key = '$url_key'");
// Notify on bad feedback
if ($feedback == "Bad") {
mysqli_query($mysqli, "INSERT INTO notifications SET notification_type = 'Feedback', notification = 'Guest rated ticket ID $ticket_id as bad'");
}
$_SESSION['alert_message'] = "Feedback recorded - thank you";
header("Location: " . $_SERVER["HTTP_REFERER"]);
} else {
echo "Invalid!!";
}
}

View File

@@ -135,18 +135,45 @@ if ($invoice_status !== "Paid" && $invoice_status !== "Draft" && $invoice_status
// Invoice individual items
$sql_invoice_items = mysqli_query($mysqli, "SELECT * FROM invoice_items WHERE item_invoice_id = $invoice_id ORDER BY item_order ASC");
// Get Total Account Balance
//Add up all the payments for the invoice and get the total amount paid to the invoice
$sql_invoice_amounts = mysqli_query($mysqli, "SELECT SUM(invoice_amount) AS invoice_amounts FROM invoices WHERE invoice_client_id = $client_id AND invoice_status NOT LIKE 'Draft' AND invoice_status NOT LIKE 'Cancelled' ");
$row = mysqli_fetch_array($sql_invoice_amounts);
$account_balance = floatval($row['invoice_amounts']);
$sql_amount_paid = mysqli_query($mysqli, "SELECT SUM(payment_amount) AS amount_paid FROM payments, invoices WHERE payment_invoice_id = invoice_id AND invoice_client_id = $client_id");
$row = mysqli_fetch_array($sql_amount_paid);
$account_amount_paid = floatval($row['amount_paid']);
$account_balance = $account_balance - $account_amount_paid;
//set Text color on balance
if ($balance > 0) {
$balance_text_color = "text-danger font-weight-bold";
} else {
$balance_text_color = "";
}
?>
<div class="card">
<div class="card-header bg-light d-print-none">
<div class="float-right">
<a class="btn btn-secondary" data-toggle="collapse" href="#collapsePreviousInvoices"><i class="fas fa-fw fa-history mr-2"></i>Invoice History</a>
<a class="btn btn-primary" href="#" onclick="window.print();"><i class="fas fa-fw fa-print mr-2"></i>Print</a>
<a class="btn btn-primary" href="#" onclick="pdfMake.createPdf(docDefinition).download('<?php echo strtoAZaz09(html_entity_decode("$invoice_date-$company_name-Invoice-$invoice_prefix$invoice_number")); ?>');"><i class="fa fa-fw fa-download mr-2"></i>Download</a>
<?php
if ($invoice_status !== "Paid" && $invoice_status !== "Cancelled" && $invoice_status !== "Draft" && $config_stripe_enable == 1) { ?>
<a class="btn btn-success" href="guest_pay_invoice_stripe.php?invoice_id=<?php echo $invoice_id; ?>&url_key=<?php echo $url_key; ?>"><i class="fa fa-fw fa-credit-card mr-2"></i>Pay Online <?php if($config_stripe_client_pays_fees == 1) { echo "(Gateway Fee: " . numfmt_format_currency($currency_format, $gateway_fee, $invoice_currency_code) . ")"; } ?></a>
<?php } ?>
<div class="row">
<div class="col-6">
<h4 class="mt-1">Account Balance: <b><?php echo numfmt_format_currency($currency_format, $account_balance, $invoice_currency_code); ?></b></h4>
</div>
<div class="col-6">
<div class="float-right">
<a class="btn btn-default" href="#" onclick="window.print();"><i class="fas fa-fw fa-print mr-2"></i>Print</a>
<a class="btn btn-default" href="#" onclick="pdfMake.createPdf(docDefinition).download('<?php echo strtoAZaz09(html_entity_decode("$invoice_date-$company_name-Invoice-$invoice_prefix$invoice_number")); ?>');"><i class="fa fa-fw fa-download mr-2"></i>Download</a>
<?php
if ($invoice_status !== "Paid" && $invoice_status !== "Cancelled" && $invoice_status !== "Draft" && $config_stripe_enable == 1) { ?>
<a class="btn btn-success" href="guest_pay_invoice_stripe.php?invoice_id=<?php echo $invoice_id; ?>&url_key=<?php echo $url_key; ?>"><i class="fa fa-fw fa-credit-card mr-2"></i>Pay Now <?php if($config_stripe_client_pays_fees == 1) { echo "(Gateway Fee: " . numfmt_format_currency($currency_format, $gateway_fee, $invoice_currency_code) . ")"; } ?></a>
<?php } ?>
</div>
</div>
</div>
</div>
<div class="card-body">
@@ -773,79 +800,23 @@ $sql_invoice_items = mysqli_query($mysqli, "SELECT * FROM invoice_items WHERE it
<?php
// PREVIOUS UNPAID INVOICES
$sql = mysqli_query($mysqli, "SELECT * FROM invoices WHERE invoice_client_id = $client_id AND invoice_due < CURDATE() AND(invoice_status = 'Sent' OR invoice_status = 'Viewed' OR invoice_status = 'Partial') ORDER BY invoice_date DESC");
if (mysqli_num_rows($sql) > 1) { ?>
<div class="card d-print-none card-danger">
<div class="card-header">
<strong><i class="fa fa-fw fa-exclamation-triangle mr-2"></i>Previous Unpaid Invoices</strong>
</div>
<div card="card-body">
<table class="table">
<thead>
<tr>
<th class="text-center">Invoice #</th>
<th>Date</th>
<th>Due Date</th>
<th class="text-right">Amount</th>
</tr>
</thead>
<tbody>
<?php
while ($row = mysqli_fetch_array($sql)) {
$invoice_id = intval($row['invoice_id']);
$invoice_prefix = nullable_htmlentities($row['invoice_prefix']);
$invoice_number = intval($row['invoice_number']);
$invoice_date = nullable_htmlentities($row['invoice_date']);
$invoice_due = nullable_htmlentities($row['invoice_due']);
$invoice_amount = floatval($row['invoice_amount']);
$invoice_currency_code = nullable_htmlentities($row['invoice_currency_code']);
$invoice_url_key = nullable_htmlentities($row['invoice_url_key']);
$invoice_tally_total = $invoice_amount + $invoice_tally_total;
$difference = time() - strtotime($invoice_due);
$days = floor($difference / (60*60*24));
?>
<tr <?php if ($_GET['invoice_id'] == $invoice_id) { echo "class='table-active'"; } ?>>
<th class="text-center"><a href="guest_view_invoice.php?invoice_id=<?php echo $invoice_id; ?>&url_key=<?php echo $invoice_url_key; ?>"><?php echo "$invoice_prefix$invoice_number"; ?></a></th>
<td><?php echo $invoice_date; ?></td>
<td class="text-danger text-bold"><?php echo $invoice_due; ?> (<?php echo $days; ?> Days Late)</td>
<td class="text-right"><?php echo numfmt_format_currency($currency_format, $invoice_amount, $invoice_currency_code); ?></td>
</tr>
<?php
}
?>
</tbody>
</table>
</div>
</div>
<?php } // End previous unpaid invoices
// CURRENT INVOICES
$sql = mysqli_query($mysqli, "SELECT * FROM invoices WHERE invoice_client_id = $client_id AND invoice_due > CURDATE() AND(invoice_status = 'Sent' OR invoice_status = 'Viewed' OR invoice_status = 'Partial') ORDER BY invoice_number DESC");
$sql_current_invoices = mysqli_query($mysqli, "SELECT * FROM invoices WHERE invoice_client_id = $client_id AND invoice_due > CURDATE() AND(invoice_status = 'Sent' OR invoice_status = 'Viewed' OR invoice_status = 'Partial') ORDER BY invoice_number DESC");
if (mysqli_num_rows($sql) > 1) { ?>
$current_invoices_count = mysqli_num_rows($sql_current_invoices);
if ($current_invoices_count > 0) { ?>
<div class="card d-print-none card-light">
<div class="card d-print-none card-dark">
<div class="card-header">
<strong><i class="fas fa-fw fa-clock mr-2"></i>Current Invoices</strong>
<strong><i class="fas fa-fw fa-clock mr-2"></i><b><?php echo $current_invoices_count; ?></b> Current Invoices</strong>
</div>
<div card="card-body">
<table class="table">
<table class="table table-sm">
<thead>
<tr>
<th class="text-center">Invoice #</th>
<th class="text-center">Invoice</th>
<th>Date</th>
<th>Due</th>
<th class="text-right">Amount</th>
@@ -854,7 +825,7 @@ if (mysqli_num_rows($sql) > 1) { ?>
<tbody>
<?php
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_array($sql_current_invoices)) {
$invoice_id = intval($row['invoice_id']);
$invoice_prefix = nullable_htmlentities($row['invoice_prefix']);
$invoice_number = intval($row['invoice_number']);
@@ -869,11 +840,11 @@ if (mysqli_num_rows($sql) > 1) { ?>
?>
<tr <?php if ($_GET['invoice_id'] == $invoice_id) { echo "class='table-active'"; } ?>>
<tr <?php if ($_GET['invoice_id'] == $invoice_id) { echo "class='table-primary'"; } ?>>
<th class="text-center"><a href="guest_view_invoice.php?invoice_id=<?php echo $invoice_id; ?>&url_key=<?php echo $invoice_url_key; ?>"><?php echo "$invoice_prefix$invoice_number"; ?></a></th>
<td><?php echo $invoice_date; ?></td>
<td><?php echo $invoice_due; ?> (Due in <?php echo $days; ?> Days)</td>
<td class="text-right"><?php echo numfmt_format_currency($currency_format, $invoice_amount, $invoice_currency_code); ?></td>
<td class="text-right text-bold"><?php echo numfmt_format_currency($currency_format, $invoice_amount, $invoice_currency_code); ?></td>
</tr>
<?php } ?>
@@ -882,37 +853,41 @@ if (mysqli_num_rows($sql) > 1) { ?>
</table>
</div>
</div>
<?php
}
?>
<?php
}
?>
<?php
// PREVIOUS PAID INVOICES
// OUTSTANDING INVOICES
$sql = mysqli_query($mysqli, "SELECT * FROM invoices WHERE invoice_client_id = $client_id AND invoice_status = 'Paid' ORDER BY invoice_date DESC");
$sql_outstanding_invoices = mysqli_query($mysqli, "SELECT * FROM invoices WHERE invoice_client_id = $client_id AND invoice_due < CURDATE() AND(invoice_status = 'Sent' OR invoice_status = 'Viewed' OR invoice_status = 'Partial') ORDER BY invoice_date DESC");
if (mysqli_num_rows($sql) > 1) { ?>
$outstanding_invoices_count = mysqli_num_rows($sql_outstanding_invoices);
<div class="card d-print-none collapse" id="collapsePreviousInvoices">
<div class="card-header bg-dark">
<strong><i class="fas fa-fw fa-history mr-2"></i>Previous Invoices Paid</strong>
if ($outstanding_invoices_count > 0) { ?>
<div class="card d-print-none card-danger">
<div class="card-header">
<strong><i class="fa fa-fw fa-exclamation-triangle mr-2"></i><b><?php echo $outstanding_invoices_count; ?></b> Outstanding Invoices</strong>
</div>
<div card="card-body">
<table class="table">
<table class="table table-sm">
<thead>
<tr>
<th class="text-center">Invoice #</th>
<th class="text-center">Invoice</th>
<th>Date</th>
<th>Due Date</th>
<th>Due</th>
<th></th>
<th class="text-right">Amount</th>
</tr>
</thead>
<tbody>
<?php
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_array($sql_outstanding_invoices)) {
$invoice_id = intval($row['invoice_id']);
$invoice_prefix = nullable_htmlentities($row['invoice_prefix']);
$invoice_number = intval($row['invoice_number']);
@@ -922,60 +897,27 @@ if (mysqli_num_rows($sql) > 1) { ?>
$invoice_currency_code = nullable_htmlentities($row['invoice_currency_code']);
$invoice_url_key = nullable_htmlentities($row['invoice_url_key']);
$invoice_tally_total = $invoice_amount + $invoice_tally_total;
$difference = time() - strtotime($invoice_due);
$days = floor($difference / (60*60*24));
?>
<tr <?php if ($_GET['invoice_id'] == $invoice_id) { echo "class='table-active'"; } ?>>
<tr <?php if ($_GET['invoice_id'] == $invoice_id) { echo "class='table-primary'"; } ?>>
<th class="text-center"><a href="guest_view_invoice.php?invoice_id=<?php echo $invoice_id; ?>&url_key=<?php echo $invoice_url_key; ?>"><?php echo "$invoice_prefix$invoice_number"; ?></a></th>
<td><?php echo $invoice_date; ?></td>
<td><?php echo $invoice_due; ?></td>
<td class="text-right"><?php echo numfmt_format_currency($currency_format, $invoice_amount, $invoice_currency_code); ?></td>
</tr>
<tr>
<th colspan="4">Payments</th>
<td class="text-danger"><?php echo $invoice_due; ?> (Over Due by <?php echo $days; ?> Days)</td>
<td class="text-right text-bold"><?php echo numfmt_format_currency($currency_format, $invoice_amount, $invoice_currency_code); ?></td>
</tr>
<?php
$sql_payments = mysqli_query($mysqli, "SELECT * FROM payments WHERE payment_invoice_id = $invoice_id ORDER BY payment_date DESC");
while ($row = mysqli_fetch_array($sql_payments)) {
$payment_id = intval($row['payment_id']);
$payment_date = nullable_htmlentities($row['payment_date']);
$payment_amount = floatval($row['payment_amount']);
$payment_currency_code = nullable_htmlentities($row['payment_currency_code']);
$payment_method = nullable_htmlentities($row['payment_method']);
$payment_reference = nullable_htmlentities($row['payment_reference']);
if (strtotime($payment_date) > strtotime($invoice_due)) {
$payment_note = "Late";
$difference = strtotime($payment_date) - strtotime($invoice_due);
$days = floor($difference / (60*60*24)) . " Days";
} else {
$payment_note = "";
$days = "";
}
$invoice_tally_total = $invoice_amount + $invoice_tally_total;
?>
<tr>
<td colspan="4"><?php echo $payment_date; ?> - <?php echo numfmt_format_currency($currency_format, $payment_amount, $payment_currency_code); ?> - <?php echo $payment_method; ?> - <?php echo $payment_reference; ?> - <?php echo $days; ?> <?php echo $payment_note; ?></td>
</tr>
<?php } ?>
<?php } ?>
}
?>
</tbody>
</table>
</div>
</div>
<?php } // End previous paid invoices
<?php } // End previous unpaid invoices
require_once "guest_footer.php";

220
guest_view_ticket.php Normal file
View File

@@ -0,0 +1,220 @@
<?php
require_once "guest_header.php";
//Initialize the HTML Purifier to prevent XSS
require "plugins/htmlpurifier/HTMLPurifier.standalone.php";
$purifier_config = HTMLPurifier_Config::createDefault();
$purifier_config->set('URI.AllowedSchemes', ['data' => true, 'src' => true, 'http' => true, 'https' => true]);
$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 "guest_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_phone = formatPhoneNumber($company_sql_row['company_phone']);
$company_website = nullable_htmlentities($company_sql_row['company_website']);
$url_key = sanitizeInput($_GET['url_key']);
$ticket_id = intval($_GET['ticket_id']);
$ticket_sql = mysqli_query($mysqli,
"SELECT * FROM tickets
LEFT JOIN users on ticket_assigned_to = user_id
LEFT JOIN ticket_statuses ON ticket_status = ticket_status_id
WHERE ticket_id = $ticket_id AND ticket_url_key = '$url_key'"
);
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 "guest_footer.php";
exit();
}
$ticket_row = mysqli_fetch_array($ticket_sql);
if ($ticket_row) {
$ticket_prefix = nullable_htmlentities($ticket_row['ticket_prefix']);
$ticket_number = intval($ticket_row['ticket_number']);
$ticket_status = nullable_htmlentities($ticket_row['ticket_status_name']);
$ticket_priority = nullable_htmlentities($ticket_row['ticket_priority']);
$ticket_subject = nullable_htmlentities($ticket_row['ticket_subject']);
$ticket_details = $purifier->purify($ticket_row['ticket_details']);
$ticket_assigned_to = nullable_htmlentities($ticket_row['user_name']);
$ticket_resolved_at = nullable_htmlentities($ticket_row['ticket_resolved_at']);
$ticket_closed_at = nullable_htmlentities($ticket_row['ticket_closed_at']);
$ticket_feedback = nullable_htmlentities($ticket_row['ticket_feedback']);
?>
<ol class="breadcrumb d-print-none">
<li class="breadcrumb-item">
<a href="portal/index.php">Portal Home</a>
</li>
<li class="breadcrumb-item">
<a href="portal/tickets.php">Tickets</a>
</li>
<li class="breadcrumb-item active">Ticket <?php echo $ticket_prefix . $ticket_number; ?></li>
</ol>
<div class="card">
<div class="card-header bg-dark text-center">
<h4 class="mt-1">
Ticket <?php echo $ticket_prefix, $ticket_number ?>
</h4>
</div>
<div class="card-body prettyContent">
<h5><strong>Subject:</strong> <?php echo $ticket_subject ?></h5>
<hr>
<p>
<strong>State:</strong> <?php echo $ticket_status ?>
<br>
<strong>Priority:</strong> <?php echo $ticket_priority ?>
<br>
<?php if (empty($ticket_closed_at)) { ?>
<strong>Assigned to: </strong> <?php echo $ticket_assigned_to ?>
<?php } ?>
</p>
<?php echo $ticket_details ?>
</div>
</div>
<hr>
<!-- Either show the reply comments box, option to re-open ticket, show ticket smiley feedback or thanks for feedback -->
<?php if (empty($ticket_resolved_at)) { ?>
<!-- Reply - guest users should email or login so we know exactly who replied -->
<h6><i>Please <a href="portal">log in</a> or reply to the ticket via email to respond</i></h6>
<?php } elseif (empty($ticket_closed_at)) { ?>
<!-- Re-open -->
<h4>Your ticket has been resolved</h4>
<div class="col-4">
<div class="row">
<div class="col">
<a href="guest_post.php?reopen_ticket&ticket_id=<?php echo $ticket_id; ?>&url_key=<?php echo $url_key ?>" class="btn btn-secondary btn-lg"><i class="fas fa-fw fa-redo text-white"></i> Reopen ticket</a>
</div>
<div class="col">
<a href="guest_post.php?close_ticket=&ticket_id=<?php echo $ticket_id; ?>&url_key=<?php echo $url_key ?>" class="btn btn-success btn-lg"><i class="fas fa-fw fa-gavel text-white"></i> Close ticket</a>
</div>
</div>
</div>
<br>
<?php } elseif (empty($ticket_feedback)) { ?>
<h4>Ticket closed. Please rate your ticket</h4>
<div class="col-4">
<div class="row">
<div class="col">
<a href="guest_post.php?add_ticket_feedback&ticket_id=<?php echo $ticket_id; ?>&url_key=<?php echo $url_key ?>&feedback=Good" class="btn btn-success btn-lg"><i class="fas fa-fw fa-smile text-white"></i> Good</a>
</div>
<div class="col">
<a href="guest_post.php?add_ticket_feedback&ticket_id=<?php echo $ticket_id; ?>&url_key=<?php echo $url_key ?>&feedback=Bad" class="btn btn-danger btn-lg"><i class="fas fa-fw fa-frown text-white"></i> Bad</a>
</div>
</div>
</div>
<br>
<?php } else { ?>
<h4>Rated <?php echo $ticket_feedback ?> -- Thanks for your feedback!</h4>
<?php } ?>
<!-- End comments/reopen/feedback -->
<hr>
<br>
<?php
$sql = mysqli_query($mysqli, "SELECT * FROM ticket_replies LEFT JOIN users ON ticket_reply_by = user_id LEFT JOIN contacts ON ticket_reply_by = contact_id WHERE ticket_reply_ticket_id = $ticket_id AND ticket_reply_archived_at IS NULL AND ticket_reply_type != 'Internal' ORDER BY ticket_reply_id DESC");
while ($row = mysqli_fetch_array($sql)) {
$ticket_reply_id = intval($row['ticket_reply_id']);
$ticket_reply = $purifier->purify($row['ticket_reply']);
$ticket_reply_created_at = nullable_htmlentities($row['ticket_reply_created_at']);
$ticket_reply_updated_at = nullable_htmlentities($row['ticket_reply_updated_at']);
$ticket_reply_by = intval($row['ticket_reply_by']);
$ticket_reply_type = $row['ticket_reply_type'];
if ($ticket_reply_type == "Client") {
$ticket_reply_by_display = nullable_htmlentities($row['contact_name']);
$user_initials = initials($row['contact_name']);
$user_avatar = $row['contact_photo'];
$avatar_link = "../uploads/clients/$ticket_reply_by/$user_avatar";
} else {
$ticket_reply_by_display = nullable_htmlentities($row['user_name']);
$user_id = intval($row['user_id']);
$user_avatar = $row['user_avatar'];
$user_initials = initials($row['user_name']);
$avatar_link = "../uploads/users/$user_id/$user_avatar";
}
?>
<div class="card card-outline <?php if ($ticket_reply_type == 'Client') { echo "card-warning"; } else { echo "card-info"; } ?> mb-3">
<div class="card-header">
<h3 class="card-title">
<div class="media">
<?php
if (!empty($user_avatar)) {
?>
<img src="<?php echo $avatar_link ?>" alt="User Avatar" class="img-size-50 mr-3 img-circle">
<?php
} else {
?>
<span class="fa-stack fa-2x">
<i class="fa fa-circle fa-stack-2x text-secondary"></i>
<span class="fa fa-stack-1x text-white"><?php echo $user_initials; ?></span>
</span>
<?php
}
?>
<div class="media-body">
<?php echo $ticket_reply_by_display; ?>
<br>
<small class="text-muted"><?php echo $ticket_reply_created_at; ?> <?php if (!empty($ticket_reply_updated_at)) { echo "(edited: $ticket_reply_updated_at)"; } ?></small>
</div>
</div>
</h3>
</div>
<div class="card-body prettyContent">
<?php echo $ticket_reply; ?>
</div>
</div>
<?php
}
?>
<script src="../js/pretty_content.js"></script>
<?php } else {
echo "Ticket ID not found!";
} ?>
<div class="card-footer">
<?php echo "<i class='fas fa-phone fa-fw mr-2'></i>$company_phone | <i class='fas fa-globe fa-fw mr-2 ml-2'></i>$company_website"; ?>
</div>
<?php
require_once "guest_footer.php";

View File

@@ -210,12 +210,6 @@ if (isset($_GET['client_id'])) {
$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT COUNT('trip_id') AS num FROM trips WHERE trip_archived_at IS NULL AND trip_client_id = $client_id"));
$num_trips = $row['num'];
$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT COUNT('item_id') AS num FROM shared_items WHERE item_client_id = $client_id"));
$num_shared_links = $row['num'];
$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT COUNT('log_id') AS num FROM logs WHERE log_client_id = $client_id"));
$num_logs = $row['num'];
// Expiring Items
// Count Domains Expiring within 30 Days
@@ -229,6 +223,20 @@ if (isset($_GET['client_id'])) {
));
$num_domains_expiring = intval($row['num']);
// Count Domains Expired or within 5 days
$row = mysqli_fetch_assoc(mysqli_query(
$mysqli,
"SELECT COUNT('domain_id') AS num FROM domains
WHERE domain_client_id = $client_id
AND domain_expire IS NOT NULL
AND (
domain_expire < CURRENT_DATE
OR domain_expire < CURRENT_DATE + INTERVAL 5 DAY
)
AND domain_archived_at IS NULL"
));
$num_domains_expired = intval($row['num']);
// Count Certificates Expiring within 30 Days
$row = mysqli_fetch_assoc(mysqli_query(
$mysqli,

View File

@@ -2,7 +2,7 @@
<div class="card-body py-2">
<div class="row">
<div class="col">
<a href="#" data-toggle="collapse" data-target="#clientHeader"><h4 class="text-secondary"><strong><?php echo $client_name; ?></strong> <?php if ($client_archived_at) { echo "(archived)"; } ?></h4></a>
<a href="#" data-toggle="collapse" data-target="#clientHeader"><h4 class="text-secondary" data-toggle="tooltip" data-placement="right" title="Client ID: <?php echo $client_id; ?>"><strong><?php echo $client_name; ?></strong> <?php if ($client_archived_at) { echo "(archived)"; } ?></h4></a>
</div>
<div class="col">
<?php if ($session_user_role == 3) { ?>

View File

@@ -52,7 +52,7 @@
}
timerInterval = setInterval(countTime, 1000);
isPaused = false;
document.getElementById("startStopTimer").innerText = "Pause";
document.getElementById("startStopTimer").innerHTML = "<i class='fas fa-pause'></i>";
localStorage.setItem("ticket-timer-running-" + ticketID, "true");
}
@@ -66,7 +66,7 @@
localStorage.setItem(getLocalStorageKey("pausedTime"), currentElapsed.toString());
localStorage.removeItem(getLocalStorageKey("startTime"));
isPaused = true;
document.getElementById("startStopTimer").innerText = "Start";
document.getElementById("startStopTimer").innerHTML = "<i class='fas fa-play'></i>";
localStorage.setItem("ticket-timer-running-" + ticketID, "false");
}
@@ -84,7 +84,7 @@
elapsedSecs = 0;
clearTimeStorage();
displayTime();
document.getElementById("startStopTimer").innerText = "Start";
document.getElementById("startStopTimer").innerHTML = "<i class='fas fa-play'></i>";
}
localStorage.setItem("ticket-timer-running-" + ticketID, "false");
}
@@ -95,7 +95,7 @@
elapsedSecs = 0;
clearTimeStorage();
displayTime();
document.getElementById("startStopTimer").innerText = "Start";
document.getElementById("startStopTimer").innerHTML = "<i class='fas fa-play'></i>";
}
function handleInputFocus() {

View File

@@ -121,7 +121,10 @@ function addTicket($contact_id, $contact_name, $contact_email, $client_id, $date
$message = nl2br($message);
$message = mysqli_escape_string($mysqli, "<i>Email from: $contact_email at $date:-</i> <br><br>$message");
mysqli_query($mysqli, "INSERT INTO tickets SET ticket_prefix = '$config_ticket_prefix', ticket_number = $ticket_number, ticket_subject = '$subject', ticket_details = '$message', ticket_priority = 'Low', ticket_status = 1, ticket_created_by = 0, ticket_contact_id = $contact_id, ticket_client_id = $client_id");
//Generate a unique URL key for clients to access
$url_key = randomString(156);
mysqli_query($mysqli, "INSERT INTO tickets SET ticket_prefix = '$config_ticket_prefix', ticket_number = $ticket_number, ticket_subject = '$subject', ticket_details = '$message', ticket_priority = 'Low', ticket_status = 1, ticket_created_by = 0, ticket_contact_id = $contact_id, ticket_url_key = '$url_key', ticket_client_id = $client_id");
$id = mysqli_insert_id($mysqli);
// Logging
@@ -366,7 +369,7 @@ function addReply($from_email, $date, $subject, $ticket_number, $message, $attac
}
// Update Ticket Last Response Field & set ticket to open as client has replied
mysqli_query($mysqli, "UPDATE tickets SET ticket_status = 2 WHERE ticket_id = $ticket_id AND ticket_client_id = $client_id LIMIT 1");
mysqli_query($mysqli, "UPDATE tickets SET ticket_status = 2, ticket_resolved_at = NULL WHERE ticket_id = $ticket_id AND ticket_client_id = $client_id LIMIT 1");
echo "Updated existing ticket.<br>";
mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Ticket', log_action = 'Update', log_description = 'Email parser: Client contact $from_email updated ticket $config_ticket_prefix$ticket_number ($subject)', log_client_id = $client_id");

View File

@@ -145,7 +145,7 @@ if (isset($_POST['login'])) {
}
// Validate MFA code
if (TokenAuth6238::verify($token, $current_code)) {
if (!empty($current_code) && TokenAuth6238::verify($token, $current_code)) {
$mfa_is_complete = true;
$extended_log = 'with 2FA';
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
/**
* TinyMCE version 7.1.1 (2024-05-22)
* TinyMCE version 7.3.0 (2024-08-07)
*/
!function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager");const e=(t,e,s)=>{const r="UL"===e?"InsertUnorderedList":"InsertOrderedList";t.execCommand(r,!1,!1===s?null:{"list-style-type":s})},s=t=>e=>e.options.get(t),r=s("advlist_number_styles"),n=s("advlist_bullet_styles"),i=t=>null==t,l=t=>!i(t);var o=tinymce.util.Tools.resolve("tinymce.util.Tools");class a{constructor(t,e){this.tag=t,this.value=e}static some(t){return new a(!0,t)}static none(){return a.singletonNone}fold(t,e){return this.tag?e(this.value):t()}isSome(){return this.tag}isNone(){return!this.tag}map(t){return this.tag?a.some(t(this.value)):a.none()}bind(t){return this.tag?t(this.value):a.none()}exists(t){return this.tag&&t(this.value)}forall(t){return!this.tag||t(this.value)}filter(t){return!this.tag||t(this.value)?this:a.none()}getOr(t){return this.tag?this.value:t}or(t){return this.tag?this:t}getOrThunk(t){return this.tag?this.value:t()}orThunk(t){return this.tag?this:t()}getOrDie(t){if(this.tag)return this.value;throw new Error(null!=t?t:"Called getOrDie on None")}static from(t){return l(t)?a.some(t):a.none()}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(t){this.tag&&t(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}a.singletonNone=new a(!1);const u=t=>e=>l(e)&&t.test(e.nodeName),d=u(/^(OL|UL|DL)$/),g=u(/^(TH|TD)$/),c=t=>i(t)||"default"===t?"":t,h=(t,e)=>s=>((t,e)=>{const s=t.selection.getNode();return e({parents:t.dom.getParents(s),element:s}),t.on("NodeChange",e),()=>t.off("NodeChange",e)})(t,(r=>((t,r)=>{const n=t.selection.getStart(!0);s.setActive(((t,e,s)=>((t,e,s)=>{for(let e=0,n=t.length;e<n;e++){const n=t[e];if(d(r=n)&&!/\btox\-/.test(r.className))return a.some(n);if(s(n,e))break}var r;return a.none()})(e,0,g).exists((e=>e.nodeName===s&&((t,e)=>t.dom.isChildOf(e,t.getBody()))(t,e))))(t,r,e)),s.setEnabled(!((t,e)=>{const s=t.dom.getParent(e,"ol,ul,dl");return((t,e)=>null!==e&&!t.dom.isEditable(e))(t,s)&&t.selection.isEditable()})(t,n)&&t.selection.isEditable())})(t,r.parents))),m=(t,s,r,n,i,l)=>{l.length>1?((t,s,r,n,i,l)=>{t.ui.registry.addSplitButton(s,{tooltip:r,icon:"OL"===i?"ordered-list":"unordered-list",presets:"listpreview",columns:3,fetch:t=>{t(o.map(l,(t=>{const e="OL"===i?"num":"bull",s="disc"===t||"decimal"===t?"default":t,r=c(t),n=(t=>t.replace(/\-/g," ").replace(/\b\w/g,(t=>t.toUpperCase())))(t);return{type:"choiceitem",value:r,icon:"list-"+e+"-"+s,text:n}})))},onAction:()=>t.execCommand(n),onItemAction:(s,r)=>{e(t,i,r)},select:e=>{const s=(t=>{const e=t.dom.getParent(t.selection.getNode(),"ol,ul"),s=t.dom.getStyle(e,"listStyleType");return a.from(s)})(t);return s.map((t=>e===t)).getOr(!1)},onSetup:h(t,i)})})(t,s,r,n,i,l):((t,s,r,n,i,l)=>{t.ui.registry.addToggleButton(s,{active:!1,tooltip:r,icon:"OL"===i?"ordered-list":"unordered-list",onSetup:h(t,i),onAction:()=>t.queryCommandState(n)||""===l?t.execCommand(n):e(t,i,l)})})(t,s,r,n,i,c(l[0]))};t.add("advlist",(t=>{t.hasPlugin("lists")?((t=>{const e=t.options.register;e("advlist_number_styles",{processor:"string[]",default:"default,lower-alpha,lower-greek,lower-roman,upper-alpha,upper-roman".split(",")}),e("advlist_bullet_styles",{processor:"string[]",default:"default,circle,square".split(",")})})(t),(t=>{m(t,"numlist","Numbered list","InsertOrderedList","OL",r(t)),m(t,"bullist","Bullet list","InsertUnorderedList","UL",n(t))})(t),(t=>{t.addCommand("ApplyUnorderedListStyle",((s,r)=>{e(t,"UL",r["list-style-type"])})),t.addCommand("ApplyOrderedListStyle",((s,r)=>{e(t,"OL",r["list-style-type"])}))})(t)):console.error("Please use the Lists plugin together with the List Styles plugin.")}))}();

View File

@@ -1,4 +1,4 @@
/**
* TinyMCE version 7.1.1 (2024-05-22)
* TinyMCE version 7.3.0 (2024-08-07)
*/
!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),t=tinymce.util.Tools.resolve("tinymce.dom.RangeUtils"),o=tinymce.util.Tools.resolve("tinymce.util.Tools");const n=("allow_html_in_named_anchor",e=>e.options.get("allow_html_in_named_anchor"));const a="a:not([href])",r=e=>!e,i=e=>e.getAttribute("id")||e.getAttribute("name")||"",l=e=>(e=>"a"===e.nodeName.toLowerCase())(e)&&!e.getAttribute("href")&&""!==i(e),s=e=>e.dom.getParent(e.selection.getStart(),a),d=(e,a)=>{const r=s(e);r?((e,t,o)=>{o.removeAttribute("name"),o.id=t,e.addVisual(),e.undoManager.add()})(e,a,r):((e,a)=>{e.undoManager.transact((()=>{n(e)||e.selection.collapse(!0),e.selection.isCollapsed()?e.insertContent(e.dom.createHTML("a",{id:a})):((e=>{const n=e.dom;t(n).walk(e.selection.getRng(),(e=>{o.each(e,(e=>{var t;l(t=e)&&!t.firstChild&&n.remove(e,!1)}))}))})(e),e.formatter.remove("namedAnchor",void 0,void 0,!0),e.formatter.apply("namedAnchor",{value:a}),e.addVisual())}))})(e,a),e.focus()},c=e=>(e=>r(e.attr("href"))&&!r(e.attr("id")||e.attr("name")))(e)&&!e.firstChild,m=e=>t=>{for(let o=0;o<t.length;o++){const n=t[o];c(n)&&n.attr("contenteditable",e)}},u=e=>t=>{const o=()=>{t.setEnabled(e.selection.isEditable())};return e.on("NodeChange",o),o(),()=>{e.off("NodeChange",o)}};e.add("anchor",(e=>{(e=>{(0,e.options.register)("allow_html_in_named_anchor",{processor:"boolean",default:!1})})(e),(e=>{e.on("PreInit",(()=>{e.parser.addNodeFilter("a",m("false")),e.serializer.addNodeFilter("a",m(null))}))})(e),(e=>{e.addCommand("mceAnchor",(()=>{(e=>{const t=(e=>{const t=s(e);return t?i(t):""})(e);e.windowManager.open({title:"Anchor",size:"normal",body:{type:"panel",items:[{name:"id",type:"input",label:"ID",placeholder:"example"}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:{id:t},onSubmit:t=>{((e,t)=>/^[A-Za-z][A-Za-z0-9\-:._]*$/.test(t)?(d(e,t),!0):(e.windowManager.alert("ID should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores."),!1))(e,t.getData().id)&&t.close()}})})(e)}))})(e),(e=>{const t=()=>e.execCommand("mceAnchor");e.ui.registry.addToggleButton("anchor",{icon:"bookmark",tooltip:"Anchor",onAction:t,onSetup:t=>{const o=e.selection.selectorChangedWithUnbind("a:not([href])",t.setActive).unbind,n=u(e)(t);return()=>{o(),n()}}}),e.ui.registry.addMenuItem("anchor",{icon:"bookmark",text:"Anchor...",onAction:t,onSetup:u(e)})})(e),e.on("PreInit",(()=>{(e=>{e.formatter.register("namedAnchor",{inline:"a",selector:a,remove:"all",split:!0,deep:!0,attributes:{id:"%value"},onmatch:(e,t,o)=>l(e)})})(e)}))}))}();

View File

@@ -1,4 +1,4 @@
/**
* TinyMCE version 7.1.1 (2024-05-22)
* TinyMCE version 7.3.0 (2024-08-07)
*/
!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=e=>t=>t.options.get(e),n=t("autolink_pattern"),o=t("link_default_target"),r=t("link_default_protocol"),a=t("allow_unsafe_link_target"),s=("string",e=>"string"===(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&(n=o=e,(r=String).prototype.isPrototypeOf(n)||(null===(a=o.constructor)||void 0===a?void 0:a.name)===r.name)?"string":t;var n,o,r,a})(e));const l=(void 0,e=>undefined===e);const i=e=>!(e=>null==e)(e),c=Object.hasOwnProperty,d=e=>"\ufeff"===e;var u=tinymce.util.Tools.resolve("tinymce.dom.TextSeeker");const f=e=>/^[(\[{ \u00a0]$/.test(e),g=(e,t,n)=>{for(let o=t-1;o>=0;o--){const t=e.charAt(o);if(!d(t)&&n(t))return o}return-1},m=(e,t)=>{var o;const a=e.schema.getVoidElements(),s=n(e),{dom:i,selection:d}=e;if(null!==i.getParent(d.getNode(),"a[href]"))return null;const m=d.getRng(),k=u(i,(e=>{return i.isBlock(e)||(t=a,n=e.nodeName.toLowerCase(),c.call(t,n))||"false"===i.getContentEditable(e);var t,n})),{container:p,offset:y}=((e,t)=>{let n=e,o=t;for(;1===n.nodeType&&n.childNodes[o];)n=n.childNodes[o],o=3===n.nodeType?n.data.length:n.childNodes.length;return{container:n,offset:o}})(m.endContainer,m.endOffset),w=null!==(o=i.getParent(p,i.isBlock))&&void 0!==o?o:i.getRoot(),h=k.backwards(p,y+t,((e,t)=>{const n=e.data,o=g(n,t,(r=f,e=>!r(e)));var r,a;return-1===o||(a=n[o],/[?!,.;:]/.test(a))?o:o+1}),w);if(!h)return null;let v=h.container;const _=k.backwards(h.container,h.offset,((e,t)=>{v=e;const n=g(e.data,t,f);return-1===n?n:n+1}),w),A=i.createRng();_?A.setStart(_.container,_.offset):A.setStart(v,0),A.setEnd(h.container,h.offset);const C=A.toString().replace(/\uFEFF/g,"").match(s);if(C){let t=C[0];return $="www.",(b=t).length>=4&&b.substr(0,4)===$?t=r(e)+"://"+t:((e,t,n=0,o)=>{const r=e.indexOf(t,n);return-1!==r&&(!!l(o)||r+t.length<=o)})(t,"@")&&!(e=>/^([A-Za-z][A-Za-z\d.+-]*:\/\/)|mailto:/.test(e))(t)&&(t="mailto:"+t),{rng:A,url:t}}var b,$;return null},k=(e,t)=>{const{dom:n,selection:r}=e,{rng:l,url:i}=t,c=r.getBookmark();r.setRng(l);const d="createlink",u={command:d,ui:!1,value:i};if(!e.dispatch("BeforeExecCommand",u).isDefaultPrevented()){e.getDoc().execCommand(d,!1,i),e.dispatch("ExecCommand",u);const t=o(e);if(s(t)){const o=r.getNode();n.setAttrib(o,"target",t),"_blank"!==t||a(e)||n.setAttrib(o,"rel","noopener")}}r.moveToBookmark(c),e.nodeChanged()},p=e=>{const t=m(e,-1);i(t)&&k(e,t)},y=p;e.add("autolink",(e=>{(e=>{const t=e.options.register;t("autolink_pattern",{processor:"regexp",default:new RegExp("^"+/(?:[A-Za-z][A-Za-z\d.+-]{0,14}:\/\/(?:[-.~*+=!&;:'%@?^${}(),\w]+@)?|www\.|[-;:&=+$,.\w]+@)[A-Za-z\d-]+(?:\.[A-Za-z\d-]+)*(?::\d+)?(?:\/(?:[-.~*+=!;:'%@$(),\/\w]*[-~*+=%@$()\/\w])?)?(?:\?(?:[-.~*+=!&;:'%@?^${}(),\/\w]+))?(?:#(?:[-.~*+=!&;:'%@?^${}(),\/\w]+))?/g.source+"$","i")}),t("link_default_target",{processor:"string"}),t("link_default_protocol",{processor:"string",default:"https"})})(e),(e=>{e.on("keydown",(t=>{13!==t.keyCode||t.isDefaultPrevented()||(e=>{const t=m(e,0);i(t)&&k(e,t)})(e)})),e.on("keyup",(t=>{32===t.keyCode?p(e):(48===t.keyCode&&t.shiftKey||221===t.keyCode)&&y(e)}))})(e)}))}();

View File

@@ -1,4 +1,4 @@
/**
* TinyMCE version 7.1.1 (2024-05-22)
* TinyMCE version 7.3.0 (2024-08-07)
*/
!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),t=tinymce.util.Tools.resolve("tinymce.Env");const o=e=>t=>t.options.get(e),s=o("min_height"),i=o("max_height"),n=o("autoresize_overflow_padding"),r=o("autoresize_bottom_margin"),l=(e,t)=>{const o=e.getBody();o&&(o.style.overflowY=t?"":"hidden",t||(o.scrollTop=0))},g=(e,t,o,s)=>{var i;const n=parseInt(null!==(i=e.getStyle(t,o,s))&&void 0!==i?i:"",10);return isNaN(n)?0:n},a=(e,o,r,c)=>{var d;const f=e.dom,u=e.getDoc();if(!u)return;if((e=>e.plugins.fullscreen&&e.plugins.fullscreen.isFullscreen())(e))return void l(e,!0);const m=u.documentElement,h=c?c():n(e),p=null!==(d=s(e))&&void 0!==d?d:e.getElement().offsetHeight;let y=p;const S=g(f,m,"margin-top",!0),v=g(f,m,"margin-bottom",!0);let C=m.offsetHeight+S+v+h;C<0&&(C=0);const b=e.getContainer().offsetHeight-e.getContentAreaContainer().offsetHeight;C+b>p&&(y=C+b);const w=i(e);if(w&&y>w?(y=w,l(e,!0)):l(e,!1),y!==o.get()){const s=y-o.get();if(f.setStyle(e.getContainer(),"height",y+"px"),o.set(y),(e=>{e.dispatch("ResizeEditor")})(e),t.browser.isSafari()&&(t.os.isMacOS()||t.os.isiOS())){const t=e.getWin();t.scrollTo(t.pageXOffset,t.pageYOffset)}e.hasFocus()&&(e=>{if("setcontent"===(null==e?void 0:e.type.toLowerCase())){const t=e;return!0===t.selection||!0===t.paste}return!1})(r)&&e.selection.scrollIntoView(),(t.browser.isSafari()||t.browser.isChromium())&&s<0&&a(e,o,r,c)}};e.add("autoresize",(e=>{if((e=>{const t=e.options.register;t("autoresize_overflow_padding",{processor:"number",default:1}),t("autoresize_bottom_margin",{processor:"number",default:50})})(e),e.options.isSet("resize")||e.options.set("resize",!1),!e.inline){const o=(e=>{let t=0;return{get:()=>t,set:e=>{t=e}}})();((e,t)=>{e.addCommand("mceAutoResize",(()=>{a(e,t)}))})(e,o),((e,o)=>{let s,i,l=()=>r(e);e.on("init",(i=>{s=0;const r=n(e),g=e.dom;g.setStyles(e.getDoc().documentElement,{height:"auto"}),t.browser.isEdge()||t.browser.isIE()?g.setStyles(e.getBody(),{paddingLeft:r,paddingRight:r,"min-height":0}):g.setStyles(e.getBody(),{paddingLeft:r,paddingRight:r}),a(e,o,i,l),s+=1})),e.on("NodeChange SetContent keyup FullscreenStateChanged ResizeContent",(t=>{if(1===s)i=e.getContainer().offsetHeight,a(e,o,t,l),s+=1;else if(2===s){const t=i<e.getContainer().offsetHeight;if(t){const t=e.dom,o=e.getDoc();t.setStyles(o.documentElement,{"min-height":0}),t.setStyles(e.getBody(),{"min-height":"inherit"})}l=t?(0,()=>0):l,s+=1}else a(e,o,t,l)}))})(e,o)}}))}();
!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),t=tinymce.util.Tools.resolve("tinymce.Env");const o=e=>t=>t.options.get(e),n=o("min_height"),s=o("max_height"),i=o("autoresize_overflow_padding"),r=o("autoresize_bottom_margin"),g=(e,t)=>{const o=e.getBody();o&&(o.style.overflowY=t?"":"hidden",t||(o.scrollTop=0))},l=(e,t,o,n)=>{var s;const i=parseInt(null!==(s=e.getStyle(t,o,n))&&void 0!==s?s:"",10);return isNaN(i)?0:i},a=(e,o,r,c)=>{var d;const u=e.dom,h=e.getDoc();if(!h)return;if((e=>e.plugins.fullscreen&&e.plugins.fullscreen.isFullscreen())(e))return void g(e,!0);const m=h.documentElement,f=c?c():i(e),p=null!==(d=n(e))&&void 0!==d?d:e.getElement().offsetHeight;let y=p;const S=l(u,m,"margin-top",!0),v=l(u,m,"margin-bottom",!0);let C=m.offsetHeight+S+v+f;C<0&&(C=0);const H=e.getContainer().offsetHeight-e.getContentAreaContainer().offsetHeight;C+H>p&&(y=C+H);const b=s(e);b&&y>b?(y=b,g(e,!0)):g(e,!1);const w=o.get();if(w.set&&(e.dom.setStyles(e.getDoc().documentElement,{"min-height":0}),e.dom.setStyles(e.getBody(),{"min-height":"inherit"})),y!==w.totalHeight&&(C-f!==w.contentHeight||!w.set)){const n=y-w.totalHeight;if(u.setStyle(e.getContainer(),"height",y+"px"),o.set({totalHeight:y,contentHeight:C,set:!0}),(e=>{e.dispatch("ResizeEditor")})(e),t.browser.isSafari()&&(t.os.isMacOS()||t.os.isiOS())){const t=e.getWin();t.scrollTo(t.pageXOffset,t.pageYOffset)}e.hasFocus()&&(e=>{if("setcontent"===(null==e?void 0:e.type.toLowerCase())){const t=e;return!0===t.selection||!0===t.paste}return!1})(r)&&e.selection.scrollIntoView(),(t.browser.isSafari()||t.browser.isChromium())&&n<0&&a(e,o,r,c)}};e.add("autoresize",(e=>{if((e=>{const t=e.options.register;t("autoresize_overflow_padding",{processor:"number",default:1}),t("autoresize_bottom_margin",{processor:"number",default:50})})(e),e.options.isSet("resize")||e.options.set("resize",!1),!e.inline){const o=(e=>{let t={totalHeight:0,contentHeight:0,set:!1};return{get:()=>t,set:e=>{t=e}}})();((e,t)=>{e.addCommand("mceAutoResize",(()=>{a(e,t)}))})(e,o),((e,o)=>{const n=()=>r(e);e.on("init",(s=>{const r=i(e),g=e.dom;g.setStyles(e.getDoc().documentElement,{height:"auto"}),t.browser.isEdge()||t.browser.isIE()?g.setStyles(e.getBody(),{paddingLeft:r,paddingRight:r,"min-height":0}):g.setStyles(e.getBody(),{paddingLeft:r,paddingRight:r}),a(e,o,s,n)})),e.on("NodeChange SetContent keyup FullscreenStateChanged ResizeContent",(t=>{a(e,o,t,n)}))})(e,o)}}))}();

View File

@@ -1,4 +1,4 @@
/**
* TinyMCE version 7.1.1 (2024-05-22)
* TinyMCE version 7.3.0 (2024-08-07)
*/
!function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager");const e=("string",t=>"string"===(t=>{const e=typeof t;return null===t?"null":"object"===e&&Array.isArray(t)?"array":"object"===e&&(r=o=t,(a=String).prototype.isPrototypeOf(r)||(null===(s=o.constructor)||void 0===s?void 0:s.name)===a.name)?"string":e;var r,o,a,s})(t));const r=(void 0,t=>undefined===t);var o=tinymce.util.Tools.resolve("tinymce.util.Delay"),a=tinymce.util.Tools.resolve("tinymce.util.LocalStorage"),s=tinymce.util.Tools.resolve("tinymce.util.Tools");const n=t=>{const e=/^(\d+)([ms]?)$/.exec(t);return(e&&e[2]?{s:1e3,m:6e4}[e[2]]:1)*parseInt(t,10)},i=t=>e=>e.options.get(t),u=i("autosave_ask_before_unload"),l=i("autosave_restore_when_empty"),c=i("autosave_interval"),d=i("autosave_retention"),m=t=>{const e=document.location;return t.options.get("autosave_prefix").replace(/{path}/g,e.pathname).replace(/{query}/g,e.search).replace(/{hash}/g,e.hash).replace(/{id}/g,t.id)},v=(t,e)=>{if(r(e))return t.dom.isEmpty(t.getBody());{const r=s.trim(e);if(""===r)return!0;{const e=(new DOMParser).parseFromString(r,"text/html");return t.dom.isEmpty(e)}}},f=t=>{var e;const r=parseInt(null!==(e=a.getItem(m(t)+"time"))&&void 0!==e?e:"0",10)||0;return!((new Date).getTime()-r>d(t)&&(p(t,!1),1))},p=(t,e)=>{const r=m(t);a.removeItem(r+"draft"),a.removeItem(r+"time"),!1!==e&&(t=>{t.dispatch("RemoveDraft")})(t)},g=t=>{const e=m(t);!v(t)&&t.isDirty()&&(a.setItem(e+"draft",t.getContent({format:"raw",no_events:!0})),a.setItem(e+"time",(new Date).getTime().toString()),(t=>{t.dispatch("StoreDraft")})(t))},y=t=>{var e;const r=m(t);f(t)&&(t.setContent(null!==(e=a.getItem(r+"draft"))&&void 0!==e?e:"",{format:"raw"}),(t=>{t.dispatch("RestoreDraft")})(t))};var D=tinymce.util.Tools.resolve("tinymce.EditorManager");const h=t=>e=>{e.setEnabled(f(t));const r=()=>e.setEnabled(f(t));return t.on("StoreDraft RestoreDraft RemoveDraft",r),()=>t.off("StoreDraft RestoreDraft RemoveDraft",r)};t.add("autosave",(t=>((t=>{const r=t.options.register,o=t=>{const r=e(t);return r?{value:n(t),valid:r}:{valid:!1,message:"Must be a string."}};r("autosave_ask_before_unload",{processor:"boolean",default:!0}),r("autosave_prefix",{processor:"string",default:"tinymce-autosave-{path}{query}{hash}-{id}-"}),r("autosave_restore_when_empty",{processor:"boolean",default:!1}),r("autosave_interval",{processor:o,default:"30s"}),r("autosave_retention",{processor:o,default:"20m"})})(t),(t=>{t.editorManager.on("BeforeUnload",(t=>{let e;s.each(D.get(),(t=>{t.plugins.autosave&&t.plugins.autosave.storeDraft(),!e&&t.isDirty()&&u(t)&&(e=t.translate("You have unsaved changes are you sure you want to navigate away?"))})),e&&(t.preventDefault(),t.returnValue=e)}))})(t),(t=>{(t=>{const e=c(t);o.setEditorInterval(t,(()=>{g(t)}),e)})(t);const e=()=>{(t=>{t.undoManager.transact((()=>{y(t),p(t)})),t.focus()})(t)};t.ui.registry.addButton("restoredraft",{tooltip:"Restore last draft",icon:"restore-draft",onAction:e,onSetup:h(t)}),t.ui.registry.addMenuItem("restoredraft",{text:"Restore last draft",icon:"restore-draft",onAction:e,onSetup:h(t)})})(t),t.on("init",(()=>{l(t)&&t.dom.isEmpty(t.getBody())&&y(t)})),(t=>({hasDraft:()=>f(t),storeDraft:()=>g(t),restoreDraft:()=>y(t),removeDraft:e=>p(t,e),isEmpty:e=>v(t,e)}))(t))))}();

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
/**
* TinyMCE version 7.1.1 (2024-05-22)
* TinyMCE version 7.3.0 (2024-08-07)
*/
!function(){"use strict";tinymce.util.Tools.resolve("tinymce.PluginManager").add("code",(e=>((e=>{e.addCommand("mceCodeEditor",(()=>{(e=>{const o=(e=>e.getContent({source_view:!0}))(e);e.windowManager.open({title:"Source Code",size:"large",body:{type:"panel",items:[{type:"textarea",name:"code"}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:{code:o},onSubmit:o=>{((e,o)=>{e.focus(),e.undoManager.transact((()=>{e.setContent(o)})),e.selection.setCursorLocation(),e.nodeChanged()})(e,o.getData().code),o.close()}})})(e)}))})(e),(e=>{const o=()=>e.execCommand("mceCodeEditor");e.ui.registry.addButton("code",{icon:"sourcecode",tooltip:"Source code",onAction:o}),e.ui.registry.addMenuItem("code",{icon:"sourcecode",text:"Source code",onAction:o})})(e),{})))}();

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
/**
* TinyMCE version 7.1.1 (2024-05-22)
* TinyMCE version 7.3.0 (2024-08-07)
*/
!function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager");const e=t=>e=>typeof e===t,o=t=>"string"===(t=>{const e=typeof t;return null===t?"null":"object"===e&&Array.isArray(t)?"array":"object"===e&&(o=r=t,(n=String).prototype.isPrototypeOf(o)||(null===(i=r.constructor)||void 0===i?void 0:i.name)===n.name)?"string":e;var o,r,n,i})(t),r=e("boolean"),n=t=>!(t=>null==t)(t),i=e("function"),s=e("number"),l=(!1,()=>false);class a{constructor(t,e){this.tag=t,this.value=e}static some(t){return new a(!0,t)}static none(){return a.singletonNone}fold(t,e){return this.tag?e(this.value):t()}isSome(){return this.tag}isNone(){return!this.tag}map(t){return this.tag?a.some(t(this.value)):a.none()}bind(t){return this.tag?t(this.value):a.none()}exists(t){return this.tag&&t(this.value)}forall(t){return!this.tag||t(this.value)}filter(t){return!this.tag||t(this.value)?this:a.none()}getOr(t){return this.tag?this.value:t}or(t){return this.tag?this:t}getOrThunk(t){return this.tag?this.value:t()}orThunk(t){return this.tag?this:t()}getOrDie(t){if(this.tag)return this.value;throw new Error(null!=t?t:"Called getOrDie on None")}static from(t){return n(t)?a.some(t):a.none()}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(t){this.tag&&t(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}a.singletonNone=new a(!1);const u=(t,e)=>{for(let o=0,r=t.length;o<r;o++)e(t[o],o)},c=t=>{if(null==t)throw new Error("Node cannot be null or undefined");return{dom:t}},d=c,h=(t,e)=>{const o=t.dom;if(1!==o.nodeType)return!1;{const t=o;if(void 0!==t.matches)return t.matches(e);if(void 0!==t.msMatchesSelector)return t.msMatchesSelector(e);if(void 0!==t.webkitMatchesSelector)return t.webkitMatchesSelector(e);if(void 0!==t.mozMatchesSelector)return t.mozMatchesSelector(e);throw new Error("Browser lacks native selectors")}};"undefined"!=typeof window?window:Function("return this;")();const m=t=>e=>(t=>t.dom.nodeType)(e)===t,g=m(1),f=m(3),v=m(9),y=m(11),p=(t,e)=>{t.dom.removeAttribute(e)},w=i(Element.prototype.attachShadow)&&i(Node.prototype.getRootNode)?t=>d(t.dom.getRootNode()):t=>v(t)?t:d(t.dom.ownerDocument),b=t=>d(t.dom.host),N=t=>{const e=f(t)?t.dom.parentNode:t.dom;if(null==e||null===e.ownerDocument)return!1;const o=e.ownerDocument;return(t=>{const e=w(t);return y(o=e)&&n(o.dom.host)?a.some(e):a.none();var o})(d(e)).fold((()=>o.body.contains(e)),(r=N,i=b,t=>r(i(t))));var r,i},S=t=>"rtl"===((t,e)=>{const o=t.dom,r=window.getComputedStyle(o).getPropertyValue(e);return""!==r||N(t)?r:((t,e)=>(t=>void 0!==t.style&&i(t.style.getPropertyValue))(t)?t.style.getPropertyValue(e):"")(o,e)})(t,"direction")?"rtl":"ltr",A=(t,e)=>((t,o)=>((t,e)=>{const o=[];for(let r=0,n=t.length;r<n;r++){const n=t[r];e(n,r)&&o.push(n)}return o})(((t,e)=>{const o=t.length,r=new Array(o);for(let n=0;n<o;n++){const o=t[n];r[n]=e(o,n)}return r})(t.dom.childNodes,d),(t=>h(t,e))))(t),E=("li",t=>g(t)&&"li"===t.dom.nodeName.toLowerCase());const T=(t,e,n)=>{u(e,(e=>{const c=d(e),m=E(c),f=((t,e)=>{return(e?(o=t,r="ol,ul",((t,e,o)=>{let n=t.dom;const s=i(o)?o:l;for(;n.parentNode;){n=n.parentNode;const t=d(n);if(h(t,r))return a.some(t);if(s(t))break}return a.none()})(o,0,n)):a.some(t)).getOr(t);var o,r,n})(c,m);var v;(v=f,(t=>a.from(t.dom.parentNode).map(d))(v).filter(g)).each((e=>{if(t.setStyle(f.dom,"direction",null),S(e)===n?p(f,"dir"):((t,e,n)=>{((t,e,n)=>{if(!(o(n)||r(n)||s(n)))throw console.error("Invalid call to Attribute.set. Key ",e,":: Value ",n,":: Element ",t),new Error("Attribute value was not simple");t.setAttribute(e,n+"")})(t.dom,e,n)})(f,"dir",n),S(f)!==n&&t.setStyle(f.dom,"direction",n),m){const e=A(f,"li[dir],li[style]");u(e,(e=>{p(e,"dir"),t.setStyle(e.dom,"direction",null)}))}}))}))},C=(t,e)=>{t.selection.isEditable()&&(T(t.dom,t.selection.getSelectedBlocks(),e),t.nodeChanged())},D=(t,e)=>o=>{const r=r=>{const n=d(r.element);o.setActive(S(n)===e),o.setEnabled(t.selection.isEditable())};return t.on("NodeChange",r),o.setEnabled(t.selection.isEditable()),()=>t.off("NodeChange",r)};t.add("directionality",(t=>{(t=>{t.addCommand("mceDirectionLTR",(()=>{C(t,"ltr")})),t.addCommand("mceDirectionRTL",(()=>{C(t,"rtl")}))})(t),(t=>{t.ui.registry.addToggleButton("ltr",{tooltip:"Left to right",icon:"ltr",onAction:()=>t.execCommand("mceDirectionLTR"),onSetup:D(t,"ltr")}),t.ui.registry.addToggleButton("rtl",{tooltip:"Right to left",icon:"rtl",onAction:()=>t.execCommand("mceDirectionRTL"),onSetup:D(t,"rtl")})})(t)}))}();
!function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager");const e=t=>e=>typeof e===t,o=t=>"string"===(t=>{const e=typeof t;return null===t?"null":"object"===e&&Array.isArray(t)?"array":"object"===e&&(o=r=t,(n=String).prototype.isPrototypeOf(o)||(null===(i=r.constructor)||void 0===i?void 0:i.name)===n.name)?"string":e;var o,r,n,i})(t),r=e("boolean"),n=t=>!(t=>null==t)(t),i=e("function"),s=e("number"),l=(!1,()=>false);class a{constructor(t,e){this.tag=t,this.value=e}static some(t){return new a(!0,t)}static none(){return a.singletonNone}fold(t,e){return this.tag?e(this.value):t()}isSome(){return this.tag}isNone(){return!this.tag}map(t){return this.tag?a.some(t(this.value)):a.none()}bind(t){return this.tag?t(this.value):a.none()}exists(t){return this.tag&&t(this.value)}forall(t){return!this.tag||t(this.value)}filter(t){return!this.tag||t(this.value)?this:a.none()}getOr(t){return this.tag?this.value:t}or(t){return this.tag?this:t}getOrThunk(t){return this.tag?this.value:t()}orThunk(t){return this.tag?this:t()}getOrDie(t){if(this.tag)return this.value;throw new Error(null!=t?t:"Called getOrDie on None")}static from(t){return n(t)?a.some(t):a.none()}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(t){this.tag&&t(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}a.singletonNone=new a(!1);const u=(t,e)=>{for(let o=0,r=t.length;o<r;o++)e(t[o],o)},c=t=>{if(null==t)throw new Error("Node cannot be null or undefined");return{dom:t}},d=c,h=(t,e)=>{const o=t.dom;if(1!==o.nodeType)return!1;{const t=o;if(void 0!==t.matches)return t.matches(e);if(void 0!==t.msMatchesSelector)return t.msMatchesSelector(e);if(void 0!==t.webkitMatchesSelector)return t.webkitMatchesSelector(e);if(void 0!==t.mozMatchesSelector)return t.mozMatchesSelector(e);throw new Error("Browser lacks native selectors")}};"undefined"!=typeof window?window:Function("return this;")();const m=t=>e=>(t=>t.dom.nodeType)(e)===t,g=m(1),f=m(3),v=m(11),y=(t,e)=>{t.dom.removeAttribute(e)},p=t=>d(t.dom.host),w=t=>{const e=f(t)?t.dom.parentNode:t.dom;if(null==e||null===e.ownerDocument)return!1;const o=e.ownerDocument;return(t=>{const e=(t=>d(t.dom.getRootNode()))(t);return v(o=e)&&n(o.dom.host)?a.some(e):a.none();var o})(d(e)).fold((()=>o.body.contains(e)),(r=w,i=p,t=>r(i(t))));var r,i},b=t=>"rtl"===((t,e)=>{const o=t.dom,r=window.getComputedStyle(o).getPropertyValue(e);return""!==r||w(t)?r:((t,e)=>(t=>void 0!==t.style&&i(t.style.getPropertyValue))(t)?t.style.getPropertyValue(e):"")(o,e)})(t,"direction")?"rtl":"ltr",S=(t,e)=>((t,o)=>((t,e)=>{const o=[];for(let r=0,n=t.length;r<n;r++){const n=t[r];e(n,r)&&o.push(n)}return o})(((t,e)=>{const o=t.length,r=new Array(o);for(let n=0;n<o;n++){const o=t[n];r[n]=e(o,n)}return r})(t.dom.childNodes,d),(t=>h(t,e))))(t),N=("li",t=>g(t)&&"li"===t.dom.nodeName.toLowerCase());const A=(t,e,n)=>{u(e,(e=>{const c=d(e),m=N(c),f=((t,e)=>{return(e?(o=t,r="ol,ul",((t,e,o)=>{let n=t.dom;const s=i(o)?o:l;for(;n.parentNode;){n=n.parentNode;const t=d(n);if(h(t,r))return a.some(t);if(s(t))break}return a.none()})(o,0,n)):a.some(t)).getOr(t);var o,r,n})(c,m);var v;(v=f,(t=>a.from(t.dom.parentNode).map(d))(v).filter(g)).each((e=>{if(t.setStyle(f.dom,"direction",null),b(e)===n?y(f,"dir"):((t,e,n)=>{((t,e,n)=>{if(!(o(n)||r(n)||s(n)))throw console.error("Invalid call to Attribute.set. Key ",e,":: Value ",n,":: Element ",t),new Error("Attribute value was not simple");t.setAttribute(e,n+"")})(t.dom,e,n)})(f,"dir",n),b(f)!==n&&t.setStyle(f.dom,"direction",n),m){const e=S(f,"li[dir],li[style]");u(e,(e=>{y(e,"dir"),t.setStyle(e.dom,"direction",null)}))}}))}))},T=(t,e)=>{t.selection.isEditable()&&(A(t.dom,t.selection.getSelectedBlocks(),e),t.nodeChanged())},C=(t,e)=>o=>{const r=r=>{const n=d(r.element);o.setActive(b(n)===e),o.setEnabled(t.selection.isEditable())};return t.on("NodeChange",r),o.setEnabled(t.selection.isEditable()),()=>t.off("NodeChange",r)};t.add("directionality",(t=>{(t=>{t.addCommand("mceDirectionLTR",(()=>{T(t,"ltr")})),t.addCommand("mceDirectionRTL",(()=>{T(t,"rtl")}))})(t),(t=>{t.ui.registry.addToggleButton("ltr",{tooltip:"Left to right",icon:"ltr",onAction:()=>t.execCommand("mceDirectionLTR"),onSetup:C(t,"ltr")}),t.ui.registry.addToggleButton("rtl",{tooltip:"Right to left",icon:"rtl",onAction:()=>t.execCommand("mceDirectionRTL"),onSetup:C(t,"rtl")})})(t)}))}();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
/**
* TinyMCE version 7.1.1 (2024-05-22)
* TinyMCE version 7.3.0 (2024-08-07)
*/
!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=e=>t=>(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&(s=r=e,(o=String).prototype.isPrototypeOf(s)||(null===(n=r.constructor)||void 0===n?void 0:n.name)===o.name)?"string":t;var s,r,o,n})(t)===e,s=t("string"),r=t("object"),o=t("array"),n=("function",e=>"function"==typeof e);var c=tinymce.util.Tools.resolve("tinymce.dom.DOMUtils"),i=tinymce.util.Tools.resolve("tinymce.EditorManager"),l=tinymce.util.Tools.resolve("tinymce.Env"),a=tinymce.util.Tools.resolve("tinymce.util.Tools");const p=e=>t=>t.options.get(e),u=p("importcss_merge_classes"),m=p("importcss_exclusive"),f=p("importcss_selector_converter"),y=p("importcss_selector_filter"),d=p("importcss_groups"),h=p("importcss_append"),g=p("importcss_file_filter"),_=p("skin"),v=p("skin_url"),b=Array.prototype.push,x=/^\.(?:ephox|tiny-pageembed|mce)(?:[.-]+\w+)+$/,T=e=>s(e)?t=>-1!==t.indexOf(e):e instanceof RegExp?t=>e.test(t):e,S=(e,t)=>{let s={};const r=/^(?:([a-z0-9\-_]+))?(\.[a-z0-9_\-\.]+)$/i.exec(t);if(!r)return;const o=r[1],n=r[2].substr(1).split(".").join(" "),c=a.makeMap("a,img");return r[1]?(s={title:t},e.schema.getTextBlockElements()[o]?s.block=o:e.schema.getBlockElements()[o]||c[o.toLowerCase()]?s.selector=o:s.inline=o):r[2]&&(s={inline:"span",title:t.substr(1),classes:n}),u(e)?s.classes=n:s.attributes={class:n},s},k=(e,t)=>null===t||m(e),M=e=>{e.on("init",(()=>{const t=(()=>{const e=[],t=[],s={};return{addItemToGroup:(e,r)=>{s[e]?s[e].push(r):(t.push(e),s[e]=[r])},addItem:t=>{e.push(t)},toFormats:()=>{return(r=t,n=e=>{const t=s[e];return 0===t.length?[]:[{title:e,items:t}]},(e=>{const t=[];for(let s=0,r=e.length;s<r;++s){if(!o(e[s]))throw new Error("Arr.flatten item "+s+" was not an array, input: "+e);b.apply(t,e[s])}return t})(((e,t)=>{const s=e.length,r=new Array(s);for(let o=0;o<s;o++){const s=e[o];r[o]=t(s,o)}return r})(r,n))).concat(e);var r,n}}})(),r={},n=T(y(e)),p=(e=>a.map(e,(e=>a.extend({},e,{original:e,selectors:{},filter:T(e.filter)}))))(d(e)),u=(t,s)=>{if(((e,t,s,r)=>!(k(e,s)?t in r:t in s.selectors))(e,t,s,r)){((e,t,s,r)=>{k(e,s)?r[t]=!0:s.selectors[t]=!0})(e,t,s,r);const o=((e,t,s,r)=>{let o;const n=f(e);return o=r&&r.selector_converter?r.selector_converter:n||(()=>S(e,s)),o.call(t,s,r)})(e,e.plugins.importcss,t,s);if(o){const t=o.name||c.DOM.uniqueId();return e.formatter.register(t,o),{title:o.title,format:t}}}return null};a.each(((e,t,r)=>{const o=[],n={},c=(t,n)=>{let p,u=t.href;if(u=(e=>{const t=l.cacheSuffix;return s(e)&&(e=e.replace("?"+t,"").replace("&"+t,"")),e})(u),u&&(!r||r(u,n))&&!((e,t)=>{const s=_(e);if(s){const r=v(e),o=r?e.documentBaseURI.toAbsolute(r):i.baseURL+"/skins/ui/"+s,n=i.baseURL+"/skins/content/",c=e.editorManager.suffix;return t===o+"/content"+(e.inline?".inline":"")+`${c}.css`||-1!==t.indexOf(n)}return!1})(e,u)){a.each(t.imports,(e=>{c(e,!0)}));try{p=t.cssRules||t.rules}catch(e){}a.each(p,(e=>{e.styleSheet&&e.styleSheet?c(e.styleSheet,!0):e.selectorText&&a.each(e.selectorText.split(","),(e=>{o.push(a.trim(e))}))}))}};a.each(e.contentCSS,(e=>{n[e]=!0})),r||(r=(e,t)=>t||n[e]);try{a.each(t.styleSheets,(e=>{c(e)}))}catch(e){}return o})(e,e.getDoc(),T(g(e))),(e=>{if(!x.test(e)&&(!n||n(e))){const s=((e,t)=>a.grep(e,(e=>!e.filter||e.filter(t))))(p,e);if(s.length>0)a.each(s,(s=>{const r=u(e,s);r&&t.addItemToGroup(s.title,r)}));else{const s=u(e,null);s&&t.addItem(s)}}}));const m=t.toFormats();e.dispatch("addStyleModifications",{items:m,replace:!h(e)})}))};e.add("importcss",(e=>((e=>{const t=e.options.register,o=e=>s(e)||n(e)||r(e);t("importcss_merge_classes",{processor:"boolean",default:!0}),t("importcss_exclusive",{processor:"boolean",default:!0}),t("importcss_selector_converter",{processor:"function"}),t("importcss_selector_filter",{processor:o}),t("importcss_file_filter",{processor:o}),t("importcss_groups",{processor:"object[]"}),t("importcss_append",{processor:"boolean",default:!1})})(e),M(e),(e=>({convertSelectorToFormat:t=>S(e,t)}))(e))))}();

View File

@@ -1,4 +1,4 @@
/**
* TinyMCE version 7.1.1 (2024-05-22)
* TinyMCE version 7.3.0 (2024-08-07)
*/
!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=e=>t=>t.options.get(e),a=t("insertdatetime_dateformat"),n=t("insertdatetime_timeformat"),r=t("insertdatetime_formats"),s=t("insertdatetime_element"),i="Sun Mon Tue Wed Thu Fri Sat Sun".split(" "),o="Sunday Monday Tuesday Wednesday Thursday Friday Saturday Sunday".split(" "),l="Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),m="January February March April May June July August September October November December".split(" "),c=(e,t)=>{if((e=""+e).length<t)for(let a=0;a<t-e.length;a++)e="0"+e;return e},d=(e,t,a=new Date)=>(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=t.replace("%D","%m/%d/%Y")).replace("%r","%I:%M:%S %p")).replace("%Y",""+a.getFullYear())).replace("%y",""+a.getYear())).replace("%m",c(a.getMonth()+1,2))).replace("%d",c(a.getDate(),2))).replace("%H",""+c(a.getHours(),2))).replace("%M",""+c(a.getMinutes(),2))).replace("%S",""+c(a.getSeconds(),2))).replace("%I",""+((a.getHours()+11)%12+1))).replace("%p",a.getHours()<12?"AM":"PM")).replace("%B",""+e.translate(m[a.getMonth()]))).replace("%b",""+e.translate(l[a.getMonth()]))).replace("%A",""+e.translate(o[a.getDay()]))).replace("%a",""+e.translate(i[a.getDay()]))).replace("%%","%"),u=(e,t)=>{if(s(e)){const a=d(e,t);let n;n=/%[HMSIp]/.test(t)?d(e,"%Y-%m-%dT%H:%M"):d(e,"%Y-%m-%d");const r=e.dom.getParent(e.selection.getStart(),"time");r?((e,t,a,n)=>{const r=e.dom.create("time",{datetime:a},n);e.dom.replace(r,t),e.selection.select(r,!0),e.selection.collapse(!1)})(e,r,n,a):e.insertContent('<time datetime="'+n+'">'+a+"</time>")}else e.insertContent(d(e,t))};var p=tinymce.util.Tools.resolve("tinymce.util.Tools");const g=e=>t=>{const a=()=>{t.setEnabled(e.selection.isEditable())};return e.on("NodeChange",a),a(),()=>{e.off("NodeChange",a)}};e.add("insertdatetime",(e=>{(e=>{const t=e.options.register;t("insertdatetime_dateformat",{processor:"string",default:e.translate("%Y-%m-%d")}),t("insertdatetime_timeformat",{processor:"string",default:e.translate("%H:%M:%S")}),t("insertdatetime_formats",{processor:"string[]",default:["%H:%M:%S","%Y-%m-%d","%I:%M:%S %p","%D"]}),t("insertdatetime_element",{processor:"boolean",default:!1})})(e),(e=>{e.addCommand("mceInsertDate",((t,n)=>{u(e,null!=n?n:a(e))})),e.addCommand("mceInsertTime",((t,a)=>{u(e,null!=a?a:n(e))}))})(e),(e=>{const t=r(e),a=(e=>{let t=e;return{get:()=>t,set:e=>{t=e}}})((e=>{const t=r(e);return t.length>0?t[0]:n(e)})(e)),s=t=>e.execCommand("mceInsertDate",!1,t);e.ui.registry.addSplitButton("insertdatetime",{icon:"insert-time",tooltip:"Insert date/time",select:e=>e===a.get(),fetch:a=>{a(p.map(t,(t=>({type:"choiceitem",text:d(e,t),value:t}))))},onAction:e=>{s(a.get())},onItemAction:(e,t)=>{a.set(t),s(t)},onSetup:g(e)});const i=e=>()=>{a.set(e),s(e)};e.ui.registry.addNestedMenuItem("insertdatetime",{icon:"insert-time",text:"Date/time",getSubmenuItems:()=>p.map(t,(t=>({type:"menuitem",text:d(e,t),onAction:i(t)}))),onSetup:g(e)})})(e)}))}();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
/**
* TinyMCE version 7.1.1 (2024-05-22)
* TinyMCE version 7.3.0 (2024-08-07)
*/
!function(){"use strict";var n=tinymce.util.Tools.resolve("tinymce.PluginManager");const e=n=>e=>typeof e===n,o=e("boolean"),a=e("number"),t=n=>e=>e.options.get(n),i=t("nonbreaking_force_tab"),s=t("nonbreaking_wrap"),r=(n,e)=>{let o="";for(let a=0;a<e;a++)o+=n;return o},c=(n,e)=>{const o=s(n)||n.plugins.visualchars?`<span class="${(n=>!!n.plugins.visualchars&&n.plugins.visualchars.isEnabled())(n)?"mce-nbsp-wrap mce-nbsp":"mce-nbsp-wrap"}" contenteditable="false">${r("&nbsp;",e)}</span>`:r("&nbsp;",e);n.undoManager.transact((()=>n.insertContent(o)))};var l=tinymce.util.Tools.resolve("tinymce.util.VK");const u=n=>e=>{const o=()=>{e.setEnabled(n.selection.isEditable())};return n.on("NodeChange",o),o(),()=>{n.off("NodeChange",o)}};n.add("nonbreaking",(n=>{(n=>{const e=n.options.register;e("nonbreaking_force_tab",{processor:n=>o(n)?{value:n?3:0,valid:!0}:a(n)?{value:n,valid:!0}:{valid:!1,message:"Must be a boolean or number."},default:!1}),e("nonbreaking_wrap",{processor:"boolean",default:!0})})(n),(n=>{n.addCommand("mceNonBreaking",(()=>{c(n,1)}))})(n),(n=>{const e=()=>n.execCommand("mceNonBreaking");n.ui.registry.addButton("nonbreaking",{icon:"non-breaking",tooltip:"Nonbreaking space",onAction:e,onSetup:u(n)}),n.ui.registry.addMenuItem("nonbreaking",{icon:"non-breaking",text:"Nonbreaking space",onAction:e,onSetup:u(n)})})(n),(n=>{const e=i(n);e>0&&n.on("keydown",(o=>{if(o.keyCode===l.TAB&&!o.isDefaultPrevented()){if(o.shiftKey)return;o.preventDefault(),o.stopImmediatePropagation(),c(n,e)}}))})(n)}))}();

View File

@@ -1,4 +1,4 @@
/**
* TinyMCE version 7.1.1 (2024-05-22)
* TinyMCE version 7.3.0 (2024-08-07)
*/
!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),a=tinymce.util.Tools.resolve("tinymce.Env");const t=e=>a=>a.options.get(e),n=t("pagebreak_separator"),o=t("pagebreak_split_block"),r="mce-pagebreak",s=e=>{const t=`<img src="${a.transparentSrc}" class="${r}" data-mce-resize="false" data-mce-placeholder />`;return e?`<p>${t}</p>`:t},c=e=>a=>{const t=()=>{a.setEnabled(e.selection.isEditable())};return e.on("NodeChange",t),t(),()=>{e.off("NodeChange",t)}};e.add("pagebreak",(e=>{(e=>{const a=e.options.register;a("pagebreak_separator",{processor:"string",default:"\x3c!-- pagebreak --\x3e"}),a("pagebreak_split_block",{processor:"boolean",default:!1})})(e),(e=>{e.addCommand("mcePageBreak",(()=>{e.insertContent(s(o(e)))}))})(e),(e=>{const a=()=>e.execCommand("mcePageBreak");e.ui.registry.addButton("pagebreak",{icon:"page-break",tooltip:"Page break",onAction:a,onSetup:c(e)}),e.ui.registry.addMenuItem("pagebreak",{text:"Page break",icon:"page-break",onAction:a,onSetup:c(e)})})(e),(e=>{const a=n(e),t=()=>o(e),c=new RegExp(a.replace(/[\?\.\*\[\]\(\)\{\}\+\^\$\:]/g,(e=>"\\"+e)),"gi");e.on("BeforeSetContent",(e=>{e.content=e.content.replace(c,s(t()))})),e.on("PreInit",(()=>{e.serializer.addNodeFilter("img",(n=>{let o,s,c=n.length;for(;c--;)if(o=n[c],s=o.attr("class"),s&&-1!==s.indexOf(r)){const n=o.parent;if(n&&e.schema.getBlockElements()[n.name]&&t()){n.type=3,n.value=a,n.raw=!0,o.remove();continue}o.type=3,o.value=a,o.raw=!0}}))}))})(e),(e=>{e.on("ResolveName",(a=>{"IMG"===a.target.nodeName&&e.dom.hasClass(a.target,r)&&(a.name="pagebreak")}))})(e)}))}();

View File

@@ -1,4 +1,4 @@
/**
* TinyMCE version 7.1.1 (2024-05-22)
* TinyMCE version 7.3.0 (2024-08-07)
*/
!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),t=tinymce.util.Tools.resolve("tinymce.Env"),o=tinymce.util.Tools.resolve("tinymce.util.Tools");const n=e=>t=>t.options.get(e),i=n("content_style"),s=n("content_css_cors"),c=n("body_class"),r=n("body_id");e.add("preview",(e=>{(e=>{e.addCommand("mcePreview",(()=>{(e=>{const n=(e=>{var n;let l="";const a=e.dom.encode,d=null!==(n=i(e))&&void 0!==n?n:"";l+='<base href="'+a(e.documentBaseURI.getURI())+'">';const m=s(e)?' crossorigin="anonymous"':"";o.each(e.contentCSS,(t=>{l+='<link type="text/css" rel="stylesheet" href="'+a(e.documentBaseURI.toAbsolute(t))+'"'+m+">"})),d&&(l+='<style type="text/css">'+d+"</style>");const y=r(e),u=c(e),v='<script>document.addEventListener && document.addEventListener("click", function(e) {for (var elm = e.target; elm; elm = elm.parentNode) {if (elm.nodeName === "A" && !('+(t.os.isMacOS()||t.os.isiOS()?"e.metaKey":"e.ctrlKey && !e.altKey")+")) {e.preventDefault();}}}, false);<\/script> ",p=e.getBody().dir,w=p?' dir="'+a(p)+'"':"";return"<!DOCTYPE html><html><head>"+l+'</head><body id="'+a(y)+'" class="mce-content-body '+a(u)+'"'+w+">"+e.getContent()+v+"</body></html>"})(e);e.windowManager.open({title:"Preview",size:"large",body:{type:"panel",items:[{name:"preview",type:"iframe",sandboxed:!0,transparent:!1}]},buttons:[{type:"cancel",name:"close",text:"Close",primary:!0}],initialData:{preview:n}}).focus("close")})(e)}))})(e),(e=>{const t=()=>e.execCommand("mcePreview");e.ui.registry.addButton("preview",{icon:"preview",tooltip:"Preview",onAction:t}),e.ui.registry.addMenuItem("preview",{icon:"preview",text:"Preview",onAction:t})})(e)}))}();

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
/**
* TinyMCE version 7.1.1 (2024-05-22)
* TinyMCE version 7.3.0 (2024-08-07)
*/
!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const o=("function",e=>"function"==typeof e);var n=tinymce.util.Tools.resolve("tinymce.dom.DOMUtils"),t=tinymce.util.Tools.resolve("tinymce.util.Tools");const a=e=>o=>o.options.get(e),c=a("save_enablewhendirty"),i=a("save_onsavecallback"),s=a("save_oncancelcallback"),r=(e,o)=>{e.notificationManager.open({text:o,type:"error"})},l=e=>o=>{const n=()=>{o.setEnabled(!c(e)||e.isDirty())};return n(),e.on("NodeChange dirty",n),()=>e.off("NodeChange dirty",n)};e.add("save",(e=>{(e=>{const o=e.options.register;o("save_enablewhendirty",{processor:"boolean",default:!0}),o("save_onsavecallback",{processor:"function"}),o("save_oncancelcallback",{processor:"function"})})(e),(e=>{e.ui.registry.addButton("save",{icon:"save",tooltip:"Save",enabled:!1,onAction:()=>e.execCommand("mceSave"),onSetup:l(e),shortcut:"Meta+S"}),e.ui.registry.addButton("cancel",{icon:"cancel",tooltip:"Cancel",enabled:!1,onAction:()=>e.execCommand("mceCancel"),onSetup:l(e)}),e.addShortcut("Meta+S","","mceSave")})(e),(e=>{e.addCommand("mceSave",(()=>{(e=>{const t=n.DOM.getParent(e.id,"form");if(c(e)&&!e.isDirty())return;e.save();const a=i(e);if(o(a))return a.call(e,e),void e.nodeChanged();t?(e.setDirty(!1),t.onsubmit&&!t.onsubmit()||("function"==typeof t.submit?t.submit():r(e,"Error: Form submit field collision.")),e.nodeChanged()):r(e,"Error: No form element found.")})(e)})),e.addCommand("mceCancel",(()=>{(e=>{const n=t.trim(e.startContent),a=s(e);o(a)?a.call(e,e):e.resetContent(n)})(e)}))})(e)}))}();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
/**
* TinyMCE version 7.1.1 (2024-05-22)
* TinyMCE version 7.3.0 (2024-08-07)
*/
!function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager");const s=(t,s,o)=>{t.dom.toggleClass(t.getBody(),"mce-visualblocks"),o.set(!o.get()),((t,s)=>{t.dispatch("VisualBlocks",{state:s})})(t,o.get())},o=("visualblocks_default_state",t=>t.options.get("visualblocks_default_state"));const e=(t,s)=>o=>{o.setActive(s.get());const e=t=>o.setActive(t.state);return t.on("VisualBlocks",e),()=>t.off("VisualBlocks",e)};t.add("visualblocks",((t,l)=>{(t=>{(0,t.options.register)("visualblocks_default_state",{processor:"boolean",default:!1})})(t);const a=(t=>{let s=!1;return{get:()=>s,set:t=>{s=t}}})();((t,o,e)=>{t.addCommand("mceVisualBlocks",(()=>{s(t,0,e)}))})(t,0,a),((t,s)=>{const o=()=>t.execCommand("mceVisualBlocks");t.ui.registry.addToggleButton("visualblocks",{icon:"visualblocks",tooltip:"Show blocks",onAction:o,onSetup:e(t,s)}),t.ui.registry.addToggleMenuItem("visualblocks",{text:"Show blocks",icon:"visualblocks",onAction:o,onSetup:e(t,s)})})(t,a),((t,e,l)=>{t.on("PreviewFormats AfterPreviewFormats",(s=>{l.get()&&t.dom.toggleClass(t.getBody(),"mce-visualblocks","afterpreviewformats"===s.type)})),t.on("init",(()=>{o(t)&&s(t,0,l)}))})(t,0,a)}))}();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +1 @@
tinymce.Resource.add('content/dark/content.css', "body{background-color:#222f3e;color:#fff;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}a{color:#4099ff}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border=\"0\"]):not([style*=border-width]) td,table[border]:not([border=\"0\"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border=\"0\"]):not([style*=border-style]) td,table[border]:not([border=\"0\"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border=\"0\"]):not([style*=border-color]) td,table[border]:not([border=\"0\"]):not([style*=border-color]) th{border-color:#6d737b}figure{display:table;margin:1rem auto}figure figcaption{color:#8a8f97;display:block;margin-top:.25rem;text-align:center}hr{border-color:#6d737b;border-style:solid;border-width:1px 0 0 0}code{background-color:#6d737b;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #6d737b;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #6d737b;margin-right:1.5rem;padding-right:1rem}")
//# sourceMappingURL=content.js.map
tinymce.Resource.add('content/dark/content.css', `body{background-color:#222f3e;color:#fff;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}a{color:#4099ff}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#6d737b}figure{display:table;margin:1rem auto}figure figcaption{color:#8a8f97;display:block;margin-top:.25rem;text-align:center}hr{border-color:#6d737b;border-style:solid;border-width:1px 0 0 0}code{background-color:#6d737b;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #6d737b;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #6d737b;margin-right:1.5rem;padding-right:1rem}`)

View File

@@ -1,2 +1 @@
tinymce.Resource.add('content/default/content.css', "body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border=\"0\"]):not([style*=border-width]) td,table[border]:not([border=\"0\"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border=\"0\"]):not([style*=border-style]) td,table[border]:not([border=\"0\"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border=\"0\"]):not([style*=border-color]) td,table[border]:not([border=\"0\"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem}")
//# sourceMappingURL=content.js.map
tinymce.Resource.add('content/default/content.css', `body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem}`)

View File

@@ -1,2 +1 @@
tinymce.Resource.add('content/document/content.css', "@media screen{html{background:#f4f4f4;min-height:100%}}body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif}@media screen{body{background-color:#fff;box-shadow:0 0 4px rgba(0,0,0,.15);box-sizing:border-box;margin:1rem auto 0;max-width:820px;min-height:calc(100vh - 1rem);padding:4rem 6rem 6rem 6rem}}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border=\"0\"]):not([style*=border-width]) td,table[border]:not([border=\"0\"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border=\"0\"]):not([style*=border-style]) td,table[border]:not([border=\"0\"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border=\"0\"]):not([style*=border-color]) td,table[border]:not([border=\"0\"]):not([style*=border-color]) th{border-color:#ccc}figure figcaption{color:#999;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem}")
//# sourceMappingURL=content.js.map
tinymce.Resource.add('content/document/content.css', `@media screen{html{background:#f4f4f4;min-height:100%}}body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif}@media screen{body{background-color:#fff;box-shadow:0 0 4px rgba(0,0,0,.15);box-sizing:border-box;margin:1rem auto 0;max-width:820px;min-height:calc(100vh - 1rem);padding:4rem 6rem 6rem 6rem}}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure figcaption{color:#999;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem}`)

View File

@@ -1,2 +1 @@
tinymce.Resource.add('content/tinymce-5-dark/content.css', "body{background-color:#2f3742;color:#dfe0e4;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}a{color:#4099ff}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border=\"0\"]):not([style*=border-width]) td,table[border]:not([border=\"0\"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border=\"0\"]):not([style*=border-style]) td,table[border]:not([border=\"0\"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border=\"0\"]):not([style*=border-color]) td,table[border]:not([border=\"0\"]):not([style*=border-color]) th{border-color:#6d737b}figure{display:table;margin:1rem auto}figure figcaption{color:#8a8f97;display:block;margin-top:.25rem;text-align:center}hr{border-color:#6d737b;border-style:solid;border-width:1px 0 0 0}code{background-color:#6d737b;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #6d737b;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #6d737b;margin-right:1.5rem;padding-right:1rem}")
//# sourceMappingURL=content.js.map
tinymce.Resource.add('content/tinymce-5-dark/content.css', `body{background-color:#2f3742;color:#dfe0e4;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}a{color:#4099ff}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#6d737b}figure{display:table;margin:1rem auto}figure figcaption{color:#8a8f97;display:block;margin-top:.25rem;text-align:center}hr{border-color:#6d737b;border-style:solid;border-width:1px 0 0 0}code{background-color:#6d737b;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #6d737b;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #6d737b;margin-right:1.5rem;padding-right:1rem}`)

View File

@@ -1,2 +1 @@
tinymce.Resource.add('content/tinymce-5/content.css', "body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border=\"0\"]):not([style*=border-width]) td,table[border]:not([border=\"0\"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border=\"0\"]):not([style*=border-style]) td,table[border]:not([border=\"0\"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border=\"0\"]):not([style*=border-color]) td,table[border]:not([border=\"0\"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem}")
//# sourceMappingURL=content.js.map
tinymce.Resource.add('content/tinymce-5/content.css', `body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem}`)

View File

@@ -1,2 +1 @@
tinymce.Resource.add('content/writer/content.css', "body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem auto;max-width:900px}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border=\"0\"]):not([style*=border-width]) td,table[border]:not([border=\"0\"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border=\"0\"]):not([style*=border-style]) td,table[border]:not([border=\"0\"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border=\"0\"]):not([style*=border-color]) td,table[border]:not([border=\"0\"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem}")
//# sourceMappingURL=content.js.map
tinymce.Resource.add('content/writer/content.css', `body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem auto;max-width:900px}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem}`)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +1 @@
tinymce.Resource.add('ui/dark/skin.shadowdom.css', "body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201}")
//# sourceMappingURL=skin.shadowdom.js.map
tinymce.Resource.add('ui/dark/skin.shadowdom.css', `body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201}`)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More