prepare("SELECT username FROM users WHERE province = ?"); $stmt->execute([$province]); $usernames = $stmt->fetchAll(PDO::FETCH_COLUMN); break; case '2': // สถานศึกษา - ตำบลในสังกัด + ตัวเอง $stmt = $pdo->prepare("SELECT username FROM users WHERE (user_type_id = '4' AND leader = ?) OR username = ?"); $stmt->execute([$username, $username]); $usernames = $stmt->fetchAll(PDO::FETCH_COLUMN); break; case '3': // ขึ้นตรง - เฉพาะตัวเอง case '4': // ตำบล - เฉพาะตัวเอง $usernames = [$username]; break; case '9': // ผู้ดูแลระบบ (Super Admin) - เห็นข้อมูลทั้งหมดในฐานข้อมูล $usernames = $pdo->query("SELECT username FROM users")->fetchAll(PDO::FETCH_COLUMN); break; } $_ACCESS_CACHE[$cache_key] = $usernames; return $usernames; } /** * ได้ WHERE clause สำหรับ SQL query ตามสิทธิ์ผู้ใช้ * @param array $user ข้อมูลผู้ใช้จาก session * @param string $table ชื่อตาราง (learning_resources, featured_activities, knowledge_bank) * @return string WHERE clause */ function getAccessControlWhere($user, $table = 'learning_resources') { if (!$user || !isset($user['user_type_id'])) { return "1=0"; // ไม่มีสิทธิ์ } $usernames = getAccessibleUsernames($user); if (empty($usernames)) { return "1=0"; } $placeholders = implode(',', array_fill(0, count($usernames), '?')); return "$table.created_by IN ($placeholders)"; } /** * ได้ข้อมูล resources ตามสิทธิ์ (เร็ว) */ function getAccessibleResources($pdo, $user) { if (!$user) return []; $usernames = getAccessibleUsernames($user); if (empty($usernames)) return []; $placeholders = implode(',', array_fill(0, count($usernames), '?')); $stmt = $pdo->prepare("SELECT * FROM learning_resources WHERE created_by IN ($placeholders) ORDER BY id DESC"); $stmt->execute($usernames); return $stmt->fetchAll(); } /** * ได้ข้อมูล activities ตามสิทธิ์ (เร็ว) */ function getAccessibleActivities($pdo, $user) { if (!$user) return []; $usernames = getAccessibleUsernames($user); if (empty($usernames)) return []; $placeholders = implode(',', array_fill(0, count($usernames), '?')); $stmt = $pdo->prepare("SELECT * FROM featured_activities WHERE created_by IN ($placeholders) ORDER BY activity_date DESC"); $stmt->execute($usernames); return $stmt->fetchAll(); } /** * ได้ข้อมูล knowledge bank ตามสิทธิ์ (เร็ว) */ function getAccessibleKnowledge($pdo, $user) { if (!$user) return []; $usernames = getAccessibleUsernames($user); if (empty($usernames)) return []; $placeholders = implode(',', array_fill(0, count($usernames), '?')); $stmt = $pdo->prepare("SELECT * FROM knowledge_bank WHERE created_by IN ($placeholders) ORDER BY id DESC"); $stmt->execute($usernames); return $stmt->fetchAll(); } /** * ตรวจสอบว่าผู้ใช้มีสิทธิ์ดูรายการนี้หรือไม่ */ function canAccessRecord($user, $record) { if (!$user) return false; $user_type_id = $user['user_type_id']; $username = $user['username']; $province = $user['province'] ?? ''; $created_by = $record['created_by'] ?? ''; // ถ้าเป็นเจ้าของเอง ให้เข้าได้ if ($created_by === $username) { return true; } $pdo = $GLOBALS['pdo'] ?? null; if (!$pdo) return false; // Super Admin (type 9) เข้าถึงทุกรายการ if ($user_type_id === '9') return true; switch ($user_type_id) { case '1': // จังหวัด - เห็นตามจังหวัดของผู้สร้าง $stmt = $pdo->prepare("SELECT province FROM users WHERE username = ?"); $stmt->execute([$created_by]); $creator = $stmt->fetch(); return $creator && $creator['province'] === $province; case '2': // สถานศึกษา - เห็นตำบลที่ leader = username $stmt = $pdo->prepare("SELECT COUNT(*) FROM users WHERE username = ? AND user_type_id = '4' AND leader = ?"); $stmt->execute([$created_by, $username]); return $stmt->fetchColumn() > 0; case '3': // ขึ้นตรง - เห็นแค่ตัวเอง case '4': // ตำบล - เห็นแค่ตัวเอง return false; default: return false; } } /** * ตรวจสอบว่าผู้ใช้สามารถแก้ไขรายการได้หรือไม่ */ function canEditRecord($user, $record) { if (!$user) return false; $username = $user['username']; $created_by = $record['created_by'] ?? ''; $status = $record['status'] ?? 'pending'; // ต้องเป็นเจ้าของเอง และสถานะต้องเป็น pending หรือ rejected return $created_by === $username && ($status === 'pending' || $status === 'rejected'); } /** * ตรวจสอบว่าผู้ใช้สามารถลบรายการได้หรือไม่ */ function canDeleteRecord($user, $record) { if (!$user) return false; $username = $user['username']; $created_by = $record['created_by'] ?? ''; $status = $record['status'] ?? 'pending'; // ลบได้เฉพาะถ้าเป็นเจ้าของ และสถานะเป็น pending หรือ rejected return $created_by === $username && ($status === 'pending' || $status === 'rejected'); } /** * นับข้อมูล resources ตามสิทธิ์ (เร็ว) */ function countAccessibleResources($pdo, $user) { if (!$user) return 0; $usernames = getAccessibleUsernames($user); if (empty($usernames)) return 0; $placeholders = implode(',', array_fill(0, count($usernames), '?')); $stmt = $pdo->prepare("SELECT COUNT(*) FROM learning_resources WHERE created_by IN ($placeholders)"); $stmt->execute($usernames); return $stmt->fetchColumn() ?: 0; } /** * นับข้อมูล activities ตามสิทธิ์ (เร็ว) */ function countAccessibleActivities($pdo, $user) { if (!$user) return 0; $usernames = getAccessibleUsernames($user); if (empty($usernames)) return 0; $placeholders = implode(',', array_fill(0, count($usernames), '?')); $stmt = $pdo->prepare("SELECT COUNT(*) FROM featured_activities WHERE created_by IN ($placeholders)"); $stmt->execute($usernames); return $stmt->fetchColumn() ?: 0; } /** * นับข้อมูล knowledge bank ตามสิทธิ์ (เร็ว) */ function countAccessibleKnowledge($pdo, $user) { if (!$user) return 0; $usernames = getAccessibleUsernames($user); if (empty($usernames)) return 0; $placeholders = implode(',', array_fill(0, count($usernames), '?')); $stmt = $pdo->prepare("SELECT COUNT(*) FROM knowledge_bank WHERE created_by IN ($placeholders)"); $stmt->execute($usernames); return $stmt->fetchColumn() ?: 0; } /** * ตรวจสอบว่าผู้ใช้สามารถอนุมัติรายการได้หรือไม่ * ต้องเป็นผู้มีสิทธิ์สูงกว่าผู้สร้าง (user_type_id ต่ำกว่า) และไม่ใช่ผู้สร้าง */ function canApproveRecord($pdo, $user, $record) { if (!$user || !$record) return false; $username = $user['username']; $user_type_id = $user['user_type_id']; $created_by = $record['created_by'] ?? ''; // ผู้สร้างไม่สามารถอนุมัติตัวเองได้ if ($created_by === $username) { return false; } // Super Admin (type 9) อนุมัติได้ทุกรายการ if ($user_type_id === '9') return true; // ได้ user_type_id ของผู้สร้าง $stmt = $pdo->prepare("SELECT user_type_id FROM users WHERE username = ?"); $stmt->execute([$created_by]); $creator = $stmt->fetch(); if (!$creator) return false; $creator_type = $creator['user_type_id']; // สามารถอนุมัติได้ถ้า user_type_id ตัวเองน้อยกว่า (สิทธิ์สูงกว่า) return $user_type_id < $creator_type; } /** * Self-healing INDEX สำหรับ pagination/filter performance * เรียกครั้งเดียวต่อ request — idempotent */ function ensureIndexes($pdo) { static $done = false; if ($done) return; $done = true; $indexes = [ // featured_activities ['featured_activities', 'idx_status_date', 'status, start_date, activity_date, id'], ['featured_activities', 'idx_created_by', 'created_by'], ['featured_activities', 'idx_province', 'province, district, sub_district'], // learning_resources ['learning_resources', 'idx_status_geo', 'status, geo, province'], ['learning_resources', 'idx_created_by', 'created_by'], ['learning_resources', 'idx_province', 'province, district, sub_district'], // knowledge_bank ['knowledge_bank', 'idx_status', 'status, id'], ['knowledge_bank', 'idx_created_by', 'created_by'], ]; foreach ($indexes as [$table, $name, $cols]) { try { $chk = $pdo->prepare("SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = ? AND index_name = ?"); $chk->execute([$table, $name]); if ($chk->fetchColumn() == 0) { $pdo->exec("CREATE INDEX `$name` ON `$table` ($cols)"); } } catch (Exception $e) { // ตารางอาจยังไม่ถูกสร้าง — ข้าม } } } /** * สร้าง WHERE clause + bindings ตามสิทธิ์ผู้ใช้ + filters * Return: [string $where_sql, array $params] */ function buildAccessFilter($user, $table, array $filters = []): array { $where = []; $params = []; // === Access control === $usernames = getAccessibleUsernames($user); if (empty($usernames)) { return ['1=0', []]; // ไม่มีสิทธิ์ } $ph = implode(',', array_fill(0, count($usernames), '?')); $where[] = "$table.created_by IN ($ph)"; $params = array_merge($params, $usernames); // === filters === if (!empty($filters['status'])) { $where[] = "$table.status = ?"; $params[] = $filters['status']; } if (!empty($filters['name'])) { $col = ($table === 'featured_activities') ? 'title' : 'name'; $where[] = "$table.$col LIKE ?"; $params[] = '%' . $filters['name'] . '%'; } if (!empty($filters['province'])) { $where[] = "$table.province = ?"; $params[] = $filters['province']; } if (!empty($filters['district'])) { $where[] = "$table.district = ?"; $params[] = $filters['district']; } if (!empty($filters['sub_district'])) { $where[] = "$table.sub_district = ?"; $params[] = $filters['sub_district']; } if (!empty($filters['region']) && $table === 'learning_resources') { $where[] = "$table.geo = ?"; $params[] = (int)$filters['region']; } if (!empty($filters['region']) && $table === 'featured_activities') { // ต้องแมป geo → จังหวัด เพราะ featured_activities ไม่มี geo $pdo = $GLOBALS['pdo'] ?? null; if ($pdo) { $st = $pdo->prepare("SELECT DISTINCT province FROM learning_resources WHERE geo = ? AND province IS NOT NULL AND province != ''"); $st->execute([(int)$filters['region']]); $provs = $st->fetchAll(PDO::FETCH_COLUMN); if (empty($provs)) { $where[] = '1=0'; } else { $pph = implode(',', array_fill(0, count($provs), '?')); $where[] = "$table.province IN ($pph)"; $params = array_merge($params, $provs); } } } if (!empty($filters['activity_type']) && $table === 'featured_activities') { if ($filters['activity_type'] === '__OTHER__') { $pdo = $GLOBALS['pdo'] ?? null; $known = []; if ($pdo) { try { $known = $pdo->query("SELECT name FROM activity_types WHERE name <> 'อื่นๆ'")->fetchAll(PDO::FETCH_COLUMN); } catch (Exception $e) {} } if (!empty($known)) { $kph = implode(',', array_fill(0, count($known), '?')); $where[] = "($table.activity_type NOT IN ($kph) OR $table.activity_type IS NULL OR $table.activity_type = 'อื่นๆ')"; $params = array_merge($params, $known); } else { $where[] = "($table.activity_type IS NULL OR $table.activity_type = 'อื่นๆ')"; } } else { $where[] = "$table.activity_type = ?"; $params[] = $filters['activity_type']; } } if (!empty($filters['type']) && $table === 'learning_resources') { $where[] = "$table.type = ?"; $params[] = $filters['type']; } if (!empty($filters['school_creators']) && is_array($filters['school_creators'])) { if (empty($filters['school_creators'])) { $where[] = '1=0'; } else { $sph = implode(',', array_fill(0, count($filters['school_creators']), '?')); $where[] = "$table.created_by IN ($sph)"; $params = array_merge($params, $filters['school_creators']); } } $where_sql = implode(' AND ', $where); return [$where_sql, $params]; } /** * นับและดึงรายการแบบ paginated สำหรับ activities/resources/knowledge * * @param array $opts [ * 'filters' => array, // ตัวกรอง (status/name/province/...) * 'order_by' => string, // เช่น "id DESC" * 'featured_first' => array|null, // [int $ids] — ลอย ids เหล่านี้ขึ้นบน * 'page' => int, 'per_page' => int, * 'columns' => string, // default '*' * ] * @return array [$rows, $total] */ function queryAccessiblePaginated($pdo, $user, string $table, array $opts = []): array { $filters = $opts['filters'] ?? []; $order_by = $opts['order_by'] ?? 'id DESC'; $featured_ids = $opts['featured_first'] ?? null; $page = max(1, (int)($opts['page'] ?? 1)); $per_page = (int)($opts['per_page'] ?? 20); if (!in_array($per_page, [20, 50, 100, 150], true)) $per_page = 20; $columns = $opts['columns'] ?? "$table.*"; [$where_sql, $params] = buildAccessFilter($user, $table, $filters); // COUNT $count_sql = "SELECT COUNT(*) FROM $table WHERE $where_sql"; $stmt = $pdo->prepare($count_sql); $stmt->execute($params); $total = (int)$stmt->fetchColumn(); // ลำดับ $order_sql = ''; if (!empty($featured_ids) && is_array($featured_ids)) { $ids_safe = implode(',', array_map('intval', $featured_ids)); if ($ids_safe !== '') { $order_sql = "CASE WHEN $table.id IN ($ids_safe) THEN 0 ELSE 1 END, "; } } $order_sql .= $order_by; $offset = ($page - 1) * $per_page; $sql = "SELECT $columns FROM $table WHERE $where_sql ORDER BY $order_sql LIMIT $per_page OFFSET $offset"; $stmt = $pdo->prepare($sql); $stmt->execute($params); $rows = $stmt->fetchAll(); return [$rows, $total, $page, $per_page]; } /** * ได้ user_type_id จากชื่อผู้ใช้ */ function getUserTypeId($pdo, $username) { if (!$username) return null; $stmt = $pdo->prepare("SELECT user_type_id FROM users WHERE username = ?"); $stmt->execute([$username]); $result = $stmt->fetch(); return $result ? $result['user_type_id'] : null; } /** * ตรวจสอบว่าระบบเปิดให้ user_type_id = 4 สามารถเพิ่มข้อมูลในโมดูลนี้หรือไม่ * @param PDO $pdo * @param string $module ชื่อโมดูล: resources, activities, knowledge, programs * @return bool true = เปิดให้เพิ่มได้, false = ปิดอยู่ */ function isAddAllowed($pdo, $module) { $col = 'allow_add_' . $module; try { $stmt = $pdo->query("SELECT `$col` FROM system_settings WHERE id=1"); $row = $stmt->fetch(); return ($row[$col] ?? 1) == 1; } catch (Exception $e) { return true; // ถ้ายังไม่มีคอลัมน์ ให้เปิดเป็นค่าเริ่มต้น } } ?>