recreate project
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
// app/Libraries/AttendanceAutoPublish.php
|
||||
namespace App\Libraries;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use DateTimeZone;
|
||||
|
||||
class AttendanceAutoPublish
|
||||
{
|
||||
public static function tz(string $tzFromCfg = null): DateTimeZone
|
||||
{
|
||||
$tz = $tzFromCfg ?: (class_exists(\Config\School::class) ? (config('School')->attendance['timezone'] ?? null) : null);
|
||||
return new DateTimeZone($tz ?: date_default_timezone_get());
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given attendance day (Y-m-d), return the timestamp (Y-m-d H:i:s)
|
||||
* of the end of the SECOND Sunday AFTER that day, in school TZ.
|
||||
* This is when it should hard-lock automatically.
|
||||
*/
|
||||
public static function secondSundayAfterEndOfDay(string $ymd, string $tz = null): string
|
||||
{
|
||||
$zone = self::tz($tz);
|
||||
$day = new DateTimeImmutable($ymd . ' 00:00:00', $zone);
|
||||
|
||||
// w: 0=Sun, 1=Mon, ... 6=Sat
|
||||
$w = (int) $day->format('w');
|
||||
$daysToNextSunday = (7 - $w) % 7; // 0 if Sunday -> next Sunday is +7
|
||||
if ($daysToNextSunday === 0) $daysToNextSunday = 7;
|
||||
|
||||
$firstSunday = $day->modify("+{$daysToNextSunday} days");
|
||||
$secondSunday = $firstSunday->modify('+7 days');
|
||||
|
||||
return $secondSunday->setTime(23, 59, 59)->format('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
/**
|
||||
* Given "now", compute the cutoff *date* (Y-m-d) that should be published
|
||||
* RIGHT NOW under the "lock the 2nd Sunday backward" rule.
|
||||
* i.e., Sunday of the current week minus 14 days.
|
||||
*/
|
||||
public static function secondSundayBackwardDate(\DateTimeImmutable $now, string $tz = null): string
|
||||
{
|
||||
$zone = self::tz($tz);
|
||||
$n = $now->setTimezone($zone);
|
||||
$w = (int) $n->format('w'); // 0=Sun
|
||||
$thisSunday = $n->setTime(0,0,0)->modify("-{$w} days"); // start of current Sun
|
||||
$twoBack = $thisSunday->modify('-14 days'); // two Sundays ago
|
||||
return $twoBack->format('Y-m-d');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
// app/Libraries/AttendancePolicy.php
|
||||
namespace App\Libraries;
|
||||
|
||||
use App\Models\RoleModel;
|
||||
|
||||
class AttendancePolicy
|
||||
{
|
||||
/**
|
||||
* Return a normalized set of tokens (slug and name variants) for roles
|
||||
* considered "admin-like" according to the roles table.
|
||||
*
|
||||
* Rule: any active role whose slug is NOT in the excluded non-admin set
|
||||
* (guest, parent, student, teacher, teacher_assistant, ta) is treated
|
||||
* as admin-like. Fallback to config('Roles')->roles if the table is
|
||||
* unavailable.
|
||||
*/
|
||||
private static function adminRoleTokens(): array
|
||||
{
|
||||
static $cache = null;
|
||||
if ($cache !== null) return $cache;
|
||||
|
||||
$normalize = static function (string $v): string {
|
||||
$v = strtolower(trim($v));
|
||||
return str_replace([' ', '-'], '_', $v);
|
||||
};
|
||||
|
||||
$tokens = [];
|
||||
try {
|
||||
$model = new RoleModel();
|
||||
$rows = $model
|
||||
->select('name, slug')
|
||||
->where('is_active', 1)
|
||||
->findAll();
|
||||
|
||||
// Slugs that are explicitly NOT admin-like
|
||||
$excluded = ['guest','parent','student','teacher','teacher_assistant','ta'];
|
||||
$excluded = array_map($normalize, $excluded);
|
||||
|
||||
foreach ($rows as $r) {
|
||||
$name = $normalize((string)($r['name'] ?? ''));
|
||||
$slug = $normalize((string)($r['slug'] ?? $name));
|
||||
if ($name === '' && $slug === '') continue;
|
||||
if (in_array($slug, $excluded, true) || in_array($name, $excluded, true)) {
|
||||
continue;
|
||||
}
|
||||
if ($name !== '') $tokens[$name] = true;
|
||||
if ($slug !== '') $tokens[$slug] = true;
|
||||
}
|
||||
|
||||
// Safety net: ensure common admin labels are present if defined
|
||||
foreach (['administrator','admin','principal','vice_principal','vice-principal'] as $s) {
|
||||
$tokens[$normalize($s)] = true;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// Fallback: use Roles config (if provided)
|
||||
try {
|
||||
$cfg = config('Roles');
|
||||
if ($cfg && is_array($cfg->roles ?? null)) {
|
||||
foreach ($cfg->roles as $r) {
|
||||
$tokens[$normalize((string)$r)] = true;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e2) {
|
||||
// minimal fallback
|
||||
foreach (['administrator','admin','principal','vice_principal'] as $r) {
|
||||
$tokens[$normalize($r)] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $cache = array_keys($tokens);
|
||||
}
|
||||
|
||||
public static function canManageEarlyDismissals(array $userRoles): bool
|
||||
{
|
||||
$normalizedRoles = array_map(static function ($r) {
|
||||
$r = strtolower((string) $r);
|
||||
$r = str_replace([' ', '-'], '_', $r);
|
||||
return $r;
|
||||
}, $userRoles);
|
||||
|
||||
$adminTokens = self::adminRoleTokens();
|
||||
$isAdminLike = count(array_intersect($normalizedRoles, $adminTokens)) > 0;
|
||||
|
||||
$isTeacher = in_array('teacher', $normalizedRoles, true) || in_array('teacher_assistant', $normalizedRoles, true);
|
||||
|
||||
return $isAdminLike || $isTeacher;
|
||||
}
|
||||
|
||||
public static function canEditDay(array $day, array $user): bool
|
||||
{
|
||||
// Normalize roles (e.g., "Vice Principal" -> "vice_principal")
|
||||
$roles = array_map(static function ($r) {
|
||||
$r = strtolower((string) $r);
|
||||
$r = str_replace([' ', '-'], '_', $r);
|
||||
return $r;
|
||||
}, (array) ($user['roles'] ?? []));
|
||||
|
||||
// Helper: permissions may be provided either as an associative map
|
||||
// ['attendance.admin_edit' => true] or as a flat list ['attendance.admin_edit']
|
||||
$perms = $user['permissions'] ?? [];
|
||||
$hasPerm = static function (string $key) use ($perms): bool {
|
||||
if (is_array($perms)) {
|
||||
// Associative-style map
|
||||
if (array_key_exists($key, $perms)) {
|
||||
return (bool) $perms[$key];
|
||||
}
|
||||
// Flat list of strings
|
||||
return in_array($key, $perms, true);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Determine admin-like status from roles table
|
||||
$adminTokens = self::adminRoleTokens();
|
||||
$isAdminLike = count(array_intersect($roles, $adminTokens)) > 0;
|
||||
|
||||
// Allow via explicit permissions as well
|
||||
$canAdminEditSubmitted = $hasPerm('attendance.admin_edit') || $hasPerm('attendance_admin_edit');
|
||||
$canOverridePublished = $hasPerm('attendance.override_publish') || $hasPerm('attendance_override_publish');
|
||||
|
||||
switch ($day['status']) {
|
||||
case 'draft':
|
||||
// Open for teachers and admins
|
||||
return true;
|
||||
case 'submitted':
|
||||
// Teachers locked; allow admin-like users, or holders of admin-edit permission
|
||||
return $isAdminLike || $canAdminEditSubmitted;
|
||||
case 'published':
|
||||
// Only allow if explicit override permission is granted
|
||||
return $canOverridePublished;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace App\Libraries;
|
||||
|
||||
/**
|
||||
* Generates and validates signed tokens for staff time-off approval links.
|
||||
*/
|
||||
class StaffTimeOffLinkService
|
||||
{
|
||||
private const TOKEN_CONTEXT = 'timeoff_notify';
|
||||
private const DEFAULT_TTL = 1209600; // 14 days
|
||||
|
||||
private string $secret;
|
||||
private int $ttl;
|
||||
|
||||
public function __construct(?string $secret = null, ?int $ttlSeconds = null)
|
||||
{
|
||||
helper('jwt');
|
||||
|
||||
$this->secret = $secret ?: (string)env('JWT_SECRET', 'change-me-in-env');
|
||||
$this->ttl = ($ttlSeconds !== null && $ttlSeconds > 0) ? $ttlSeconds : self::DEFAULT_TTL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a signed token embedding request metadata.
|
||||
*
|
||||
* @param array $payload
|
||||
*/
|
||||
public function createToken(array $payload): string
|
||||
{
|
||||
$now = time();
|
||||
$data = array_merge($payload, [
|
||||
'iat' => $now,
|
||||
'exp' => $now + $this->ttl,
|
||||
'ctx' => self::TOKEN_CONTEXT,
|
||||
]);
|
||||
|
||||
return jwt_encode($data, $this->secret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a token and return its payload if valid.
|
||||
*/
|
||||
public function parseToken(?string $token): ?array
|
||||
{
|
||||
if (!is_string($token) || $token === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$payload = jwt_decode($token, $this->secret);
|
||||
if (!is_array($payload)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (($payload['ctx'] ?? null) !== self::TOKEN_CONTEXT) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$exp = (int)($payload['exp'] ?? 0);
|
||||
if ($exp > 0 && $exp < time()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user