fix head fa access issue
Deploy to Shared Hosting / Shared hosting deploy (push) Failing after 48s
Tests / PHPUnit (push) Successful in 1m24s

This commit is contained in:
root
2026-09-06 20:49:25 -04:00
parent 2ad7dc1170
commit 485341875a
7 changed files with 280 additions and 6 deletions
+1 -1
View File
@@ -522,7 +522,7 @@ class FilesController extends Controller
$roles[] = $activeRole;
}
foreach (['administrator', 'administrative staff', 'principal', 'teacher', 'teacher_assistant'] as $role) {
foreach (['administrator', 'administrative staff', 'principal', 'teacher', 'teacher_assistant', 'head fa', 'head of fa', 'head_of_fa', 'financial_contributor'] as $role) {
if (in_array($role, $roles, true)) {
return true;
}
@@ -2293,6 +2293,10 @@ private function getGradeLevel($grade): array
'administrative staff',
'principal',
'admin',
'head fa',
'head of fa',
'head_of_fa',
'financial_contributor',
]);
if ($userId <= 0 || (! $isStaff && $userId !== $parentId)) {
+1 -1
View File
@@ -1685,7 +1685,7 @@ class PaymentController extends ResourceController
$roles[] = $activeRole;
}
$staffRoles = ['administrator', 'administrative staff', 'principal', 'teacher', 'teacher_assistant'];
$staffRoles = ['administrator', 'administrative staff', 'principal', 'teacher', 'teacher_assistant', 'head fa', 'head of fa', 'head_of_fa', 'financial_contributor'];
foreach ($staffRoles as $role) {
if (in_array($role, $roles, true)) {
return true;
+1 -1
View File
@@ -1414,7 +1414,7 @@ class RefundController extends BaseController
$roles[] = $activeRole;
}
foreach (['administrator', 'administrative staff', 'principal', 'teacher', 'teacher_assistant'] as $role) {
foreach (['administrator', 'administrative staff', 'principal', 'teacher', 'teacher_assistant', 'head fa', 'head of fa', 'head_of_fa', 'financial_contributor'] as $role) {
if (in_array($role, $roles, true)) {
return true;
}
@@ -46,8 +46,10 @@ class GrantRequestedRolePageAccess extends Migration
);
$this->grantNavItemsToRoles(
['head of fa', 'head_of_fa', 'head of department (finance)', 'head of department finance'],
$financialNavItems
['head fa', 'head of fa', 'head_of_fa', 'head of department (finance)', 'head of department finance'],
array_merge($financialNavItems, [
['url' => 'admin/enrollment/new-students'],
])
);
$this->grantNavItemsToRoles(
@@ -80,8 +82,9 @@ class GrantRequestedRolePageAccess extends Migration
);
$this->grantPermissionsToRoles(
['head of fa', 'head_of_fa', 'head of department (finance)', 'head of department finance'],
['head fa', 'head of fa', 'head_of_fa', 'head of department (finance)', 'head of department finance'],
[
'view_new_students' => ['read' => true],
'view_invoice' => ['read' => true],
'view_payment' => ['read' => true],
'view_financial_reports' => ['create' => true, 'read' => true, 'update' => true],
@@ -0,0 +1,195 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class BackfillHeadFaNewStudentsAccess extends Migration
{
public function up(): void
{
$roleIds = $this->resolveRoleIds([
'head fa',
'head of fa',
'head_of_fa',
'head of department (finance)',
'head of department finance',
]);
if ($roleIds === []) {
return;
}
$this->grantPermission($roleIds, 'view_new_students', ['read' => true]);
$this->grantNavUrl($roleIds, 'admin/enrollment/new-students');
cache()->clean();
}
public function down(): void
{
}
/**
* @param list<string> $roleKeys
* @return list<int>
*/
private function resolveRoleIds(array $roleKeys): array
{
if (! $this->db->tableExists('roles')) {
return [];
}
$normalized = array_values(array_unique(array_map('strtolower', $roleKeys)));
$builder = $this->db->table('roles')->select('id');
$builder->groupStart()->whereIn('LOWER(name)', $normalized);
if ($this->db->fieldExists('slug', 'roles')) {
$builder->orWhereIn('LOWER(slug)', $normalized);
}
$rows = $builder->groupEnd()->get()->getResultArray();
return array_values(array_unique(array_map(static fn (array $row): int => (int) $row['id'], $rows)));
}
/**
* @param list<int> $roleIds
* @param array{create?: bool, read?: bool, update?: bool, delete?: bool} $flags
*/
private function grantPermission(array $roleIds, string $permissionName, array $flags): void
{
if (! $this->db->tableExists('permissions') || ! $this->db->tableExists('role_permissions')) {
return;
}
$permissionId = $this->resolvePermissionId($permissionName);
$now = date('Y-m-d H:i:s');
foreach ($roleIds as $roleId) {
$existing = $this->db->table('role_permissions')
->where('role_id', $roleId)
->where('permission_id', $permissionId)
->get()
->getRowArray();
$grant = [
'can_create' => ! empty($flags['create']) ? 1 : 0,
'can_read' => ! empty($flags['read']) ? 1 : 0,
'can_update' => ! empty($flags['update']) ? 1 : 0,
'can_delete' => ! empty($flags['delete']) ? 1 : 0,
'updated_at' => $now,
];
if ($existing === null) {
$grant['role_id'] = $roleId;
$grant['permission_id'] = $permissionId;
$grant['created_at'] = $now;
if ($this->db->fieldExists('can_manage', 'role_permissions')) {
$grant['can_manage'] = 0;
}
$this->db->table('role_permissions')->insert($grant);
continue;
}
$this->db->table('role_permissions')
->where('id', (int) $existing['id'])
->update([
'can_create' => max((int) ($existing['can_create'] ?? 0), $grant['can_create']),
'can_read' => max((int) ($existing['can_read'] ?? 0), $grant['can_read']),
'can_update' => max((int) ($existing['can_update'] ?? 0), $grant['can_update']),
'can_delete' => max((int) ($existing['can_delete'] ?? 0), $grant['can_delete']),
'updated_at' => $now,
]);
}
}
private function resolvePermissionId(string $permissionName): int
{
$permission = $this->db->table('permissions')
->select('id')
->where('LOWER(name)', strtolower($permissionName))
->get()
->getRowArray();
if ($permission !== null) {
return (int) $permission['id'];
}
$now = date('Y-m-d H:i:s');
$insert = [
'name' => $permissionName,
'created_at' => $now,
'updated_at' => $now,
];
if ($this->db->fieldExists('description', 'permissions')) {
$insert['description'] = 'Seeded route permission.';
}
$this->db->table('permissions')->insert($insert);
return (int) $this->db->insertID();
}
/**
* @param list<int> $roleIds
*/
private function grantNavUrl(array $roleIds, string $url): void
{
if (
! $this->db->tableExists('nav_items')
|| ! $this->db->tableExists('role_nav_items')
|| ! $this->db->fieldExists('role_id', 'role_nav_items')
) {
return;
}
$navIds = $this->resolveNavItemIdsByUrl($url);
if ($navIds === []) {
return;
}
$now = date('Y-m-d H:i:s');
foreach ($roleIds as $roleId) {
foreach ($navIds as $navId) {
$exists = $this->db->table('role_nav_items')
->where('role_id', $roleId)
->where('nav_item_id', $navId)
->countAllResults() > 0;
if (! $exists) {
$this->db->table('role_nav_items')->insert([
'role_id' => $roleId,
'nav_item_id' => $navId,
'created_at' => $now,
'updated_at' => $now,
]);
}
}
}
}
/**
* @return list<int>
*/
private function resolveNavItemIdsByUrl(string $url): array
{
$targetUrl = $this->normalizePath($url);
$rows = $this->db->table('nav_items')
->select('id, url')
->where('url IS NOT NULL', null, false)
->get()
->getResultArray();
$ids = [];
foreach ($rows as $row) {
if ($this->normalizePath((string) ($row['url'] ?? '')) === $targetUrl) {
$ids[] = (int) $row['id'];
}
}
return array_values(array_unique(array_filter($ids)));
}
private function normalizePath(string $path): string
{
return trim(preg_replace('#/+#', '/', $path), '/');
}
}
+72
View File
@@ -85,6 +85,14 @@ class AuthFilter implements FilterInterface
return;
}
if ($this->isAllowedFamilyCardRequest($request, $roleIds)) {
return;
}
if ($this->isAllowedPrintRequestSupportRequest($request, $roleIds)) {
return;
}
return $this->deny($request, "You don't have permission to use this feature.");
}
@@ -322,6 +330,70 @@ class AuthFilter implements FilterInterface
return false;
}
private function isAllowedFamilyCardRequest(RequestInterface $request, array $roleIds): bool
{
if ($this->normalizePath($request->getUri()->getPath()) !== 'family/card') {
return false;
}
foreach (['view_new_students', 'view_financial_reports'] as $permissionName) {
if ($this->userHasNamedPermission($roleIds, $permissionName, 'read')) {
return true;
}
}
return false;
}
private function isAllowedPrintRequestSupportRequest(RequestInterface $request, array $roleIds): bool
{
$path = $this->normalizePath($request->getUri()->getPath());
$printRequestPaths = [
'print-requests/update/',
'print-requests/delete/',
'print-requests/file/',
'uploads/print_requests/',
];
foreach ($printRequestPaths as $prefix) {
if (str_starts_with($path, $prefix)) {
return $this->hasGrantedNavUrl($roleIds, 'admin/print-requests');
}
}
return false;
}
private function hasGrantedNavUrl(array $roleIds, string $url): bool
{
if (
empty($roleIds)
|| ! $this->db->tableExists('role_nav_items')
|| ! $this->db->tableExists('nav_items')
|| ! $this->db->fieldExists('role_id', 'role_nav_items')
) {
return false;
}
$targetUrl = $this->normalizePath($url);
$rows = $this->db->table('role_nav_items rni')
->select('ni.url')
->join('nav_items ni', 'ni.id = rni.nav_item_id')
->whereIn('rni.role_id', $roleIds)
->where('ni.url IS NOT NULL', null, false)
->where('ni.is_enabled', 1)
->get()
->getResultArray();
foreach ($rows as $row) {
if ($this->normalizePath((string) ($row['url'] ?? '')) === $targetUrl) {
return true;
}
}
return false;
}
private function normalizePath(string $path): string
{
$path = trim(preg_replace('#/+#', '/', $path), '/');