PHP Classes

File: plugins/audit2.php

Recommend this page to a friend!
  Packages of Uku-Kaarel Jo~esaar   too-vark   plugins/audit2.php   Download  
File: plugins/audit2.php
Role: Application script
Content type: text/plain
Description: Application script
Class: too-vark
Manage people work schedule times and tasks
Author: By
Last change:
Date: 8 days ago
Size: 24,876 bytes
 

Contents

Class file image Download
<?php declare(strict_types=1); /** * PLUGIN: audit2 ? checklists with access control (admin + selected workers). * * Access model: admin always allowed; workers need a row in audit_access. * Handlers follow P11: explicit params, [code, body] return. */ // ??? LOCAL I18N $_aud_i18n = [ 'nav_audit' => ['Audits', 'Auditid'], 'aud_runs' => ['Runs', 'Auditid'], 'aud_templates' => ['Templates', '?abloonid'], 'aud_report' => ['Report', 'Aruanne'], 'aud_access' => ['Access', 'Juurdepääs'], 'aud_no_runs' => ['No audits yet.', 'Auditeid pole veel.'], 'aud_no_templates' => ['No templates yet.', '?abloone pole veel.'], 'aud_draft' => ['Draft', 'Mustand'], 'aud_issues' => ['Issues', 'Probleemid'], 'aud_due' => ['Due', 'Tähtajaks'], 'aud_overdue' => ['Overdue', 'Tähtaeg möödas'], 'aud_start_new' => ['Start audit', 'Alusta auditit'], 'aud_back' => ['? Back', '? Tagasi'], 'aud_commit' => ['Commit audit', 'Kinnita audit'], 'aud_read_only' => ['Committed ? read only.', 'Kinnitatud ? kirjutuskaitstud.'], 'aud_committed_at' => ['Committed at', 'Kinnitatud'], 'aud_print' => ['? Print', '? Prindi'], 'aud_comment' => ['Comment', 'Kommentaar'], 'aud_saved_locally' => ['Saved locally', 'Salvestatud kohapeal'], 'aud_save' => ['Save template', 'Salvesta ?abloon'], 'aud_delete' => ['Disable', 'Keela'], 'aud_new_template' => ['New template', 'Uus ?abloon'], 'aud_title' => ['Title', 'Pealkiri'], 'aud_target' => ['Target task', 'Sihtülesanne'], 'aud_target_any' => ['? any ?', '? kõik ?'], 'aud_interval' => ['Interval', 'Intervall'], 'aud_int_task_done' => ['On task completion', 'Ülesande lõpetamisel'], 'aud_int_week_end' => ['Weekly', 'Nädalane'], 'aud_int_month_end' => ['Monthly', 'Kuine'], 'aud_subtasks' => ['Checklist items', 'Kontrollnimekiri'], 'aud_add_subtask' => ['+ item', '+ punkt'], 'aud_active' => ['Active', 'Aktiivne'], 'aud_disabled' => ['Disabled', 'Välja lülitatud'], 'aud_no_access' => ['No audit access.', 'Auditile juurdepääs puudub.'], 'aud_access_title' => ['Audit access', 'Auditi juurdepääs'], 'aud_subtask_passrate'=> ['Item pass rate', 'Punkti edukus'], 'aud_worker_compl' => ['Worker compliance', 'Töötaja vastavus'], 'aud_print_auditor' => ['Auditor', 'Auditeerija'], 'aud_print_date' => ['Audit date', 'Auditi kuupäev'], 'aud_print_committed' => ['Committed', 'Kinnitatud'], 'aud_print_summary' => ['Summary', 'Kokkuvõte'], 'aud_print_pass' => ['? OK', '? Korras'], 'aud_print_fail' => ['? Issue', '? Probleem'], ]; function __aud(string $key): string { global $_aud_i18n, $langi; $li = $langi ?? 0; return $_aud_i18n[$key][$li] ?? ($_aud_i18n[$key][0] ?? $key); } // ??? HELPERS function _aud_intervals(): array { return ['task_done', 'week_end', 'month_end']; } /** Worker has audit access: admin always yes; others need a row in audit_access. */ function _aud_can(bool $is_admin, int $uid, PDO $pdo): bool { if ($is_admin) return true; $s = $pdo->prepare("SELECT 1 FROM audit_access WHERE user_id=?"); $s->execute([$uid]); return (bool)$s->fetchColumn(); } /** Parse subtasks input (array or JSON string) ? trimmed string[], or null on bad input. */ function _aud_parse_subtasks(mixed $raw): ?array { if (is_string($raw)) { $decoded = json_decode($raw, true); if (!is_array($decoded)) return null; $raw = $decoded; } if (!is_array($raw)) return null; $out = []; foreach ($raw as $s) { if (!is_string($s)) return null; $t = trim($s); if ($t !== '') $out[] = $t; } return $out; } /** Fetch template + ordered subtasks. Returns null if missing. */ function _aud_template_by_id(PDO $pdo, int $id): ?array { $s = $pdo->prepare("SELECT id, title, target, interval, subtasks, active FROM audit_templates WHERE id=?"); $s->execute([$id]); $t = $s->fetch(); if (!$t) return null; $sub = $pdo->prepare("SELECT id, sort_order, name FROM audit_subtasks WHERE template_id=? ORDER BY sort_order, id"); $sub->execute([$id]); $t['subtasks_ordered'] = $sub->fetchAll(); $t['id'] = (int)$t['id']; $t['active'] = (int)$t['active']; return $t; } /** Delete + re-insert audit_subtasks rows for a template. */ function _aud_sync_subtasks(PDO $pdo, int $template_id, array $names): void { $pdo->prepare("DELETE FROM audit_subtasks WHERE template_id=?")->execute([$template_id]); $ins = $pdo->prepare("INSERT INTO audit_subtasks (template_id, sort_order, name) VALUES (?,?,?)"); foreach ($names as $i => $n) $ins->execute([$template_id, $i, $n]); } /** ISO week Monday?Sunday bounds for a Y-m-d date. */ function _aud_week_bounds(string $ymd): array { $ts= strtotime($ymd); $dow = (int)date('N', $ts); $mon = date('Y-m-d', strtotime($ymd . ' -' . ($dow - 1) . ' days')); return [$mon, date('Y-m-d', strtotime($mon . ' +6 days'))]; } /** Calendar-month first/last day for a Y-m-d. */ function _aud_month_bounds(string $ymd): array { return [date('Y-m-01', strtotime($ymd)), date('Y-m-t', strtotime($ymd))]; } // ??? TEMPLATES function plugin_audit_template(PDO $pdo, array $d, int $uid, bool $is_admin, DateContext $dc, string $method = 'GET'): array { return match($method) { 'GET' => _aud_templates_list($pdo, $is_admin), 'POST'=> _aud_template_save($pdo, $d, $is_admin), default => [405, ['error' => 'method_not_allowed']], }; } function _aud_templates_list(PDO $pdo, bool $is_admin): array { $sql = "SELECT id, title, target, interval, subtasks, active FROM audit_templates"; if (!$is_admin) $sql .= " WHERE active=1"; $sql .= " ORDER BY title"; $out = []; foreach ($pdo->query($sql)->fetchAll() as $r) { $decoded = json_decode($r['subtasks'] ?? '[]', true); $out[] = [ 'id' => (int)$r['id'], 'title' => $r['title'], 'target' => $r['target'], 'interval' => $r['interval'], 'subtasks' => is_array($decoded) ? $decoded : [], 'active' => (int)$r['active'], ]; } return [200, $out]; } function _aud_template_save(PDO $pdo, array $d, bool $is_admin): array { if (!$is_admin) return [403, ['error' => 'forbidden']]; $title = trim($d['title'] ?? ''); $target = trim($d['target'] ?? ''); $interval = $d['interval'] ?? ''; $subtasks = _aud_parse_subtasks($d['subtasks'] ?? []); if ($title === '') return [400, ['error' => 'title required']]; if (!in_array($interval, _aud_intervals(), true)) return [400, ['error' => 'invalid_interval']]; if ($subtasks === null) return [400, ['error' => 'invalid_subtasks']]; if (!$subtasks) return [400, ['error' => 'subtasks required']]; $js = json_encode(array_values($subtasks), JSON_UNESCAPED_UNICODE); $active = isset($d['active']) ? (int)!!$d['active'] : 1; return db_try('audit_template/save', function() use ($pdo, $d, $title, $target, $interval, $subtasks, $js, $active) { if (!empty($d['id'])) { $id = (int)$d['id']; $pdo->prepare("UPDATE audit_templates SET title=?, target=?, interval=?, subtasks=?, active=? WHERE id=?") ->execute([$title, $target !== '' ? $target : null, $interval, $js, $active, $id]); } else { $pdo->prepare("INSERT INTO audit_templates (title, target, interval, subtasks, active) VALUES (?,?,?,?,?)") ->execute([$title, $target !== '' ? $target : null, $interval, $js, $active]); $id = (int)$pdo->lastInsertId(); } _aud_sync_subtasks($pdo, $id, $subtasks); return [200, ['msg' => 'ok', 'id' => $id]]; }); } /** Soft-disable a template (active=0). Admin only. Hard-delete would cascade runs. */ function plugin_audit_template_delete(PDO $pdo, array $d, int $uid, bool $is_admin, DateContext $dc, string $method = 'POST'): array { if (!$is_admin) return [403, ['error' => 'forbidden']]; $id = (int)($d['id'] ?? 0); if ($id <= 0) return [400, ['error' => 'id required']]; return db_try('audit_template/delete', function() use ($pdo, $id) { $pdo->prepare("UPDATE audit_templates SET active=0 WHERE id=?")->execute([$id]); return [200, ['msg' => 'ok']]; }); } // ??? RUNS function plugin_audit_run(PDO $pdo, array $d, int $uid, bool $is_admin, DateContext $dc, string $method = 'GET'): array { if ($method !== 'GET') return [405, ['error' => 'method_not_allowed']]; if (!_aud_can($is_admin, $uid, $pdo)) return [403, ['error' => 'forbidden']]; if (!empty($_GET['id'])) return _aud_run_get($pdo, (int)$_GET['id'], $uid, $is_admin); $where = []; $params = []; if (!$is_admin) { $where[] = 'r.user_id=?'; $params[] = $uid; } elseif (!empty($_GET['user_id'])) { $where[] = 'r.user_id=?'; $params[] = (int)$_GET['user_id']; } if (!empty($_GET['template_id'])) { $where[] = 'r.template_id=?'; $params[] = (int)$_GET['template_id']; } if (!empty($_GET['from'])) { $where[] = 'r.run_date>=?'; $params[] = $_GET['from']; } if (!empty($_GET['to'])) { $where[] = 'r.run_date<=?'; $params[] = $_GET['to']; } $sql = "SELECT r.id, r.template_id, r.run_date, r.user_id, r.results, r.has_issues, r.committed_at, t.title AS template_title, u.username FROM audit_runs r LEFT JOIN audit_templates t ON t.id=r.template_id LEFT JOIN users u ON u.id=r.user_id"; if ($where) $sql .= ' WHERE ' . implode(' AND ', $where); $sql .= ' ORDER BY r.run_date DESC, r.id DESC LIMIT 200'; $stmt = $pdo->prepare($sql); $stmt->execute($params); $out = []; foreach ($stmt->fetchAll() as $r) { $results = json_decode($r['results'] ?? '[]', true); $results = is_array($results) ? $results : []; $done = count(array_filter($results, fn($it) => !empty($it['done']))); $out[] = [ 'id' => (int)$r['id'], 'template_id' => (int)$r['template_id'], 'template_title' => $r['template_title'], 'run_date' => $r['run_date'], 'user_id' => (int)$r['user_id'], 'username' => $r['username'], 'has_issues' => (int)$r['has_issues'], 'committed_at' => $r['committed_at'], 'done_count' => $done, 'total_count' => count($results), ]; } return [200, $out]; } function _aud_run_get(PDO $pdo, int $run_id, int $uid, bool $is_admin): array { $stmt = $pdo->prepare("SELECT id, template_id, run_date, user_id, results, has_issues, committed_at FROM audit_runs WHERE id=?"); $stmt->execute([$run_id]); $r = $stmt->fetch(); if (!$r) return [404, ['error' => 'not_found']]; if (!$is_admin && (int)$r['user_id'] !== $uid) return [403, ['error' => 'forbidden']]; $results = json_decode($r['results'] ?? '[]', true); return [200, [ 'id' => (int)$r['id'], 'template_id'=> (int)$r['template_id'], 'template' => _aud_template_by_id($pdo, (int)$r['template_id']), 'run_date' => $r['run_date'], 'user_id' => (int)$r['user_id'], 'results' => is_array($results) ? $results : [], 'has_issues' => (int)$r['has_issues'], 'committed_at' => $r['committed_at'], ]]; } function plugin_audit_run_create(PDO $pdo, array $d, int $uid, bool $is_admin, DateContext $dc, string $method = 'POST'): array { if ($method !== 'POST') return [405, ['error' => 'method_not_allowed']]; if (!_aud_can($is_admin, $uid, $pdo)) return [403, ['error' => 'forbidden']]; $tpl_id = (int)($d['template_id'] ?? 0); if ($tpl_id <= 0) return [400, ['error' => 'template_id required']]; $tpl = _aud_template_by_id($pdo, $tpl_id); if (!$tpl || !$tpl['active']) return [404, ['error' => 'template_not_found']]; $target_uid = ($is_admin && !empty($d['user_id'])) ? (int)$d['user_id'] : $uid; return db_try('audit_run/create', function() use ($pdo, $tpl_id, $target_uid, $dc) { $pdo->prepare("INSERT INTO audit_runs (template_id, run_date, user_id, results, has_issues) VALUES (?,?,?,?,0)") ->execute([$tpl_id, $dc->today, $target_uid, '[]']); return [200, ['msg' => 'ok', 'id' => (int)$pdo->lastInsertId()]]; }); } /** * Post-task-completion hook ? called by client when a task reaches status=2. * Creates a draft run if an active task_done template matches the task title. * Exact-title target preferred; falls back to NULL (global) template. */ function plugin_audit_check_task(PDO $pdo, array $d, int $uid, bool $is_admin, DateContext $dc, string $method = 'POST'): array { if ($method !== 'POST') return [405, ['error' => 'method_not_allowed']]; $task_id = (int)($d['task_id'] ?? 0); if ($task_id <= 0) return [400, ['error' => 'task_id required']]; $stmt = $pdo->prepare("SELECT title, user_id, status FROM tasks WHERE id=?"); $stmt->execute([$task_id]); $task = $stmt->fetch(); if (!$task) return [404, ['error' => 'task_not_found']]; if (!$is_admin && (int)$task['user_id'] !== $uid) return [403, ['error' => 'forbidden']]; if ((int)$task['status'] !== 2) return [200, ['msg' => 'not_done', 'run_id' => null]]; $t = $pdo->prepare("SELECT id FROM audit_templates WHERE active=1 AND interval='task_done' AND target=? LIMIT 1"); $t->execute([$task['title']]); $tpl_id = (int)$t->fetchColumn(); if (!$tpl_id) { $t2 = $pdo->prepare("SELECT id FROM audit_templates WHERE active=1 AND interval='task_done' AND target IS NULL LIMIT 1"); $t2->execute(); $tpl_id = (int)$t2->fetchColumn(); } if (!$tpl_id) return [200, ['msg' => 'no_template', 'run_id' => null]]; $ex = $pdo->prepare("SELECT id FROM audit_runs WHERE template_id=? AND user_id=? AND run_date=? AND committed_at IS NULL LIMIT 1"); $ex->execute([$tpl_id, (int)$task['user_id'], $dc->today]); if ($rid = (int)$ex->fetchColumn()) return [200, ['msg' => 'existing', 'run_id' => $rid]]; return db_try('audit/check_task', function() use ($pdo, $tpl_id, $task, $dc) { $pdo->prepare("INSERT INTO audit_runs (template_id, run_date, user_id, results, has_issues) VALUES (?,?,?,?,0)") ->execute([$tpl_id, $dc->today, (int)$task['user_id'], '[]']); return [200, ['msg' => 'created', 'run_id' => (int)$pdo->lastInsertId()]]; }); } /** Templates overdue for this user: no committed run in the current week/month window. */ function plugin_audit_due(PDO $pdo, array $d, int $uid, bool $is_admin, DateContext $dc, string $method = 'GET'): array { if ($method !== 'GET') return [405, ['error' => 'method_not_allowed']]; if (!_aud_can($is_admin, $uid, $pdo)) return [403, ['error' => 'forbidden']]; [$wkStart] = _aud_week_bounds($dc->today); [$moStart] = _aud_month_bounds($dc->today); $target_uid = ($is_admin && !empty($_GET['user_id'])) ? (int)$_GET['user_id'] : $uid; $stmt = $pdo->prepare("SELECT t.id, t.title, t.interval, (SELECT MAX(r.run_date) FROM audit_runs r WHERE r.template_id=t.id AND r.user_id=? AND r.committed_at IS NOT NULL) AS last_run FROM audit_templates t WHERE t.active=1 AND t.interval IN ('week_end','month_end') ORDER BY t.title"); $stmt->execute([$target_uid]); $out = []; foreach ($stmt->fetchAll() as $r) { $last = $r['last_run']; $window = $r['interval'] === 'week_end' ? $wkStart : $moStart; if (!$last || $last < $window) $out[] = [ 'template_id' => (int)$r['id'], 'title' => $r['title'], 'interval' => $r['interval'], 'last_run' => $last, 'overdue' => $last && $last < $window, ]; } return [200, $out]; } function plugin_audit_commit(PDO $pdo, array $d, int $uid, bool $is_admin, DateContext $dc, string $method = 'POST'): array { if ($method !== 'POST') return [405, ['error' => 'method_not_allowed']]; $run_id = (int)($d['run_id'] ?? 0); if ($run_id <= 0) return [400, ['error' => 'run_id required']]; if (!is_array($d['results'] ?? null)) return [400, ['error' => 'results must be array']]; $chk = $pdo->prepare("SELECT user_id, committed_at FROM audit_runs WHERE id=?"); $chk->execute([$run_id]); $row = $chk->fetch(); if (!$row) return [404, ['error' => 'not_found']]; if (!$is_admin && (int)$row['user_id'] !== $uid) return [403, ['error' => 'forbidden']]; if ($row['committed_at']) return [400, ['error' => 'already_committed']]; $issues = 0; $clean = []; foreach ($d['results'] as $r) { if (!is_array($r)) continue; $done = !empty($r['done']) ? 1 : 0; if (!$done) $issues++; $clean[] = ['done' => $done, 'comment' => is_string($r['comment'] ?? null) ? trim((string)$r['comment']) : '']; } return db_try('audit_commit', function() use ($pdo, $run_id, $clean, $issues, $dc) { $pdo->prepare("UPDATE audit_runs SET results=?, has_issues=?, committed_at=? WHERE id=? AND committed_at IS NULL") ->execute([json_encode($clean, JSON_UNESCAPED_UNICODE), $issues > 0 ? 1 : 0, $dc->today . ' ' . $dc->time, $run_id]); return [200, ['msg' => 'ok', 'has_issues' => $issues > 0 ? 1 : 0]]; }); } function plugin_audit_print(PDO $pdo, array $d, int $uid, bool $is_admin, DateContext $dc, string $method = 'GET'): array { if ($method !== 'GET') return [405, ['error' => 'method_not_allowed']]; $run_id = (int)($_GET['run_id'] ?? 0); if ($run_id <= 0) return [400, ['error' => 'run_id required']]; $stmt = $pdo->prepare("SELECT r.*, u.username, u.real_name, u.contact AS user_contact FROM audit_runs r LEFT JOIN users u ON u.id=r.user_id WHERE r.id=?"); $stmt->execute([$run_id]); $row = $stmt->fetch(); if (!$row) return [404, ['error' => 'not_found']]; if (!$is_admin && (int)$row['user_id'] !== $uid) return [403, ['error' => 'forbidden']]; if (!$row['committed_at']) return [400, ['error' => 'not_committed']]; $results = json_decode($row['results'] ?? '[]', true); if (!is_array($results)) $results = []; global $cfg; $org = array_filter($cfg ?? [], fn($v, $k) => str_starts_with($k, 'org_'), ARRAY_FILTER_USE_BOTH); return [200, [ 'run' => ['id' => (int)$row['id'], 'run_date' => $row['run_date'], 'committed_at' => $row['committed_at'], 'has_issues' => (int)$row['has_issues'], 'results' => $results], 'template' => _aud_template_by_id($pdo, (int)$row['template_id']), 'auditor'=> ['username' => $row['username'] ?? '', 'real_name' => $row['real_name'] ?? '', 'contact' => $row['user_contact'] ?? ''], 'org' => $org, 'today' => $dc->today, ]]; } // ??? REPORT (admin) /** Aggregate pass rates per subtask + per-worker compliance. */ function plugin_audit_report(PDO $pdo, array $d, int $uid, bool $is_admin, DateContext $dc, string $method = 'GET'): array { if ($method !== 'GET') return [405, ['error' => 'method_not_allowed']]; if (!$is_admin) return [403, ['error' => 'forbidden']]; $where = ['r.committed_at IS NOT NULL']; $params = []; if (!empty($_GET['from'])) { $where[] = 'r.run_date>=?'; $params[] = $_GET['from']; } if (!empty($_GET['to'])) { $where[] = 'r.run_date<=?'; $params[] = $_GET['to']; } if (!empty($_GET['template_id'])) { $where[] = 'r.template_id=?'; $params[] = (int)$_GET['template_id']; } $stmt = $pdo->prepare("SELECT r.template_id, r.user_id, r.results, r.has_issues, t.title AS template_title, u.username FROM audit_runs r LEFT JOIN audit_templates t ON t.id=r.template_id LEFT JOIN users u ON u.id=r.user_id WHERE " . implode(' AND ', $where)); $stmt->execute($params); $by_tpl = []; $by_worker = []; foreach ($stmt->fetchAll() as $r) { $tid= (int)$r['template_id']; $uidr = (int)$r['user_id']; $by_worker[$uidr] ??= ['user_id' => $uidr, 'username' => $r['username'], 'runs' => 0, 'issues' => 0]; $by_worker[$uidr]['runs']++; if ((int)$r['has_issues']) $by_worker[$uidr]['issues']++; $results = json_decode($r['results'] ?? '[]', true); if (!is_array($results)) continue; $by_tpl[$tid] ??= ['template_id' => $tid, 'template_title' => $r['template_title'], 'items' => []]; foreach ($results as $idx => $item) { $by_tpl[$tid]['items'][$idx] ??= ['done' => 0, 'total' => 0]; $by_tpl[$tid]['items'][$idx]['total']++; if (!empty($item['done'])) $by_tpl[$tid]['items'][$idx]['done']++; } } foreach ($by_tpl as $tid => &$entry) { $tpl = _aud_template_by_id($pdo, $tid); $names = $tpl ? array_column($tpl['subtasks_ordered'], 'name') : []; $items = []; foreach ($entry['items'] as $idx => $stat) $items[] = ['idx' => $idx, 'name' => $names[$idx] ?? ('#' . ($idx + 1)), 'done' => $stat['done'], 'total' => $stat['total'], 'rate' => $stat['total'] ? round(100 * $stat['done'] / $stat['total']) : 0]; usort($items, fn($a, $b) => $a['rate'] <=> $b['rate']); $entry['items'] = $items; } unset($entry); return [200, ['by_template' => array_values($by_tpl), 'by_worker' => array_values($by_worker)]]; } // ??? ACCESS (admin) /** GET ? all users with has_access flag; POST {user_id, grant:0|1} ? toggle. */ function plugin_audit_access(PDO $pdo, array $d, int $uid, bool $is_admin, DateContext $dc, string $method = 'GET'): array { if (!$is_admin) return [403, ['error' => 'forbidden']]; if ($method === 'GET') { $stmt = $pdo->query("SELECT u.id, u.username, u.real_name, (a.user_id IS NOT NULL) AS has_access FROM users u LEFT JOIN audit_access a ON a.user_id=u.id ORDER BY u.username"); return [200, $stmt->fetchAll()]; } if ($method === 'POST') { $target = (int)($d['user_id'] ?? 0); if ($target <= 1) return [400, ['error' => 'invalid_user']]; $grant = !empty($d['grant']); return db_try('audit_access', function() use ($pdo, $target, $grant) { if ($grant) $pdo->prepare("INSERT OR IGNORE INTO audit_access (user_id) VALUES (?)")->execute([$target]); else $pdo->prepare("DELETE FROM audit_access WHERE user_id=?")->execute([$target]); return [200, ['msg' => 'ok']]; }); } return [405, ['error' => 'method_not_allowed']]; } // ??? VIEWS function view_audit(): void { ?> <div id="audit-shell"> <div id="audit-toolbar" class="sub-nav no_Print"> <a href="#" data-aud-tab="runs" class="active"><?= __aud('aud_runs') ?></a> <a href="#" data-aud-tab="templates" class="aud-admin-only hidden"><?= __aud('aud_templates') ?></a> <a href="#" data-aud-tab="report" class="aud-admin-only hidden"><?= __aud('aud_report') ?></a> <a href="#" data-aud-tab="access" class="aud-admin-only hidden"><?= __aud('aud_access') ?></a> </div> <div id="audit-due-banner" class="hidden"></div> <section id="audit-panel-runs"></section> <section id="audit-panel-templates" class="hidden aud-admin-only"></section> <section id="audit-panel-report" class="hidden aud-admin-only"></section> <section id="audit-panel-access" class="hidden aud-admin-only"></section> <section id="audit-panel-run" class="hidden"></section> </div> <?php } function view_audit_print(): void { ?> <div id="audit-print-container"></div> <?php } // ??? REGISTRATION $_preg = [ 'id' => 'audit', 'schema_version' => 2, 'schema' => [ "CREATE TABLE IF NOT EXISTS audit_templates ( id INTEGER PRIMARY KEY, title TEXT NOT NULL, target TEXT, interval TEXT NOT NULL, subtasks TEXT NOT NULL, active INTEGER DEFAULT 1 )", "CREATE TABLE IF NOT EXISTS audit_runs ( id INTEGER PRIMARY KEY, template_id INTEGER REFERENCES audit_templates(id) ON DELETE CASCADE, run_date TEXT NOT NULL, user_id INTEGER REFERENCES users(id), results TEXT NOT NULL, has_issues INTEGER DEFAULT 0, committed_at TEXT )", "CREATE TABLE IF NOT EXISTS audit_subtasks ( id INTEGER PRIMARY KEY, template_id INTEGER REFERENCES audit_templates(id) ON DELETE CASCADE, sort_order INTEGER NOT NULL, name TEXT NOT NULL )", "CREATE TABLE IF NOT EXISTS audit_access ( user_id INTEGER PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE )", "CREATE INDEX IF NOT EXISTS idx_audit_runs_tmpl ON audit_runs(template_id, run_date)", "CREATE INDEX IF NOT EXISTS idx_audit_runs_issues ON audit_runs(has_issues) WHERE has_issues=1", ], 'routes' => [ 'audit_template' => 'plugin_audit_template', 'audit_template/delete' => 'plugin_audit_template_delete', 'audit_run' => 'plugin_audit_run', 'audit_run/create' => 'plugin_audit_run_create', 'audit_run/check_task'=> 'plugin_audit_check_task', 'audit_run/due' => 'plugin_audit_due', 'audit_run/print' => 'plugin_audit_print', 'audit_commit' => 'plugin_audit_commit', 'audit_report' => 'plugin_audit_report', 'audit_access' => 'plugin_audit_access', ], 'views' => [ 'audit' => 'view_audit', 'audit_print' => 'view_audit_print', ], 'nav' => ['audit' => __aud('nav_audit')], 'etag_routes'=> ['audit_template', 'audit_run', 'audit_run/due', 'audit_run/print', 'audit_report', 'audit_access'], 'write_routes' => ['audit_template', 'audit_template/delete', 'audit_run/create', 'audit_run/check_task', 'audit_commit', 'audit_access'], ]; $_pid = $_preg['id'] ?? ''; if ($_pid) { $plugins[$_pid] = $_preg; if (isset($_preg['routes'])) foreach ($_preg['routes'] as $_r=> $_h) $plugin_routes[$_r]= $_h; if (isset($_preg['views'])) foreach ($_preg['views'] as $_vk => $_vfn) $plugin_views[$_vk]= $_vfn; if (isset($_preg['nav'])) $plugin_nav = array_merge($plugin_nav, $_preg['nav']); if (isset($_preg['etag_routes']))$plugin_etag_routes= array_merge($plugin_etag_routes,$_preg['etag_routes']); if (isset($_preg['write_routes'])) $plugin_write_routes = array_merge($plugin_write_routes, $_preg['write_routes']); }