Compare commits

..

28 Commits

Author SHA1 Message Date
root 4e2d12e524 fix events logic 2026-04-11 20:08:47 -04:00
root 7bc999643a exam draft fix 2026-04-11 15:33:12 -04:00
root 89e95b7851 add command to send teacher exam draft notification 2026-04-10 16:31:32 -04:00
root 2543df3d33 exam draft notification and submission 2026-04-09 01:50:24 -04:00
root 112d073235 fic exam draft submission 2026-04-08 19:45:47 -04:00
root 2611730ec6 update homework tracking 2026-04-07 18:42:39 -04:00
root 05dad52e10 fix score submission, and class progress 2026-04-04 17:44:40 -04:00
Administrator d158650be9 Merge branch 'develop_fix_expenses' into 'develop'
Fixed the date shift by making date-only strings stay in the user timezone...

See merge request root/alrahma_sunday_school!5
2026-04-02 04:05:31 +00:00
root 61facee902 fix the exam draft 2026-04-02 00:03:59 -04:00
root ed67836701 fix class progress unit numbers calculation 2026-03-31 00:16:55 -04:00
root d2abbc1458 fix duplicate restored student 2026-03-30 23:50:20 -04:00
root b52475ff0b fix teacher submissions 2026-03-30 18:46:04 -04:00
root b2026812d5 AVP-87 Multiple Class updated not showing for multiple students in parent portal 2026-03-29 14:03:50 -04:00
root 58445b2a48 fix all issues 2026-03-24 01:02:36 -04:00
root 0f8a1fa0b1 Fixed the date shift by making date-only strings stay in the user timezone instead of being converted from UTC, which caused the one-day rollback. 2026-03-03 16:46:36 -05:00
Administrator fee07bcceb Merge branch 'develop_fix_bugs' into 'develop'
remove zip file

See merge request root/alrahma_sunday_school!4
2026-03-03 16:24:23 +00:00
root 70a6e2c104 remove zip file 2026-03-02 15:18:30 -05:00
Administrator 11c93d3e82 Merge branch 'develop_fix_bugs' into 'develop'
update financial controller and fix curriculum subjects

See merge request root/alrahma_sunday_school!3
2026-03-02 05:06:01 +00:00
root 7c5028a76d update financial controller and fix curriculum subjects 2026-03-02 00:04:38 -05:00
Administrator aa1260afd6 Merge branch 'develop_fix_bugs' into 'develop'
Develop fix bugs

See merge request root/alrahma_sunday_school!2
2026-03-01 22:25:37 +00:00
root 35b9cba882 AVP-85 Active link when clicking of Parent's email address 2026-03-01 17:24:20 -05:00
root a18198d547 AVP-86 Below 60 score 2026-03-01 16:56:45 -05:00
root 97c3b03c39 Merge branch 'develop' of https://gitlab.rentaldrivego.ma/root/alrahma_sunday_school into develop 2026-03-01 16:35:13 -05:00
root 7770b60658 Add full CodeIgniter 4 + Composer + npm + Docker .gitignore 2026-03-01 16:29:16 -05:00
Administrator 4f5925e10a Merge branch 'feature_updatecode' into 'develop'
Feature updatecode

