Files
yogibook_aury_new/public/class/SchoolScope.php
T
2026-10-14 10:21:55 +02:00

132 lines
4.8 KiB
PHP

<?php
/**
* SchoolScope
* Verifica che record e utenti appartengano alla scuola corrente.
* Da usare in ogni pagina/endpoint che riceve un ID dall'esterno.
*
* Uso:
* SchoolScope::assert('bookingclass', $idbookingclass); // blocca con 403 se non è della scuola
* if (!SchoolScope::belongs('service', $id)) { ... }
* SchoolScope::assertMember($userid); // utente iscritto alla scuola
* SchoolScope::addMember($newUserId); // iscrive un nuovo utente come allievo
*/
if (!class_exists('DBHandlerSelect')) {
require_once __DIR__ . '/db-functions.php';
}
class SchoolScope
{
/**
* Tabelle ammesse e relativa chiave primaria.
* (whitelist: i nomi non arrivano mai dall'esterno nella query)
*/
const TABLES = [
'bookingclass' => 'idbookingclass',
'serviceschedule' => 'idserviceschedule',
'service' => 'idservice',
'servicecategory' => 'idservicecategory',
'associateclass' => 'idassociateclass',
'propagation' => 'idpropagation',
'orderbook' => 'idorderbook',
'employee' => 'idemployee',
'dayoff' => 'iddayoff',
'expiryparameter' => 'idexpiryparameter',
'certificateuserprofile' => 'idcertificateuserprofile',
'events' => 'id',
'event_bookings' => 'id',
'event_locations' => 'id',
'school_images' => 'idimage',
'products' => 'idproduct',
'product_variants' => 'idvariant',
'product_services' => 'idproductservice',
'product_images' => 'idimage',
'orderbook_services' => 'idorderbookservice',
'memberships' => 'idmembership',
'gift_vouchers' => 'idvoucher',
'coupons' => 'idcoupon',
'coupon_products' => 'idcouponproduct',
'coupon_redemptions' => 'idredemption',
];
/** Il record $id della tabella appartiene alla scuola corrente? */
public static function belongs(string $table, int $id, ?int $idschool = null): bool
{
if ($id <= 0 || !isset(self::TABLES[$table])) {
return false;
}
$pk = self::TABLES[$table];
$st = self::pdo()->prepare("SELECT 1 FROM `$table` WHERE `$pk` = ? AND idschool = ? LIMIT 1");
$st->execute([$id, $idschool ?? SchoolContext::id()]);
return (bool) $st->fetchColumn();
}
/** Come belongs(), ma blocca la richiesta (403) se il record non è della scuola */
public static function assert(string $table, int $id): void
{
if (!self::belongs($table, $id)) {
self::deny('Elemento non trovato in questa scuola.');
}
}
/** L'utente è membro attivo della scuola corrente? */
public static function isMember(int $userId, ?int $idschool = null): bool
{
if ($userId <= 0) {
return false;
}
$st = self::pdo()->prepare(
"SELECT 1 FROM school_user WHERE user_id = ? AND idschool = ? AND active = 'Y' LIMIT 1"
);
$st->execute([$userId, $idschool ?? SchoolContext::id()]);
return (bool) $st->fetchColumn();
}
public static function assertMember(int $userId): void
{
if (!self::isMember($userId)) {
self::deny('Utente non iscritto a questa scuola.');
}
}
/**
* Iscrive un utente alla scuola corrente (default: allievo).
* Se è già membro non cambia il ruolo, lo riattiva soltanto.
*/
public static function addMember(int $userId, string $role = 'student', ?int $idschool = null): void
{
if ($userId <= 0) {
return;
}
self::pdo()->prepare(
"INSERT INTO school_user (user_id, idschool, school_role, active)
VALUES (?, ?, ?, 'Y')
ON DUPLICATE KEY UPDATE active = 'Y'"
)->execute([$userId, $idschool ?? SchoolContext::id(), $role]);
}
/**
* Risposta 403. Per le chiamate AJAX restituisce JSON,
* così il .fail() di jQuery scatta e il messaggio è leggibile.
*/
private static function deny(string $msg): void
{
http_response_code(403);
$isAjax = (($_SERVER['HTTP_X_REQUESTED_WITH'] ?? '') === 'XMLHttpRequest')
|| strpos($_SERVER['HTTP_ACCEPT'] ?? '', 'application/json') !== false;
if ($isAjax) {
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['success' => false, 'error' => $msg]);
} else {
echo htmlspecialchars($msg, ENT_QUOTES, 'UTF-8');
}
exit;
}
private static function pdo(): PDO
{
return DBHandlerSelect::getInstance()->getConnection();
}
}