| Server IP : 188.114.96.3 / Your IP : 216.73.216.143 Web Server : Apache System : Linux sv058.home.net.pl 6.8.2 husak_dirty_pages_fix #1 SMP PREEMPT_DYNAMIC Tue Apr 2 22:06:51 CEST 2024 x86_64 User : ( 3194769) PHP Version : 8.5.12 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : OFF Directory : /polana/includes/ |
Upload File : |
<?php
declare(strict_types=1);
function ensure_operations_schema(): void
{
ensure_reservations_schema();
$cols = db()->query('SHOW COLUMNS FROM reservations')->fetchAll();
$names = array_map(static fn(array $r): string => (string)$r['Field'], $cols);
if (!in_array('assigned_waiter_user_id', $names, true)) {
db()->exec('ALTER TABLE reservations ADD COLUMN assigned_waiter_user_id INT UNSIGNED DEFAULT NULL AFTER internal_note');
db()->exec('ALTER TABLE reservations ADD INDEX idx_reservation_waiter (assigned_waiter_user_id, reservation_date)');
}
if (!in_array('table_label', $names, true)) {
db()->exec('ALTER TABLE reservations ADD COLUMN table_label VARCHAR(40) DEFAULT NULL AFTER assigned_waiter_user_id');
}
$userCols = db()->query('SHOW COLUMNS FROM users')->fetchAll();
$userNames = array_map(static fn(array $r): string => (string)$r['Field'], $userCols);
if (!in_array('staff_pin_hash', $userNames, true)) {
db()->exec('ALTER TABLE users ADD COLUMN staff_pin_hash VARCHAR(255) DEFAULT NULL');
}
if (!in_array('staff_pin_updated_at', $userNames, true)) {
db()->exec('ALTER TABLE users ADD COLUMN staff_pin_updated_at DATETIME DEFAULT NULL');
}
db()->exec(
"CREATE TABLE IF NOT EXISTS daily_shift_notes (
work_date DATE PRIMARY KEY,
note TEXT DEFAULT NULL,
updated_by_user_id INT UNSIGNED DEFAULT NULL,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT fk_daily_note_user FOREIGN KEY (updated_by_user_id) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_polish_ci"
);
db()->exec(
"CREATE TABLE IF NOT EXISTS staff_notifications (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id INT UNSIGNED NOT NULL,
type VARCHAR(40) NOT NULL DEFAULT 'info',
title VARCHAR(160) NOT NULL,
body VARCHAR(500) DEFAULT NULL,
link VARCHAR(255) DEFAULT NULL,
read_at DATETIME DEFAULT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_staff_notify_user (user_id, read_at, created_at),
CONSTRAINT fk_staff_notify_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_polish_ci"
);
db()->exec(
"CREATE TABLE IF NOT EXISTS staff_pin_devices (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id INT UNSIGNED NOT NULL,
token_hash CHAR(64) NOT NULL,
device_label VARCHAR(120) DEFAULT NULL,
last_used_at DATETIME DEFAULT NULL,
expires_at DATETIME NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uq_staff_pin_token (token_hash),
INDEX idx_staff_pin_user (user_id, expires_at),
CONSTRAINT fk_staff_pin_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_polish_ci"
);
}
function operations_manager_roles(): array { return ['admin','editor','manager']; }
function operations_month_roles(): array { return ['admin','manager']; }
function daily_shift_note(string $date): ?array
{
ensure_operations_schema();
$stmt=db()->prepare('SELECT n.*,u.name,u.username FROM daily_shift_notes n LEFT JOIN users u ON u.id=n.updated_by_user_id WHERE n.work_date=? LIMIT 1');
$stmt->execute([$date]);
return $stmt->fetch() ?: null;
}
function daily_shift_note_save(string $date, string $note, int $userId): void
{
ensure_operations_schema();
$d=DateTimeImmutable::createFromFormat('!Y-m-d',$date);
if(!$d || $d->format('Y-m-d')!==$date) throw new RuntimeException('Nieprawidłowa data notatki.');
$stmt=db()->prepare("INSERT INTO daily_shift_notes (work_date,note,updated_by_user_id) VALUES (?,?,?) ON DUPLICATE KEY UPDATE note=VALUES(note),updated_by_user_id=VALUES(updated_by_user_id),updated_at=NOW()");
$stmt->execute([$date,trim($note)!==''?trim($note):null,$userId]);
}
function staff_notify_user(int $userId,string $type,string $title,string $body='',string $link=''): void
{
ensure_operations_schema();
$stmt=db()->prepare('INSERT INTO staff_notifications (user_id,type,title,body,link) VALUES (?,?,?,?,?)');
$stmt->execute([$userId,$type,$title,$body!==''?$body:null,$link!==''?$link:null]);
}
function staff_notification_unread_count(int $userId): int
{
ensure_operations_schema();
$stmt=db()->prepare('SELECT COUNT(*) FROM staff_notifications WHERE user_id=? AND read_at IS NULL');
$stmt->execute([$userId]); return (int)$stmt->fetchColumn();
}
function staff_notifications_for_user(int $userId,int $limit=80,bool $unreadOnly=false): array
{
ensure_operations_schema(); $limit=max(1,min(200,$limit));
$sql='SELECT * FROM staff_notifications WHERE user_id=?';
if($unreadOnly)$sql.=' AND read_at IS NULL';
$sql.=" ORDER BY id DESC LIMIT $limit";
$stmt=db()->prepare($sql);$stmt->execute([$userId]);return $stmt->fetchAll();
}
function staff_notifications_mark_read(int $userId,?int $notificationId=null): void
{
ensure_operations_schema();
if($notificationId){$stmt=db()->prepare('UPDATE staff_notifications SET read_at=COALESCE(read_at,NOW()) WHERE id=? AND user_id=?');$stmt->execute([$notificationId,$userId]);}
else{$stmt=db()->prepare('UPDATE staff_notifications SET read_at=COALESCE(read_at,NOW()) WHERE user_id=?');$stmt->execute([$userId]);}
}
function staff_notify_schedule_change(string $date,array $before,array $after): void
{
$old=[];$new=[];
foreach($before as $r)$old[(int)$r['user_id']]=[$r['start_time']??null,$r['end_time']??null];
foreach($after as $r)$new[(int)$r['user_id']]=[$r['start_time']??null,$r['end_time']??null];
$ids=array_unique(array_merge(array_keys($old),array_keys($new)));
$pretty=(new DateTimeImmutable($date))->format('d.m.Y');
foreach($ids as $uid){
if(isset($old[$uid]) && isset($new[$uid]) && $old[$uid]===$new[$uid]) continue;
if(!isset($new[$uid])) staff_notify_user((int)$uid,'schedule','Zmiana grafiku','Usunięto Cię z grafiku na '.$pretty,'/pracownik/grafik.php?month='.substr($date,0,7));
elseif(!isset($old[$uid])) staff_notify_user((int)$uid,'schedule','Nowy dzień w grafiku','Dodano Cię do grafiku na '.$pretty,'/pracownik/grafik.php?month='.substr($date,0,7));
else staff_notify_user((int)$uid,'schedule','Zmiana godzin','Zmieniono Twój grafik na '.$pretty,'/pracownik/grafik.php?month='.substr($date,0,7));
}
}
function reservation_waiters_for_date(string $date): array
{
ensure_schedule_schema();
$stmt=db()->prepare("SELECT u.id,u.username,u.name FROM staff_schedule s JOIN users u ON u.id=s.user_id WHERE s.work_date=? AND u.active=1 AND u.role='waiter' ORDER BY COALESCE(u.name,u.username)");
$stmt->execute([$date]); return $stmt->fetchAll();
}
function reservation_assign_waiter(int $reservationId,?int $waiterUserId,string $tableLabel,int $actorUserId): void
{
ensure_operations_schema();
$reservation=reservation_by_id($reservationId); if(!$reservation) throw new RuntimeException('Nie znaleziono rezerwacji.');
$old=(int)($reservation['assigned_waiter_user_id']??0);
if($waiterUserId!==null){
$stmt=db()->prepare("SELECT COUNT(*) FROM users WHERE id=? AND active=1 AND role='waiter'");$stmt->execute([$waiterUserId]);
if((int)$stmt->fetchColumn()!==1) throw new RuntimeException('Wybrany kelner jest niedostępny.');
}
$tableLabel=mb_substr(trim($tableLabel),0,40,'UTF-8');
db()->prepare('UPDATE reservations SET assigned_waiter_user_id=?,table_label=? WHERE id=?')->execute([$waiterUserId,$tableLabel!==''?$tableLabel:null,$reservationId]);
reservation_add_event($reservationId,'assignment','Zmieniono przypisanie kelnera/stolika.',$actorUserId);
if($old>0 && $old!==($waiterUserId??0)) staff_notify_user($old,'reservation','Zmiana rezerwacji','Rezerwacja '.$reservation['reservation_code'].' nie jest już przypisana do Ciebie.','/pracownik/rezerwacje.php');
if(($waiterUserId??0)>0 && $old!==$waiterUserId){
$body=(new DateTimeImmutable($reservation['reservation_date']))->format('d.m').' · '.substr((string)$reservation['reservation_time'],0,5).' · '.$reservation['guest_name'];
if($tableLabel!=='')$body.=' · stolik '.$tableLabel;
staff_notify_user((int)$waiterUserId,'reservation','Przypisano rezerwację',$body,'/pracownik/rezerwacje.php');
}
}
function staff_pin_cookie_name(): string { return 'POLANA_STAFF_DEVICE'; }
function staff_pin_user_from_device(): ?array
{
ensure_operations_schema();
$token=(string)($_COOKIE[staff_pin_cookie_name()]??''); if(strlen($token)<32)return null;
$hash=hash('sha256',$token);
$stmt=db()->prepare("SELECT d.id AS device_id,d.user_id,u.username,u.name,u.role,u.staff_pin_hash FROM staff_pin_devices d JOIN users u ON u.id=d.user_id WHERE d.token_hash=? AND d.expires_at>NOW() AND u.active=1 AND u.role IN ('waiter','kitchen') LIMIT 1");
$stmt->execute([$hash]);return $stmt->fetch() ?: null;
}
function staff_pin_set(int $userId,string $pin): void
{
ensure_operations_schema();
if(!preg_match('/^\\d{4}$/',$pin)) throw new RuntimeException('PIN musi mieć dokładnie 4 cyfry.');
if(function_exists('staff_pin_is_unique') && !staff_pin_is_unique($pin,$userId)) {
throw new RuntimeException('Ten PIN jest już używany przez innego pracownika.');
}
db()->prepare('UPDATE users SET staff_pin_hash=?,staff_pin_updated_at=NOW() WHERE id=?')
->execute([password_hash($pin,PASSWORD_DEFAULT),$userId]);
}
function staff_pin_enable_device(int $userId,string $label=''): void
{
ensure_operations_schema();
$stmt=db()->prepare("SELECT staff_pin_hash FROM users WHERE id=? AND active=1 AND role IN ('waiter','kitchen')");$stmt->execute([$userId]);
if(!(string)$stmt->fetchColumn())throw new RuntimeException('Najpierw ustaw PIN.');
$token=bin2hex(random_bytes(32));$hash=hash('sha256',$token);$expires=(new DateTimeImmutable('+180 days'))->format('Y-m-d H:i:s');
db()->prepare('INSERT INTO staff_pin_devices (user_id,token_hash,device_label,expires_at) VALUES (?,?,?,?)')->execute([$userId,$hash,mb_substr(trim($label),0,120,'UTF-8')?:null,$expires]);
$secure=(!empty($_SERVER['HTTPS'])&&$_SERVER['HTTPS']!=='off')||(($_SERVER['HTTP_X_FORWARDED_PROTO']??'')==='https');
setcookie(staff_pin_cookie_name(),$token,['expires'=>time()+180*86400,'path'=>'/','secure'=>$secure,'httponly'=>true,'samesite'=>'Strict']);
$_COOKIE[staff_pin_cookie_name()]=$token;
}
function staff_pin_disable_current_device(int $userId): void
{
ensure_operations_schema();$token=(string)($_COOKIE[staff_pin_cookie_name()]??'');
if($token!=='')db()->prepare('DELETE FROM staff_pin_devices WHERE user_id=? AND token_hash=?')->execute([$userId,hash('sha256',$token)]);
setcookie(staff_pin_cookie_name(),'',['expires'=>time()-3600,'path'=>'/','httponly'=>true,'samesite'=>'Strict']);unset($_COOKIE[staff_pin_cookie_name()]);
}
function staff_pin_verify_device(string $pin): ?int
{
$device=staff_pin_user_from_device();if(!$device || empty($device['staff_pin_hash']))return null;
if(!password_verify($pin,(string)$device['staff_pin_hash']))return null;
db()->prepare('UPDATE staff_pin_devices SET last_used_at=NOW() WHERE id=?')->execute([(int)$device['device_id']]);
return (int)$device['user_id'];
}