See merge request root/alrahma_sunday_school!1
2026-02-27 06:27:32 +00:00
root 3cc5546733 AVP-78 Feature to check if parents looked at report card 2026-02-27 01:21:39 -05:00
root fe7b99581d update code 2026-02-26 23:50:24 -05:00
root a6880ea14f update code 2026-02-26 23:27:50 -05:00
7278 changed files with 647 additions and 1002620 deletions
Vendored
BIN
View File
Binary file not shown.
+9 -5
View File
@@ -26,7 +26,7 @@ SMTP_HOST = smtp.gmail.com
SMTP_USER = alrahma.sunday.school@gmail.com
SMTP_PASS = "psnp emdq dykw ypul"
SMTP_PORT = 587
SMTP_ENCRYPTION = tls # was SSL; set to ssl to match 587
SMTP_ENCRYPTION = tls # was ssl; use tls for port 587
# Reply-To for all mail profiles
MAIL_DEFAULT_REPLY_TO=alrahma.isgl@gmail.com
@@ -35,7 +35,6 @@ MAIL_COMMUNICATION_REPLY_TO=alrahma.isgl@gmail.com
MAIL_COMMUNICATION_REPLY_TO_NAME="School Communications"
MAIL_PAYMENT_REPLY_TO=alrahma.isgl@gmail.com
MAIL_PAYMENT_REPLY_TO_NAME="School Payments"
MAIL_DEFAULT_REPLY_TO="alrahma.isgl@gmail.com"
# Optional sender directory you already use elsewhere
MAIL_SENDERS="{\"general\":{\"email\":\"alrahma.isgl@gmail.com\",\"name\":\"Al Rahma No-Reply\"},\
@@ -44,8 +43,11 @@ MAIL_SENDERS="{\"general\":{\"email\":\"alrahma.isgl@gmail.com\",\"name\":\"Al R
\"finance\":{\"email\":\"alrahma.isgl@gmail.com\",\"name\":\"Al Rahma Finance Office\"}}"
# Principal notification recipient for TimeOff requests (comma-separated allowed)
PRINCIPAL_EMAIL="principal@alrahmaisgl.org"
PRINCIPAL_EMAIL="melabidi@alrahmaisgl.org"
MAIL_PARENT_REPORT_TO="alrahma.isgl@gmail.com"
MAIL_FROM_ADDRESS = "alrahma.sunday.school@gmail.com"
session.expiration = 43200
#--------------------------------------------------------------------
# APP
#--------------------------------------------------------------------
@@ -61,7 +63,7 @@ PRINCIPAL_EMAIL="principal@alrahmaisgl.org"
database.default.hostname = 127.0.0.1
database.default.database = school
database.default.username = root
database.default.password = root
database.default.password = rootpassword
database.default.DBDriver = MySQLi
database.default.DBPrefix =
database.default.port = 3306
@@ -155,7 +157,9 @@ database.default.port = 3306
# LOGGER
#--------------------------------------------------------------------
# logger.threshold = 4
logger.threshold = 4
MAIL_DEBUG = true
MAIL_TIMEOUT = 15
#--------------------------------------------------------------------
# CURLRequest
+39
View File
@@ -0,0 +1,39 @@
.env
.env.*
!.env.example
/vendor/
/node_modules/
/writable/cache/*
/writable/debugbar/*
/writable/logs/*
/writable/session/*
/writable/uploads/*
/writable/tmp/*
/writable/*.log
!/writable/.gitkeep
!/writable/cache/.gitkeep
!/writable/debugbar/.gitkeep
!/writable/logs/.gitkeep
!/writable/session/.gitkeep
!/writable/uploads/.gitkeep
!/writable/tmp/.gitkeep
/public/build/
/public/hot/
docker-compose.override.yml
docker-compose.local.yml
*.log
.DS_Store
Thumbs.db
.idea/
.vscode/
/coverage/
/build/
/phpunit.xml.cache
/.phpunit.result.cache
BIN
View File
Binary file not shown.
View File
View File
View File
View File
Executable → Regular
View File
View File
View File
View File
View File
View File
View File
View File
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
+3 -9
View File
@@ -393,8 +393,8 @@ $routes->get('print-requests/file/(:segment)', 'PrintRequests::serveFile/$1', ['
$routes->get('print-requests/file/(:segment)/(:alpha)', 'PrintRequests::serveFile/$1/$2', ['filter' => 'auth:teacher,teacher_assistant,admin']);
$routes->get('uploads/print_requests/(:segment)', 'PrintRequests::serveFile/$1', ['filter' => 'auth:teacher,teacher_assistant,admin']);
$routes->get('exam-drafts/files/teacher/(:segment)', 'View\FilesController::examDraftTeacher/$1', ['filter' => 'auth:teacher,teacher_assistant,admin,administrator,principal']);
$routes->get('exam-drafts/files/final/(:segment)', 'View\FilesController::examDraftFinal/$1', ['filter' => 'auth:teacher,teacher_assistant,admin,administrator,principal']);
$routes->get('exam-drafts/files/teacher/(:segment)', 'View\FilesController::examDraftTeacher/$1', ['filter' => 'auth:teacher,teacher_assistant,admin']);
$routes->get('exam-drafts/files/final/(:segment)', 'View\FilesController::examDraftFinal/$1', ['filter' => 'auth:teacher,teacher_assistant,admin']);
@@ -535,12 +535,9 @@ $routes->post('payment/event_charges', 'View\EventController::eventUpdate');
// Parent event participation
$routes->get('administrator/event-charges', 'View\EventController::eventShow');
$routes->post('administrator/event-charges/remove/(:num)', 'View\EventController::removeCharge/$1');
$routes->post('administrator/event-charges/payment/(:num)', 'View\EventController::toggleEventPayment/$1');
$routes->post('administrator/event-charges/waiver/(:num)', 'View\EventController::toggleWaiverStatus/$1');
$routes->get('administrator/get-students-with-charges', 'View\EventController::getStudentsWithCharges');
$routes->get('parent/events', 'View\ParentController::parentEventPage', ['filter' => 'auth:parent']);
$routes->get('parent/events', 'View\ParentController::parentEventPage');
// parent event participation page
$routes->post('parent/updateParticipation', 'View\ParentController::updateParticipation'); // handle parent participation updates
@@ -745,9 +742,6 @@ $routes->post('/administrator/teacher-submissions/notify', 'View\AdministratorCo
$routes->get('/administrator/exam-drafts', 'View\ExamDraftController::adminIndex', ['filter' => 'auth:admin']);
$routes->post('/administrator/exam-drafts/review', 'View\ExamDraftController::adminReview', ['filter' => 'auth:admin']);
$routes->post('/administrator/exam-drafts/upload-legacy', 'View\ExamDraftController::adminUploadLegacy', ['filter' => 'auth:admin']);
$routes->get('/principal/exam-drafts', 'View\ExamDraftController::principalIndex', ['filter' => 'auth:admin']);
$routes->post('/principal/exam-drafts/review', 'View\ExamDraftController::principalReview', ['filter' => 'auth:admin']);
$routes->post('/principal/exam-drafts/upload-legacy', 'View\ExamDraftController::principalUploadLegacy', ['filter' => 'auth:admin']);
/*
* --------------------------------------------------------------------
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
View File
View File
View File
+5 -74
View File
@@ -56,32 +56,13 @@ class AuthController extends Controller
public function loginMask()
{
$redirectTo = $this->sanitizeRedirectTarget((string) ($this->request->getGet('redirect_to') ?? ''));
if (session()->get('is_logged_in')) {
if ($redirectTo !== null) {
return redirect()->to($redirectTo);
}
$roles = session()->get('roles') ?? [];
if (empty($roles) && session()->get('role')) {
$roles = [session()->get('role')];
}
if (!empty($roles)) {
return $this->redirectToDashboard($roles);
}
}
return view('user/login', [
'redirect_to' => $redirectTo ?? '',
]);
// Serve the login view directly here
return view('user/login'); // Adjust the view path as needed
}
public function login()
{
log_message('info', 'Processing login form submission.');
$redirectTo = $this->sanitizeRedirectTarget((string) ($this->request->getPost('redirect_to') ?? $this->request->getGet('redirect_to') ?? ''));
// Step 1: Get email, password, and IP from the request
$email = $this->request->getPost('email');
@@ -117,7 +98,7 @@ class AuthController extends Controller
$this->logLoginAttempt($user['id'], $user['email'], $ip, $this->request->getUserAgent());
// ✅ Step 7: Call loginUser() to set session and redirect
return $this->loginUser($user, $redirectTo);
return $this->loginUser($user);
} else {
// Step 8: Password mismatch — log failed attempt
$this->handleFailedLogin($user['id'], $user['email'], $ip);
@@ -510,7 +491,7 @@ class AuthController extends Controller
return true;
}
private function loginUser($user, ?string $redirectTo = null)
private function loginUser($user)
{
$userRoleModel = new UserRoleModel();
$roles = $userRoleModel->select('roles.name')
@@ -544,17 +525,9 @@ class AuthController extends Controller
if (count($roleNames) === 1) {
// One role → set and redirect directly
session()->set('role', $roleNames[0]);
if ($redirectTo !== null) {
return redirect()->to($redirectTo);
}
return $this->redirectToDashboard([$roleNames[0]]);
} else {
// Multiple roles → redirect to role selection view
if ($redirectTo !== null) {
session()->set('post_login_redirect', $redirectTo);
} else {
session()->remove('post_login_redirect');
}
return redirect()->to('/select-role');
}
@@ -619,17 +592,12 @@ private function redirectToDashboard(array $roles)
{
$selectedRole = $this->request->getPost('selected_role');
$availableRoles = session()->get('roles');
$redirectTo = $this->sanitizeRedirectTarget((string) ($this->request->getPost('redirect_to') ?? session()->get('post_login_redirect') ?? ''));
if (!$selectedRole || !in_array($selectedRole, $availableRoles)) {
return redirect()->to('/select-role')->with('error', 'Invalid role selected.');
}
session()->set('role', $selectedRole);
session()->remove('post_login_redirect');
if ($redirectTo !== null) {
return redirect()->to($redirectTo);
}
return $this->redirectToDashboard([$selectedRole]);
}
@@ -641,44 +609,7 @@ private function redirectToDashboard(array $roles)
return redirect()->to('/login')->with('error', 'No roles available.');
}
return view('auth/select_role', [
'roles' => $roles,
'redirect_to' => (string) (session()->get('post_login_redirect') ?? ''),
]);
}
private function sanitizeRedirectTarget(string $redirectTo): ?string
{
$redirectTo = trim($redirectTo);
if ($redirectTo === '') {
return null;
}
if (preg_match('#^https?://#i', $redirectTo)) {
$appHost = (string) parse_url(base_url('/'), PHP_URL_HOST);
$targetHost = (string) parse_url($redirectTo, PHP_URL_HOST);
$targetPath = (string) parse_url($redirectTo, PHP_URL_PATH);
$targetQuery = (string) parse_url($redirectTo, PHP_URL_QUERY);
if ($appHost === '' || $targetHost === '' || strcasecmp($appHost, $targetHost) !== 0) {
return null;
}
$redirectTo = $targetPath !== '' ? $targetPath : '/';
if ($targetQuery !== '') {
$redirectTo .= '?' . $targetQuery;
}
}
if (!str_starts_with($redirectTo, '/')) {
$redirectTo = '/' . ltrim($redirectTo, '/');
}
if (str_starts_with($redirectTo, '//')) {
return null;
}
return $redirectTo;
return view('auth/select_role', ['roles' => $roles]);
}
}
View File
View File
View File
View File
Executable → Regular
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
+2 -54
View File
@@ -2003,58 +2003,6 @@ class AttendanceTrackingController extends BaseController
return [$subject, $body];
}
private function buildFallbackTemplate(string $code, string $variant, array $context): array
{
$studentName = (string) ($context['{{student_name}}'] ?? 'the student');
$incident = (string) ($context['{{incident_date}}'] ?? date('Y-m-d'));
$parentName = (string) ($context['{{parent_name}}'] ?? 'Parent/Guardian');
$phone = (string) ($context['{{school_phone}}'] ?? 'the school office');
$voicemail = (string) ($context['{{voicemail_phone}}'] ?? 'your voicemail');
$subject = match (true) {
str_starts_with($code, 'ABS_1') => 'Attendance Notice: Unreported Absence',
str_starts_with($code, 'ABS_2') => 'Attendance Follow-Up: Two Consecutive Absences',
str_starts_with($code, 'ABS_3') => 'Attendance Follow-Up: Three Absences',
str_starts_with($code, 'LATE_2') => 'Attendance Notice: Repeated Lateness',
str_starts_with($code, 'LATE_3'),
str_starts_with($code, 'LATE_4'),
str_starts_with($code, 'MIX') => 'Attendance Follow-Up: Repeated Lateness and Absence',
default => 'Attendance Notice',
};
$intro = "<p>Insha Allah this email finds you well";
if ($variant === 'answered') {
$intro .= ", <strong>{$parentName}</strong>. Jazakum Allahu khayran for taking the time to speak with us today.</p>";
} elseif ($variant === 'no_answer') {
$intro .= ".</p><p>We tried to reach you but could not connect and left a voice message at <strong>{$voicemail}</strong>.</p>";
} else {
$intro .= ".</p>";
}
$details = match (true) {
str_starts_with($code, 'ABS_1')
=> "<p>This is to let you know that <strong>{$studentName}</strong> was absent on <strong>{$incident}</strong> without prior notice.</p>",
str_starts_with($code, 'ABS_2')
=> "<p>This is a reminder that <strong>{$studentName}</strong> has had repeated absences, most recently on <strong>{$incident}</strong>, without prior notice.</p>",
str_starts_with($code, 'ABS_3')
=> "<p>This is a reminder that <strong>{$studentName}</strong> has been absent three times, most recently on <strong>{$incident}</strong>, without prior notice.</p>",
str_starts_with($code, 'LATE_2')
=> "<p>This is a reminder that <strong>{$studentName}</strong> has been late multiple times, most recently on <strong>{$incident}</strong>.</p>",
str_starts_with($code, 'LATE_3'),
str_starts_with($code, 'LATE_4')
=> "<p>This is a follow-up that <strong>{$studentName}</strong> has had repeated lateness concerns, most recently on <strong>{$incident}</strong>.</p>",
str_starts_with($code, 'MIX')
=> "<p>This is a follow-up that <strong>{$studentName}</strong> has had a mix of repeated lateness and absence concerns, most recently on <strong>{$incident}</strong>.</p>",
default
=> "<p>We'd like to inform you about <strong>{$studentName}</strong>'s recent attendance concern dated <strong>{$incident}</strong>.</p>",
};
$closing = "<p>If your child will be absent or late due to illness or family commitments, please let us know ahead of time.</p>"
. "<p>You can email/call/text us at <strong>{$phone}</strong>.</p>";
return [$subject, $intro . $details . $closing];
}
public function compose()
{
@@ -2085,8 +2033,8 @@ class AttendanceTrackingController extends BaseController
$rendered = $this->renderTemplate($code, $variant, $ctx);
if (!$rendered) {
log_message('warning', 'Attendance email template missing; using fallback compose body. code=' . $code . ' variant=' . $variant);
$rendered = $this->buildFallbackTemplate($code, $variant, $ctx);
return redirect()->to(site_url('attendance/violations'))
->with('error', 'Template not found for ' . $code . ' (' . $variant . ').');
}
[$subject, $body] = $rendered;
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
+2 -5
View File
@@ -750,9 +750,7 @@ class DiscountController extends BaseController
}
} catch (\Throwable $e) {}
$discountableTotal = $tuitionSubtotal + $additionalSubtotal;
$nonDiscountableTotal = $eventSubtotal;
$newTotal = round($discountableTotal + $nonDiscountableTotal, 2);
$newTotal = round($tuitionSubtotal + $eventSubtotal + $additionalSubtotal, 2);
// ---- Payments / Discounts / Refunds ----
$db = $this->db;
@@ -796,8 +794,7 @@ class DiscountController extends BaseController
->get()->getRowArray();
$totalRefundPaid = (float)($refundRow['total_refund_paid'] ?? 0);
$appliedDiscount = min($totalDisc, $discountableTotal);
$newBalance = max(0.0, $newTotal - $appliedDiscount - $totalPaid - $totalRefundPaid);
$newBalance = max(0.0, $newTotal - $totalDisc - $totalPaid - $totalRefundPaid);
$newStatus = ($newBalance <= 0.00001) ? 'Paid' : (($totalPaid > 0) ? 'Partially Paid' : 'Unpaid');
$updateData = [
View File

Some files were not shown because too many files have changed in this diff Show More