Files
itflow/admin/modals/audit_log/audit_log_export.php
johnnyq 2311d1f9d8 Speed up Admin Audit Logs, Sign-ins and Users, Transactions, Income, Invoices, Projects and the invoice page on installs with years of history: database 3.6.6 adds logs (log_user_id, log_type, log_action), logs (log_type, log_action, log_user_id, log_ip, log_created_at) and tickets (ticket_invoice_id), each only where missing and in db.sql, leaving out one it cannot add with a warning in App Logs; Audit Logs drops SQL_CALC_FOUND_ROWS and counts its views with one GROUP BY log_type, log_action in the new auditLogViewCounts, which also hands back the types and actions in the view so auditLogKindsSql finds a sparse view or action on the index, and builds its who, client, type and action lists with auditLogDistinctSql in place of EXISTS and DISTINCT over the log, as the export window now does; Sign-ins reads only the new log_type index, counts unknown emails for the page's addresses in their own query and ends every sort on log_ip or log_user_id so tied rows keep one order; the new listNewestFirst in functions/app.php pages a newest-first list from the last month, year and four years back before the whole of it, used by Transactions without the running balance and by Income, which both take their count from their pill in place of SQL_CALC_FOUND_ROWS, with incomeUnionSql(false) leaving out the name joins for Income's counts when there is no search; Invoices replaces the LEFT JOIN of every payment grouped by invoice with a paid lookup for the page's invoices and the unpaid ones in the totals; the project lookups, through the new PROJECT_TICKETS_SQL, the client onboarding ticket counts and the expense refund lookups also say the column is > 0, so the database stays on the index where nearly every row has 0 and its statistics say a lookup finds every row, Projects having read 33.6 million rows with no ticket on a project; checked on an eight-year test install against the old code over 103 filter, sort and page combinations and a crawl of all 233 pages; add the changes to the 26.11.8 changelog section
2026-11-13 12:58:04 -05:00

151 lines
8.5 KiB
PHP

