<?php
declare(strict_types=1);session_start(['cookie_httponly'=>true,'cookie_samesite'=>'Strict','use_strict_mode'=>true]);$config=require dirname(__DIR__).'/config.php';$pdo=new PDO('sqlite:'.$config['database_path'],null,null,[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,PDO::ATTR_DEFAULT_FETCH_MODE=>PDO::FETCH_ASSOC]);$pdo->exec('PRAGMA foreign_keys=ON;PRAGMA busy_timeout=5000;PRAGMA journal_mode=WAL;');
function e(?string $v):string{return htmlspecialchars($v??'',ENT_QUOTES|ENT_SUBSTITUTE,'UTF-8');}function u(string $p=''):string{global $config;return rtrim($config['base_url'],'/').'/'.ltrim($p,'/');}function token():string{return $_SESSION['csrf']??=bin2hex(random_bytes(32));}function csrf():void{if(!hash_equals(token(),(string)($_POST['csrf']??''))){http_response_code(419);exit('Invalid form token');}}function go(string $p):never{header('Location: '.u($p),true,303);exit;}function flash(string $m,string $t='success'):void{$_SESSION['flash']=[$m,$t];}function user():?array{return $_SESSION['auth']??null;}function require_login():void{if(!user())go('login.php');}function can_write():bool{return in_array(user()['role']??'', ['admin','standard'],true);}function require_roles(array $roles):void{require_login();if(!in_array(user()['role'],$roles,true)){audit('authorization','ACCESS_DENIED','denied');http_response_code(403);exit('Forbidden');}}function norm_serial(string $v):string{$v=trim($v);return strlen($v)===12?strtoupper(substr($v,0,11)).strtolower(substr($v,11)):$v;}function valid_serial(string $v):bool{return preg_match('/^[A-Z0-9]{4}\/[A-Z0-9]{5}\/[a-z]$/',$v)===1;}function cid():string{return bin2hex(random_bytes(12));}
function audit(string $category,string $action,string $outcome='success',?string $entity=null,?int $id=null,?string $summary=null,$old=null,$new=null):void{global $pdo;$x=user();$st=$pdo->prepare('INSERT INTO audit_log(user_id,username_snapshot,role_snapshot,event_category,event_action,entity_type,entity_id,outcome,change_summary,old_values_json,new_values_json,request_method,request_path,ip_address,user_agent,correlation_id) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)');$st->execute([$x['id']??null,$x['username']??null,$x['role']??null,$category,$action,$entity,$id,$outcome,$summary,$old?json_encode($old):null,$new?json_encode($new):null,$_SERVER['REQUEST_METHOD']??'CLI',$_SERVER['REQUEST_URI']??'CLI',$_SERVER['REMOTE_ADDR']??null,substr($_SERVER['HTTP_USER_AGENT']??'',0,255),cid()]);}
function queue_event(string $event,string $subject,string $body,?string $entity=null,?int $id=null):void{global $pdo;$s=$pdo->prepare('SELECT recipient_email FROM notification_subscriptions WHERE event_code=? AND active=1');$s->execute([$event]);foreach($s as $r)$pdo->prepare('INSERT INTO email_queue(event_code,entity_type,entity_id,recipient_email,subject,body_text,correlation_id) VALUES(?,?,?,?,?,?,?)')->execute([$event,$entity,$id,$r['recipient_email'],$subject,$body,cid()]);}