<?php
/*
* Export the audit log to CSV or PDF - pre-filled from the list's filters and
* view, every one editable: the export runs what this form posts, not what
* the page happened to show (export_audit_logs, admin/post/audit_log.php).
*/
require_once '../../includes/modal_header.php';
$views = auditLogViews();
$q_filter = is_string($_GET['q'] ?? null) ? $_GET['q'] : '';
$view_filter = is_string($_GET['view'] ?? null) && isset($views[$_GET['view']]) ? $_GET['view'] : '';
$client_filter = is_scalar($_GET['client'] ?? null) ? intval($_GET['client']) : 0;
$user_filter = is_string($_GET['user'] ?? null) ? $_GET['user'] : '';
$type_filter = is_string($_GET['type'] ?? null) ? $_GET['type'] : '';
$action_filter = is_string($_GET['action'] ?? null) ? $_GET['action'] : '';
$entity_filter = is_scalar($_GET['entity'] ?? null) ? intval($_GET['entity']) : 0;
$date_from_filter = (is_string($_GET['dtf'] ?? null) && preg_match('/^\d{4}-\d{2}-\d{2}$/', $_GET['dtf']) && $_GET['dtf'] !== '1970-01-01') ? $_GET['dtf'] : '';
$date_to_filter = (is_string($_GET['dtt'] ?? null) && preg_match('/^\d{4}-\d{2}-\d{2}$/', $_GET['dtt']) && $_GET['dtt'] !== '2099-12-31') ? $_GET['dtt'] : '';
ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fas fa-fw fa-download me-2"></i>Export Audit Logs</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<?php exportTabsNav(); ?>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<?php if ($entity_filter) { ?><input type="hidden" name="entity" value="<?= $entity_filter ?>"><?php } ?>
<div class="modal-body">
<?php exportTabsFiltersOpen(); ?>
<?php if ($entity_filter) { ?>
<div class="alert alert-info small py-2"><i class="fas fa-fw fa-filter me-1"></i>Just one record: <?= escapeHtml($type_filter !== '' ? $type_filter : 'record') ?> #<?= $entity_filter ?></div>
<?php } ?>
<div class="mb-3">
<label class="form-label">Search</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-search"></i></span>
<input type="text" class="form-control" name="q" value="<?= escapeHtml($q_filter) ?>" placeholder="What was done, who, the client, the IP address or the browser">
</div>
</div>
<div class="mb-3">
<label class="form-label">Showing</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-layer-group"></i></span>
<select class="form-select select2" name="view">
<?php foreach ($views as $key => $definition) { ?>
<option value="<?= $key ?>" <?= $view_filter === $key ? 'selected' : '' ?><?= selectOptionData(['icon' => 'fas ' . $definition['icon'], 'description' => $definition['title']]) ?>><?= $key === '' ? 'Everything' : escapeHtml($definition['label']) ?></option>
<?php } ?>
</select>
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-6">
<label class="form-label">Who</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-user"></i></span>
<select class="form-select select2" name="user">
<option value="">- Anyone -</option>
<option value="system" <?= $user_filter === 'system' ? 'selected' : '' ?><?= selectOptionData(['icon' => 'fas fa-cog']) ?>>ITFlow itself</option>
<?php
$sql = mysqli_query($mysqli, "SELECT user_id, user_name, user_avatar, user_type FROM users JOIN (" . auditLogDistinctSql('log_user_id') . ") AS logged ON logged.log_user_id = user_id ORDER BY user_type ASC, user_name ASC");
while ($row = mysqli_fetch_assoc($sql)) {
$option_id = intval($row['user_id']); ?>
<option value="<?= $option_id ?>" <?= $user_filter === (string) $option_id ? 'selected' : '' ?><?= selectOptionUser($row['user_id'], $row['user_name'], $row['user_avatar'] ?? '') ?><?= intval($row['user_type']) === 2 ? ' data-description="Portal"' : '' ?>><?= escapeHtml($row['user_name']) ?></option>
<?php } ?>
</select>
</div>
</div>
<div class="col-md-6">
<label class="form-label">Client</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-building"></i></span>
<select class="form-select select2" name="client">
<option value="">- Any client -</option>
<?php
$sql = mysqli_query($mysqli, "SELECT client_id, client_name, client_abbreviation FROM clients JOIN (" . auditLogDistinctSql('log_client_id') . ") AS logged ON logged.log_client_id = client_id ORDER BY client_name ASC");
while ($row = mysqli_fetch_assoc($sql)) {
$option_id = intval($row['client_id']); ?>
<option value="<?= $option_id ?>" <?= $client_filter === $option_id ? 'selected' : '' ?><?= selectOptionClient($row['client_id'], $row['client_name'], $row['client_abbreviation'] ?? '') ?>><?= escapeHtml($row['client_name']) ?></option>
<?php } ?>
</select>
</div>
</div>
<div class="col-md-6">
<label class="form-label">Type</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-shapes"></i></span>
<select class="form-select select2" name="type">
<option value="">- Any type -</option>
<?php
$sql = mysqli_query($mysqli, auditLogDistinctSql('log_type') . " ORDER BY log_type ASC");
while ($row = mysqli_fetch_row($sql)) { ?>
<option value="<?= escapeHtml($row[0]) ?>" <?= $type_filter === $row[0] ? 'selected' : '' ?><?= selectOptionData(['icon' => 'fas ' . auditLogTypeIcon($row[0])]) ?>><?= escapeHtml($row[0]) ?></option>
<?php } ?>
</select>
</div>
</div>
<div class="col-md-6">
<label class="form-label">Action</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-bolt"></i></span>
<select class="form-select select2" name="action">
<option value="">- Any action -</option>
<?php
$sql = mysqli_query($mysqli, "SELECT DISTINCT log_action FROM (" . auditLogDistinctSql('log_type, log_action') . ") AS actions ORDER BY log_action ASC");
while ($row = mysqli_fetch_row($sql)) { ?>
<option value="<?= escapeHtml($row[0]) ?>" <?= $action_filter === $row[0] ? 'selected' : '' ?><?= selectOptionData(['icon' => 'fas ' . auditLogActionStyle($row[0])['icon']]) ?>><?= escapeHtml($row[0]) ?></option>
<?php } ?>
</select>
</div>
</div>
<div class="col-md-6">
<label class="form-label">From</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-calendar"></i></span>
<input type="date" class="form-control" name="dtf" value="<?= $date_from_filter ?>" max="2999-12-31">
</div>
</div>
<div class="col-md-6">
<label class="form-label">To</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-calendar"></i></span>
<input type="date" class="form-control" name="dtt" value="<?= $date_to_filter ?>" max="2999-12-31">
</div>
</div>
</div>
<?php exportTabsColumns('audit_logs'); ?>
</div>
<div class="modal-footer">
<?php renderExportButtons('export_audit_logs'); ?>
</div>
</form>
<?php
require_once '../../../includes/modal_footer.php';