diff --git a/.env.example b/.env.example deleted file mode 100644 index f67a2fc..0000000 --- a/.env.example +++ /dev/null @@ -1,34 +0,0 @@ -# Application -APP_ENV=production -APP_URL=https://yourdomain.example.com - -# Application Database (read/write) -DB_HOST=127.0.0.1 -DB_NAME=gbu_reg -DB_USER=root -DB_PASS='' - -# Accounts Department Database (read-only) -ACCOUNTS_DB_HOST=127.0.0.1 -ACCOUNTS_DB_NAME=accounts_dept -ACCOUNTS_DB_USER=accounts_readonly -ACCOUNTS_DB_PASS=change_me - -# SMS Gateway -SMS_API_KEY=your_sms_api_key_here - -# Razorpay Payment Gateway -RAZORPAY_KEY_ID=rzp_live_xxxxxxxxxxxx -RAZORPAY_KEY_SECRET=your_razorpay_secret_here - -# SMS Gateway (MSG91) -SMS_SENDER_ID=SEMREG -SMS_DOMAIN=api.msg91.com - -# SMTP (email fallback) -SMTP_HOST=smtp.example.com -SMTP_USER=your_smtp_user -SMTP_PASS=your_smtp_password -SMTP_PORT=587 -MAIL_FROM_ADDRESS=noreply@gbu.ac.in -MAIL_FROM_NAME=Semester Online diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 2f4a77c..0000000 --- a/.gitignore +++ /dev/null @@ -1,72 +0,0 @@ -# Composer dependencies -/vendor/ -composer.phar - -# Environment files -.env -.env.local -.env.*.local - -# IDE and editor files -.vscode/ -.idea/ -*.sublime-project -*.sublime-workspace -.DS_Store -Thumbs.db - -# Log files -*.log -/logs/ -*.log.* - -# Uploaded files and storage -/storage/uploads/* -!/storage/uploads/.gitkeep - -# Cache and temporary files -/cache/ -/tmp/ -*.cache -*.tmp - -# Session files -/sessions/ - -# Database files (if using SQLite for testing) -*.sqlite -*.sqlite3 -*.db - -# PHP specific -*.swp -*~ -.phpunit.result.cache -.phpunit.cache - -# Build artifacts -/build/ -/dist/ - -# OS generated files -.DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Trashes -ehthumbs.db -Desktop.ini - -# Backup files -*.bak -*.backup -*.old - -# Node modules (if using any frontend build tools) -node_modules/ -npm-debug.log -yarn-error.log - -# Test coverage -/coverage/ -/phpunit.xml diff --git a/.kiro/specs/semester-online-registration/.config.kiro b/.kiro/specs/semester-online-registration/.config.kiro deleted file mode 100644 index a56b971..0000000 --- a/.kiro/specs/semester-online-registration/.config.kiro +++ /dev/null @@ -1 +0,0 @@ -{"specId": "8f182581-b6a6-42e1-b280-df9fe677caf7", "workflowType": "design-first", "specType": "feature"} diff --git a/.kiro/specs/semester-online-registration/design.md b/.kiro/specs/semester-online-registration/design.md deleted file mode 100644 index 336b62f..0000000 --- a/.kiro/specs/semester-online-registration/design.md +++ /dev/null @@ -1,975 +0,0 @@ -# Design Document: Semester Online Registration System - -## Overview - -"Semester Online" is a PHP-based web application that digitizes the university's existing offline semester registration and payment process. It integrates with the main GBU university website as an end-part module, allowing enrolled students to register for their current semester, submit payment details, and track approval status — all online. The system enforces strict access control (college students only), links with the Accounts Department database for payment verification, and provides an admin dashboard for final approval. - -## Architecture - -The system follows a layered MVC architecture with a PHP backend, MySQL databases (application DB + Accounts Department DB), and a session-based authentication layer with MFA support. - -```mermaid -graph TD - A[GBU Main Website] -->|Embedded Link| B[Semester Online App] - B --> C[Authentication Layer] - C --> D[Student Portal] - C --> E[Admin Dashboard] - D --> F[Registration Form Module] - D --> G[Payment Module] - D --> H[Status Tracker] - G --> I[UPI / Razorpay Gateway] - G --> J[Bank Transfer Form] - F --> K[Application Database] - G --> K - K --> L[Payment Verification Service] - L --> M[Accounts Department Database] - E --> K - E --> N[Approval Engine] - N --> K -``` - -## Sequence Diagrams - -### Student Registration & Payment Flow - -```mermaid -sequenceDiagram - participant S as Student Browser - participant A as Auth Controller - participant P as Portal Controller - participant R as Registration Controller - participant Pay as Payment Controller - participant V as Verification Service - participant AccDB as Accounts DB - participant AppDB as Application DB - - S->>A: Sign Up (College ID, personal details) - A->>AppDB: Validate College ID exists - AppDB-->>A: ID valid / invalid - A-->>S: Account created / error - - S->>A: Login (Roll No, Mobile No) - A->>AppDB: Verify credentials - A-->>S: Send OTP to mobile - S->>A: Submit OTP - A-->>S: Session token, redirect to portal - - S->>P: Load student portal - P->>AppDB: Fetch student profile + semester history - AppDB-->>P: Student data - P-->>S: Render portal dashboard - - S->>R: Initiate semester registration - R->>AppDB: Check eligibility (no pending dues, not already registered) - AppDB-->>R: Eligible - R-->>S: Render registration form - - S->>R: Submit registration form - R->>AppDB: Save registration record (status: pending_payment) - R-->>S: Redirect to payment step - - S->>Pay: Choose payment method (UPI/Razorpay or Bank Transfer) - alt UPI / Razorpay - Pay-->>S: Redirect to payment gateway - S->>Pay: Gateway callback (transaction ID) - else Bank Transfer - S->>Pay: Submit bank transfer details - end - Pay->>AppDB: Save payment record - - Pay->>V: Trigger payment verification - V->>AccDB: Query payment by student ID + amount + date - AccDB-->>V: Match found / not found - V->>AppDB: Update payment status (verified / pending) - V-->>S: Notify verification result - - S->>P: View status (student part complete) -``` - -### Admin Approval Flow - -```mermaid -sequenceDiagram - participant Ad as Admin Browser - participant AC as Admin Controller - participant AppDB as Application DB - participant N as Notification Service - - Ad->>AC: Login to admin dashboard - AC-->>Ad: Dashboard with pending registrations - - Ad->>AC: Open registration record - AC->>AppDB: Fetch full student + payment details - AppDB-->>AC: Record data - AC-->>Ad: Display details for cross-verification - - Ad->>AC: Approve registration - AC->>AppDB: Update registration status = approved - AC->>N: Trigger student notification - N-->>Ad: Confirmation -``` - -## Components and Interfaces - -### 1. AuthController - -**Purpose**: Handles sign-up, login (MFA), OTP generation/verification, and session management. - -**Interface**: -```php -interface AuthControllerInterface { - public function signUp(array $formData): array; // returns ['success' => bool, 'errors' => array] - public function login(string $rollNo, string $mobile): array; - public function verifyOtp(string $rollNo, string $otp): array; - public function logout(): void; - public function isAuthenticated(): bool; - public function isAdmin(): bool; -} -``` - -**Responsibilities**: -- Validate College ID against the enrolled students table on sign-up -- Hash passwords using bcrypt before storage -- Generate time-limited OTP (5 minutes), store hashed in DB -- Issue PHP session with role (student / admin) on successful MFA -- Enforce rate limiting on OTP requests (max 3 per 10 minutes) - ---- - -### 2. StudentPortalController - -**Purpose**: Renders the student's personal dashboard with profile and semester history. - -**Interface**: -```php -interface StudentPortalControllerInterface { - public function dashboard(int $studentId): array; - public function getSemesterHistory(int $studentId): array; - public function getRegistrationStatus(int $studentId, int $semesterId): array; -} -``` - -**Responsibilities**: -- Aggregate student profile data from the application DB -- Display semester-wise academic and registration records -- Show current registration and payment status - ---- - -### 3. RegistrationController - -**Purpose**: Manages the semester registration form submission and eligibility checks. - -**Interface**: -```php -interface RegistrationControllerInterface { - public function checkEligibility(int $studentId, int $semesterId): array; - public function getRegistrationForm(int $studentId, int $semesterId): array; - public function submitRegistration(int $studentId, array $formData): array; -} -``` - -**Responsibilities**: -- Verify student has no pending dues and is not already registered for the semester -- Serve the online form mirroring the offline registration form fields -- Persist registration record with status `pending_payment` - ---- - -### 4. PaymentController - -**Purpose**: Handles payment method selection, gateway integration, and bank transfer form submission. - -**Interface**: -```php -interface PaymentControllerInterface { - public function initiatePayment(int $registrationId, string $method): array; - public function handleGatewayCallback(array $callbackData): array; - public function submitBankTransfer(int $registrationId, array $transferDetails): array; - public function getPaymentStatus(int $registrationId): array; -} -``` - -**Responsibilities**: -- Route to Razorpay SDK or bank transfer form based on student choice -- Record transaction ID / bank transfer reference in the application DB -- Trigger the PaymentVerificationService after payment submission - ---- - -### 5. PaymentVerificationService - -**Purpose**: Cross-references submitted payment data against the Accounts Department database. - -**Interface**: -```php -interface PaymentVerificationServiceInterface { - public function verify(int $registrationId): array; // returns ['verified' => bool, 'message' => string] - public function pollVerification(int $registrationId): array; -} -``` - -**Responsibilities**: -- Query Accounts DB using student ID, expected amount, and date range -- Update application DB payment status to `verified` or `pending_manual` -- Support both real-time and scheduled (cron) verification modes - ---- - -### 6. AdminController - -**Purpose**: Provides the admin dashboard for reviewing and approving completed registrations. - -**Interface**: -```php -interface AdminControllerInterface { - public function getPendingRegistrations(): array; - public function getRegistrationDetail(int $registrationId): array; - public function approveRegistration(int $registrationId, int $adminId): array; - public function rejectRegistration(int $registrationId, int $adminId, string $reason): array; -} -``` - -**Responsibilities**: -- List all registrations with status `payment_verified` -- Allow admin to view full student + payment details -- Update registration status to `approved` or `rejected` -- Trigger notification to student on status change - ---- - -### 7. NotificationService - -**Purpose**: Sends SMS/email notifications to students on key status changes. - -**Interface**: -```php -interface NotificationServiceInterface { - public function sendOtp(string $mobile, string $otp): bool; - public function sendStatusUpdate(int $studentId, string $status, string $message): bool; -} -``` - ---- - -## Data Models - -### students - -```sql -CREATE TABLE students ( - id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, - college_id VARCHAR(20) NOT NULL UNIQUE, -- Roll No / Registration No - full_name VARCHAR(100) NOT NULL, - mobile VARCHAR(15) NOT NULL UNIQUE, - email VARCHAR(100), - department VARCHAR(100), - program VARCHAR(100), - current_semester TINYINT UNSIGNED, - password_hash VARCHAR(255) NOT NULL, - is_active TINYINT(1) DEFAULT 1, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); -``` - -**Validation Rules**: -- `college_id` must exist in the enrolled students master list before account creation -- `mobile` must be a valid 10-digit Indian mobile number -- `password_hash` stored as bcrypt (cost factor ≥ 12) - ---- - -### otp_tokens - -```sql -CREATE TABLE otp_tokens ( - id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, - student_id INT UNSIGNED NOT NULL, - otp_hash VARCHAR(255) NOT NULL, - expires_at TIMESTAMP NOT NULL, - used TINYINT(1) DEFAULT 0, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (student_id) REFERENCES students(id) -); -``` - ---- - -### registrations - -```sql -CREATE TABLE registrations ( - id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, - student_id INT UNSIGNED NOT NULL, - semester_id TINYINT UNSIGNED NOT NULL, - academic_year VARCHAR(9) NOT NULL, -- e.g. "2024-2025" - subjects JSON NOT NULL, -- array of subject codes enrolled - hostel_required TINYINT(1) DEFAULT 0, - transport VARCHAR(50), - remarks TEXT, - status ENUM('draft','pending_payment','payment_submitted', - 'payment_verified','approved','rejected') DEFAULT 'draft', - submitted_at TIMESTAMP NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (student_id) REFERENCES students(id) -); -``` - ---- - -### payments - -```sql -CREATE TABLE payments ( - id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, - registration_id INT UNSIGNED NOT NULL UNIQUE, - student_id INT UNSIGNED NOT NULL, - amount DECIMAL(10,2) NOT NULL, - payment_method ENUM('upi','razorpay','bank_transfer') NOT NULL, - transaction_ref VARCHAR(100), -- gateway txn ID or bank UTR - bank_name VARCHAR(100), - account_holder VARCHAR(100), - transfer_date DATE, - transfer_amount DECIMAL(10,2), - receipt_path VARCHAR(255), -- uploaded receipt file path - verification_status ENUM('pending','verified','failed') DEFAULT 'pending', - verified_at TIMESTAMP NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (registration_id) REFERENCES registrations(id), - FOREIGN KEY (student_id) REFERENCES students(id) -); -``` - ---- - -### admin_actions - -```sql -CREATE TABLE admin_actions ( - id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, - registration_id INT UNSIGNED NOT NULL, - admin_id INT UNSIGNED NOT NULL, - action ENUM('approved','rejected') NOT NULL, - notes TEXT, - acted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (registration_id) REFERENCES registrations(id) -); -``` - ---- - -## Algorithmic Pseudocode - -### Sign-Up Algorithm - -```pascal -PROCEDURE signUp(formData) - INPUT: formData {college_id, full_name, mobile, email, password, confirm_password} - OUTPUT: result {success: bool, errors: array} - - BEGIN - errors ← [] - - IF formData.password ≠ formData.confirm_password THEN - errors.add("Passwords do not match") - END IF - - IF NOT isValidMobile(formData.mobile) THEN - errors.add("Invalid mobile number") - END IF - - IF NOT collegeIdExistsInMasterList(formData.college_id) THEN - errors.add("College ID not found. Only enrolled students may register.") - END IF - - IF studentAccountExists(formData.college_id) THEN - errors.add("An account already exists for this College ID.") - END IF - - IF errors.length > 0 THEN - RETURN {success: false, errors: errors} - END IF - - hash ← bcrypt(formData.password, cost=12) - student ← createStudentRecord(formData, hash) - sendWelcomeSms(formData.mobile) - - RETURN {success: true, errors: []} - END -END PROCEDURE -``` - -**Preconditions:** -- `formData` contains all required fields (non-empty) -- `collegeIdExistsInMasterList` queries the enrolled students master table - -**Postconditions:** -- If successful: a new row exists in `students` with `is_active = 1` -- Password is never stored in plaintext -- College ID uniqueness is enforced at DB level - ---- - -### MFA Login Algorithm - -```pascal -PROCEDURE login(rollNo, mobile) - INPUT: rollNo (string), mobile (string) - OUTPUT: result {success: bool, message: string} - - BEGIN - student ← findStudentByRollNo(rollNo) - - IF student IS NULL THEN - RETURN {success: false, message: "Invalid credentials"} - END IF - - IF student.mobile ≠ mobile THEN - RETURN {success: false, message: "Invalid credentials"} - END IF - - IF student.is_active = 0 THEN - RETURN {success: false, message: "Account is deactivated"} - END IF - - IF otpRequestCount(student.id, last=10 minutes) ≥ 3 THEN - RETURN {success: false, message: "Too many OTP requests. Try after 10 minutes."} - END IF - - otp ← generateNumericOtp(length=6) - otpHash ← bcrypt(otp, cost=10) - storeOtpToken(student.id, otpHash, expiresIn=5 minutes) - sendOtpSms(mobile, otp) - - RETURN {success: true, message: "OTP sent to registered mobile"} - END -END PROCEDURE - -PROCEDURE verifyOtp(rollNo, submittedOtp) - INPUT: rollNo (string), submittedOtp (string) - OUTPUT: result {success: bool, sessionToken: string} - - BEGIN - student ← findStudentByRollNo(rollNo) - token ← getLatestUnusedOtpToken(student.id) - - IF token IS NULL OR token.expires_at < NOW() THEN - RETURN {success: false, message: "OTP expired or not found"} - END IF - - IF NOT bcrypt_verify(submittedOtp, token.otp_hash) THEN - RETURN {success: false, message: "Invalid OTP"} - END IF - - markOtpTokenUsed(token.id) - session ← createSession(student.id, role="student") - - RETURN {success: true, sessionToken: session.token} - END -END PROCEDURE -``` - -**Preconditions:** -- `rollNo` and `mobile` are non-empty strings -- OTP is 6 digits, numeric only - -**Postconditions (verifyOtp):** -- OTP token is marked used; cannot be reused -- PHP session is created with `student_id` and `role` -- Session expires after 30 minutes of inactivity - -**Loop Invariants:** N/A - ---- - -### Registration Eligibility Check Algorithm - -```pascal -PROCEDURE checkEligibility(studentId, semesterId) - INPUT: studentId (int), semesterId (int) - OUTPUT: result {eligible: bool, reason: string} - - BEGIN - student ← fetchStudent(studentId) - - IF student.current_semester ≠ semesterId THEN - RETURN {eligible: false, reason: "Semester mismatch"} - END IF - - existingReg ← findRegistration(studentId, semesterId, academicYear=currentYear()) - IF existingReg IS NOT NULL AND existingReg.status ≠ 'rejected' THEN - RETURN {eligible: false, reason: "Already registered for this semester"} - END IF - - pendingDues ← checkPendingDues(studentId) - IF pendingDues > 0 THEN - RETURN {eligible: false, reason: "Pending dues of ₹" + pendingDues} - END IF - - RETURN {eligible: true, reason: ""} - END -END PROCEDURE -``` - -**Preconditions:** -- Student session is authenticated -- `semesterId` corresponds to the current active semester - -**Postconditions:** -- Returns eligibility without mutating any state - ---- - -### Payment Verification Algorithm - -```pascal -PROCEDURE verifyPayment(registrationId) - INPUT: registrationId (int) - OUTPUT: result {verified: bool, message: string} - - BEGIN - payment ← fetchPayment(registrationId) - student ← fetchStudent(payment.student_id) - - IF payment.payment_method IN ['upi', 'razorpay'] THEN - gatewayResult ← queryPaymentGateway(payment.transaction_ref) - IF gatewayResult.status = 'captured' THEN - updatePaymentStatus(payment.id, 'verified') - updateRegistrationStatus(registrationId, 'payment_verified') - RETURN {verified: true, message: "Payment verified via gateway"} - ELSE - RETURN {verified: false, message: "Gateway payment not confirmed"} - END IF - END IF - - IF payment.payment_method = 'bank_transfer' THEN - accRecord ← queryAccountsDB( - studentId = student.college_id, - amount = payment.transfer_amount, - dateRange = [payment.transfer_date - 2 days, payment.transfer_date + 2 days] - ) - - IF accRecord IS NOT NULL THEN - updatePaymentStatus(payment.id, 'verified') - updateRegistrationStatus(registrationId, 'payment_verified') - notifyStudent(student.id, "Payment verified. Awaiting admin approval.") - RETURN {verified: true, message: "Bank transfer matched in Accounts DB"} - ELSE - updatePaymentStatus(payment.id, 'pending') - RETURN {verified: false, message: "No matching record in Accounts DB. Will retry."} - END IF - END IF - END -END PROCEDURE -``` - -**Preconditions:** -- `registrationId` exists and has status `payment_submitted` -- Accounts DB connection is available - -**Postconditions:** -- If verified: `payments.verification_status = 'verified'`, `registrations.status = 'payment_verified'` -- If not verified: status remains `pending`; a scheduled cron job retries every 6 hours - -**Loop Invariants:** N/A - ---- - -### Admin Approval Algorithm - -```pascal -PROCEDURE approveRegistration(registrationId, adminId) - INPUT: registrationId (int), adminId (int) - OUTPUT: result {success: bool, message: string} - - BEGIN - registration ← fetchRegistration(registrationId) - - IF registration IS NULL THEN - RETURN {success: false, message: "Registration not found"} - END IF - - IF registration.status ≠ 'payment_verified' THEN - RETURN {success: false, message: "Registration is not in a verifiable state"} - END IF - - updateRegistrationStatus(registrationId, 'approved') - logAdminAction(registrationId, adminId, action='approved') - notifyStudent(registration.student_id, "Your semester registration has been approved.") - - RETURN {success: true, message: "Registration approved successfully"} - END -END PROCEDURE -``` - -**Preconditions:** -- Admin session is authenticated with `role = 'admin'` -- Registration status must be `payment_verified` - -**Postconditions:** -- `registrations.status = 'approved'` -- Admin action logged in `admin_actions` -- Student receives SMS/email notification - ---- - -## Key Functions with Formal Specifications - -### `collegeIdExistsInMasterList(string $collegeId): bool` - -**Preconditions:** -- `$collegeId` is a non-empty string -- Master list table (enrolled students) is accessible in the DB - -**Postconditions:** -- Returns `true` if and only if `$collegeId` exists in the enrolled students master table -- No mutations to any table - ---- - -### `generateNumericOtp(int $length): string` - -**Preconditions:** -- `$length` is a positive integer (typically 6) - -**Postconditions:** -- Returns a string of exactly `$length` numeric digits -- Generated using a cryptographically secure random source (`random_int`) -- Each call produces an independent, uniformly distributed value - ---- - -### `queryAccountsDB(string $collegeId, float $amount, array $dateRange): ?array` - -**Preconditions:** -- `$collegeId` is non-empty -- `$amount > 0` -- `$dateRange` is a two-element array `[startDate, endDate]` where `startDate <= endDate` -- Read-only access to Accounts DB - -**Postconditions:** -- Returns the first matching payment record or `null` if none found -- Does not mutate the Accounts DB -- Uses a dedicated read-only DB connection - ---- - -### `updateRegistrationStatus(int $registrationId, string $status): bool` - -**Preconditions:** -- `$registrationId` exists in `registrations` -- `$status` is one of the valid ENUM values - -**Postconditions:** -- `registrations.status` is updated to `$status` -- Returns `true` on success, `false` on DB error -- Status transitions are one-directional (no rollback to earlier states) - ---- - -## Error Handling - -### Invalid College ID on Sign-Up - -**Condition**: Student submits a College ID not present in the enrolled students master list. -**Response**: Return validation error "College ID not found. Only enrolled students may register." Do not create account. -**Recovery**: Student must contact the university registrar to verify enrollment. - ---- - -### OTP Expired or Invalid - -**Condition**: Student submits an OTP after 5-minute expiry or enters wrong digits. -**Response**: Return error "OTP expired or invalid." Invalidate the token. -**Recovery**: Student can request a new OTP (subject to rate limit of 3 per 10 minutes). - ---- - -### Accounts DB Unavailable - -**Condition**: The Accounts Department DB connection fails during payment verification. -**Response**: Log the error, set payment status to `pending`, return a user-friendly message "Verification is in progress. You will be notified." -**Recovery**: Cron job retries verification every 6 hours. Admin can also trigger manual verification. - ---- - -### Duplicate Registration Attempt - -**Condition**: Student attempts to register for a semester they are already registered for. -**Response**: Eligibility check returns `eligible: false` with reason. Block form submission. -**Recovery**: Student is redirected to their existing registration status page. - ---- - -### Payment Gateway Callback Failure - -**Condition**: Razorpay/UPI callback does not arrive (network issue). -**Response**: Payment status remains `pending`. Student portal shows "Payment pending confirmation." -**Recovery**: Student can check status; admin can manually verify and update if needed. - ---- - -## Testing Strategy - -### Unit Testing Approach - -Test each controller method and service function in isolation using PHPUnit. Key test cases: -- `signUp()` with valid data → account created -- `signUp()` with unrecognized College ID → error returned -- `login()` with mismatched mobile → error returned -- `verifyOtp()` with expired token → error returned -- `checkEligibility()` with pending dues → ineligible -- `verifyPayment()` with matching Accounts DB record → verified - -### Property-Based Testing Approach - -**Property Test Library**: PHPUnit with custom data providers (or `eris` library for PHP property-based testing) - -Key properties to verify: -- For any College ID not in the master list, `signUp()` must always return `success: false` -- For any OTP submitted after expiry time, `verifyOtp()` must always return `success: false` -- For any registration not in `payment_verified` status, `approveRegistration()` must always return `success: false` -- `generateNumericOtp(6)` always returns a string of exactly 6 numeric characters - -### Integration Testing Approach - -- Test the full sign-up → login → registration → payment → verification → approval flow end-to-end using a test database -- Mock the Accounts DB with known fixture data to test payment verification matching -- Mock the Razorpay SDK to test gateway callback handling - ---- - -## Performance Considerations - -- Accounts DB queries during payment verification should use indexed lookups on `college_id` and `transfer_date` -- OTP token table should be purged of expired tokens via a scheduled cron job to prevent table bloat -- Student portal dashboard queries should be cached per session (5-minute TTL) to reduce DB load during peak registration periods -- Razorpay webhook endpoint must respond within 5 seconds to avoid gateway retries - ---- - -## Security Considerations - -- **Access Control**: College ID validation at sign-up is the primary enrollment gate. All routes are protected by session middleware checking `role`. -- **SQL Injection**: All DB queries use PDO prepared statements. The Accounts DB connection uses a separate read-only credential. -- **CSRF Protection**: All POST forms include a CSRF token validated server-side. -- **Password Storage**: bcrypt with cost factor ≥ 12. -- **OTP Security**: OTPs are hashed before storage; raw OTP is never persisted. Rate limiting prevents brute force. -- **File Uploads** (bank transfer receipts): Validated for MIME type (PDF/JPEG/PNG), stored outside the web root, served via a controller with session check. -- **HTTPS**: The entire application must be served over TLS. HTTP requests redirected to HTTPS. -- **Session Hardening**: `session.cookie_httponly = 1`, `session.cookie_secure = 1`, `session.use_strict_mode = 1`. Session regenerated on privilege change. - ---- - -## Dependencies - -| Dependency | Purpose | -|---|---| -| PHP 8.1+ | Backend runtime | -| MySQL 8.0+ | Application database | -| Accounts Dept. MySQL/Oracle DB | Payment cross-reference (read-only access) | -| Razorpay PHP SDK | UPI / card payment gateway | -| PHPMailer or SMTP service | Email notifications | -| SMS Gateway API (e.g., MSG91, Twilio) | OTP and status SMS delivery | -| PHPUnit | Unit and integration testing | -| Composer | PHP dependency management | -| Apache / Nginx | Web server with mod_rewrite / try_files | -| Let's Encrypt / SSL Certificate | HTTPS enforcement | - ---- - -## Correctness Properties - -*A property is a characteristic or behavior that should hold true across all valid executions of a system — essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.* - ---- - -### Property 1: Invalid College ID always rejected at sign-up - -*For any* string that does not exist in the enrolled students Master_List, calling `signUp()` with that College ID must always return `success: false` and must never create a student record. - -**Validates: Requirements 1.1** - ---- - -### Property 2: Duplicate College ID always rejected at sign-up - -*For any* College ID for which a student account already exists, a second `signUp()` call with that College ID must always return `success: false` and must not create a second record. - -**Validates: Requirements 1.2** - ---- - -### Property 3: Invalid sign-up inputs always rejected - -*For any* sign-up form where the password and confirm_password fields are not equal, or where the mobile number is not a valid 10-digit Indian mobile number, `signUp()` must return `success: false` and must not create a student record. - -**Validates: Requirements 1.3, 1.4** - ---- - -### Property 4: Passwords are never stored in plaintext - -*For any* password string submitted during sign-up, the value stored in the `password_hash` column must not equal the original plaintext password and must be a valid bcrypt hash with cost factor ≥ 12. - -**Validates: Requirements 1.6, 10.7** - ---- - -### Property 5: Invalid login credentials always rejected - -*For any* combination of roll number and mobile number where either the roll number does not exist, the mobile does not match the stored value, or the account has `is_active = 0`, calling `login()` must always return `success: false`. - -**Validates: Requirements 2.1, 2.2, 2.3** - ---- - -### Property 6: OTP rate limiting enforced - -*For any* student, after 3 OTP requests within a 10-minute window, any further `login()` call within that window must return `success: false` with the rate-limit message, regardless of credential validity. - -**Validates: Requirements 2.4, 10.6** - ---- - -### Property 7: OTP format invariant - -*For any* call to `generateNumericOtp(6)`, the returned value must be a string of exactly 6 characters where every character is a decimal digit (0–9). - -**Validates: Requirements 2.5, 12.1** - ---- - -### Property 8: OTP is never stored in plaintext - -*For any* OTP generated and stored in the `otp_tokens` table, the `otp_hash` column value must not equal the raw OTP string and must be a valid bcrypt hash. - -**Validates: Requirements 2.5, 12.3, 10.8** - ---- - -### Property 9: Expired OTP always rejected - -*For any* OTP token whose `expires_at` timestamp is in the past, calling `verifyOtp()` with that token's value must always return `success: false`. - -**Validates: Requirements 2.6, 12.5** - ---- - -### Property 10: Used OTP cannot be reused - -*For any* OTP token that has been successfully used for authentication (marked `used = 1`), a subsequent call to `verifyOtp()` with the same OTP value must return `success: false`. - -**Validates: Requirements 2.8, 12.4** - ---- - -### Property 11: Eligibility check is read-only (pure function) - -*For any* student ID and semester ID, calling `checkEligibility()` any number of times must produce the same result without mutating any row in the Application_DB. - -**Validates: Requirements 4.5** - ---- - -### Property 12: Ineligible students cannot register - -*For any* student who fails any eligibility condition (semester mismatch, existing non-rejected registration, or pending dues > 0), calling `submitRegistration()` must return an error and must not create a registration record. - -**Validates: Requirements 4.1, 4.2, 4.3, 5.3** - ---- - -### Property 13: Registration submission creates correct initial state - -*For any* eligible student submitting a valid registration form, the resulting registration record must have `status = 'pending_payment'` and a non-null `submitted_at` timestamp. - -**Validates: Requirements 5.1, 5.2** - ---- - -### Property 14: Subjects JSON round-trip - -*For any* array of subject codes submitted in a registration form, serializing to JSON for storage and then deserializing must produce an array equivalent to the original input. - -**Validates: Requirements 5.4** - ---- - -### Property 15: Payment submission only allowed from pending_payment status - -*For any* registration whose status is not `pending_payment`, calling `initiatePayment()` or `submitBankTransfer()` must return an error and must not create or modify a payment record. - -**Validates: Requirements 6.5** - ---- - -### Property 16: Gateway payment verification outcome is deterministic - -*For any* payment record with method `upi` or `razorpay`, if the payment gateway returns status `captured` then `verify()` must set `verification_status = 'verified'` and `registration.status = 'payment_verified'`; if the gateway returns any other status then `verify()` must return `verified: false` and must not update the registration status. - -**Validates: Requirements 7.1, 7.2** - ---- - -### Property 17: Bank transfer verification matches on correct criteria - -*For any* bank transfer payment record, `verify()` must return `verified: true` if and only if the Accounts_DB contains a record matching the student's college ID, the submitted amount, and a transfer date within ±2 days of the recorded transfer date. - -**Validates: Requirements 7.3, 7.4** - ---- - -### Property 18: Accounts_DB is never mutated by verification - -*For any* call to `verify()` or `pollVerification()`, the Accounts_DB must contain exactly the same rows before and after the call. - -**Validates: Requirements 7.7** - ---- - -### Property 19: Admin dashboard shows only payment_verified registrations - -*For any* set of registrations in the Application_DB, `getPendingRegistrations()` must return only those records whose `status = 'payment_verified'` and must not include records in any other status. - -**Validates: Requirements 8.1** - ---- - -### Property 20: Admin approval/rejection only allowed from payment_verified status - -*For any* registration whose status is not `payment_verified`, calling `approveRegistration()` or `rejectRegistration()` must return an error and must not modify the registration record or create an `admin_actions` entry. - -**Validates: Requirements 8.5** - ---- - -### Property 21: Admin action is fully recorded - -*For any* successful `approveRegistration()` or `rejectRegistration()` call, the `admin_actions` table must contain a new row with the correct `registration_id`, `admin_id`, `action` value, and a non-null `acted_at` timestamp. - -**Validates: Requirements 8.3, 8.4, 8.7, 11.4** - ---- - -### Property 22: Registration status transitions are strictly forward - -*For any* registration record, calling `updateRegistrationStatus()` with a status that precedes the current status in the lifecycle (`draft` → `pending_payment` → `payment_submitted` → `payment_verified` → `approved`/`rejected`) must return `false` and must not modify the record. - -**Validates: Requirements 11.1, 11.2** - ---- - -### Property 23: CSRF protection rejects requests without valid token - -*For any* POST request to any system endpoint that does not include a valid CSRF token matching the current session, the System must reject the request and must not process the submitted data. - -**Validates: Requirements 10.2** - ---- - -### Property 24: Non-admin sessions cannot access admin routes - -*For any* session with `role = 'student'` or with no active session, any request to an admin dashboard route must be denied and must not return any registration or student data. - -**Validates: Requirements 8.6, 10.10** diff --git a/.kiro/specs/semester-online-registration/requirements.md b/.kiro/specs/semester-online-registration/requirements.md deleted file mode 100644 index 985d388..0000000 --- a/.kiro/specs/semester-online-registration/requirements.md +++ /dev/null @@ -1,225 +0,0 @@ -# Requirements Document - -## Introduction - -The Semester Online Registration System is a PHP-based web module integrated into the GBU university website that digitizes the offline semester registration and payment process. It allows enrolled students to create accounts, authenticate via multi-factor authentication, register for their current semester, submit payment details, and track approval status online. The system enforces enrollment-based access control, cross-references payments against the Accounts Department database, and provides an admin dashboard for final registration approval. - -## Glossary - -- **System**: The Semester Online Registration web application -- **AuthController**: The component responsible for sign-up, login, OTP generation/verification, and session management -- **StudentPortalController**: The component responsible for rendering the student dashboard and semester history -- **RegistrationController**: The component responsible for eligibility checks and registration form submission -- **PaymentController**: The component responsible for payment method routing, gateway integration, and bank transfer submission -- **PaymentVerificationService**: The component responsible for cross-referencing submitted payment data against the Accounts Department database -- **AdminController**: The component responsible for the admin dashboard, registration review, and approval/rejection actions -- **NotificationService**: The component responsible for sending SMS and email notifications to students -- **Student**: An enrolled university student who has created an account in the system -- **Admin**: A university staff member with administrative access to the system -- **College_ID**: The unique roll number or registration number assigned to an enrolled student -- **OTP**: A one-time password consisting of 6 numeric digits, valid for 5 minutes -- **Registration**: A semester registration record linking a student to a specific semester and academic year -- **Payment**: A payment record associated with a registration, submitted via UPI, Razorpay, or bank transfer -- **Accounts_DB**: The read-only Accounts Department database used for payment cross-reference -- **Application_DB**: The primary application database storing students, registrations, payments, and admin actions -- **Master_List**: The enrolled students master table used to validate College IDs at sign-up -- **Session**: A server-side PHP session issued after successful MFA, carrying student ID and role -- **Verification_Status**: The payment verification state: `pending`, `verified`, or `failed` -- **Registration_Status**: The registration lifecycle state: `draft`, `pending_payment`, `payment_submitted`, `payment_verified`, `approved`, or `rejected` - ---- - -## Requirements - -### Requirement 1: Student Account Creation (Sign-Up) - -**User Story:** As an enrolled student, I want to create an account using my College ID, so that I can access the semester registration system. - -#### Acceptance Criteria - -1. WHEN a student submits a sign-up form with a College ID that does not exist in the Master_List, THEN THE AuthController SHALL reject the request and return the error "College ID not found. Only enrolled students may register." -2. WHEN a student submits a sign-up form with a College ID that already has an existing account, THEN THE AuthController SHALL reject the request and return the error "An account already exists for this College ID." -3. WHEN a student submits a sign-up form where the password and confirm_password fields do not match, THEN THE AuthController SHALL reject the request and return a password mismatch error. -4. WHEN a student submits a sign-up form with a mobile number that is not a valid 10-digit Indian mobile number, THEN THE AuthController SHALL reject the request and return a mobile validation error. -5. WHEN a student submits a valid sign-up form with a College ID present in the Master_List, THEN THE AuthController SHALL create a student account with `is_active = 1` and return `success: true`. -6. WHEN a student account is created, THE AuthController SHALL store the password as a bcrypt hash with a cost factor of at least 12 and SHALL NOT store the plaintext password. -7. WHEN a student account is created successfully, THE NotificationService SHALL send a welcome SMS to the student's registered mobile number. - ---- - -### Requirement 2: Multi-Factor Authentication (Login) - -**User Story:** As a registered student, I want to log in using my roll number and mobile number with OTP verification, so that my account is protected by multi-factor authentication. - -#### Acceptance Criteria - -1. WHEN a student submits a login request with a roll number that does not exist in the Application_DB, THEN THE AuthController SHALL return `success: false` with the message "Invalid credentials." -2. WHEN a student submits a login request with a roll number that exists but a non-matching mobile number, THEN THE AuthController SHALL return `success: false` with the message "Invalid credentials." -3. WHEN a student submits a login request for an account with `is_active = 0`, THEN THE AuthController SHALL return `success: false` with the message "Account is deactivated." -4. WHEN a student submits more than 3 OTP requests within a 10-minute window, THEN THE AuthController SHALL reject further OTP requests and return the message "Too many OTP requests. Try after 10 minutes." -5. WHEN a student submits valid login credentials and is within the OTP rate limit, THEN THE AuthController SHALL generate a 6-digit numeric OTP, store its bcrypt hash with a 5-minute expiry in the Application_DB, and send the OTP to the student's registered mobile number. -6. WHEN a student submits an OTP that has expired (past its `expires_at` timestamp), THEN THE AuthController SHALL return `success: false` with the message "OTP expired or invalid." -7. WHEN a student submits an OTP that does not match the stored hash, THEN THE AuthController SHALL return `success: false` with the message "OTP expired or invalid." -8. WHEN a student submits a valid, unexpired OTP, THEN THE AuthController SHALL mark the OTP token as used, create a PHP session with the student's ID and role set to "student", and return `success: true` with a session token. -9. WHILE a student session is active, THE AuthController SHALL expire the session after 30 minutes of inactivity. -10. WHEN a session is created or a privilege change occurs, THE AuthController SHALL regenerate the session ID to prevent session fixation. - ---- - -### Requirement 3: Student Portal Dashboard - -**User Story:** As an authenticated student, I want to view my personal dashboard with my profile and semester history, so that I can track my academic and registration records. - -#### Acceptance Criteria - -1. WHEN an authenticated student loads the portal dashboard, THE StudentPortalController SHALL retrieve and display the student's profile data and semester history from the Application_DB. -2. WHEN an authenticated student views the portal, THE StudentPortalController SHALL display the current registration status and payment status for the active semester. -3. IF a student attempts to access the portal without an active authenticated session, THEN THE System SHALL redirect the student to the login page. -4. WHILE a student session is active, THE StudentPortalController SHALL cache dashboard query results for up to 5 minutes to reduce database load during peak registration periods. - ---- - -### Requirement 4: Semester Registration Eligibility - -**User Story:** As an authenticated student, I want the system to check my eligibility before allowing me to register, so that only eligible students can submit a registration. - -#### Acceptance Criteria - -1. WHEN a student initiates semester registration and their `current_semester` does not match the requested `semesterId`, THEN THE RegistrationController SHALL return `eligible: false` with the reason "Semester mismatch." -2. WHEN a student initiates semester registration and an existing non-rejected registration record already exists for the same student, semester, and academic year, THEN THE RegistrationController SHALL return `eligible: false` with the reason "Already registered for this semester." -3. WHEN a student initiates semester registration and they have pending dues greater than zero, THEN THE RegistrationController SHALL return `eligible: false` with the reason "Pending dues of ₹{amount}." -4. WHEN a student passes all eligibility checks, THE RegistrationController SHALL return `eligible: true` and render the registration form. -5. THE RegistrationController SHALL perform eligibility checks without mutating any data in the Application_DB. - ---- - -### Requirement 5: Semester Registration Form Submission - -**User Story:** As an eligible student, I want to submit my semester registration form online, so that I can complete the registration process digitally. - -#### Acceptance Criteria - -1. WHEN an eligible student submits a valid registration form, THE RegistrationController SHALL persist a registration record in the Application_DB with status `pending_payment` and redirect the student to the payment step. -2. WHEN a registration form is submitted, THE RegistrationController SHALL record the `submitted_at` timestamp on the registration record. -3. IF a student attempts to submit a registration form without passing the eligibility check, THEN THE RegistrationController SHALL reject the submission and return an eligibility error. -4. THE RegistrationController SHALL store the selected subjects as a JSON array of subject codes in the registration record. - ---- - -### Requirement 6: Payment Submission - -**User Story:** As a student with a pending payment registration, I want to submit payment via UPI, Razorpay, or bank transfer, so that I can complete the financial step of registration. - -#### Acceptance Criteria - -1. WHEN a student selects UPI or Razorpay as the payment method, THE PaymentController SHALL redirect the student to the Razorpay payment gateway. -2. WHEN the Razorpay gateway returns a callback with a transaction ID, THE PaymentController SHALL record the transaction reference in the Application_DB and update the registration status to `payment_submitted`. -3. WHEN a student selects bank transfer as the payment method and submits bank transfer details, THE PaymentController SHALL record the bank name, account holder, transfer date, transfer amount, transaction reference, and receipt file path in the Application_DB and update the registration status to `payment_submitted`. -4. WHEN a payment record is saved, THE PaymentController SHALL trigger the PaymentVerificationService to begin verification. -5. IF a student attempts to submit payment for a registration that is not in `pending_payment` status, THEN THE PaymentController SHALL reject the submission. -6. WHEN a bank transfer receipt file is uploaded, THE System SHALL validate the file MIME type as PDF, JPEG, or PNG and store the file outside the web root. - ---- - -### Requirement 7: Payment Verification - -**User Story:** As a student who has submitted payment, I want the system to automatically verify my payment against the Accounts Department records, so that my registration can proceed to admin approval without manual intervention. - -#### Acceptance Criteria - -1. WHEN the PaymentVerificationService verifies a UPI or Razorpay payment and the payment gateway returns status `captured`, THE PaymentVerificationService SHALL update the payment `verification_status` to `verified` and the registration status to `payment_verified`. -2. WHEN the PaymentVerificationService verifies a UPI or Razorpay payment and the payment gateway does not return status `captured`, THE PaymentVerificationService SHALL return `verified: false` without updating the registration status. -3. WHEN the PaymentVerificationService verifies a bank transfer and a matching record is found in the Accounts_DB (matching college ID, amount, and date within a ±2-day range), THE PaymentVerificationService SHALL update the payment `verification_status` to `verified`, update the registration status to `payment_verified`, and notify the student. -4. WHEN the PaymentVerificationService verifies a bank transfer and no matching record is found in the Accounts_DB, THE PaymentVerificationService SHALL set the payment `verification_status` to `pending` and schedule a retry. -5. IF the Accounts_DB connection is unavailable during payment verification, THEN THE PaymentVerificationService SHALL log the error, set the payment status to `pending`, and return a user-friendly message "Verification is in progress. You will be notified." -6. WHILE a payment verification is in `pending` status, THE System SHALL retry verification via a scheduled cron job every 6 hours. -7. THE PaymentVerificationService SHALL query the Accounts_DB using a dedicated read-only database connection and SHALL NOT mutate any data in the Accounts_DB. - ---- - -### Requirement 8: Admin Dashboard and Registration Approval - -**User Story:** As an admin, I want to review and approve or reject verified registrations from a dashboard, so that I can complete the final step of the registration process. - -#### Acceptance Criteria - -1. WHEN an admin logs in to the admin dashboard, THE AdminController SHALL display all registrations with status `payment_verified`. -2. WHEN an admin opens a registration record, THE AdminController SHALL retrieve and display the full student profile and payment details from the Application_DB. -3. WHEN an admin approves a registration that is in `payment_verified` status, THE AdminController SHALL update the registration status to `approved`, log the action in `admin_actions`, and trigger a student notification. -4. WHEN an admin rejects a registration that is in `payment_verified` status, THE AdminController SHALL update the registration status to `rejected`, record the rejection reason in `admin_actions`, and trigger a student notification. -5. IF an admin attempts to approve or reject a registration that is not in `payment_verified` status, THEN THE AdminController SHALL return an error "Registration is not in a verifiable state." -6. IF a user without admin role attempts to access the admin dashboard, THEN THE System SHALL deny access and redirect to the login page. -7. WHEN an admin action is taken, THE System SHALL record the admin ID, registration ID, action type, and timestamp in the `admin_actions` table. - ---- - -### Requirement 9: Student Notifications - -**User Story:** As a student, I want to receive SMS or email notifications on key status changes, so that I am informed of my registration progress without having to check the portal manually. - -#### Acceptance Criteria - -1. WHEN a student account is created, THE NotificationService SHALL send a welcome SMS to the student's registered mobile number. -2. WHEN a student's payment is verified, THE NotificationService SHALL send a status update notification to the student. -3. WHEN an admin approves a student's registration, THE NotificationService SHALL send a notification with the message "Your semester registration has been approved." -4. WHEN an admin rejects a student's registration, THE NotificationService SHALL send a notification informing the student of the rejection. -5. WHEN an OTP is requested, THE NotificationService SHALL deliver the OTP to the student's registered mobile number. - ---- - -### Requirement 10: Security and Access Control - -**User Story:** As a system administrator, I want the application to enforce strict security controls, so that student data and the registration process are protected from unauthorized access and attacks. - -#### Acceptance Criteria - -1. THE System SHALL serve all pages exclusively over HTTPS and SHALL redirect any HTTP request to HTTPS. -2. THE System SHALL protect all POST form endpoints with a CSRF token that is validated server-side. -3. THE System SHALL use PDO prepared statements for all database queries to prevent SQL injection. -4. WHEN a session is created or a privilege change occurs, THE AuthController SHALL regenerate the session ID. -5. THE System SHALL set PHP session cookies with `httponly = 1`, `secure = 1`, and `use_strict_mode = 1`. -6. WHEN a student requests more than 3 OTPs within 10 minutes, THE AuthController SHALL block further OTP requests for that student for the remainder of the 10-minute window. -7. THE System SHALL store all passwords as bcrypt hashes with a cost factor of at least 12 and SHALL NOT store plaintext passwords. -8. THE System SHALL store OTP values as bcrypt hashes and SHALL NOT persist the raw OTP value. -9. WHEN a bank transfer receipt is uploaded, THE System SHALL validate the file MIME type as PDF, JPEG, or PNG and store the file outside the web root, accessible only through a session-authenticated controller. -10. THE System SHALL enforce role-based access control on all routes, verifying the session role before processing any request. - ---- - -### Requirement 11: Registration Status Lifecycle - -**User Story:** As a student or admin, I want registration status transitions to follow a defined lifecycle, so that the process is predictable and auditable. - -#### Acceptance Criteria - -1. THE System SHALL only allow registration status transitions in the forward direction: `draft` → `pending_payment` → `payment_submitted` → `payment_verified` → `approved` or `rejected`. -2. IF a registration status update would result in a backward transition, THEN THE System SHALL reject the update and return an error. -3. THE System SHALL record a timestamp (`submitted_at`) when a registration transitions from `draft` to `pending_payment`. -4. WHEN a registration is approved or rejected, THE System SHALL record the admin ID and timestamp in the `admin_actions` table. - ---- - -### Requirement 12: OTP Generation - -**User Story:** As a developer, I want the OTP generation function to produce cryptographically secure, correctly formatted tokens, so that the MFA system is reliable and secure. - -#### Acceptance Criteria - -1. THE AuthController SHALL generate OTPs consisting of exactly 6 numeric digits. -2. THE AuthController SHALL generate OTPs using a cryptographically secure random source (`random_int`). -3. WHEN an OTP is generated, THE AuthController SHALL store only the bcrypt hash of the OTP in the Application_DB and SHALL NOT store the raw OTP value. -4. WHEN an OTP token is successfully used for authentication, THE AuthController SHALL mark the token as used so it cannot be reused. -5. THE System SHALL expire OTP tokens after 5 minutes from generation. - ---- - -### Requirement 13: Performance - -**User Story:** As a student or admin, I want the system to respond promptly during peak registration periods, so that the registration process is not disrupted by slow performance. - -#### Acceptance Criteria - -1. THE System SHALL respond to Razorpay webhook callbacks within 5 seconds to prevent gateway retries. -2. THE System SHALL use indexed database lookups on `college_id` and `transfer_date` columns for Accounts_DB queries during payment verification. -3. THE System SHALL purge expired OTP tokens from the `otp_tokens` table via a scheduled cron job to prevent table bloat. -4. WHILE a student session is active, THE StudentPortalController SHALL cache dashboard query results with a 5-minute TTL to reduce database load. diff --git a/.kiro/specs/semester-online-registration/tasks.md b/.kiro/specs/semester-online-registration/tasks.md deleted file mode 100644 index 2c03b79..0000000 --- a/.kiro/specs/semester-online-registration/tasks.md +++ /dev/null @@ -1,300 +0,0 @@ -# Implementation Plan: Semester Online Registration System - -## Overview - -Implement a PHP/MySQL MVC web application that digitizes the university semester registration and payment process. The implementation follows the layered architecture defined in the design: AuthController → StudentPortalController → RegistrationController → PaymentController → PaymentVerificationService → AdminController, backed by a MySQL application database and a read-only Accounts Department database. - -## Tasks - -- [x] 1. Set up project structure, database schema, and core infrastructure - - Create Composer-managed PHP project with directory structure: `app/Controllers/`, `app/Services/`, `app/Models/`, `app/Views/`, `config/`, `public/`, `tests/` - - Write `composer.json` requiring PHP 8.1+, Razorpay PHP SDK, PHPMailer, and PHPUnit - - Create `config/database.php` with PDO connection factory for Application_DB and read-only Accounts_DB - - Create `config/app.php` for session hardening settings (`httponly`, `secure`, `use_strict_mode`) - - Create `public/index.php` front controller with HTTPS redirect and CSRF middleware bootstrap - - Write all five SQL migration files: `students`, `otp_tokens`, `registrations`, `payments`, `admin_actions` - - Add DB indexes on `college_id` and `transfer_date` for Accounts_DB query performance - - _Requirements: 10.1, 10.3, 10.5, 13.2_ - -- [-] 2. Implement AuthController — sign-up - - [x] 2.1 Implement `signUp()` in `AuthController` - - Validate College ID against Master_List using PDO prepared statement - - Check for duplicate College ID in `students` table - - Validate password match and 10-digit Indian mobile format - - Hash password with `password_hash()` at bcrypt cost ≥ 12 - - Insert student record with `is_active = 1`; trigger `NotificationService::sendWelcomeSms()` - - _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7_ - - - [ ]* 2.2 Write property test for sign-up rejection of invalid College IDs - - **Property 1: Invalid College ID always rejected at sign-up** - - **Validates: Requirements 1.1** - - - [ ]* 2.3 Write property test for duplicate College ID rejection - - **Property 2: Duplicate College ID always rejected at sign-up** - - **Validates: Requirements 1.2** - - - [ ]* 2.4 Write property test for invalid sign-up inputs - - **Property 3: Invalid sign-up inputs always rejected** - - **Validates: Requirements 1.3, 1.4** - - - [ ]* 2.5 Write property test for password storage - - **Property 4: Passwords are never stored in plaintext** - - **Validates: Requirements 1.6, 10.7** - -- [-] 3. Implement AuthController — MFA login and OTP - - [x] 3.1 Implement `generateNumericOtp()` helper - - Use `random_int()` to generate a 6-digit numeric string - - _Requirements: 12.1, 12.2_ - - - [ ]* 3.2 Write property test for OTP format invariant - - **Property 7: OTP format invariant** - - **Validates: Requirements 2.5, 12.1** - - - [x] 3.3 Implement `login()` in `AuthController` - - Look up student by roll number; verify mobile match and `is_active = 1` - - Enforce OTP rate limit: count `otp_tokens` rows for student in last 10 minutes; block if ≥ 3 - - Generate OTP via `generateNumericOtp(6)`, store bcrypt hash with 5-minute `expires_at` in `otp_tokens` - - Call `NotificationService::sendOtp()` to deliver OTP via SMS - - _Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 10.6, 10.8, 12.3_ - - - [ ]* 3.4 Write property test for invalid login credentials rejection - - **Property 5: Invalid login credentials always rejected** - - **Validates: Requirements 2.1, 2.2, 2.3** - - - [ ]* 3.5 Write property test for OTP rate limiting - - **Property 6: OTP rate limiting enforced** - - **Validates: Requirements 2.4, 10.6** - - - [ ]* 3.6 Write property test for OTP plaintext storage - - **Property 8: OTP is never stored in plaintext** - - **Validates: Requirements 2.5, 12.3, 10.8** - - - [x] 3.7 Implement `verifyOtp()` in `AuthController` - - Fetch latest unused, unexpired OTP token for student - - Verify submitted OTP against stored bcrypt hash - - Mark token `used = 1`; create PHP session with `student_id` and `role = 'student'` - - Regenerate session ID on session creation; configure 30-minute inactivity timeout - - _Requirements: 2.6, 2.7, 2.8, 2.9, 2.10, 10.4, 10.5_ - - - [ ]* 3.8 Write property test for expired OTP rejection - - **Property 9: Expired OTP always rejected** - - **Validates: Requirements 2.6, 12.5** - - - [ ]* 3.9 Write property test for OTP reuse prevention - - **Property 10: Used OTP cannot be reused** - - **Validates: Requirements 2.8, 12.4** - -- [x] 4. Checkpoint — Ensure all authentication tests pass - - Ensure all tests pass, ask the user if questions arise. - -- [-] 5. Implement session middleware, CSRF protection, and role-based access control - - [x] 5.1 Implement session middleware - - Create `Middleware/SessionMiddleware.php` that checks for active session on protected routes - - Redirect unauthenticated requests to login page - - _Requirements: 3.3, 10.10_ - - - [x] 5.2 Implement CSRF middleware - - Generate and store CSRF token in session; inject into all forms via a view helper - - Validate CSRF token on every POST request; reject and abort if missing or mismatched - - _Requirements: 10.2_ - - - [ ]* 5.3 Write property test for CSRF protection - - **Property 23: CSRF protection rejects requests without valid token** - - **Validates: Requirements 10.2** - - - [x] 5.4 Implement admin role guard - - Create `Middleware/AdminMiddleware.php` that checks `session.role === 'admin'`; deny and redirect otherwise - - _Requirements: 8.6, 10.10_ - - - [ ]* 5.5 Write property test for non-admin session access denial - - **Property 24: Non-admin sessions cannot access admin routes** - - **Validates: Requirements 8.6, 10.10** - -- [-] 6. Implement StudentPortalController - - [x] 6.1 Implement `dashboard()` and `getSemesterHistory()` in `StudentPortalController` - - Fetch student profile and semester history from Application_DB using PDO prepared statements - - Cache query results in session with 5-minute TTL - - _Requirements: 3.1, 3.2, 3.4, 13.4_ - - - [x] 6.2 Implement `getRegistrationStatus()` in `StudentPortalController` - - Fetch current registration and payment status for the active semester - - _Requirements: 3.2_ - - - [ ]* 6.3 Write unit tests for StudentPortalController - - Test dashboard data aggregation, cache hit/miss behavior, and status display - - _Requirements: 3.1, 3.2, 3.4_ - -- [-] 7. Implement RegistrationController - - [x] 7.1 Implement `checkEligibility()` in `RegistrationController` - - Check semester match, existing non-rejected registration, and pending dues — all read-only queries - - Return `eligible: bool` and `reason: string` without mutating any DB row - - _Requirements: 4.1, 4.2, 4.3, 4.4, 4.5_ - - - [ ]* 7.2 Write property test for eligibility check read-only invariant - - **Property 11: Eligibility check is read-only (pure function)** - - **Validates: Requirements 4.5** - - - [ ]* 7.3 Write property test for ineligible student registration block - - **Property 12: Ineligible students cannot register** - - **Validates: Requirements 4.1, 4.2, 4.3, 5.3** - - - [x] 7.4 Implement `submitRegistration()` in `RegistrationController` - - Re-run eligibility check before persisting; reject if ineligible - - Insert registration record with `status = 'pending_payment'`, `submitted_at = NOW()`, subjects as JSON - - Redirect student to payment step - - _Requirements: 5.1, 5.2, 5.3, 5.4_ - - - [ ]* 7.5 Write property test for registration initial state - - **Property 13: Registration submission creates correct initial state** - - **Validates: Requirements 5.1, 5.2** - - - [ ]* 7.6 Write property test for subjects JSON round-trip - - **Property 14: Subjects JSON round-trip** - - **Validates: Requirements 5.4** - -- [x] 8. Checkpoint — Ensure all registration tests pass - - Ensure all tests pass, ask the user if questions arise. - -- [-] 9. Implement PaymentController - - [x] 9.1 Implement `initiatePayment()` for UPI/Razorpay in `PaymentController` - - Verify registration is in `pending_payment` status before proceeding - - Integrate Razorpay PHP SDK to create an order and redirect student to gateway - - _Requirements: 6.1, 6.5_ - - - [x] 9.2 Implement `handleGatewayCallback()` in `PaymentController` - - Validate Razorpay callback signature; record `transaction_ref` in `payments` table - - Update registration status to `payment_submitted`; trigger `PaymentVerificationService::verify()` - - _Requirements: 6.2, 6.4_ - - - [x] 9.3 Implement `submitBankTransfer()` in `PaymentController` - - Verify registration is in `pending_payment` status - - Validate uploaded receipt file MIME type (PDF/JPEG/PNG); store file outside web root - - Persist bank transfer details in `payments` table; update registration to `payment_submitted` - - Trigger `PaymentVerificationService::verify()` - - _Requirements: 6.3, 6.4, 6.5, 6.6, 10.9_ - - - [ ]* 9.4 Write property test for payment submission status guard - - **Property 15: Payment submission only allowed from pending_payment status** - - **Validates: Requirements 6.5** - - - [ ]* 9.5 Write unit tests for PaymentController - - Test Razorpay redirect, callback recording, bank transfer validation, and file upload rejection for invalid MIME types - - _Requirements: 6.1, 6.2, 6.3, 6.6_ - -- [-] 10. Implement PaymentVerificationService - - [x] 10.1 Implement `verify()` for UPI/Razorpay in `PaymentVerificationService` - - Query Razorpay SDK for payment status using stored `transaction_ref` - - If `captured`: update `payments.verification_status = 'verified'` and `registrations.status = 'payment_verified'` - - If not `captured`: return `verified: false` without modifying registration - - _Requirements: 7.1, 7.2_ - - - [ ]* 10.2 Write property test for gateway payment verification determinism - - **Property 16: Gateway payment verification outcome is deterministic** - - **Validates: Requirements 7.1, 7.2** - - - [x] 10.3 Implement `verify()` for bank transfer in `PaymentVerificationService` - - Query Accounts_DB via read-only PDO connection using college ID, amount, and ±2-day date range - - If match found: update statuses to `verified`/`payment_verified`; call `NotificationService::sendStatusUpdate()` - - If no match: set `verification_status = 'pending'`; handle Accounts_DB unavailability with error log and user-friendly message - - _Requirements: 7.3, 7.4, 7.5, 7.7_ - - - [ ]* 10.4 Write property test for bank transfer verification criteria - - **Property 17: Bank transfer verification matches on correct criteria** - - **Validates: Requirements 7.3, 7.4** - - - [ ]* 10.5 Write property test for Accounts_DB immutability - - **Property 18: Accounts_DB is never mutated by verification** - - **Validates: Requirements 7.7** - - - [x] 10.6 Implement `pollVerification()` and cron job script - - Create `scripts/retry_verification.php` that queries all `pending` payments and calls `verify()` for each - - Register cron job to run every 6 hours - - _Requirements: 7.6_ - -- [-] 11. Implement registration status lifecycle enforcement - - [x] 11.1 Implement `updateRegistrationStatus()` with forward-only transition guard - - Define ordered lifecycle array: `['draft','pending_payment','payment_submitted','payment_verified','approved','rejected']` - - Reject any update where the target status index ≤ current status index (except `rejected` as terminal) - - Return `false` and do not mutate the record on invalid transitions - - _Requirements: 11.1, 11.2, 11.3_ - - - [ ]* 11.2 Write property test for forward-only status transitions - - **Property 22: Registration status transitions are strictly forward** - - **Validates: Requirements 11.1, 11.2** - -- [x] 12. Checkpoint — Ensure all payment and lifecycle tests pass - - Ensure all tests pass, ask the user if questions arise. - -- [-] 13. Implement AdminController - - [x] 13.1 Implement `getPendingRegistrations()` in `AdminController` - - Query Application_DB for all registrations with `status = 'payment_verified'` only - - _Requirements: 8.1_ - - - [ ]* 13.2 Write property test for admin dashboard filter - - **Property 19: Admin dashboard shows only payment_verified registrations** - - **Validates: Requirements 8.1** - - - [x] 13.3 Implement `getRegistrationDetail()` in `AdminController` - - Fetch full student profile and payment details for a given registration ID - - _Requirements: 8.2_ - - - [x] 13.4 Implement `approveRegistration()` and `rejectRegistration()` in `AdminController` - - Guard: reject if registration status is not `payment_verified` - - On approve: call `updateRegistrationStatus('approved')`, insert into `admin_actions`, call `NotificationService::sendStatusUpdate()` - - On reject: call `updateRegistrationStatus('rejected')`, record reason in `admin_actions`, notify student - - _Requirements: 8.3, 8.4, 8.5, 8.7, 11.4_ - - - [ ]* 13.5 Write property test for admin approval/rejection status guard - - **Property 20: Admin approval/rejection only allowed from payment_verified status** - - **Validates: Requirements 8.5** - - - [ ]* 13.6 Write property test for admin action recording - - **Property 21: Admin action is fully recorded** - - **Validates: Requirements 8.3, 8.4, 8.7, 11.4** - -- [-] 14. Implement NotificationService - - [x] 14.1 Implement `sendOtp()` and `sendStatusUpdate()` in `NotificationService` - - Integrate SMS gateway API (MSG91 or Twilio) for OTP delivery and status updates - - Integrate PHPMailer for email notifications as a fallback channel - - _Requirements: 9.1, 9.2, 9.3, 9.4, 9.5_ - - - [ ]* 14.2 Write unit tests for NotificationService - - Mock SMS gateway and email transport; verify correct message content and recipient for each notification type - - _Requirements: 9.1, 9.2, 9.3, 9.4, 9.5_ - -- [x] 15. Implement OTP token cleanup cron job - - Create `scripts/purge_otp_tokens.php` that deletes `otp_tokens` rows where `expires_at < NOW()` - - Register cron job to run on a scheduled basis (e.g., hourly) - - _Requirements: 13.3_ - -- [-] 16. Wire all components together via the front controller and router - - [x] 16.1 Implement router in `public/index.php` - - Map URL routes to controller actions; apply `SessionMiddleware` to all protected routes and `AdminMiddleware` to admin routes - - _Requirements: 3.3, 8.6, 10.10_ - - - [x] 16.2 Create view templates for all student-facing pages - - Sign-up form, login form, OTP entry, student portal dashboard, registration form, payment selection, bank transfer form, status tracker - - Inject CSRF token into all form templates - - _Requirements: 10.2_ - - - [x] 16.3 Create view templates for admin dashboard - - Pending registrations list, registration detail view with approve/reject actions - - _Requirements: 8.1, 8.2, 8.3, 8.4_ - - - [ ]* 16.4 Write integration tests for the full student flow - - Test sign-up → login → OTP → portal → eligibility → registration → payment → verification → approval end-to-end using a test database with fixture data - - Mock Razorpay SDK and Accounts_DB with known fixture records - - _Requirements: 1.5, 2.8, 4.4, 5.1, 6.2, 7.1, 8.3_ - -- [x] 17. Final checkpoint — Ensure all tests pass - - Ensure all tests pass, ask the user if questions arise. - -## Notes - -- Tasks marked with `*` are optional and can be skipped for a faster MVP -- Each task references specific requirements for traceability -- Checkpoints at tasks 4, 8, 12, and 17 ensure incremental validation -- Property tests use PHPUnit data providers or the `eris` library for PHP property-based testing -- Unit tests and property tests are complementary — both should be run together -- The Accounts_DB connection must always use a separate read-only PDO credential -- All DB queries must use PDO prepared statements (no raw string interpolation) diff --git a/README.md b/README.md deleted file mode 100644 index 516d7a4..0000000 --- a/README.md +++ /dev/null @@ -1,92 +0,0 @@ -# Semester Online Registration System - -A PHP/MySQL web application that digitizes the GBU university semester registration and payment process. - -## Requirements - -- PHP 8.1+ -- MySQL 8.0+ -- Composer -- Apache with `mod_rewrite` (or Nginx with equivalent `try_files` config) -- SSL certificate (HTTPS required) - -## Setup - -1. **Clone the repository** - ```bash - git clone - cd semester-online-registration - ``` - -2. **Copy environment file and fill in values** - ```bash - cp .env.example .env - # Edit .env with your database credentials, API keys, and app URL - ``` - -3. **Install PHP dependencies** - ```bash - composer install --no-dev # production - # or - composer install # development (includes PHPUnit) - ``` - -4. **Run database migrations** (in order) - ```bash - mysql -u -p < database/migrations/001_create_students.sql - mysql -u -p < database/migrations/002_create_otp_tokens.sql - mysql -u -p < database/migrations/003_create_registrations.sql - mysql -u -p < database/migrations/004_create_payments.sql - mysql -u -p < database/migrations/005_create_admin_actions.sql - ``` - -5. **Configure web server** - - Point the document root to the `public/` directory. - - Ensure `mod_rewrite` is enabled (Apache) or configure `try_files` (Nginx). - - Enable HTTPS — HTTP requests are automatically redirected. - -6. **Set up cron jobs** - ```cron - # Retry pending payment verifications every 6 hours - 0 */6 * * * php /path/to/app/scripts/retry_verification.php - - # Purge expired OTP tokens hourly - 0 * * * * php /path/to/app/scripts/purge_otp_tokens.php - ``` - -7. **Create the uploads directory** (outside web root) - ```bash - mkdir -p storage/uploads - chmod 750 storage/uploads - ``` - -## Running Tests - -```bash -./vendor/bin/phpunit --testdox -``` - -## Directory Structure - -``` -├── app/ -│ ├── Controllers/ -│ ├── Services/ -│ ├── Models/ -│ ├── Views/ -│ └── Middleware/ -├── config/ -│ ├── app.php -│ └── database.php -├── database/ -│ └── migrations/ -├── public/ ← web root -│ ├── index.php -│ └── .htaccess -├── scripts/ -├── storage/ -│ └── uploads/ ← receipt files (outside web root) -├── tests/ -├── .env.example -└── composer.json -``` diff --git a/admin/dashboard.php b/admin/dashboard.php new file mode 100644 index 0000000..acc6ae1 --- /dev/null +++ b/admin/dashboard.php @@ -0,0 +1,147 @@ +query("SELECT COUNT(*) FROM registrations")->fetchColumn(); + $today_count = $conn->query("SELECT COUNT(*) FROM registrations WHERE DATE(created_at) = CURDATE()")->fetchColumn(); // Need to check if created_at exists + + // Check if created_at exists, if not use a fallback or skip today_count for now + // Based on the SQL dump, created_at is NOT in the schema. Let's stick to total for now. + $today_count = 0; // Fallback + + $programme_stats = $conn->query("SELECT nameOfProgramme, COUNT(*) as count FROM registrations GROUP BY nameOfProgramme")->fetchAll(); + +} catch (PDOException $e) { + // Silent fail for stats or show 0 + $total_count = 0; + $programme_stats = []; +} +?> + +
+
+
+
+
+

Total Registrations

+

+
+
+
+
+
+
+
+

Recent Activity

+

View All

+
+
+
+
+
+
+
+

Active Semester

+

2024-25

+
+
+
+
+ +
+
+

Recent Registrations

+ +
+ +
+ + + + + + + + + + + + + + query("SELECT * FROM registrations ORDER BY id DESC LIMIT 50"); + while ($row = $stmt->fetch()): + ?> + + + + + + + + + + "; + } + ?> + +
Roll NumberFull NameProgrammeBranchYear/SemContactActions
Year / Sem + + + +
Error loading data: " . $e->getMessage() . "
+
+
+ + + + + + diff --git a/admin/delete_student.php b/admin/delete_student.php new file mode 100644 index 0000000..698d64e --- /dev/null +++ b/admin/delete_student.php @@ -0,0 +1,26 @@ +prepare("DELETE FROM registrations WHERE id = :id"); + $stmt->execute([':id' => $id]); + + set_flash_message("Registration record deleted successfully.", "success"); + redirect('dashboard.php'); +} catch (PDOException $e) { + set_flash_message("Error deleting record: " . $e->getMessage(), "danger"); + redirect('dashboard.php'); +} +?> diff --git a/admin/edit_student.php b/admin/edit_student.php new file mode 100644 index 0000000..d5dc935 --- /dev/null +++ b/admin/edit_student.php @@ -0,0 +1,319 @@ +prepare("SELECT * FROM registrations WHERE id = :id"); + $stmt->execute([':id' => $id]); + $student = $stmt->fetch(); + + if (!$student) { + set_flash_message("Student record not found.", "danger"); + redirect('dashboard.php'); + } +} catch (PDOException $e) { + set_flash_message("Error fetching record: " . $e->getMessage(), "danger"); + redirect('dashboard.php'); +} + +// Handle update form submission +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + try { + $sql = "UPDATE registrations SET + fullName = :fullName, + fathersName = :fathersName, + nameOfProgramme = :nameOfProgramme, + branchSpecialization = :branchSpecialization, + year = :year, + semester = :semester, + category = :category, + gender = :gender, + aadharCard = :aadharCard, + permanentAddress = :permanentAddress, + hostelAddress = :hostelAddress, + studentContact = :studentContact, + studentEmail = :studentEmail, + oddSemesterAmount = :oddSemesterAmount, + oddSemesterRemaining = :oddSemesterRemaining, + oddSemesterTxnDetails = :oddSemesterTxnDetails, + oddSemesterPlatform = :oddSemesterPlatform, + oddSemesterDate = :oddSemesterDate, + evenSemesterAmount = :evenSemesterAmount, + evenSemesterRemaining = :evenSemesterRemaining, + evenSemesterTxnDetails = :evenSemesterTxnDetails, + evenSemesterPlatform = :evenSemesterPlatform, + evenSemesterDate = :evenSemesterDate, + hostelAmount = :hostelAmount, + hostelRemaining = :hostelRemaining, + hostelTxnDetails = :hostelTxnDetails, + hostelPlatform = :hostelPlatform, + hostelDate = :hostelDate, + messAmount = :messAmount, + messRemaining = :messRemaining, + messTxnDetails = :messTxnDetails, + messPlatform = :messPlatform, + messDate = :messDate + WHERE id = :id"; + + $stmt = $conn->prepare($sql); + $stmt->execute([ + ':id' => $id, + ':fullName' => sanitize_input($_POST['fullName']), + ':fathersName' => sanitize_input($_POST['fathersName']), + ':nameOfProgramme' => sanitize_input($_POST['nameOfProgramme']), + ':branchSpecialization' => sanitize_input($_POST['branchSpecialization']), + ':year' => (int)$_POST['year'], + ':semester' => (int)$_POST['semester'], + ':category' => sanitize_input($_POST['category']), + ':gender' => sanitize_input($_POST['gender']), + ':aadharCard' => sanitize_input($_POST['aadharCard']), + ':permanentAddress' => sanitize_input($_POST['permanentAddress']), + ':hostelAddress' => sanitize_input($_POST['hostelAddress']), + ':studentContact' => sanitize_input($_POST['studentContact']), + ':studentEmail' => sanitize_input($_POST['studentEmail']), + ':oddSemesterAmount' => $_POST['oddSemesterAmount'] ?: 0, + ':oddSemesterRemaining' => $_POST['oddSemesterRemaining'] ?: 0, + ':oddSemesterTxnDetails' => $_POST['oddSemesterTxnDetails'] ?? '', + ':oddSemesterPlatform' => $_POST['oddSemesterPlatform'] ?? '', + ':oddSemesterDate' => $_POST['oddSemesterDate'] ?: null, + ':evenSemesterAmount' => $_POST['evenSemesterAmount'] ?: 0, + ':evenSemesterRemaining' => $_POST['evenSemesterRemaining'] ?: 0, + ':evenSemesterTxnDetails' => $_POST['evenSemesterTxnDetails'] ?? '', + ':evenSemesterPlatform' => $_POST['evenSemesterPlatform'] ?? '', + ':evenSemesterDate' => $_POST['evenSemesterDate'] ?: null, + ':hostelAmount' => $_POST['hostelAmount'] ?: 0, + ':hostelRemaining' => $_POST['hostelRemaining'] ?: 0, + ':hostelTxnDetails' => $_POST['hostelTxnDetails'] ?? '', + ':hostelPlatform' => $_POST['hostelPlatform'] ?? '', + ':hostelDate' => $_POST['hostelDate'] ?: null, + ':messAmount' => $_POST['messAmount'] ?: 0, + ':messRemaining' => $_POST['messRemaining'] ?: 0, + ':messTxnDetails' => $_POST['messTxnDetails'] ?? '', + ':messPlatform' => $_POST['messPlatform'] ?? '', + ':messDate' => $_POST['messDate'] ?: null + ]); + + set_flash_message("Registration record updated successfully.", "success"); + redirect('dashboard.php'); + } catch (PDOException $e) { + set_flash_message("Error updating record: " . $e->getMessage(), "danger"); + } +} +?> + +
+
+

Edit Registration:

+ Back to Dashboard +
+ +
+ +
+

Personal Details

+
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ +
+

Contact Details

+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+

Fee Details

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParticularsAmount PaidRemainingTxn DetailsPlatformDate
Odd Semester + +
Even Semester + +
Hostel Fee + +
Mess Fee + +
+
+
+ +
+ +
+
+
+ + + + diff --git a/admin/includes/footer.php b/admin/includes/footer.php new file mode 100644 index 0000000..335956d --- /dev/null +++ b/admin/includes/footer.php @@ -0,0 +1,27 @@ + + + + + + diff --git a/admin/includes/header.php b/admin/includes/header.php new file mode 100644 index 0000000..954b098 --- /dev/null +++ b/admin/includes/header.php @@ -0,0 +1,157 @@ + + + + + + + Admin Dashboard - GBU Registration + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +

Admin Portal

+
+
+ +
+
+ + diff --git a/admin/login.php b/admin/login.php new file mode 100644 index 0000000..2c11f0d --- /dev/null +++ b/admin/login.php @@ -0,0 +1,138 @@ + ['username' => 'superadmin', 'password' => 'ICT@admin25'], + 'office_admin' => ['username' => 'admin', 'password' => 'ICT@202525'] + ]; + + $logged_in = false; + foreach ($admins as $role => $credentials) { + if ($username === $credentials['username'] && $password === $credentials['password']) { + $_SESSION['admin_logged_in'] = true; + $_SESSION['admin_role'] = $role; + set_flash_message("Welcome back, Administrator!", "success"); + redirect('dashboard.php'); + $logged_in = true; + break; + } + } + + if (!$logged_in) { + $error = "Invalid username or password."; + } +} +?> + + + + + + Admin Login - GBU Registration + + + + + + + + + + + + + + + diff --git a/admin/logout.php b/admin/logout.php new file mode 100644 index 0000000..53aacf5 --- /dev/null +++ b/admin/logout.php @@ -0,0 +1,22 @@ + diff --git a/app/Controllers/.gitkeep b/app/Controllers/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/app/Controllers/AdminController.php b/app/Controllers/AdminController.php deleted file mode 100644 index 1fdd899..0000000 --- a/app/Controllers/AdminController.php +++ /dev/null @@ -1,257 +0,0 @@ -db = $db; - $this->verificationService = $verificationService; - $this->notification = $notification; - } - - // ------------------------------------------------------------------------- - // Dashboard — pending registrations - // ------------------------------------------------------------------------- - - /** - * Return all registrations with status = 'payment_verified'. - * - * Property 19: Admin dashboard shows only payment_verified registrations. - * Requirement 8.1 - * - * @return list - */ - public function getPendingRegistrations(): array - { - $stmt = $this->db->prepare( - "SELECT r.id, - r.student_id, - r.semester_id, - r.academic_year, - r.submitted_at, - s.full_name, - s.college_id, - s.department, - s.program, - p.payment_method, - p.amount, - p.verification_status AS payment_status - FROM registrations r - JOIN students s ON s.id = r.student_id - LEFT JOIN payments p ON p.registration_id = r.id - WHERE r.status = 'payment_verified' - ORDER BY r.submitted_at ASC" - ); - $stmt->execute(); - - return $stmt->fetchAll(); - } - - // ------------------------------------------------------------------------- - // Registration detail - // ------------------------------------------------------------------------- - - /** - * Return full student profile and payment details for a registration. - * - * Requirement 8.2 - * - * @return array{registration: array, student: array, payment: array|null}|array{error: string} - */ - public function getRegistrationDetail(int $registrationId): array - { - $stmt = $this->db->prepare( - 'SELECT r.id, - r.student_id, - r.semester_id, - r.academic_year, - r.subjects, - r.hostel_required, - r.transport, - r.remarks, - r.status, - r.submitted_at - FROM registrations r - WHERE r.id = :id - LIMIT 1' - ); - $stmt->execute([':id' => $registrationId]); - $registration = $stmt->fetch(); - - if ($registration === false) { - return ['error' => 'Registration not found']; - } - - // Fetch student profile - $stmt = $this->db->prepare( - 'SELECT id, college_id, full_name, mobile, email, department, program, current_semester - FROM students - WHERE id = :id - LIMIT 1' - ); - $stmt->execute([':id' => $registration['student_id']]); - $student = $stmt->fetch() ?: []; - - // Fetch payment details - $stmt = $this->db->prepare( - 'SELECT payment_method, amount, transaction_ref, bank_name, account_holder, - transfer_date, transfer_amount, receipt_path, verification_status, verified_at - FROM payments - WHERE registration_id = :registration_id - LIMIT 1' - ); - $stmt->execute([':registration_id' => $registrationId]); - $payment = $stmt->fetch() ?: null; - - return [ - 'registration' => $registration, - 'student' => $student, - 'payment' => $payment, - ]; - } - - // ------------------------------------------------------------------------- - // Approve registration - // ------------------------------------------------------------------------- - - /** - * Approve a registration that is in 'payment_verified' status. - * - * Property 20: Admin approval only allowed from payment_verified status. - * Property 21: Admin action is fully recorded. - * Requirements: 8.3, 8.5, 8.7, 11.4 - * - * @return array{success: bool, message: string} - */ - public function approveRegistration(int $registrationId, int $adminId): array - { - $registration = $this->fetchRegistration($registrationId); - - if ($registration === null) { - return ['success' => false, 'message' => 'Registration not found']; - } - - // Guard: must be in payment_verified status (Requirement 8.5) - if ($registration['status'] !== 'payment_verified') { - return ['success' => false, 'message' => 'Registration is not in a verifiable state']; - } - - // Forward-only status update via PaymentVerificationService (Requirement 11.1) - $updated = $this->verificationService->updateRegistrationStatus($registrationId, 'approved'); - - if (!$updated) { - return ['success' => false, 'message' => 'Failed to update registration status']; - } - - // Log admin action (Requirements 8.7, 11.4) - $this->logAdminAction($registrationId, $adminId, 'approved', null); - - // Notify student (Requirement 8.3) - $this->notification->sendStatusUpdate( - (int) $registration['student_id'], - 'approved', - 'Your semester registration has been approved.' - ); - - return ['success' => true, 'message' => 'Registration approved successfully']; - } - - // ------------------------------------------------------------------------- - // Reject registration - // ------------------------------------------------------------------------- - - /** - * Reject a registration that is in 'payment_verified' status. - * - * Property 20: Admin rejection only allowed from payment_verified status. - * Property 21: Admin action is fully recorded. - * Requirements: 8.4, 8.5, 8.7, 11.4 - * - * @return array{success: bool, message: string} - */ - public function rejectRegistration(int $registrationId, int $adminId, string $reason): array - { - $registration = $this->fetchRegistration($registrationId); - - if ($registration === null) { - return ['success' => false, 'message' => 'Registration not found']; - } - - // Guard: must be in payment_verified status (Requirement 8.5) - if ($registration['status'] !== 'payment_verified') { - return ['success' => false, 'message' => 'Registration is not in a verifiable state']; - } - - // Forward-only status update (Requirement 11.1) - $updated = $this->verificationService->updateRegistrationStatus($registrationId, 'rejected'); - - if (!$updated) { - return ['success' => false, 'message' => 'Failed to update registration status']; - } - - // Log admin action with reason (Requirements 8.7, 11.4) - $this->logAdminAction($registrationId, $adminId, 'rejected', $reason); - - // Notify student (Requirement 8.4) - $this->notification->sendStatusUpdate( - (int) $registration['student_id'], - 'rejected', - 'Your semester registration has been rejected. Reason: ' . $reason - ); - - return ['success' => true, 'message' => 'Registration rejected']; - } - - // ------------------------------------------------------------------------- - // Private helpers - // ------------------------------------------------------------------------- - - private function fetchRegistration(int $registrationId): ?array - { - $stmt = $this->db->prepare( - 'SELECT id, student_id, status FROM registrations WHERE id = :id LIMIT 1' - ); - $stmt->execute([':id' => $registrationId]); - $row = $stmt->fetch(); - - return $row ?: null; - } - - /** - * Insert a row into admin_actions for audit trail. - * Requirements: 8.7, 11.4 - */ - private function logAdminAction(int $registrationId, int $adminId, string $action, ?string $notes): void - { - $stmt = $this->db->prepare( - 'INSERT INTO admin_actions (registration_id, admin_id, action, notes) - VALUES (:registration_id, :admin_id, :action, :notes)' - ); - $stmt->execute([ - ':registration_id' => $registrationId, - ':admin_id' => $adminId, - ':action' => $action, - ':notes' => $notes, - ]); - } -} diff --git a/app/Controllers/AuthController.php b/app/Controllers/AuthController.php deleted file mode 100644 index a06f48a..0000000 --- a/app/Controllers/AuthController.php +++ /dev/null @@ -1,335 +0,0 @@ -db = $db; - $this->notification = $notification; - } - - // ------------------------------------------------------------------------- - // Sign-Up - // ------------------------------------------------------------------------- - - /** - * Register a new student account. - * - * @param array{ - * college_id: string, - * full_name: string, - * mobile: string, - * email?: string, - * department?: string, - * program?: string, - * current_semester?: int, - * password: string, - * confirm_password: string - * } $formData - * @return array{success: bool, errors: list} - */ - public function signUp(array $formData): array - { - $errors = []; - - $collegeId = trim((string) ($formData['college_id'] ?? '')); - $fullName = trim((string) ($formData['full_name'] ?? '')); - $mobile = trim((string) ($formData['mobile'] ?? '')); - $email = trim((string) ($formData['email'] ?? '')); - $department = trim((string) ($formData['department'] ?? '')); - $program = trim((string) ($formData['program'] ?? '')); - $currentSemester = isset($formData['current_semester']) - ? (int) $formData['current_semester'] - : null; - $password = (string) ($formData['password'] ?? ''); - $confirmPassword = (string) ($formData['confirm_password'] ?? ''); - - // 1. Password match (Requirement 1.3) - if ($password !== $confirmPassword) { - $errors[] = 'Passwords do not match'; - } - - // 2. Mobile validation — 10-digit Indian mobile (Requirement 1.4) - if (!preg_match('/^[6-9]\d{9}$/', $mobile)) { - $errors[] = 'Invalid mobile number'; - } - - // 3. College ID must exist in the enrolled_students master table (Requirement 1.1) - if (!$this->collegeIdExistsInMasterList($collegeId)) { - $errors[] = 'College ID not found. Only enrolled students may register.'; - } - - // 4. No duplicate account for this College ID (Requirement 1.2) - if ($this->studentAccountExists($collegeId)) { - $errors[] = 'An account already exists for this College ID.'; - } - - if (!empty($errors)) { - return ['success' => false, 'errors' => $errors]; - } - - // 5. Hash password with bcrypt cost 12 (Requirement 1.6) - $passwordHash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]); - - // 6. Insert student record with is_active = 1 (Requirement 1.5) - $stmt = $this->db->prepare( - 'INSERT INTO students - (college_id, full_name, mobile, email, department, program, current_semester, password_hash, is_active) - VALUES - (:college_id, :full_name, :mobile, :email, :department, :program, :current_semester, :password_hash, 1)' - ); - - $stmt->execute([ - ':college_id' => $collegeId, - ':full_name' => $fullName, - ':mobile' => $mobile, - ':email' => $email ?: null, - ':department' => $department ?: null, - ':program' => $program ?: null, - ':current_semester' => $currentSemester, - ':password_hash' => $passwordHash, - ]); - - // 7. Send welcome SMS (Requirement 1.7) - $this->notification->sendWelcomeSms($mobile); - - return ['success' => true, 'errors' => []]; - } - - // ------------------------------------------------------------------------- - // Login / MFA - // ------------------------------------------------------------------------- - - /** - * Initiate MFA login: validate credentials and dispatch OTP. - * - * Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 10.6, 10.8, 12.3 - * - * @return array{success: bool, message: string} - */ - public function login(string $rollNo, string $mobile): array - { - $rollNo = trim($rollNo); - $mobile = trim($mobile); - - // Look up student by roll number (college_id) - $stmt = $this->db->prepare( - 'SELECT id, mobile, is_active FROM students WHERE college_id = :roll_no LIMIT 1' - ); - $stmt->execute([':roll_no' => $rollNo]); - $student = $stmt->fetch(); - - // Requirement 2.1 — unknown roll number - if ($student === false) { - return ['success' => false, 'message' => 'Invalid credentials']; - } - - // Requirement 2.2 — mobile mismatch - if ($student['mobile'] !== $mobile) { - return ['success' => false, 'message' => 'Invalid credentials']; - } - - // Requirement 2.3 — deactivated account - if ((int) $student['is_active'] === 0) { - return ['success' => false, 'message' => 'Account is deactivated']; - } - - // Requirement 2.4 / 10.6 — OTP rate limit (max 3 per 10 minutes) - $studentId = (int) $student['id']; - if ($this->otpRequestCount($studentId, 10) >= 3) { - return ['success' => false, 'message' => 'Too many OTP requests. Try after 10 minutes.']; - } - - // Generate OTP, hash it, store in otp_tokens (Requirement 2.5, 10.8, 12.3) - $otp = $this->generateNumericOtp(6); - $otpHash = password_hash($otp, PASSWORD_BCRYPT, ['cost' => 10]); - - $stmt = $this->db->prepare( - 'INSERT INTO otp_tokens (student_id, otp_hash, expires_at) - VALUES (:student_id, :otp_hash, DATE_ADD(NOW(), INTERVAL 5 MINUTE))' - ); - $stmt->execute([ - ':student_id' => $studentId, - ':otp_hash' => $otpHash, - ]); - - // Deliver OTP via SMS - $this->notification->sendOtp($mobile, $otp); - - return ['success' => true, 'message' => 'OTP sent to registered mobile']; - } - - /** - * Verify the submitted OTP and create a session on success. - * - * Requirements: 2.6, 2.7, 2.8, 2.9, 2.10, 10.4, 10.5, 12.4, 12.5 - * - * @return array{success: bool, message: string} - */ - public function verifyOtp(string $rollNo, string $otp): array - { - $rollNo = trim($rollNo); - - $stmt = $this->db->prepare( - 'SELECT id FROM students WHERE college_id = :roll_no LIMIT 1' - ); - $stmt->execute([':roll_no' => $rollNo]); - $student = $stmt->fetch(); - - if ($student === false) { - return ['success' => false, 'message' => 'Invalid credentials']; - } - - $studentId = (int) $student['id']; - - // Fetch latest unused, unexpired token - $stmt = $this->db->prepare( - 'SELECT id, otp_hash FROM otp_tokens - WHERE student_id = :student_id - AND used = 0 - AND expires_at > NOW() - ORDER BY created_at DESC - LIMIT 1' - ); - $stmt->execute([':student_id' => $studentId]); - $token = $stmt->fetch(); - - // Requirement 2.6 / 12.5 — expired or missing token - if ($token === false) { - return ['success' => false, 'message' => 'OTP expired or invalid']; - } - - // Requirement 2.7 — wrong OTP - if (!password_verify($otp, $token['otp_hash'])) { - return ['success' => false, 'message' => 'OTP expired or invalid']; - } - - // Requirement 2.8 / 12.4 — mark token used - $stmt = $this->db->prepare('UPDATE otp_tokens SET used = 1 WHERE id = :id'); - $stmt->execute([':id' => $token['id']]); - - // Requirement 2.10 / 10.4 — regenerate session ID on privilege change - if (session_status() !== PHP_SESSION_ACTIVE) { - session_start(); - } - session_regenerate_id(true); - - // Requirement 2.8 — create session with student_id and role - $_SESSION['student_id'] = $studentId; - $_SESSION['role'] = 'student'; - $_SESSION['last_activity'] = time(); - - return ['success' => true, 'message' => 'Login successful']; - } - - /** - * Destroy the current session. - */ - public function logout(): void - { - if (session_status() === PHP_SESSION_ACTIVE) { - $_SESSION = []; - session_destroy(); - } - } - - /** - * Check whether the current request has an authenticated student session. - */ - public function isAuthenticated(): bool - { - return !empty($_SESSION['student_id']) && !empty($_SESSION['role']); - } - - /** - * Check whether the current session belongs to an admin user. - */ - public function isAdmin(): bool - { - return $this->isAuthenticated() && $_SESSION['role'] === 'admin'; - } - - // ------------------------------------------------------------------------- - // Private helpers - // ------------------------------------------------------------------------- - - /** - * Return true if $collegeId exists in the enrolled_students master table. - * Uses a PDO prepared statement to prevent SQL injection (Requirement 10.3). - */ - private function collegeIdExistsInMasterList(string $collegeId): bool - { - $stmt = $this->db->prepare( - 'SELECT 1 FROM enrolled_students WHERE college_id = :college_id LIMIT 1' - ); - $stmt->execute([':college_id' => $collegeId]); - - return $stmt->fetchColumn() !== false; - } - - /** - * Return true if a student account already exists for $collegeId. - */ - private function studentAccountExists(string $collegeId): bool - { - $stmt = $this->db->prepare( - 'SELECT 1 FROM students WHERE college_id = :college_id LIMIT 1' - ); - $stmt->execute([':college_id' => $collegeId]); - - return $stmt->fetchColumn() !== false; - } - - /** - * Generate a cryptographically secure numeric OTP of the given length. - * - * Postconditions: - * - Returns a string of exactly $length decimal digits (0–9). - * - Uses random_int() — a CSPRNG source. - * - * Requirements: 12.1, 12.2 - */ - private function generateNumericOtp(int $length = 6): string - { - $otp = ''; - for ($i = 0; $i < $length; $i++) { - $otp .= (string) random_int(0, 9); - } - return $otp; - } - - /** - * Count OTP requests made by $studentId within the last $minutes minutes. - * Used to enforce the rate limit (max 3 per 10 minutes). - * - * Requirements: 2.4, 10.6 - */ - private function otpRequestCount(int $studentId, int $minutes): int - { - $stmt = $this->db->prepare( - 'SELECT COUNT(*) FROM otp_tokens - WHERE student_id = :student_id - AND created_at >= DATE_SUB(NOW(), INTERVAL :minutes MINUTE)' - ); - $stmt->execute([ - ':student_id' => $studentId, - ':minutes' => $minutes, - ]); - - return (int) $stmt->fetchColumn(); - } -} diff --git a/app/Controllers/PaymentController.php b/app/Controllers/PaymentController.php deleted file mode 100644 index 6f5ba7c..0000000 --- a/app/Controllers/PaymentController.php +++ /dev/null @@ -1,320 +0,0 @@ -db = $db; - $this->verificationService = $verificationService; - $this->razorpay = $razorpay; - } - - // ------------------------------------------------------------------------- - // Initiate payment (UPI / Razorpay) - // ------------------------------------------------------------------------- - - /** - * Create a Razorpay order and return the order details for the checkout JS. - * - * Property 15: Payment submission only allowed from pending_payment status. - * Requirements: 6.1, 6.5 - * - * @return array{success: bool, order?: array, error?: string} - */ - public function initiatePayment(int $registrationId, string $method): array - { - $registration = $this->fetchRegistration($registrationId); - - if ($registration === null) { - return ['success' => false, 'error' => 'Registration not found']; - } - - // Guard: only allowed from pending_payment status (Requirement 6.5) - if ($registration['status'] !== 'pending_payment') { - return ['success' => false, 'error' => 'Payment cannot be initiated for this registration']; - } - - if (!in_array($method, ['upi', 'razorpay'], true)) { - return ['success' => false, 'error' => 'Invalid payment method']; - } - - // Fetch fee amount for this semester - $amount = $this->getSemesterFee($registration['semester_id'], $registration['student_id']); - - // Create Razorpay order (amount in paise) - $order = $this->razorpay->order->create([ - 'amount' => (int) round($amount * 100), - 'currency' => 'INR', - 'receipt' => 'reg_' . $registrationId, - 'payment_capture' => 1, - ]); - - // Persist payment record (verification_status = pending) - $stmt = $this->db->prepare( - 'INSERT INTO payments (registration_id, student_id, amount, payment_method, transaction_ref) - VALUES (:registration_id, :student_id, :amount, :method, :order_id)' - ); - $stmt->execute([ - ':registration_id' => $registrationId, - ':student_id' => $registration['student_id'], - ':amount' => $amount, - ':method' => $method, - ':order_id' => $order->id, - ]); - - return [ - 'success' => true, - 'order' => [ - 'id' => $order->id, - 'amount' => $order->amount, - 'currency' => $order->currency, - ], - ]; - } - - // ------------------------------------------------------------------------- - // Gateway callback - // ------------------------------------------------------------------------- - - /** - * Handle the Razorpay payment callback after checkout. - * - * Requirements: 6.2, 6.4 - * - * @param array{razorpay_order_id: string, razorpay_payment_id: string, razorpay_signature: string} $callbackData - * @return array{success: bool, error?: string} - */ - public function handleGatewayCallback(array $callbackData): array - { - $orderId = $callbackData['razorpay_order_id'] ?? ''; - $paymentId = $callbackData['razorpay_payment_id'] ?? ''; - $signature = $callbackData['razorpay_signature'] ?? ''; - - // Validate Razorpay signature - try { - $this->razorpay->utility->verifyPaymentSignature([ - 'razorpay_order_id' => $orderId, - 'razorpay_payment_id' => $paymentId, - 'razorpay_signature' => $signature, - ]); - } catch (\Exception $e) { - return ['success' => false, 'error' => 'Invalid payment signature']; - } - - // Update transaction_ref with the actual payment ID - $stmt = $this->db->prepare( - 'UPDATE payments SET transaction_ref = :payment_id WHERE transaction_ref = :order_id' - ); - $stmt->execute([ - ':payment_id' => $paymentId, - ':order_id' => $orderId, - ]); - - // Fetch registration_id for this payment - $stmt = $this->db->prepare( - 'SELECT registration_id FROM payments WHERE transaction_ref = :payment_id LIMIT 1' - ); - $stmt->execute([':payment_id' => $paymentId]); - $payment = $stmt->fetch(); - - if ($payment === false) { - return ['success' => false, 'error' => 'Payment record not found']; - } - - $registrationId = (int) $payment['registration_id']; - - // Update registration status to payment_submitted - $this->updateRegistrationStatus($registrationId, 'payment_submitted'); - - // Trigger verification (Requirement 6.4) - $this->verificationService->verify($registrationId); - - return ['success' => true]; - } - - // ------------------------------------------------------------------------- - // Bank transfer submission - // ------------------------------------------------------------------------- - - /** - * Submit bank transfer details and receipt for a registration. - * - * Requirements: 6.3, 6.4, 6.5, 6.6, 10.9 - * - * @param array{ - * bank_name: string, - * account_holder: string, - * transfer_date: string, - * transfer_amount: float, - * transaction_ref: string - * } $transferDetails - * @param array|null $uploadedFile $_FILES['receipt'] entry - * @return array{success: bool, error?: string} - */ - public function submitBankTransfer(int $registrationId, array $transferDetails, ?array $uploadedFile = null): array - { - $registration = $this->fetchRegistration($registrationId); - - if ($registration === null) { - return ['success' => false, 'error' => 'Registration not found']; - } - - // Guard: only allowed from pending_payment status (Requirement 6.5) - if ($registration['status'] !== 'pending_payment') { - return ['success' => false, 'error' => 'Payment cannot be submitted for this registration']; - } - - // Validate and store receipt file (Requirements 6.6, 10.9) - $receiptPath = null; - if ($uploadedFile !== null && $uploadedFile['error'] === UPLOAD_ERR_OK) { - $mimeType = mime_content_type($uploadedFile['tmp_name']); - if (!in_array($mimeType, self::ALLOWED_MIME_TYPES, true)) { - return ['success' => false, 'error' => 'Invalid file type. Only PDF, JPEG, and PNG are accepted.']; - } - - $ext = pathinfo($uploadedFile['name'], PATHINFO_EXTENSION); - $filename = 'receipt_' . $registrationId . '_' . time() . '.' . $ext; - $destination = self::UPLOAD_DIR . $filename; - - if (!move_uploaded_file($uploadedFile['tmp_name'], $destination)) { - return ['success' => false, 'error' => 'Failed to store receipt file']; - } - - $receiptPath = $filename; - } - - $amount = $this->getSemesterFee($registration['semester_id'], $registration['student_id']); - - // Persist payment record - $stmt = $this->db->prepare( - 'INSERT INTO payments - (registration_id, student_id, amount, payment_method, transaction_ref, - bank_name, account_holder, transfer_date, transfer_amount, receipt_path) - VALUES - (:registration_id, :student_id, :amount, \'bank_transfer\', :transaction_ref, - :bank_name, :account_holder, :transfer_date, :transfer_amount, :receipt_path)' - ); - $stmt->execute([ - ':registration_id' => $registrationId, - ':student_id' => $registration['student_id'], - ':amount' => $amount, - ':transaction_ref' => trim($transferDetails['transaction_ref'] ?? ''), - ':bank_name' => trim($transferDetails['bank_name'] ?? ''), - ':account_holder' => trim($transferDetails['account_holder'] ?? ''), - ':transfer_date' => $transferDetails['transfer_date'] ?? null, - ':transfer_amount' => $transferDetails['transfer_amount'] ?? $amount, - ':receipt_path' => $receiptPath, - ]); - - // Update registration status to payment_submitted - $this->updateRegistrationStatus($registrationId, 'payment_submitted'); - - // Trigger verification (Requirement 6.4) - $this->verificationService->verify($registrationId); - - return ['success' => true]; - } - - // ------------------------------------------------------------------------- - // Payment status - // ------------------------------------------------------------------------- - - /** - * Return the current payment status for a registration. - * - * @return array{payment_method?: string, verification_status?: string, amount?: float}|array{error: string} - */ - public function getPaymentStatus(int $registrationId): array - { - $stmt = $this->db->prepare( - 'SELECT payment_method, verification_status, amount - FROM payments - WHERE registration_id = :registration_id - LIMIT 1' - ); - $stmt->execute([':registration_id' => $registrationId]); - $row = $stmt->fetch(); - - if ($row === false) { - return ['error' => 'No payment record found']; - } - - return $row; - } - - // ------------------------------------------------------------------------- - // Private helpers - // ------------------------------------------------------------------------- - - private function fetchRegistration(int $registrationId): ?array - { - $stmt = $this->db->prepare( - 'SELECT id, student_id, semester_id, status - FROM registrations - WHERE id = :id - LIMIT 1' - ); - $stmt->execute([':id' => $registrationId]); - $row = $stmt->fetch(); - - return $row ?: null; - } - - private function updateRegistrationStatus(int $registrationId, string $status): void - { - $stmt = $this->db->prepare( - 'UPDATE registrations SET status = :status WHERE id = :id' - ); - $stmt->execute([':status' => $status, ':id' => $registrationId]); - } - - /** - * Fetch the semester fee for the student's program. - * Falls back to a default fee if no specific record is found. - */ - private function getSemesterFee(int $semesterId, int $studentId): float - { - try { - $stmt = $this->db->prepare( - 'SELECT sf.amount - FROM semester_fees sf - JOIN students s ON s.program = sf.program - WHERE sf.semester_id = :semester_id - AND s.id = :student_id - LIMIT 1' - ); - $stmt->execute([':semester_id' => $semesterId, ':student_id' => $studentId]); - $amount = $stmt->fetchColumn(); - return $amount !== false ? (float) $amount : 0.0; - } catch (\PDOException) { - return 0.0; - } - } -} diff --git a/app/Controllers/RegistrationController.php b/app/Controllers/RegistrationController.php deleted file mode 100644 index 6e0f244..0000000 --- a/app/Controllers/RegistrationController.php +++ /dev/null @@ -1,246 +0,0 @@ -db = $db; - } - - // ------------------------------------------------------------------------- - // Eligibility check - // ------------------------------------------------------------------------- - - /** - * Check whether a student is eligible to register for the given semester. - * - * This method is read-only — it does not mutate any DB row. - * Property 11: Eligibility check is read-only (pure function). - * - * Requirements: 4.1, 4.2, 4.3, 4.4, 4.5 - * - * @return array{eligible: bool, reason: string} - */ - public function checkEligibility(int $studentId, int $semesterId): array - { - $student = $this->fetchStudent($studentId); - - if ($student === null) { - return ['eligible' => false, 'reason' => 'Student not found']; - } - - // Requirement 4.1 — semester must match student's current semester - if ((int) $student['current_semester'] !== $semesterId) { - return ['eligible' => false, 'reason' => 'Semester mismatch']; - } - - // Requirement 4.2 — no existing non-rejected registration for this semester/year - $academicYear = $this->currentAcademicYear(); - $existing = $this->findRegistration($studentId, $semesterId, $academicYear); - - if ($existing !== null && $existing['status'] !== 'rejected') { - return ['eligible' => false, 'reason' => 'Already registered for this semester']; - } - - // Requirement 4.3 — no pending dues - $pendingDues = $this->getPendingDues($studentId); - if ($pendingDues > 0) { - return ['eligible' => false, 'reason' => 'Pending dues of ₹' . number_format($pendingDues, 2)]; - } - - return ['eligible' => true, 'reason' => '']; - } - - // ------------------------------------------------------------------------- - // Registration form - // ------------------------------------------------------------------------- - - /** - * Return the registration form data (available subjects, student info). - * - * Requirements: 4.4 - * - * @return array{student: array, subjects: list, semester_id: int, academic_year: string} - */ - public function getRegistrationForm(int $studentId, int $semesterId): array - { - $eligibility = $this->checkEligibility($studentId, $semesterId); - - if (!$eligibility['eligible']) { - return ['error' => $eligibility['reason']]; - } - - $student = $this->fetchStudent($studentId); - - // Fetch available subjects for this semester - $stmt = $this->db->prepare( - 'SELECT code, name, credits - FROM subjects - WHERE semester = :semester - AND is_active = 1 - ORDER BY code ASC' - ); - $stmt->execute([ - ':semester' => $semesterId, - ]); - $subjects = $stmt->fetchAll(); - - return [ - 'student' => $student, - 'subjects' => $subjects, - 'semester_id' => $semesterId, - 'academic_year' => $this->currentAcademicYear(), - ]; - } - - // ------------------------------------------------------------------------- - // Registration submission - // ------------------------------------------------------------------------- - - /** - * Submit a semester registration for the student. - * - * Re-runs eligibility check before persisting to prevent race conditions. - * Property 12: Ineligible students cannot register. - * Property 13: Registration submission creates correct initial state. - * - * Requirements: 5.1, 5.2, 5.3, 5.4 - * - * @param array{ - * semester_id: int, - * subjects: list, - * hostel_required?: bool, - * transport?: string, - * remarks?: string - * } $formData - * @return array{success: bool, registration_id?: int, error?: string} - */ - public function submitRegistration(int $studentId, array $formData): array - { - $semesterId = (int) ($formData['semester_id'] ?? 0); - - // Re-check eligibility (Requirement 5.3) - $eligibility = $this->checkEligibility($studentId, $semesterId); - if (!$eligibility['eligible']) { - return ['success' => false, 'error' => $eligibility['reason']]; - } - - $subjects = $formData['subjects'] ?? []; - if (empty($subjects) || !is_array($subjects)) { - return ['success' => false, 'error' => 'At least one subject must be selected']; - } - - $academicYear = $this->currentAcademicYear(); - $hostelRequired = !empty($formData['hostel_required']) ? 1 : 0; - $transport = trim((string) ($formData['transport'] ?? '')); - $remarks = trim((string) ($formData['remarks'] ?? '')); - - // Persist registration with status = 'pending_payment' and submitted_at = NOW() - // Requirements: 5.1, 5.2, 5.4 - $stmt = $this->db->prepare( - 'INSERT INTO registrations - (student_id, semester_id, academic_year, subjects, hostel_required, transport, remarks, status, submitted_at) - VALUES - (:student_id, :semester_id, :academic_year, :subjects, :hostel_required, :transport, :remarks, \'pending_payment\', NOW())' - ); - - $stmt->execute([ - ':student_id' => $studentId, - ':semester_id' => $semesterId, - ':academic_year' => $academicYear, - ':subjects' => json_encode(array_values($subjects), JSON_THROW_ON_ERROR), - ':hostel_required' => $hostelRequired, - ':transport' => $transport ?: null, - ':remarks' => $remarks ?: null, - ]); - - $registrationId = (int) $this->db->lastInsertId(); - - return ['success' => true, 'registration_id' => $registrationId]; - } - - // ------------------------------------------------------------------------- - // Private helpers - // ------------------------------------------------------------------------- - - private function fetchStudent(int $studentId): ?array - { - $stmt = $this->db->prepare( - 'SELECT id, college_id, current_semester, program - FROM students - WHERE id = :id - LIMIT 1' - ); - $stmt->execute([':id' => $studentId]); - $row = $stmt->fetch(); - - return $row ?: null; - } - - private function findRegistration(int $studentId, int $semesterId, string $academicYear): ?array - { - $stmt = $this->db->prepare( - 'SELECT id, status - FROM registrations - WHERE student_id = :student_id - AND semester_id = :semester_id - AND academic_year = :academic_year - ORDER BY created_at DESC - LIMIT 1' - ); - $stmt->execute([ - ':student_id' => $studentId, - ':semester_id' => $semesterId, - ':academic_year' => $academicYear, - ]); - $row = $stmt->fetch(); - - return $row ?: null; - } - - /** - * Return the total pending dues for the student (in rupees). - * Queries the payments table for any failed/unverified amounts. - * Returns 0 if no dues exist. - */ - private function getPendingDues(int $studentId): float - { - // Pending dues are tracked in a separate dues table or derived from - // registrations that were approved but have unverified payments. - // For now, query a `student_dues` view/table if it exists; default to 0. - try { - $stmt = $this->db->prepare( - 'SELECT COALESCE(SUM(amount), 0) FROM student_dues WHERE student_id = :student_id AND settled = 0' - ); - $stmt->execute([':student_id' => $studentId]); - return (float) $stmt->fetchColumn(); - } catch (\PDOException) { - // Table may not exist in all environments; treat as no dues - return 0.0; - } - } - - private function currentAcademicYear(): string - { - $month = (int) date('n'); - $year = (int) date('Y'); - if ($month >= 7) { - return $year . '-' . ($year + 1); - } - return ($year - 1) . '-' . $year; - } -} diff --git a/app/Controllers/StudentPortalController.php b/app/Controllers/StudentPortalController.php deleted file mode 100644 index 4adc92e..0000000 --- a/app/Controllers/StudentPortalController.php +++ /dev/null @@ -1,189 +0,0 @@ -db = $db; - } - - // ------------------------------------------------------------------------- - // Dashboard - // ------------------------------------------------------------------------- - - /** - * Return aggregated dashboard data for the given student. - * - * Caches the result in the PHP session for CACHE_TTL seconds to reduce - * DB load during peak registration periods (Requirement 3.4, 13.4). - * - * @return array{student: array, semester_history: list, registration_status: array} - */ - public function dashboard(int $studentId): array - { - $cacheKey = "dashboard_{$studentId}"; - - // Return cached data if still fresh - if ($this->isCacheValid($cacheKey)) { - return $_SESSION['cache'][$cacheKey]['data']; - } - - $student = $this->fetchStudent($studentId); - $semesterHistory = $this->getSemesterHistory($studentId); - $regStatus = $this->getRegistrationStatus($studentId, (int) ($student['current_semester'] ?? 0)); - - $data = [ - 'student' => $student, - 'semester_history' => $semesterHistory, - 'registration_status' => $regStatus, - ]; - - $this->cacheSet($cacheKey, $data); - - return $data; - } - - // ------------------------------------------------------------------------- - // Semester history - // ------------------------------------------------------------------------- - - /** - * Return all registration records for the student, ordered by semester. - * - * Requirements: 3.1, 3.2 - * - * @return list - */ - public function getSemesterHistory(int $studentId): array - { - $stmt = $this->db->prepare( - 'SELECT r.id, - r.semester_id, - r.academic_year, - r.status AS registration_status, - r.submitted_at, - p.payment_method, - p.verification_status AS payment_status, - p.amount - FROM registrations r - LEFT JOIN payments p ON p.registration_id = r.id - WHERE r.student_id = :student_id - ORDER BY r.semester_id ASC, r.created_at DESC' - ); - $stmt->execute([':student_id' => $studentId]); - - return $stmt->fetchAll(); - } - - // ------------------------------------------------------------------------- - // Current registration status - // ------------------------------------------------------------------------- - - /** - * Return the registration and payment status for the student's current semester. - * - * Requirement 3.2 - * - * @return array{registration: array|null, payment: array|null} - */ - public function getRegistrationStatus(int $studentId, int $semesterId): array - { - if ($semesterId === 0) { - return ['registration' => null, 'payment' => null]; - } - - $academicYear = $this->currentAcademicYear(); - - $stmt = $this->db->prepare( - 'SELECT r.id, - r.status, - r.submitted_at, - p.payment_method, - p.verification_status AS payment_status, - p.amount, - p.transaction_ref - FROM registrations r - LEFT JOIN payments p ON p.registration_id = r.id - WHERE r.student_id = :student_id - AND r.semester_id = :semester_id - AND r.academic_year = :academic_year - ORDER BY r.created_at DESC - LIMIT 1' - ); - $stmt->execute([ - ':student_id' => $studentId, - ':semester_id' => $semesterId, - ':academic_year' => $academicYear, - ]); - - $row = $stmt->fetch(); - - return [ - 'registration' => $row ?: null, - 'payment' => $row ? [ - 'method' => $row['payment_method'], - 'verification_status' => $row['payment_status'], - 'amount' => $row['amount'], - 'transaction_ref' => $row['transaction_ref'], - ] : null, - ]; - } - - // ------------------------------------------------------------------------- - // Private helpers - // ------------------------------------------------------------------------- - - private function fetchStudent(int $studentId): array - { - $stmt = $this->db->prepare( - 'SELECT id, college_id, full_name, mobile, email, department, program, current_semester - FROM students - WHERE id = :id - LIMIT 1' - ); - $stmt->execute([':id' => $studentId]); - - return $stmt->fetch() ?: []; - } - - private function currentAcademicYear(): string - { - $month = (int) date('n'); - $year = (int) date('Y'); - // Academic year starts in July - if ($month >= 7) { - return $year . '-' . ($year + 1); - } - return ($year - 1) . '-' . $year; - } - - private function isCacheValid(string $key): bool - { - if (empty($_SESSION['cache'][$key])) { - return false; - } - return (time() - $_SESSION['cache'][$key]['ts']) < self::CACHE_TTL; - } - - private function cacheSet(string $key, mixed $data): void - { - $_SESSION['cache'][$key] = ['ts' => time(), 'data' => $data]; - } -} diff --git a/app/Middleware/.gitkeep b/app/Middleware/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/app/Middleware/AdminMiddleware.php b/app/Middleware/AdminMiddleware.php deleted file mode 100644 index 24c044e..0000000 --- a/app/Middleware/AdminMiddleware.php +++ /dev/null @@ -1,31 +0,0 @@ -getToken(), ENT_QUOTES, 'UTF-8'); - return ''; - } -} diff --git a/app/Middleware/SessionMiddleware.php b/app/Middleware/SessionMiddleware.php deleted file mode 100644 index 64d1d25..0000000 --- a/app/Middleware/SessionMiddleware.php +++ /dev/null @@ -1,57 +0,0 @@ - 1800) { - $this->destroySession(); - $this->redirectToLogin(); - return; - } - } - - // Require authenticated session - if (empty($_SESSION['student_id']) || empty($_SESSION['role'])) { - $this->redirectToLogin(); - return; - } - - // Refresh last activity timestamp - $_SESSION['last_activity'] = time(); - } - - private function destroySession(): void - { - $_SESSION = []; - session_destroy(); - } - - private function redirectToLogin(): void - { - header('Location: /login', true, 302); - exit; - } -} diff --git a/app/Models/.gitkeep b/app/Models/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/app/Services/.gitkeep b/app/Services/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/app/Services/NotificationService.php b/app/Services/NotificationService.php deleted file mode 100644 index 16377bc..0000000 --- a/app/Services/NotificationService.php +++ /dev/null @@ -1,194 +0,0 @@ -smsApiKey = $_ENV['SMS_API_KEY'] ?? getenv('SMS_API_KEY') ?: ''; - $this->smsSenderId = $_ENV['SMS_SENDER_ID'] ?? getenv('SMS_SENDER_ID') ?: 'SEMREG'; - $this->smsDomain = $_ENV['SMS_DOMAIN'] ?? getenv('SMS_DOMAIN') ?: 'api.msg91.com'; - } - - // ------------------------------------------------------------------------- - // OTP delivery - // ------------------------------------------------------------------------- - - /** - * Send a 6-digit OTP to the student's mobile number via SMS. - * - * Requirement 9.5 - */ - public function sendOtp(string $mobile, string $otp): bool - { - // In development mode, store OTP in session for display instead of sending SMS - if (defined('APP_ENV') && APP_ENV === 'development') { - $_SESSION['dev_otp'] = $otp; - $_SESSION['dev_otp_mobile'] = $mobile; - error_log("DEV MODE: OTP for {$mobile} is: {$otp}"); - return true; - } - - $message = "Your Semester Online OTP is: {$otp}. Valid for 5 minutes. Do not share."; - return $this->sendSms($mobile, $message); - } - - // ------------------------------------------------------------------------- - // Status update notifications - // ------------------------------------------------------------------------- - - /** - * Notify a student of a registration or payment status change. - * - * Requirements: 9.2, 9.3, 9.4 - */ - public function sendStatusUpdate(int $studentId, string $status, string $message): bool - { - $mobile = $this->getMobileByStudentId($studentId); - $email = $this->getEmailByStudentId($studentId); - - $smsSent = $mobile ? $this->sendSms($mobile, $message) : false; - $emailSent = $email ? $this->sendEmail($email, 'Semester Registration Update', $message) : false; - - return $smsSent || $emailSent; - } - - // ------------------------------------------------------------------------- - // Welcome SMS - // ------------------------------------------------------------------------- - - /** - * Send a welcome SMS after successful account creation. - * - * Requirement 9.1 - */ - public function sendWelcomeSms(string $mobile): bool - { - $message = 'Welcome to Semester Online! Your account has been created successfully.'; - return $this->sendSms($mobile, $message); - } - - // ------------------------------------------------------------------------- - // Private — SMS via MSG91 - // ------------------------------------------------------------------------- - - private function sendSms(string $mobile, string $message): bool - { - if (empty($this->smsApiKey)) { - error_log('NotificationService: SMS_API_KEY not configured'); - return false; - } - - // MSG91 Send SMS API - $url = 'https://' . $this->smsDomain . '/api/v5/flow/'; - - $payload = json_encode([ - 'template_id' => '', // set in .env for DLT-registered templates - 'short_url' => '0', - 'mobiles' => '91' . ltrim($mobile, '0'), - 'message' => $message, - 'sender' => $this->smsSenderId, - ]); - - $ch = curl_init($url); - curl_setopt_array($ch, [ - CURLOPT_RETURNTRANSFER => true, - CURLOPT_POST => true, - CURLOPT_POSTFIELDS => $payload, - CURLOPT_HTTPHEADER => [ - 'authkey: ' . $this->smsApiKey, - 'Content-Type: application/json', - ], - CURLOPT_TIMEOUT => 10, - ]); - - $response = curl_exec($ch); - $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); - curl_close($ch); - - if ($response === false || $httpCode !== 200) { - error_log('NotificationService: SMS delivery failed for ' . $mobile); - return false; - } - - return true; - } - - // ------------------------------------------------------------------------- - // Private — Email via PHPMailer - // ------------------------------------------------------------------------- - - private function sendEmail(string $toEmail, string $subject, string $body): bool - { - try { - $mail = new PHPMailer(true); - $mail->isSMTP(); - $mail->Host = $_ENV['SMTP_HOST'] ?? getenv('SMTP_HOST') ?: 'localhost'; - $mail->SMTPAuth = true; - $mail->Username = $_ENV['SMTP_USER'] ?? getenv('SMTP_USER') ?: ''; - $mail->Password = $_ENV['SMTP_PASS'] ?? getenv('SMTP_PASS') ?: ''; - $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; - $mail->Port = (int) ($_ENV['SMTP_PORT'] ?? getenv('SMTP_PORT') ?: 587); - - $mail->setFrom( - $_ENV['MAIL_FROM_ADDRESS'] ?? getenv('MAIL_FROM_ADDRESS') ?: 'noreply@gbu.ac.in', - $_ENV['MAIL_FROM_NAME'] ?? getenv('MAIL_FROM_NAME') ?: 'Semester Online' - ); - $mail->addAddress($toEmail); - $mail->Subject = $subject; - $mail->Body = $body; - $mail->AltBody = strip_tags($body); - - $mail->send(); - return true; - } catch (MailerException $e) { - error_log('NotificationService: Email delivery failed: ' . $e->getMessage()); - return false; - } - } - - // ------------------------------------------------------------------------- - // Private — DB lookups - // ------------------------------------------------------------------------- - - private function getMobileByStudentId(int $studentId): ?string - { - try { - $db = getAppDb(); - $stmt = $db->prepare('SELECT mobile FROM students WHERE id = :id LIMIT 1'); - $stmt->execute([':id' => $studentId]); - $row = $stmt->fetch(); - return $row ? $row['mobile'] : null; - } catch (\PDOException) { - return null; - } - } - - private function getEmailByStudentId(int $studentId): ?string - { - try { - $db = getAppDb(); - $stmt = $db->prepare('SELECT email FROM students WHERE id = :id LIMIT 1'); - $stmt->execute([':id' => $studentId]); - $row = $stmt->fetch(); - return ($row && !empty($row['email'])) ? $row['email'] : null; - } catch (\PDOException) { - return null; - } - } -} diff --git a/app/Services/PaymentVerificationService.php b/app/Services/PaymentVerificationService.php deleted file mode 100644 index 9795e79..0000000 --- a/app/Services/PaymentVerificationService.php +++ /dev/null @@ -1,288 +0,0 @@ -appDb = $appDb; - $this->accountsDb = $accountsDb; - $this->razorpay = $razorpay; - $this->notification = $notification; - } - - // ------------------------------------------------------------------------- - // Verify payment - // ------------------------------------------------------------------------- - - /** - * Verify the payment for a registration. - * Routes to gateway or bank-transfer verification based on payment method. - * - * Requirements: 7.1–7.5, 7.7 - * - * @return array{verified: bool, message: string} - */ - public function verify(int $registrationId): array - { - $payment = $this->fetchPayment($registrationId); - - if ($payment === null) { - return ['verified' => false, 'message' => 'Payment record not found']; - } - - if (in_array($payment['payment_method'], ['upi', 'razorpay'], true)) { - return $this->verifyGatewayPayment($payment, $registrationId); - } - - if ($payment['payment_method'] === 'bank_transfer') { - return $this->verifyBankTransfer($payment, $registrationId); - } - - return ['verified' => false, 'message' => 'Unknown payment method']; - } - - /** - * Poll verification for a single registration (used by cron retry). - * Requirements: 7.6 - * - * @return array{verified: bool, message: string} - */ - public function pollVerification(int $registrationId): array - { - return $this->verify($registrationId); - } - - // ------------------------------------------------------------------------- - // Status lifecycle enforcement - // ------------------------------------------------------------------------- - - /** - * Update registration status with forward-only transition guard. - * - * Property 22: Registration status transitions are strictly forward. - * Requirements: 11.1, 11.2, 11.3 - * - * @return bool true on success, false if transition is invalid - */ - public function updateRegistrationStatus(int $registrationId, string $newStatus): bool - { - $registration = $this->fetchRegistration($registrationId); - - if ($registration === null) { - return false; - } - - $currentStatus = $registration['status']; - - // 'rejected' is always a valid terminal transition from payment_verified - if ($newStatus === 'rejected') { - return $this->applyStatusUpdate($registrationId, $newStatus); - } - - $currentIndex = array_search($currentStatus, self::STATUS_ORDER, true); - $newIndex = array_search($newStatus, self::STATUS_ORDER, true); - - // Reject backward or same-level transitions (Requirement 11.2) - if ($currentIndex === false || $newIndex === false || $newIndex <= $currentIndex) { - return false; - } - - return $this->applyStatusUpdate($registrationId, $newStatus); - } - - // ------------------------------------------------------------------------- - // Private — gateway verification - // ------------------------------------------------------------------------- - - /** - * Verify a UPI/Razorpay payment via the gateway API. - * - * Property 16: Gateway payment verification outcome is deterministic. - * Requirements: 7.1, 7.2 - * - * @return array{verified: bool, message: string} - */ - private function verifyGatewayPayment(array $payment, int $registrationId): array - { - try { - $gatewayPayment = $this->razorpay->payment->fetch($payment['transaction_ref']); - - if ($gatewayPayment->status === 'captured') { - $this->markPaymentVerified($payment['id']); - $this->updateRegistrationStatus($registrationId, 'payment_verified'); - return ['verified' => true, 'message' => 'Payment verified via gateway']; - } - - return ['verified' => false, 'message' => 'Gateway payment not confirmed']; - } catch (\Exception $e) { - return ['verified' => false, 'message' => 'Gateway verification error: ' . $e->getMessage()]; - } - } - - // ------------------------------------------------------------------------- - // Private — bank transfer verification - // ------------------------------------------------------------------------- - - /** - * Verify a bank transfer against the Accounts Department database. - * - * Property 17: Bank transfer verification matches on correct criteria. - * Property 18: Accounts_DB is never mutated by verification. - * Requirements: 7.3, 7.4, 7.5, 7.7 - * - * @return array{verified: bool, message: string} - */ - private function verifyBankTransfer(array $payment, int $registrationId): array - { - $student = $this->fetchStudent((int) $payment['student_id']); - - if ($student === null) { - return ['verified' => false, 'message' => 'Student not found']; - } - - try { - // Query Accounts_DB read-only — never mutates (Requirement 7.7) - $stmt = $this->accountsDb->prepare( - 'SELECT id - FROM payments - WHERE college_id = :college_id - AND amount = :amount - AND payment_date >= DATE_SUB(:transfer_date, INTERVAL 2 DAY) - AND payment_date <= DATE_ADD(:transfer_date, INTERVAL 2 DAY) - LIMIT 1' - ); - $stmt->execute([ - ':college_id' => $student['college_id'], - ':amount' => $payment['transfer_amount'], - ':transfer_date' => $payment['transfer_date'], - ]); - - $accRecord = $stmt->fetch(); - - if ($accRecord !== false) { - // Match found — mark verified (Requirements 7.3) - $this->markPaymentVerified($payment['id']); - $this->updateRegistrationStatus($registrationId, 'payment_verified'); - $this->notification->sendStatusUpdate( - (int) $payment['student_id'], - 'payment_verified', - 'Payment verified. Awaiting admin approval.' - ); - return ['verified' => true, 'message' => 'Bank transfer matched in Accounts DB']; - } - - // No match — leave as pending for cron retry (Requirement 7.4) - $this->setPendingStatus($payment['id']); - return ['verified' => false, 'message' => 'No matching record in Accounts DB. Will retry.']; - - } catch (\PDOException $e) { - // Accounts DB unavailable (Requirement 7.5) - error_log('Accounts DB unavailable: ' . $e->getMessage()); - $this->setPendingStatus($payment['id']); - return ['verified' => false, 'message' => 'Verification is in progress. You will be notified.']; - } - } - - // ------------------------------------------------------------------------- - // Private helpers - // ------------------------------------------------------------------------- - - private function fetchPayment(int $registrationId): ?array - { - $stmt = $this->appDb->prepare( - 'SELECT id, student_id, payment_method, transaction_ref, - transfer_amount, transfer_date, verification_status - FROM payments - WHERE registration_id = :registration_id - LIMIT 1' - ); - $stmt->execute([':registration_id' => $registrationId]); - $row = $stmt->fetch(); - - return $row ?: null; - } - - private function fetchRegistration(int $registrationId): ?array - { - $stmt = $this->appDb->prepare( - 'SELECT id, status FROM registrations WHERE id = :id LIMIT 1' - ); - $stmt->execute([':id' => $registrationId]); - $row = $stmt->fetch(); - - return $row ?: null; - } - - private function fetchStudent(int $studentId): ?array - { - $stmt = $this->appDb->prepare( - 'SELECT id, college_id FROM students WHERE id = :id LIMIT 1' - ); - $stmt->execute([':id' => $studentId]); - $row = $stmt->fetch(); - - return $row ?: null; - } - - private function markPaymentVerified(int $paymentId): void - { - $stmt = $this->appDb->prepare( - 'UPDATE payments SET verification_status = \'verified\', verified_at = NOW() WHERE id = :id' - ); - $stmt->execute([':id' => $paymentId]); - } - - private function setPendingStatus(int $paymentId): void - { - $stmt = $this->appDb->prepare( - 'UPDATE payments SET verification_status = \'pending\' WHERE id = :id' - ); - $stmt->execute([':id' => $paymentId]); - } - - private function applyStatusUpdate(int $registrationId, string $status): bool - { - $stmt = $this->appDb->prepare( - 'UPDATE registrations SET status = :status WHERE id = :id' - ); - $stmt->execute([':status' => $status, ':id' => $registrationId]); - - return $stmt->rowCount() > 0; - } -} diff --git a/app/Views/.gitkeep b/app/Views/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/app/Views/admin/dashboard.php b/app/Views/admin/dashboard.php deleted file mode 100644 index ec5a856..0000000 --- a/app/Views/admin/dashboard.php +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - Admin Dashboard — Semester Online - - - -
-

Admin Dashboard

- -
-

Pending Registrations

- - -
Action: successfully.
- - - -

No registrations pending approval.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
IDCollege IDStudent NameDepartmentSemesterAcademic YearPayment MethodAmount (₹)Submitted AtActions
- View -
- -
- - -
- - diff --git a/app/Views/admin/detail.php b/app/Views/admin/detail.php deleted file mode 100644 index 9d7a585..0000000 --- a/app/Views/admin/detail.php +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - Registration Detail — Semester Online - - - -
-

Registration Detail

- - -
- - - - -

Student Information

- - - - - - - - -
College ID
Full Name
Mobile
Email
Department
Program
Current Semester
- -

Registration Details

- - - - - - - - - -
Registration ID
Semester
Academic Year
Status
Hostel Required
Transport
Remarks
Submitted At
- - -

Payment Details

- - - - - - - - - - -
Method
Amount (₹)
Transaction Ref
Bank Name
Account Holder
Transfer Date
Transfer Amount (₹)
Verification Status
Verified At
- - -

Actions

- -
-
- inputField() ?> - - -
- -
- inputField() ?> - - - -
-
- -

- -
- - diff --git a/app/Views/auth/login.php b/app/Views/auth/login.php deleted file mode 100644 index 918cdf5..0000000 --- a/app/Views/auth/login.php +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - Login — Semester Online - - - -
-

Login

- - -
- - - -
Account created successfully. Please log in.
- - -
- inputField() ?> - -
- - -
- -
- - -
- - -
- - -
- - diff --git a/app/Views/auth/otp.php b/app/Views/auth/otp.php deleted file mode 100644 index 4e75d7c..0000000 --- a/app/Views/auth/otp.php +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - OTP Verification — Semester Online - - - -
-

Enter OTP

-

A 6-digit OTP has been sent to your registered mobile number. It is valid for 5 minutes.

- - -
- Development Mode: Your OTP is -
- - - -
- - -
- inputField() ?> - -
- - -
- - -
- - -
- - diff --git a/app/Views/auth/signup.php b/app/Views/auth/signup.php deleted file mode 100644 index 8d5a75d..0000000 --- a/app/Views/auth/signup.php +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - Sign Up — Semester Online - - - -
-

Create Your Account

-

Only enrolled GBU students may register.

- - -
-
    - -
  • - -
-
- - -
- inputField() ?> - -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- - -
- - -
- - diff --git a/app/Views/payment/bank_transfer.php b/app/Views/payment/bank_transfer.php deleted file mode 100644 index 8a665e6..0000000 --- a/app/Views/payment/bank_transfer.php +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - Bank Transfer — Semester Online - - - -
-

Bank Transfer Details

- - -
- - -
- inputField() ?> - - -

- -

- -

- -

- -

- -

- - -
- - -
- - diff --git a/app/Views/payment/select.php b/app/Views/payment/select.php deleted file mode 100644 index 124c80f..0000000 --- a/app/Views/payment/select.php +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - Payment — Semester Online - - - - -
-

Pay Semester Fees

- - -
- - - -
Payment successfully.
- - -

Registration ID:

- -

Choose Payment Method

- - -
- inputField() ?> - - - -
- -
- - -
- Pay via Bank Transfer -
-
- inputField() ?> - - -

- -

- -

- -

- -

- -

- - -
-
- -
- -
- - diff --git a/app/Views/portal/dashboard.php b/app/Views/portal/dashboard.php deleted file mode 100644 index 0d2b30e..0000000 --- a/app/Views/portal/dashboard.php +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - Student Portal — Semester Online - - - -
-

Welcome,

- -
-

Student Information

-
-
- College ID - -
-
- Department - -
-
- Program - -
-
- Current Semester - -
-
-
- -
-

Current Semester Status

- - -
-
- Registration Status - - - -
-
- Payment Status - - - -
-
- -

No registration found for the current semester.

- - Register for Semester - - -
- -
-

Semester History

- - - - - - - - - - - - - - - - - - - - - - - - - - -
SemesterAcademic YearRegistration StatusPayment MethodPayment StatusAmount (₹)Submitted At
- -

No previous registrations found.

- -
- - -
- - diff --git a/app/Views/registration/form.php b/app/Views/registration/form.php deleted file mode 100644 index 4db3dcd..0000000 --- a/app/Views/registration/form.php +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - Semester Registration — Semester Online - - - -
-

Semester Registration

-

- - -
- - - -

Student: ()

- - -
- inputField() ?> - - -
- - - - - - -

No subjects available for this semester.

- -
- -
- -
- -
- - -
- -
- - -
- - -
- - -
- - diff --git a/assets/css/main.css b/assets/css/main.css new file mode 100644 index 0000000..7e710ec --- /dev/null +++ b/assets/css/main.css @@ -0,0 +1,395 @@ +:root { + --primary-color: #007bff; + --secondary-color: #6c757d; + --success-color: #28a745; + --info-color: #17a2b8; + --warning-color: #ffc107; + --danger-color: #dc3545; + --light-color: #f8f9fa; + --dark-color: #343a40; + --white: #ffffff; + --gray-100: #f8f9fa; + --gray-200: #e9ecef; + --gray-300: #dee2e6; + --gray-400: #ced4da; + --gray-500: #adb5bd; + --gray-600: #6c757d; + --gray-700: #495057; + --gray-800: #343a40; + --gray-900: #212529; + --box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); + --border-radius: 0.5rem; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Poppins', sans-serif; + line-height: 1.6; + color: var(--gray-800); + background-color: var(--gray-100); + display: flex; + flex-direction: column; + min-height: 100vh; +} + +.container { + width: 100%; + max-width: 1200px; + margin: 0 auto; + padding: 0 1rem; +} + +/* Header & Nav */ +.main-header { + background-color: var(--dark-color); + color: var(--white); + padding: 1rem 0; + position: sticky; + top: 0; + z-index: 1000; +} + +.nav-container { + display: flex; + justify-content: space-between; + align-items: center; +} + +.logo-link { + display: flex; + align-items: center; + text-decoration: none; + color: var(--white); +} + +.logo-img { + height: 50px; + margin-right: 1rem; +} + +.logo-text { + font-size: 1.5rem; + font-weight: 700; +} + +.nav-list { + display: flex; + list-style: none; + align-items: center; + gap: 1.5rem; +} + +.nav-list a { + text-decoration: none; + color: var(--white); + font-weight: 500; + transition: color 0.3s; +} + +.nav-list a:hover, .nav-list a.active { + color: var(--primary-color); +} + +.nav-btn { + padding: 0.5rem 1.25rem; + border-radius: var(--border-radius); + font-weight: 600; + transition: all 0.3s; +} + +.register-btn { + background-color: var(--primary-color); + color: var(--white) !important; +} + +.register-btn:hover { + background-color: #0056b3; +} + +.login-btn { + border: 2px solid var(--primary-color); + color: var(--primary-color) !important; +} + +.login-btn:hover { + background-color: var(--primary-color); + color: var(--white) !important; +} + +.nav-toggle { + display: none; + font-size: 1.5rem; + cursor: pointer; +} + +/* Content */ +.content-wrapper { + flex: 1; + padding: 3rem 1rem; +} + +/* Hero Section (Index) */ +.hero-section { + text-align: center; + padding: 4rem 1rem; + background: linear-gradient(rgba(0,0,0,0.7), rgba(0,0,0,0.7)), url('../images/banner2.jpg'); + background-size: cover; + background-position: center; + color: var(--white); + border-radius: var(--border-radius); + margin-bottom: 3rem; +} + +.hero-title { + font-size: 2.5rem; + margin-bottom: 1rem; +} + +.hero-subtitle { + font-size: 1.2rem; + margin-bottom: 2rem; + opacity: 0.9; +} + +/* Cards & Sections */ +.section-card { + background: var(--white); + padding: 2rem; + border-radius: var(--border-radius); + box-shadow: var(--box-shadow); + margin-bottom: 2rem; +} + +.section-title { + font-size: 1.8rem; + margin-bottom: 1.5rem; + color: var(--dark-color); + border-bottom: 3px solid var(--primary-color); + display: inline-block; +} + +/* Forms */ +.form-group { + margin-bottom: 1.25rem; +} + +.form-label { + display: block; + margin-bottom: 0.5rem; + font-weight: 600; +} + +.form-control { + width: 100%; + padding: 0.75rem 1rem; + border: 1px solid var(--gray-400); + border-radius: var(--border-radius); + font-family: inherit; + font-size: 1rem; + transition: border-color 0.3s, box-shadow 0.3s; +} + +.form-control:focus { + outline: none; + border-color: var(--primary-color); + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} + +.form-row { + display: flex; + flex-wrap: wrap; + gap: 1.5rem; +} + +.form-col { + flex: 1; + min-width: 250px; +} + +/* Tables */ +.table-responsive { + overflow-x: auto; +} + +.table { + width: 100%; + border-collapse: collapse; + margin-bottom: 1rem; +} + +.table th, .table td { + padding: 0.75rem; + border-bottom: 1px solid var(--gray-300); + text-align: left; +} + +.table th { + background-color: var(--gray-200); + font-weight: 600; +} + +/* Footer */ +.main-footer { + background-color: var(--dark-color); + color: var(--white); + padding: 3rem 0 1rem; + margin-top: 3rem; +} + +.footer-content { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 2rem; + margin-bottom: 2rem; +} + +.footer-section h3 { + margin-bottom: 1.25rem; + font-size: 1.25rem; +} + +.footer-section p, .footer-section li { + font-size: 0.9rem; + opacity: 0.8; +} + +.footer-section ul { + list-style: none; +} + +.footer-section ul li { + margin-bottom: 0.5rem; +} + +.footer-section a { + color: var(--white); + text-decoration: none; + transition: color 0.3s; +} + +.footer-section a:hover { + color: var(--primary-color); +} + +.footer-bottom { + padding-top: 1.5rem; + border-top: 1px solid rgba(255, 255, 255, 0.1); + text-align: center; +} + +.footer-bottom .container { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 1rem; +} + +.social-links { + display: flex; + gap: 1rem; + margin-top: 1rem; +} + +.social-links a { + font-size: 1.25rem; +} + +/* Alerts */ +.alert { + padding: 1rem; + margin-bottom: 1.5rem; + border-radius: var(--border-radius); + border: 1px solid transparent; +} + +.alert-success { + background-color: #d4edda; + border-color: #c3e6cb; + color: #155724; +} + +.alert-danger { + background-color: #f8d7da; + border-color: #f5c6cb; + color: #721c24; +} + +.alert-info { + background-color: #d1ecf1; + border-color: #bee5eb; + color: #0c5460; +} + +/* Buttons */ +.btn { + display: inline-block; + padding: 0.75rem 1.5rem; + border-radius: var(--border-radius); + font-weight: 600; + text-decoration: none; + cursor: pointer; + transition: all 0.3s; + border: none; + font-family: inherit; + font-size: 1rem; +} + +.btn-primary { background-color: var(--primary-color); color: var(--white); } +.btn-primary:hover { background-color: #0056b3; } + +.btn-success { background-color: var(--success-color); color: var(--white); } +.btn-success:hover { background-color: #218838; } + +.btn-danger { background-color: var(--danger-color); color: var(--white); } +.btn-danger:hover { background-color: #c82333; } + +/* Responsive */ +@media (max-width: 768px) { + .nav-toggle { + display: block; + } + + .nav-menu { + display: none; + position: absolute; + top: 100%; + left: 0; + width: 100%; + background-color: var(--dark-color); + padding: 1rem; + border-top: 1px solid rgba(255,255,255,0.1); + } + + .nav-menu.show { + display: block; + } + + .nav-list { + flex-direction: column; + align-items: flex-start; + gap: 1rem; + } + + .nav-list li { + width: 100%; + } + + .nav-btn { + display: block; + text-align: center; + } + + .hero-title { + font-size: 1.8rem; + } + + .footer-bottom .container { + justify-content: center; + } +} diff --git a/assets/images/GBU Logo.webp b/assets/images/GBU Logo.webp new file mode 100644 index 0000000..9d7cf55 Binary files /dev/null and b/assets/images/GBU Logo.webp differ diff --git a/assets/images/GBU logo.png b/assets/images/GBU logo.png new file mode 100644 index 0000000..97f2c31 Binary files /dev/null and b/assets/images/GBU logo.png differ diff --git a/assets/images/GBU.jpg b/assets/images/GBU.jpg new file mode 100644 index 0000000..abf8529 Binary files /dev/null and b/assets/images/GBU.jpg differ diff --git a/assets/images/abhinav.jpg b/assets/images/abhinav.jpg new file mode 100644 index 0000000..0f9652a Binary files /dev/null and b/assets/images/abhinav.jpg differ diff --git a/assets/images/banner2.jpg b/assets/images/banner2.jpg new file mode 100644 index 0000000..c544520 Binary files /dev/null and b/assets/images/banner2.jpg differ diff --git a/assets/images/krishan.jpg b/assets/images/krishan.jpg new file mode 100644 index 0000000..b3f7c47 Binary files /dev/null and b/assets/images/krishan.jpg differ diff --git a/assets/js/main.js b/assets/js/main.js new file mode 100644 index 0000000..e6c7e81 --- /dev/null +++ b/assets/js/main.js @@ -0,0 +1,65 @@ +const texts = ["Gautam Buddha University", "School of ICT"]; +let index = 0; +let charIndex = 0; +let typingActive = true; +let stopTyping = false; // Flag to control stopping of the typing animation + +const logoText = document.getElementById("logo-text"); +const cursor = document.createElement("span"); +cursor.className = "cursor"; +logoText.appendChild(cursor); + +function updateTypingStatus() { + if (window.innerWidth > 768 && !stopTyping) { // Ensure typing happens only if not stopped + typingActive = true; + cursor.style.display = "inline"; // Show cursor on larger screens + logoText.textContent = ""; // Reset text for animation + charIndex = 0; // Reset character index + type(); // Restart animation + } else { + typingActive = false; + cursor.style.display = "none"; // Hide cursor on smaller screens + logoText.textContent = "Gautam Buddha University"; // Display static text + } +} + +function type() { + if (stopTyping) return; // Permanently stop typing if stopTyping is true + + if (charIndex < texts[index].length) { + logoText.textContent += texts[index].charAt(charIndex); + charIndex++; + setTimeout(type, 100); // Adjust typing speed here (100ms) + } else { + setTimeout(deleteText, 1000); // Pause before deleting + } +} + +function deleteText() { + if (stopTyping) return; // Permanently stop deleting if stopTyping is true + + if (charIndex > 0) { + logoText.textContent = texts[index].substring(0, charIndex - 1); + charIndex--; + setTimeout(deleteText, 50); // Adjust deleting speed here (50ms) + } else { + index = (index + 1) % texts.length; // Move to the next text + setTimeout(type, 500); // Pause before typing the next text + } +} + +// Add resize event listener +window.addEventListener("resize", updateTypingStatus); + +// Initial check for screen size +updateTypingStatus(); + +// Function to permanently stop typing +function stopTypingAnimation() { + stopTyping = true; // Set stopTyping flag to true + cursor.style.display = "none"; // Hide the cursor permanently + logoText.textContent = "Gautam Buddha University"; // Optionally display a message +} + +// Call stopTypingAnimation to permanently stop typing when needed +stopTypingAnimation(); // This will stop the animation permanently diff --git a/composer.json b/composer.json deleted file mode 100644 index ce85095..0000000 --- a/composer.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "gbu/semester-online-registration", - "description": "GBU Semester Online Registration System", - "type": "project", - "require": { - "php": ">=8.1", - "razorpay/razorpay": "^2.9", - "phpmailer/phpmailer": "^6.9" - }, - "require-dev": { - "phpunit/phpunit": "^10.5" - }, - "autoload": { - "psr-4": { - "App\\": "app/" - } - }, - "autoload-dev": { - "psr-4": { - "Tests\\": "tests/" - } - }, - "config": { - "optimize-autoloader": true, - "sort-packages": true - }, - "minimum-stability": "stable" -} diff --git a/composer.lock b/composer.lock deleted file mode 100644 index 8a97d5b..0000000 --- a/composer.lock +++ /dev/null @@ -1,1923 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "1e7649983c65fc3be0d20b2b89635ce9", - "packages": [ - { - "name": "phpmailer/phpmailer", - "version": "v6.12.0", - "source": { - "type": "git", - "url": "https://github.com/PHPMailer/PHPMailer.git", - "reference": "d1ac35d784bf9f5e61b424901d5a014967f15b12" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/d1ac35d784bf9f5e61b424901d5a014967f15b12", - "reference": "d1ac35d784bf9f5e61b424901d5a014967f15b12", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "ext-filter": "*", - "ext-hash": "*", - "php": ">=5.5.0" - }, - "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^1.0", - "doctrine/annotations": "^1.2.6 || ^1.13.3", - "php-parallel-lint/php-console-highlighter": "^1.0.0", - "php-parallel-lint/php-parallel-lint": "^1.3.2", - "phpcompatibility/php-compatibility": "^9.3.5", - "roave/security-advisories": "dev-latest", - "squizlabs/php_codesniffer": "^3.7.2", - "yoast/phpunit-polyfills": "^1.0.4" - }, - "suggest": { - "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication", - "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", - "ext-openssl": "Needed for secure SMTP sending and DKIM signing", - "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication", - "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", - "league/oauth2-google": "Needed for Google XOAUTH2 authentication", - "psr/log": "For optional PSR-3 debug logging", - "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", - "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" - }, - "type": "library", - "autoload": { - "psr-4": { - "PHPMailer\\PHPMailer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "LGPL-2.1-only" - ], - "authors": [ - { - "name": "Marcus Bointon", - "email": "phpmailer@synchromedia.co.uk" - }, - { - "name": "Jim Jagielski", - "email": "jimjag@gmail.com" - }, - { - "name": "Andy Prevost", - "email": "codeworxtech@users.sourceforge.net" - }, - { - "name": "Brent R. Matzelle" - } - ], - "description": "PHPMailer is a full-featured email creation and transfer class for PHP", - "support": { - "issues": "https://github.com/PHPMailer/PHPMailer/issues", - "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.12.0" - }, - "funding": [ - { - "url": "https://github.com/Synchro", - "type": "github" - } - ], - "time": "2025-10-15T16:49:08+00:00" - }, - { - "name": "razorpay/razorpay", - "version": "2.9.2", - "source": { - "type": "git", - "url": "https://github.com/razorpay/razorpay-php.git", - "reference": "c5cf59941eb2d888e80371328d932e6e8266d352" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/razorpay/razorpay-php/zipball/c5cf59941eb2d888e80371328d932e6e8266d352", - "reference": "c5cf59941eb2d888e80371328d932e6e8266d352", - "shasum": "" - }, - "require": { - "ext-json": "*", - "php": ">=7.3", - "rmccue/requests": "^2.0" - }, - "require-dev": { - "phpunit/phpunit": "^9", - "raveren/kint": "1.*" - }, - "type": "library", - "autoload": { - "files": [ - "Deprecated.php" - ], - "psr-4": { - "Razorpay\\Api\\": "src/", - "Razorpay\\Tests\\": "tests/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Abhay Rana", - "email": "nemo@razorpay.com", - "homepage": "https://captnemo.in", - "role": "Developer" - }, - { - "name": "Shashank Kumar", - "email": "shashank@razorpay.com", - "role": "Developer" - } - ], - "description": "Razorpay PHP Client Library", - "homepage": "https://docs.razorpay.com", - "keywords": [ - "api", - "client", - "php", - "razorpay" - ], - "support": { - "email": "contact@razorpay.com", - "issues": "https://github.com/Razorpay/razorpay-php/issues", - "source": "https://github.com/Razorpay/razorpay-php" - }, - "time": "2025-08-05T07:13:20+00:00" - }, - { - "name": "rmccue/requests", - "version": "v2.0.17", - "source": { - "type": "git", - "url": "https://github.com/WordPress/Requests.git", - "reference": "74d1648cc34e16a42ea25d548fc73ec107a90421" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/WordPress/Requests/zipball/74d1648cc34e16a42ea25d548fc73ec107a90421", - "reference": "74d1648cc34e16a42ea25d548fc73ec107a90421", - "shasum": "" - }, - "require": { - "ext-json": "*", - "php": ">=5.6" - }, - "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || ^1.0", - "php-parallel-lint/php-console-highlighter": "^0.5.0", - "php-parallel-lint/php-parallel-lint": "^1.3.1", - "phpcompatibility/php-compatibility": "^10.0.0@dev", - "requests/test-server": "dev-main", - "squizlabs/php_codesniffer": "^3.6", - "wp-coding-standards/wpcs": "^2.0", - "yoast/phpunit-polyfills": "^1.1.5" - }, - "suggest": { - "art4/requests-psr18-adapter": "For using Requests as a PSR-18 HTTP Client", - "ext-curl": "For improved performance", - "ext-openssl": "For secure transport support", - "ext-zlib": "For improved performance when decompressing encoded streams" - }, - "type": "library", - "autoload": { - "files": [ - "library/Deprecated.php" - ], - "psr-4": { - "WpOrg\\Requests\\": "src/" - }, - "classmap": [ - "library/Requests.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "ISC" - ], - "authors": [ - { - "name": "Ryan McCue", - "homepage": "https://rmccue.io/" - }, - { - "name": "Alain Schlesser", - "homepage": "https://github.com/schlessera" - }, - { - "name": "Juliette Reinders Folmer", - "homepage": "https://github.com/jrfnl" - }, - { - "name": "Contributors", - "homepage": "https://github.com/WordPress/Requests/graphs/contributors" - } - ], - "description": "A HTTP library written in PHP, for human beings.", - "homepage": "https://requests.ryanmccue.info/", - "keywords": [ - "curl", - "fsockopen", - "http", - "idna", - "ipv6", - "iri", - "sockets" - ], - "support": { - "docs": "https://requests.ryanmccue.info/", - "issues": "https://github.com/WordPress/Requests/issues", - "source": "https://github.com/WordPress/Requests" - }, - "time": "2025-12-12T17:47:19+00:00" - } - ], - "packages-dev": [ - { - "name": "myclabs/deep-copy", - "version": "1.13.4", - "source": { - "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3 <3.2.2" - }, - "require-dev": { - "doctrine/collections": "^1.6.8", - "doctrine/common": "^2.13.3 || ^3.2.2", - "phpspec/prophecy": "^1.10", - "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" - }, - "type": "library", - "autoload": { - "files": [ - "src/DeepCopy/deep_copy.php" - ], - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Create deep copies (clones) of your objects", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" - ], - "support": { - "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" - }, - "funding": [ - { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" - } - ], - "time": "2025-08-01T08:46:24+00:00" - }, - { - "name": "nikic/php-parser", - "version": "v5.7.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "ext-json": "*", - "ext-tokenizer": "*", - "php": ">=7.4" - }, - "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^9.0" - }, - "bin": [ - "bin/php-parse" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.x-dev" - } - }, - "autoload": { - "psr-4": { - "PhpParser\\": "lib/PhpParser" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov" - } - ], - "description": "A PHP parser written in PHP", - "keywords": [ - "parser", - "php" - ], - "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" - }, - "time": "2025-12-06T11:56:16+00:00" - }, - { - "name": "phar-io/manifest", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "54750ef60c58e43759730615a392c31c80e23176" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", - "reference": "54750ef60c58e43759730615a392c31c80e23176", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-phar": "*", - "ext-xmlwriter": "*", - "phar-io/version": "^3.0.1", - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "support": { - "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.4" - }, - "funding": [ - { - "url": "https://github.com/theseer", - "type": "github" - } - ], - "time": "2024-03-03T12:33:53+00:00" - }, - { - "name": "phar-io/version", - "version": "3.2.1", - "source": { - "type": "git", - "url": "https://github.com/phar-io/version.git", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Library for handling version information and constraints", - "support": { - "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.2.1" - }, - "time": "2022-02-21T01:04:05+00:00" - }, - { - "name": "phpunit/php-code-coverage", - "version": "10.1.16", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "7e308268858ed6baedc8704a304727d20bc07c77" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", - "reference": "7e308268858ed6baedc8704a304727d20bc07c77", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-xmlwriter": "*", - "nikic/php-parser": "^4.19.1 || ^5.1.0", - "php": ">=8.1", - "phpunit/php-file-iterator": "^4.1.0", - "phpunit/php-text-template": "^3.0.1", - "sebastian/code-unit-reverse-lookup": "^3.0.0", - "sebastian/complexity": "^3.2.0", - "sebastian/environment": "^6.1.0", - "sebastian/lines-of-code": "^2.0.2", - "sebastian/version": "^4.0.1", - "theseer/tokenizer": "^1.2.3" - }, - "require-dev": { - "phpunit/phpunit": "^10.1" - }, - "suggest": { - "ext-pcov": "PHP extension that provides line coverage", - "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "10.1.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-08-22T04:31:57+00:00" - }, - { - "name": "phpunit/php-file-iterator", - "version": "4.1.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", - "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-08-31T06:24:48+00:00" - }, - { - "name": "phpunit/php-invoker", - "version": "4.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", - "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "ext-pcntl": "*", - "phpunit/phpunit": "^10.0" - }, - "suggest": { - "ext-pcntl": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Invoke callables with a timeout", - "homepage": "https://github.com/sebastianbergmann/php-invoker/", - "keywords": [ - "process" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:56:09+00:00" - }, - { - "name": "phpunit/php-text-template", - "version": "3.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", - "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-08-31T14:07:24+00:00" - }, - { - "name": "phpunit/php-timer", - "version": "6.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", - "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:57:52+00:00" - }, - { - "name": "phpunit/phpunit", - "version": "10.5.63", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "33198268dad71e926626b618f3ec3966661e4d90" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/33198268dad71e926626b618f3ec3966661e4d90", - "reference": "33198268dad71e926626b618f3ec3966661e4d90", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.4", - "phar-io/manifest": "^2.0.4", - "phar-io/version": "^3.2.1", - "php": ">=8.1", - "phpunit/php-code-coverage": "^10.1.16", - "phpunit/php-file-iterator": "^4.1.0", - "phpunit/php-invoker": "^4.0.0", - "phpunit/php-text-template": "^3.0.1", - "phpunit/php-timer": "^6.0.0", - "sebastian/cli-parser": "^2.0.1", - "sebastian/code-unit": "^2.0.0", - "sebastian/comparator": "^5.0.5", - "sebastian/diff": "^5.1.1", - "sebastian/environment": "^6.1.0", - "sebastian/exporter": "^5.1.4", - "sebastian/global-state": "^6.0.2", - "sebastian/object-enumerator": "^5.0.0", - "sebastian/recursion-context": "^5.0.1", - "sebastian/type": "^4.0.0", - "sebastian/version": "^4.0.1" - }, - "suggest": { - "ext-soap": "To be able to generate mocks based on WSDL files" - }, - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "10.5-dev" - } - }, - "autoload": { - "files": [ - "src/Framework/Assert/Functions.php" - ], - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.63" - }, - "funding": [ - { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", - "type": "tidelift" - } - ], - "time": "2026-01-27T05:48:37+00:00" - }, - { - "name": "sebastian/cli-parser", - "version": "2.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", - "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for parsing CLI options", - "homepage": "https://github.com/sebastianbergmann/cli-parser", - "support": { - "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-02T07:12:49+00:00" - }, - { - "name": "sebastian/code-unit", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", - "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Collection of value objects that represent the PHP code units", - "homepage": "https://github.com/sebastianbergmann/code-unit", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:58:43+00:00" - }, - { - "name": "sebastian/code-unit-reverse-lookup", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", - "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:59:15+00:00" - }, - { - "name": "sebastian/comparator", - "version": "5.0.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55dfef806eb7dfeb6e7a6935601fef866f8ca48d", - "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-mbstring": "*", - "php": ">=8.1", - "sebastian/diff": "^5.0", - "sebastian/exporter": "^5.0" - }, - "require-dev": { - "phpunit/phpunit": "^10.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - } - ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/comparator/issues", - "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.5" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", - "type": "tidelift" - } - ], - "time": "2026-01-24T09:25:16+00:00" - }, - { - "name": "sebastian/complexity", - "version": "3.2.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "68ff824baeae169ec9f2137158ee529584553799" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", - "reference": "68ff824baeae169ec9f2137158ee529584553799", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for calculating the complexity of PHP code units", - "homepage": "https://github.com/sebastianbergmann/complexity", - "support": { - "issues": "https://github.com/sebastianbergmann/complexity/issues", - "security": "https://github.com/sebastianbergmann/complexity/security/policy", - "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-12-21T08:37:17+00:00" - }, - { - "name": "sebastian/diff", - "version": "5.1.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", - "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0", - "symfony/process": "^6.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/diff/issues", - "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-02T07:15:17+00:00" - }, - { - "name": "sebastian/environment", - "version": "6.1.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", - "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "suggest": { - "ext-posix": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "https://github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/environment/issues", - "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-23T08:47:14+00:00" - }, - { - "name": "sebastian/exporter", - "version": "5.1.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "0735b90f4da94969541dac1da743446e276defa6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0735b90f4da94969541dac1da743446e276defa6", - "reference": "0735b90f4da94969541dac1da743446e276defa6", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "php": ">=8.1", - "sebastian/recursion-context": "^5.0" - }, - "require-dev": { - "phpunit/phpunit": "^10.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "https://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/exporter/issues", - "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", - "type": "tidelift" - } - ], - "time": "2025-09-24T06:09:11+00:00" - }, - { - "name": "sebastian/global-state", - "version": "6.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", - "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "sebastian/object-reflector": "^3.0", - "sebastian/recursion-context": "^5.0" - }, - "require-dev": { - "ext-dom": "*", - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Snapshotting of global state", - "homepage": "https://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/global-state/issues", - "security": "https://github.com/sebastianbergmann/global-state/security/policy", - "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-02T07:19:19+00:00" - }, - { - "name": "sebastian/lines-of-code", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", - "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for counting the lines of code in PHP source code", - "homepage": "https://github.com/sebastianbergmann/lines-of-code", - "support": { - "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-12-21T08:38:20+00:00" - }, - { - "name": "sebastian/object-enumerator", - "version": "5.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", - "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "sebastian/object-reflector": "^3.0", - "sebastian/recursion-context": "^5.0" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T07:08:32+00:00" - }, - { - "name": "sebastian/object-reflector", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", - "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T07:06:18+00:00" - }, - { - "name": "sebastian/recursion-context", - "version": "5.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/47e34210757a2f37a97dcd207d032e1b01e64c7a", - "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "https://github.com/sebastianbergmann/recursion-context", - "support": { - "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", - "type": "tidelift" - } - ], - "time": "2025-08-10T07:50:56+00:00" - }, - { - "name": "sebastian/type", - "version": "4.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", - "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Collection of value objects that represent the types of the PHP type system", - "homepage": "https://github.com/sebastianbergmann/type", - "support": { - "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T07:10:45+00:00" - }, - { - "name": "sebastian/version", - "version": "4.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", - "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", - "support": { - "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-07T11:34:05+00:00" - }, - { - "name": "theseer/tokenizer", - "version": "1.3.1", - "source": { - "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", - "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": "^7.2 || ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - } - ], - "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "support": { - "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.3.1" - }, - "funding": [ - { - "url": "https://github.com/theseer", - "type": "github" - } - ], - "time": "2025-11-17T20:03:58+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": {}, - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": ">=8.1" - }, - "platform-dev": {}, - "plugin-api-version": "2.9.0" -} diff --git a/config/app.php b/config/app.php deleted file mode 100644 index b4f114e..0000000 --- a/config/app.php +++ /dev/null @@ -1,37 +0,0 @@ - PDO::ERRMODE_EXCEPTION, - PDO::ATTR_EMULATE_PREPARES => false, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - ] - ); - } - - return $appDb; -} - -function getAccountsDb(): PDO -{ - static $accountsDb = null; - - if ($accountsDb === null) { - $dsn = sprintf( - 'mysql:host=%s;dbname=%s;charset=utf8mb4', - $_ENV['ACCOUNTS_DB_HOST'] ?? getenv('ACCOUNTS_DB_HOST'), - $_ENV['ACCOUNTS_DB_NAME'] ?? getenv('ACCOUNTS_DB_NAME') - ); - - $accountsDb = new PDO( - $dsn, - $_ENV['ACCOUNTS_DB_USER'] ?? getenv('ACCOUNTS_DB_USER'), - $_ENV['ACCOUNTS_DB_PASS'] ?? getenv('ACCOUNTS_DB_PASS'), - [ - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, - PDO::ATTR_EMULATE_PREPARES => false, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - ] - ); - - // Enforce read-only by setting session to read-only transaction mode - $accountsDb->exec("SET SESSION TRANSACTION READ ONLY"); - } - - return $accountsDb; -} diff --git a/database/migrations/001_create_students.sql b/database/migrations/001_create_students.sql deleted file mode 100644 index cc57d1d..0000000 --- a/database/migrations/001_create_students.sql +++ /dev/null @@ -1,18 +0,0 @@ --- Migration 001: Create students table -CREATE TABLE IF NOT EXISTS students ( - id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, - college_id VARCHAR(20) NOT NULL UNIQUE, -- Roll No / Registration No - full_name VARCHAR(100) NOT NULL, - mobile VARCHAR(15) NOT NULL UNIQUE, - email VARCHAR(100), - department VARCHAR(100), - program VARCHAR(100), - current_semester TINYINT UNSIGNED, - password_hash VARCHAR(255) NOT NULL, - is_active TINYINT(1) DEFAULT 1, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - --- college_id is already covered by the UNIQUE index above. --- Explicit named index for clarity and Accounts_DB cross-reference queries. -CREATE INDEX IF NOT EXISTS idx_students_college_id ON students (college_id); diff --git a/database/migrations/002_create_otp_tokens.sql b/database/migrations/002_create_otp_tokens.sql deleted file mode 100644 index 3969dc5..0000000 --- a/database/migrations/002_create_otp_tokens.sql +++ /dev/null @@ -1,16 +0,0 @@ --- Migration 002: Create otp_tokens table -CREATE TABLE IF NOT EXISTS otp_tokens ( - id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, - student_id INT UNSIGNED NOT NULL, - otp_hash VARCHAR(255) NOT NULL, - expires_at TIMESTAMP NOT NULL, - used TINYINT(1) DEFAULT 0, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (student_id) REFERENCES students(id) -); - --- Index for rate-limit queries (count recent tokens per student) -CREATE INDEX IF NOT EXISTS idx_otp_tokens_student_id ON otp_tokens (student_id); - --- Index for cron-based purge of expired tokens -CREATE INDEX IF NOT EXISTS idx_otp_tokens_expires_at ON otp_tokens (expires_at); diff --git a/database/migrations/003_create_registrations.sql b/database/migrations/003_create_registrations.sql deleted file mode 100644 index 289e522..0000000 --- a/database/migrations/003_create_registrations.sql +++ /dev/null @@ -1,30 +0,0 @@ --- Migration 003: Create registrations table -CREATE TABLE IF NOT EXISTS registrations ( - id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, - student_id INT UNSIGNED NOT NULL, - semester_id TINYINT UNSIGNED NOT NULL, - academic_year VARCHAR(9) NOT NULL, -- e.g. "2024-2025" - subjects JSON NOT NULL, -- array of subject codes enrolled - hostel_required TINYINT(1) DEFAULT 0, - transport VARCHAR(50), - remarks TEXT, - status ENUM( - 'draft', - 'pending_payment', - 'payment_submitted', - 'payment_verified', - 'approved', - 'rejected' - ) DEFAULT 'draft', - submitted_at TIMESTAMP NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (student_id) REFERENCES students(id) -); - --- Index for per-student registration lookups -CREATE INDEX IF NOT EXISTS idx_registrations_student_id - ON registrations (student_id); - --- Composite index for duplicate-registration eligibility check -CREATE INDEX IF NOT EXISTS idx_registrations_student_semester_year - ON registrations (student_id, semester_id, academic_year); diff --git a/database/migrations/004_create_payments.sql b/database/migrations/004_create_payments.sql deleted file mode 100644 index a21f34c..0000000 --- a/database/migrations/004_create_payments.sql +++ /dev/null @@ -1,22 +0,0 @@ --- Migration 004: Create payments table -CREATE TABLE IF NOT EXISTS payments ( - id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, - registration_id INT UNSIGNED NOT NULL UNIQUE, - student_id INT UNSIGNED NOT NULL, - amount DECIMAL(10,2) NOT NULL, - payment_method ENUM('upi','razorpay','bank_transfer') NOT NULL, - transaction_ref VARCHAR(100), -- gateway txn ID or bank UTR - bank_name VARCHAR(100), - account_holder VARCHAR(100), - transfer_date DATE, - transfer_amount DECIMAL(10,2), - receipt_path VARCHAR(255), -- uploaded receipt file path - verification_status ENUM('pending','verified','failed') DEFAULT 'pending', - verified_at TIMESTAMP NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (registration_id) REFERENCES registrations(id), - FOREIGN KEY (student_id) REFERENCES students(id) -); - --- Index for Accounts_DB cross-reference queries on transfer date -CREATE INDEX IF NOT EXISTS idx_payments_transfer_date ON payments (transfer_date); diff --git a/database/migrations/005_create_admin_actions.sql b/database/migrations/005_create_admin_actions.sql deleted file mode 100644 index d8d09f8..0000000 --- a/database/migrations/005_create_admin_actions.sql +++ /dev/null @@ -1,10 +0,0 @@ --- Migration 005: Create admin_actions table -CREATE TABLE IF NOT EXISTS admin_actions ( - id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, - registration_id INT UNSIGNED NOT NULL, - admin_id INT UNSIGNED NOT NULL, - action ENUM('approved','rejected') NOT NULL, - notes TEXT, - acted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (registration_id) REFERENCES registrations(id) -); diff --git a/database/migrations/006_create_enrolled_students.sql b/database/migrations/006_create_enrolled_students.sql deleted file mode 100644 index 252c933..0000000 --- a/database/migrations/006_create_enrolled_students.sql +++ /dev/null @@ -1,25 +0,0 @@ --- Master list of enrolled students (imported from university records) --- Only students in this table can create accounts - -CREATE TABLE IF NOT EXISTS enrolled_students ( - id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, - college_id VARCHAR(20) NOT NULL UNIQUE, - full_name VARCHAR(100) NOT NULL, - department VARCHAR(100), - program VARCHAR(100), - current_semester TINYINT UNSIGNED, - enrollment_year YEAR, - status ENUM('active', 'inactive', 'graduated', 'suspended') DEFAULT 'active', - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - - INDEX idx_college_id (college_id), - INDEX idx_status (status) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - --- Insert some sample enrolled students for testing -INSERT INTO enrolled_students (college_id, full_name, department, program, current_semester, enrollment_year, status) VALUES -('235/ucd/048', 'Test Student One', 'Computer Science', 'B.Tech', 3, 2023, 'active'), -('235/ucd/049', 'Test Student Two', 'Electronics', 'B.Tech', 5, 2022, 'active'), -('235/ucd/050', 'Test Student Three', 'Mechanical', 'B.Tech', 7, 2021, 'active'), -('ADMIN001', 'Admin User', 'Administration', 'Staff', 1, 2020, 'active'); diff --git a/database/migrations/007_create_subjects.sql b/database/migrations/007_create_subjects.sql deleted file mode 100644 index 4e4cdfd..0000000 --- a/database/migrations/007_create_subjects.sql +++ /dev/null @@ -1,70 +0,0 @@ --- Subjects/Courses table for semester registration - -CREATE TABLE IF NOT EXISTS subjects ( - id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, - code VARCHAR(20) NOT NULL UNIQUE, - name VARCHAR(200) NOT NULL, - credits TINYINT UNSIGNED NOT NULL DEFAULT 3, - semester TINYINT UNSIGNED NOT NULL, - department VARCHAR(100), - is_elective BOOLEAN DEFAULT FALSE, - is_active BOOLEAN DEFAULT TRUE, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - - INDEX idx_semester (semester), - INDEX idx_department (department), - INDEX idx_active (is_active) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - --- Insert sample subjects for different semesters -INSERT INTO subjects (code, name, credits, semester, department, is_elective, is_active) VALUES --- Semester 1 -('CS101', 'Introduction to Programming', 4, 1, 'Computer Science', FALSE, TRUE), -('MA101', 'Engineering Mathematics I', 4, 1, 'Mathematics', FALSE, TRUE), -('PH101', 'Engineering Physics', 3, 1, 'Physics', FALSE, TRUE), -('CH101', 'Engineering Chemistry', 3, 1, 'Chemistry', FALSE, TRUE), -('EE101', 'Basic Electrical Engineering', 3, 1, 'Electrical', FALSE, TRUE), - --- Semester 2 -('CS102', 'Data Structures', 4, 2, 'Computer Science', FALSE, TRUE), -('MA102', 'Engineering Mathematics II', 4, 2, 'Mathematics', FALSE, TRUE), -('CS103', 'Digital Logic Design', 3, 2, 'Computer Science', FALSE, TRUE), -('ME101', 'Engineering Mechanics', 3, 2, 'Mechanical', FALSE, TRUE), - --- Semester 3 -('CS201', 'Object Oriented Programming', 4, 3, 'Computer Science', FALSE, TRUE), -('CS202', 'Database Management Systems', 4, 3, 'Computer Science', FALSE, TRUE), -('CS203', 'Computer Organization', 3, 3, 'Computer Science', FALSE, TRUE), -('MA201', 'Discrete Mathematics', 3, 3, 'Mathematics', FALSE, TRUE), -('CS204', 'Operating Systems', 4, 3, 'Computer Science', FALSE, TRUE), - --- Semester 4 -('CS301', 'Design and Analysis of Algorithms', 4, 4, 'Computer Science', FALSE, TRUE), -('CS302', 'Computer Networks', 4, 4, 'Computer Science', FALSE, TRUE), -('CS303', 'Software Engineering', 3, 4, 'Computer Science', FALSE, TRUE), -('CS304', 'Theory of Computation', 3, 4, 'Computer Science', FALSE, TRUE), - --- Semester 5 -('CS401', 'Artificial Intelligence', 4, 5, 'Computer Science', FALSE, TRUE), -('CS402', 'Web Technologies', 3, 5, 'Computer Science', FALSE, TRUE), -('CS403', 'Compiler Design', 4, 5, 'Computer Science', FALSE, TRUE), -('CS404', 'Machine Learning', 3, 5, 'Computer Science', TRUE, TRUE), - --- Semester 6 -('CS501', 'Cloud Computing', 3, 6, 'Computer Science', TRUE, TRUE), -('CS502', 'Mobile Application Development', 3, 6, 'Computer Science', TRUE, TRUE), -('CS503', 'Cyber Security', 3, 6, 'Computer Science', TRUE, TRUE), -('CS504', 'Big Data Analytics', 3, 6, 'Computer Science', TRUE, TRUE), -('CS505', 'Internet of Things', 3, 6, 'Computer Science', TRUE, TRUE), - --- Semester 7 -('CS601', 'Blockchain Technology', 3, 7, 'Computer Science', TRUE, TRUE), -('CS602', 'Natural Language Processing', 3, 7, 'Computer Science', TRUE, TRUE), -('CS603', 'Computer Vision', 3, 7, 'Computer Science', TRUE, TRUE), -('CS604', 'Project Work I', 4, 7, 'Computer Science', FALSE, TRUE), - --- Semester 8 -('CS701', 'Distributed Systems', 3, 8, 'Computer Science', TRUE, TRUE), -('CS702', 'DevOps and Automation', 3, 8, 'Computer Science', TRUE, TRUE), -('CS703', 'Project Work II', 6, 8, 'Computer Science', FALSE, TRUE); diff --git a/developer.html b/developer.html new file mode 100644 index 0000000..38476d7 --- /dev/null +++ b/developer.html @@ -0,0 +1,387 @@ + + + + + + + Our Development Team | reg.mygbu.in + + + + + +
+ +
+
+

Meet Our Development Team

+

Passionate developers building innovative solutions for a better tomorrow

+
+ +
+ +
+
+ Krishan +
+
+

Krishan

+

Full Stack Developer

+

Btech IT

+

225/UIT/029

+

Gautam Buddha University

+
+ UI/UX + Node.js + React + Mysql + Php +
+ +
+
+
3+
+
Projects
+
+
+
2+
+
Years Exp.
+
+
+
5+
+
Certifications
+
+
+
+
+ + +
+
+ Abhinav +
+
+

Abhinav

+

Full Stack Developer

+

Btech IT

+

225/UIT/001

+

Gautam Buddha University

+
+ UI/UX + Node.js + React + Mysql + Php +
+ +
+
+
3+
+
Projects
+
+
+
2+
+
Years Exp.
+
+
+
6+
+
Certifications
+
+
+
+
+
+ +
+

© 2024 Gautam Buddha University | Crafted with ❤️ by Krishan & Abhinav

+
+
+ + + + + + \ No newline at end of file diff --git a/generate_registration_slip.php b/generate_registration_slip.php new file mode 100644 index 0000000..04ac7c1 --- /dev/null +++ b/generate_registration_slip.php @@ -0,0 +1,195 @@ +prepare("SELECT * FROM registrations WHERE rollNumber = :rollNumber"); + $stmt->execute([':rollNumber' => $rollNumber]); + $studentData = $stmt->fetch(); + + if ($studentData) { + // Data is now in $studentData associative array + $fullName = $studentData['fullName']; + $fathersName = $studentData['fathersName']; + $nameOfProgramme = $studentData['nameOfProgramme']; + $branchSpecialization = $studentData['branchSpecialization']; + $year = $studentData['year']; + $semester = $studentData['semester']; + $category = $studentData['category']; + $gender = $studentData['gender']; + $state = $studentData['stateDomicile']; + $aadhar = $studentData['aadharCard']; + $permanentAddress = $studentData['permanentAddress']; + $hostelAddress = $studentData['hostelAddress']; + $studentContact = $studentData['studentContact']; + $studentEmail = $studentData['studentEmail']; + $fatherContact = $studentData['fatherContact']; + $fatherEmail = $studentData['fatherEmail']; + $motherContact = $studentData['motherContact']; + $motherEmail = $studentData['motherEmail']; + + // Semester Payment Information + $oddSemesterAmount = $studentData['oddSemesterAmount']; + $oddSemesterRemaining = $studentData['oddSemesterRemaining']; + $oddSemesterDetails = $studentData['oddSemesterDetails']; + $oddSemesterTxnDetails = $studentData['oddSemesterTxnDetails']; + $oddSemesterPlatform = $studentData['oddSemesterPlatform']; + $oddSemesterDate = $studentData['oddSemesterDate']; + + $evenSemesterAmount = $studentData['evenSemesterAmount']; + $evenSemesterRemaining = $studentData['evenSemesterRemaining']; + $evenSemesterDetails = $studentData['evenSemesterDetails']; + $evenSemesterTxnDetails = $studentData['evenSemesterTxnDetails']; + $evenSemesterPlatform = $studentData['evenSemesterPlatform']; + $evenSemesterDate = $studentData['evenSemesterDate']; + + // Hostel Payment Information + $hostelAmount = $studentData['hostelAmount']; + $hostelRemaining = $studentData['hostelRemaining']; + $hostelPaymentMode = $studentData['hostelPaymentMode']; + $hostelTxnDetails = $studentData['hostelTxnDetails']; + $hostelPlatform = $studentData['hostelPlatform']; + $hostelDate = $studentData['hostelDate']; + + // Mess Payment Information + $messAmount = $studentData['messAmount']; + $messRemaining = $studentData['messRemaining']; + $messPaymentMode = $studentData['messPaymentMode']; + $messTxnDetails = $studentData['messTxnDetails']; + $messPlatform = $studentData['messPlatform']; + $messDate = $studentData['messDate']; +?> + + + + + Registration Slip - <?php echo htmlspecialchars($rollNumber); ?> + + + +
+
+ University Logo +

University School of Information and Communication Technology

+

Gautam Buddha University, Greater Noida 2024-25

+

Date:

+
+ +
+

Student Details

+
+
Roll Number:
+
Student Name:
+
+
+
Father's Name:
+
Programme:
+
+
+
Branch/Specialization:
+
Year:
+
+
+
Semester:
+
Academic Session: 2024-2025
+
+
+
Gender:
+
Category:
+
+
+
Aadhar Card No:
+
Contact:
+
+
+
Hostel Address:
+
Permanent Address:
+
+
+ +
+

Fee Payment Details

+
+
Odd Semester Amount:
+
Odd Semester Remaining:
+
+
+
Even Semester Amount:
+
Even Semester Remaining:
+
+
+
Hostel Amount:
+
Hostel Remaining:
+
+
+
Mess Amount:
+
Mess Remaining:
+
+
+ + + +
+ + Back to Profile +
+
+ + +No data found for the provided Roll Number.

"; + } +} catch (PDOException $e) { + die("Database Error: " . $e->getMessage()); +} +?> diff --git a/includes/db.php b/includes/db.php new file mode 100644 index 0000000..4f58fe7 --- /dev/null +++ b/includes/db.php @@ -0,0 +1,20 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + // Set default fetch mode to associative array + $conn->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); +} catch (PDOException $e) { + die("Connection failed: " . $e->getMessage()); +} +?> diff --git a/includes/footer.php b/includes/footer.php new file mode 100644 index 0000000..7f02dba --- /dev/null +++ b/includes/footer.php @@ -0,0 +1,69 @@ + +
+ + + +
+ + + + + diff --git a/includes/functions.php b/includes/functions.php new file mode 100644 index 0000000..cf3d015 --- /dev/null +++ b/includes/functions.php @@ -0,0 +1,55 @@ + $message, + 'type' => $type + ]; +} + +/** + * Display the flash message if set. + */ +function display_flash_message() { + if (isset($_SESSION['flash_message'])) { + $msg = $_SESSION['flash_message']; + echo "
{$msg['text']}
"; + unset($_SESSION['flash_message']); + } +} +?> diff --git a/includes/header.php b/includes/header.php new file mode 100644 index 0000000..ba54de8 --- /dev/null +++ b/includes/header.php @@ -0,0 +1,57 @@ + + + + + + + <?php echo isset($page_title) ? $page_title . ' - Registration Portal' : 'Registration Portal'; ?> + + + + + + + + + + + + + +
+ +
+
+ diff --git a/index.php b/index.php new file mode 100644 index 0000000..adb689f --- /dev/null +++ b/index.php @@ -0,0 +1,163 @@ + + +
+
+

Academic Registration Portal 2024-2025

+

Applications are Open for the Upcoming Academic Year. Read the instructions carefully before applying.

+ +
+
+ +
+

Registration Instructions

+ +
+
+
1
+

Fill Form

+

Complete the registration form with your personal, academic, and fee details.

+
+
+
2
+

Preview & Submit

+

Review your information and submit the form to generate your registration record.

+
+
+
3
+

Download Slip

+

Login with your Roll Number and Mobile Number to download your registration slip.

+
+
+ +
+

Important Notes:

+
    +
  • Ensure all details are accurate to avoid delays.
  • +
  • Keep a copy of the registration slip for physical verification.
  • +
  • For any technical issues, contact the ICT Office.
  • +
+
+
+ +
+

Recent Announcements

+
+
+
+ Jan 04, 2025 + High Priority +
+

Registration Open for 2025

+

Online registration for the upcoming academic year has commenced. Please complete your registration before January 15, 2025.

+
+
+
+ Jan 05, 2025 +
+

Fee Receipt Submission

+

All students must submit their fee payment receipts to their respective course coordinators.

+
+
+
+ + + + diff --git a/log_RollNumber_27-Sep-2022.log b/log_RollNumber_27-Sep-2022.log new file mode 100644 index 0000000..0a07fb3 --- /dev/null +++ b/log_RollNumber_27-Sep-2022.log @@ -0,0 +1,44 @@ +Roll Number Updated On - Tue Sep 27 9:17:17 IST 2022/r/n2200101675 assigned 221/IBT/050 +Roll Number Updated On - Tue Sep 27 9:17:22 IST 2022/r/n2200101675 assigned 221/IBT/050 +Roll Number Updated On - Tue Sep 27 9:17:36 IST 2022/r/n2200101699 assigned 221/IBT/051 +Roll Number Updated On - Tue Sep 27 9:20:50 IST 2022/r/n2200101685 assigned 224/PSW/010 +Roll Number Updated On - Tue Sep 27 9:21:20 IST 2022/r/n2200101674 assigned 224/PSW/011 +Roll Number Updated On - Tue Sep 27 9:24:38 IST 2022/r/n2200101704 assigned 224/UHH/040 +Roll Number Updated On - Tue Sep 27 9:25:06 IST 2022/r/n2200101696 assigned 224/UHH/041 +Roll Number Updated On - Tue Sep 27 9:25:18 IST 2022/r/n2200101677 assigned 224/UHH/042 +Roll Number Updated On - Tue Sep 27 10:26:47 IST 2022/r/n2200101667 assigned 228/UHM/003 +Roll Number Updated On - Tue Sep 27 10:27:11 IST 2022/r/n2200101686 assigned 228/UHM/027 +Roll Number Updated On - Tue Sep 27 10:27:23 IST 2022/r/n2200101668 assigned 228/UHM/028 +Roll Number Updated On - Tue Sep 27 10:33:35 IST 2022/r/n2200101672 assigned 224/UHP/001 +Roll Number Updated On - Tue Sep 27 11:10:38 IST 2022/r/n2200101689 assigned 227/ILB/020 +Roll Number Updated On - Tue Sep 27 11:11:06 IST 2022/r/n2200101681 assigned 227/ILB/106 +Roll Number Updated On - Tue Sep 27 11:11:26 IST 2022/r/n2200101690 assigned 227/ILB/107 +Roll Number Updated On - Tue Sep 27 11:15:07 IST 2022/r/n2200101683 assigned 224/UEC/034 +Roll Number Updated On - Tue Sep 27 11:15:34 IST 2022/r/n2200101694 assigned 224/UEC/035 +Roll Number Updated On - Tue Sep 27 11:17:39 IST 2022/r/n2200101703 assigned 224/UJM/039 +Roll Number Updated On - Tue Sep 27 11:17:57 IST 2022/r/n2200101679 assigned 224/UJM/040 +Roll Number Updated On - Tue Sep 27 11:19:06 IST 2022/r/n2200101680 assigned 224/PAP/035 +Roll Number Updated On - Tue Sep 27 11:20:11 IST 2022/r/n2200101687 assigned 224/USW/008 +Roll Number Updated On - Tue Sep 27 11:28:22 IST 2022/r/n2200101705 assigned 221/UBT/044 +Roll Number Updated On - Tue Sep 27 11:28:35 IST 2022/r/n2200101682 assigned 221/UBT/045 +Roll Number Updated On - Tue Sep 27 12:51:29 IST 2022/r/n2200101678 assigned 224/UHA/012 +Roll Number Updated On - Tue Sep 27 13:06:17 IST 2022/r/n2200101227 assigned 228/UHC/006 +Roll Number Updated On - Tue Sep 27 14:02:29 IST 2022/r/n2200101697 assigned 228/UHC/012 +Roll Number Updated On - Tue Sep 27 14:02:42 IST 2022/r/n2200101688 assigned 228/UHC/013 +Roll Number Updated On - Tue Sep 27 14:03:24 IST 2022/r/n2200101693 assigned 228/UHC/014 +Roll Number Updated On - Tue Sep 27 14:10:34 IST 2022/r/n2200101691 assigned 223/UEE/001 +Roll Number Updated On - Tue Sep 27 14:11:53 IST 2022/r/n2200101695 assigned 228/PAP/003 +Roll Number Updated On - Tue Sep 27 14:13:49 IST 2022/r/n2200101698 assigned 224/PEP/005 +Roll Number Updated On - Tue Sep 27 14:14:48 IST 2022/r/n2200101702 assigned 228/PFS/009 +Roll Number Updated On - Tue Sep 27 14:34:48 IST 2022/r/n2200101356 assigned 225/UEC/005 +Roll Number Updated On - Tue Sep 27 14:35:11 IST 2022/r/n2200101701 assigned 225/UEC/012 +Roll Number Updated On - Tue Sep 27 14:39:53 IST 2022/r/n2200101671 assigned 225/UCA/009 +Roll Number Updated On - Tue Sep 27 14:59:16 IST 2022/r/n2200101709 assigned 224/UHA/038 +Roll Number Updated On - Tue Sep 27 15:01:02 IST 2022/r/n2200101712 assigned 225/UCA/049 +Roll Number Updated On - Tue Sep 27 15:02:11 IST 2022/r/n2200101707 assigned 221/IBT/052 +Roll Number Updated On - Tue Sep 27 15:03:21 IST 2022/r/n2200101708 assigned 225/ICS/051 +Roll Number Updated On - Tue Sep 27 15:06:23 IST 2022/r/n2200101706 assigned 228/UHM/029 +Roll Number Updated On - Tue Sep 27 15:10:02 IST 2022/r/n2200101711 assigned 226/UBM/015 +Roll Number Updated On - Tue Sep 27 15:11:14 IST 2022/r/n2200101710 assigned 224/UHH/043 +Roll Number Updated On - Tue Sep 27 15:26:31 IST 2022/r/n2200101713 assigned 225/PCD/025 +Roll Number Updated On - Tue Sep 27 15:29:36 IST 2022/r/n2200101684 assigned 226/UBC/029 diff --git a/login.php b/login.php new file mode 100644 index 0000000..d337674 --- /dev/null +++ b/login.php @@ -0,0 +1,70 @@ +prepare("SELECT * FROM registrations WHERE rollNumber = :rollNumber AND studentContact = :studentContact"); + $stmt->execute([ + ':rollNumber' => sanitize_input($rollNumber), + ':studentContact' => sanitize_input($studentContact) + ]); + $student = $stmt->fetch(); + + if ($student) { + $_SESSION['student_logged_in'] = true; + $_SESSION['student_id'] = $student['id']; + $_SESSION['rollNumber'] = $student['rollNumber']; + set_flash_message("Welcome, " . $student['fullName'] . "!", "success"); + redirect('profile.php'); + } else { + $error = "Invalid Roll Number or Mobile Number."; + } + } catch (PDOException $e) { + $error = "Database Error: " . $e->getMessage(); + } +} +?> + +
+ + + +
+ + +
+
+ + +
+ +
+ + +
+ + +
+ +
+

Not registered yet? Register Now

+
+
+ + diff --git a/logout.php b/logout.php new file mode 100644 index 0000000..c9962e7 --- /dev/null +++ b/logout.php @@ -0,0 +1,6 @@ + diff --git a/preview.php b/preview.php new file mode 100644 index 0000000..05b980d --- /dev/null +++ b/preview.php @@ -0,0 +1,169 @@ + + +
+
+

Registration Preview

+ +
+ +

Please review your information carefully before final submission.

+ +
+
+

Personal Information

+ + + + + + + + + + +
Roll Number
Full Name
Father's Name
Programme
Branch
Year/SemesterYear / Sem
Category
Gender
Aadhar
+
+ +
+

Contact & Address

+ + + + + +
Student Contact
Student Email
Permanent Address
Hostel
+
+
+ +
+

Fee Payment Information

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParticularsAmount PaidRemainingTxn DetailsPlatformDate
Odd Semester
Even Semester
Hostel Fee
Mess Fee
+
+
+ +
+

By clicking 'Confirm & Submit', you agree that all information provided is correct.

+ +
+
+ + + + diff --git a/process_register.php b/process_register.php new file mode 100644 index 0000000..45ca0e0 --- /dev/null +++ b/process_register.php @@ -0,0 +1,93 @@ +prepare($sql); + + // Bind parameters and execute + $stmt->execute([ + ':rollNumber' => sanitize_input($data['rollNumber']), + ':fullName' => sanitize_input($data['fullName']), + ':fathersName' => sanitize_input($data['fathersName']), + ':nameOfProgramme' => sanitize_input($data['nameOfProgramme']), + ':branchSpecialization' => sanitize_input($data['branchSpecialization']), + ':year' => (int)$data['year'], + ':semester' => (int)$data['semester'], + ':category' => sanitize_input($data['category']), + ':gender' => sanitize_input($data['gender']), + ':aadharCard' => sanitize_input($data['aadharCard']), + ':permanentAddress' => sanitize_input($data['permanentAddress']), + ':hostelAddress' => sanitize_input($data['hostelAddress']), + ':studentContact' => sanitize_input($data['studentContact']), + ':studentEmail' => sanitize_input($data['studentEmail']), + ':fatherOccupation' => sanitize_input($data['fatherOccupation'] ?? 'N/A'), + ':oddSemesterAmount' => $data['oddSemesterAmount'] ?: 0, + ':oddSemesterRemaining' => $data['oddSemesterRemaining'] ?: 0, + ':oddSemesterDetails' => $data['oddSemesterDetails'] ?? '', + ':oddSemesterTxnDetails' => $data['oddSemesterTxnDetails'] ?? '', + ':oddSemesterPlatform' => $data['oddSemesterPlatform'] ?? '', + ':oddSemesterDate' => $data['oddSemesterDate'] ?: null, + ':evenSemesterAmount' => $data['evenSemesterAmount'] ?: 0, + ':evenSemesterRemaining' => $data['evenSemesterRemaining'] ?: 0, + ':evenSemesterDetails' => $data['evenSemesterDetails'] ?? '', + ':evenSemesterTxnDetails' => $data['evenSemesterTxnDetails'] ?? '', + ':evenSemesterPlatform' => $data['evenSemesterPlatform'] ?? '', + ':evenSemesterDate' => $data['evenSemesterDate'] ?: null, + ':hostelAmount' => $data['hostelAmount'] ?: 0, + ':hostelRemaining' => $data['hostelRemaining'] ?: 0, + ':hostelPaymentMode' => $data['hostelPaymentMode'] ?? '', + ':hostelTxnDetails' => $data['hostelTxnDetails'] ?? '', + ':hostelPlatform' => $data['hostelPlatform'] ?? '', + ':hostelDate' => $data['hostelDate'] ?: null, + ':messAmount' => $data['messAmount'] ?: 0, + ':messRemaining' => $data['messRemaining'] ?: 0, + ':messPaymentMode' => $data['messPaymentMode'] ?? '', + ':messTxnDetails' => $data['messTxnDetails'] ?? '', + ':messPlatform' => $data['messPlatform'] ?? '', + ':messDate' => $data['messDate'] ?: null + ]); + + // Registration successful + unset($_SESSION['formData']); + set_flash_message("Registration successful! You can now login with your Roll Number and Mobile Number.", "success"); + redirect('login.php'); + +} catch (PDOException $e) { + // Check for duplicate roll number error (MySQL error code 1062) + if ($e->getCode() == 23000) { + set_flash_message("Error: This Roll Number is already registered.", "danger"); + } else { + set_flash_message("Database Error: " . $e->getMessage(), "danger"); + } + redirect('register.php'); +} +?> diff --git a/profile.php b/profile.php new file mode 100644 index 0000000..dcd8793 --- /dev/null +++ b/profile.php @@ -0,0 +1,70 @@ +prepare("SELECT * FROM registrations WHERE id = :id"); + $stmt->execute([':id' => $student_id]); + $student = $stmt->fetch(); + + if (!$student) { + set_flash_message("Profile not found.", "danger"); + redirect('logout.php'); + } +} catch (PDOException $e) { + set_flash_message("Database Error: " . $e->getMessage(), "danger"); + redirect('index.php'); +} +?> + +
+
+

Student Profile:

+ Download Registration Slip +
+ +
+
+
+

Personal Information

+ + + + + + + + +
Roll Number
Programme
Branch
Year/SemYear / Sem
Category
Gender
Aadhar
+
+
+
+
+

Contact & Address

+ + + + + +
Contact Number
Email Address
Permanent Address
Hostel
+
+
+
+
+ + + + diff --git a/public/.htaccess b/public/.htaccess deleted file mode 100644 index 91426ff..0000000 --- a/public/.htaccess +++ /dev/null @@ -1,28 +0,0 @@ -Options -Indexes - -# Deny direct access to sensitive directories - - RewriteEngine On - - # Deny access to app/, config/, storage/ directories - RewriteRule ^(app|config|storage)(/|$) - [F,L] - - # Route all requests through the front controller (index.php) - # Allow direct access to existing files and directories (assets, etc.) - RewriteCond %{REQUEST_FILENAME} !-f - RewriteCond %{REQUEST_FILENAME} !-d - RewriteRule ^ index.php [QSA,L] - - -# Deny access to hidden files (e.g. .env, .git) - - Require all denied - - -# Security headers - - Header always set X-Content-Type-Options "nosniff" - Header always set X-Frame-Options "DENY" - Header always set X-XSS-Protection "1; mode=block" - Header always set Referrer-Policy "strict-origin-when-cross-origin" - diff --git a/public/css/style.css b/public/css/style.css deleted file mode 100644 index c85e2fc..0000000 --- a/public/css/style.css +++ /dev/null @@ -1,520 +0,0 @@ -:root { - --primary: #4f46e5; - --primary-dark: #4338ca; - --primary-light: #818cf8; - --secondary: #06b6d4; - --success: #10b981; - --danger: #ef4444; - --warning: #f59e0b; - --dark: #1f2937; - --light: #f9fafb; - --border: #e5e7eb; - --text: #374151; - --text-light: #6b7280; - --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); - --shadow-lg: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); -} - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - background-attachment: fixed; - min-height: 100vh; - display: flex; - justify-content: center; - align-items: center; - padding: 20px; - color: var(--text); - line-height: 1.6; -} - -/* Container Styles */ -.container { - background: white; - border-radius: 16px; - box-shadow: var(--shadow-lg); - padding: 48px; - max-width: 500px; - width: 100%; - animation: fadeIn 0.3s ease-in; -} - -@keyframes fadeIn { - from { - opacity: 0; - transform: translateY(20px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -/* Typography */ -h1 { - color: var(--dark); - margin-bottom: 12px; - text-align: center; - font-size: 32px; - font-weight: 700; - letter-spacing: -0.5px; -} - -h2 { - color: var(--dark); - font-size: 24px; - font-weight: 600; - margin-bottom: 16px; - padding-bottom: 12px; - border-bottom: 2px solid var(--border); -} - -h3 { - color: var(--text); - font-size: 18px; - font-weight: 600; - margin-bottom: 12px; -} - -p { - color: var(--text-light); - margin-bottom: 8px; -} - -/* Alert Styles */ -.alert { - padding: 16px 20px; - border-radius: 8px; - margin-bottom: 24px; - font-size: 14px; - display: flex; - align-items: center; - gap: 12px; - animation: slideDown 0.3s ease-out; -} - -@keyframes slideDown { - from { - opacity: 0; - transform: translateY(-10px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.alert-error { - background-color: #fef2f2; - color: #991b1b; - border-left: 4px solid var(--danger); -} - -.alert-success { - background-color: #f0fdf4; - color: #166534; - border-left: 4px solid var(--success); -} - -.alert-warning { - background-color: #fffbeb; - color: #92400e; - border-left: 4px solid var(--warning); -} - -/* Form Styles */ -form { - display: flex; - flex-direction: column; - gap: 24px; -} - -.form-group { - display: flex; - flex-direction: column; - gap: 8px; -} - -label { - color: var(--dark); - font-weight: 500; - font-size: 14px; - display: block; -} - -input[type="text"], -input[type="tel"], -input[type="email"], -input[type="password"], -input[type="number"], -input[type="date"], -input[type="file"], -select, -textarea { - width: 100%; - padding: 12px 16px; - border: 2px solid var(--border); - border-radius: 8px; - font-size: 15px; - font-family: inherit; - transition: all 0.2s ease; - background-color: white; -} - -input:focus, -select:focus, -textarea:focus { - outline: none; - border-color: var(--primary); - box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1); -} - -input::placeholder { - color: #9ca3af; -} - -textarea { - resize: vertical; - min-height: 100px; -} - -/* Checkbox Styles */ -input[type="checkbox"] { - width: 18px; - height: 18px; - cursor: pointer; - accent-color: var(--primary); -} - -/* Button Styles */ -button[type="submit"], -.btn { - background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%); - color: white; - padding: 14px 32px; - border: none; - border-radius: 8px; - font-size: 16px; - font-weight: 600; - cursor: pointer; - transition: all 0.2s ease; - box-shadow: var(--shadow); - text-decoration: none; - display: inline-block; - text-align: center; -} - -button[type="submit"]:hover, -.btn:hover { - transform: translateY(-2px); - box-shadow: 0 10px 20px rgba(79, 70, 229, 0.3); -} - -button[type="submit"]:active, -.btn:active { - transform: translateY(0); -} - -button:disabled { - opacity: 0.6; - cursor: not-allowed; -} - -/* Link Styles */ -.link-text { - text-align: center; - margin-top: 24px; - color: var(--text-light); - font-size: 14px; -} - -.link-text a { - color: var(--primary); - text-decoration: none; - font-weight: 600; - transition: color 0.2s; -} - -.link-text a:hover { - color: var(--primary-dark); - text-decoration: underline; -} - -/* Table Styles */ -table { - width: 100%; - border-collapse: collapse; - margin-top: 20px; - background: white; - border-radius: 8px; - overflow: hidden; - box-shadow: var(--shadow); -} - -table th, -table td { - padding: 16px; - text-align: left; - border-bottom: 1px solid var(--border); -} - -table th { - background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%); - color: white; - font-weight: 600; - font-size: 14px; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -table tr:last-child td { - border-bottom: none; -} - -table tr:hover { - background-color: var(--light); -} - -table a { - color: var(--primary); - text-decoration: none; - font-weight: 500; - transition: color 0.2s; -} - -table a:hover { - color: var(--primary-dark); - text-decoration: underline; -} - -/* Dashboard Styles */ -.dashboard { - max-width: 1400px; - background: transparent; - box-shadow: none; - padding: 0; -} - -.dashboard > h1 { - background: white; - padding: 32px; - border-radius: 16px; - margin-bottom: 24px; - box-shadow: var(--shadow-lg); -} - -.card { - background: white; - border-radius: 12px; - padding: 32px; - margin-bottom: 24px; - box-shadow: var(--shadow-lg); - border: 1px solid var(--border); -} - -.card h2 { - color: var(--dark); - margin-bottom: 20px; - font-size: 22px; -} - -/* Info Grid */ -.info-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 16px; - margin-bottom: 24px; -} - -.info-item { - padding: 16px; - background: var(--light); - border-radius: 8px; - border-left: 4px solid var(--primary); -} - -.info-item strong { - display: block; - color: var(--text-light); - font-size: 12px; - text-transform: uppercase; - letter-spacing: 0.5px; - margin-bottom: 4px; -} - -.info-item span { - color: var(--dark); - font-size: 18px; - font-weight: 600; -} - -/* Status Badges */ -.badge { - display: inline-block; - padding: 6px 12px; - border-radius: 6px; - font-size: 12px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.badge-success { - background-color: #d1fae5; - color: #065f46; -} - -.badge-warning { - background-color: #fef3c7; - color: #92400e; -} - -.badge-danger { - background-color: #fee2e2; - color: #991b1b; -} - -.badge-info { - background-color: #dbeafe; - color: #1e40af; -} - -/* Details/Summary */ -details { - background: var(--light); - border: 2px solid var(--border); - border-radius: 8px; - padding: 16px; - margin-top: 16px; -} - -summary { - cursor: pointer; - font-weight: 600; - color: var(--primary); - user-select: none; - padding: 8px; - margin: -8px; - border-radius: 6px; - transition: background-color 0.2s; -} - -summary:hover { - background-color: rgba(79, 70, 229, 0.1); -} - -details[open] summary { - margin-bottom: 16px; - padding-bottom: 16px; - border-bottom: 2px solid var(--border); -} - -/* Action Buttons */ -.action-buttons { - display: flex; - gap: 12px; - flex-wrap: wrap; - margin-top: 24px; -} - -.btn-approve { - background: linear-gradient(135deg, var(--success) 0%, #059669 100%); -} - -.btn-reject { - background: linear-gradient(135deg, var(--danger) 0%, #dc2626 100%); -} - -.btn-secondary { - background: linear-gradient(135deg, var(--text-light) 0%, var(--text) 100%); -} - -/* Responsive Design */ -@media (max-width: 768px) { - body { - padding: 12px; - } - - .container { - padding: 32px 24px; - } - - h1 { - font-size: 26px; - } - - h2 { - font-size: 20px; - } - - table { - font-size: 14px; - } - - table th, - table td { - padding: 12px 8px; - } - - .info-grid { - grid-template-columns: 1fr; - } - - .action-buttons { - flex-direction: column; - } - - .action-buttons button, - .action-buttons .btn { - width: 100%; - } -} - -/* Loading State */ -.loading { - display: inline-block; - width: 20px; - height: 20px; - border: 3px solid rgba(255, 255, 255, 0.3); - border-radius: 50%; - border-top-color: white; - animation: spin 0.8s linear infinite; -} - -@keyframes spin { - to { - transform: rotate(360deg); - } -} - -/* Utility Classes */ -.text-center { - text-align: center; -} - -.mt-1 { margin-top: 8px; } -.mt-2 { margin-top: 16px; } -.mt-3 { margin-top: 24px; } -.mb-1 { margin-bottom: 8px; } -.mb-2 { margin-bottom: 16px; } -.mb-3 { margin-bottom: 24px; } - -.text-muted { - color: var(--text-light); -} - -.text-small { - font-size: 14px; -} - -hr { - border: none; - border-top: 2px solid var(--border); - margin: 32px 0; -} diff --git a/public/index.php b/public/index.php deleted file mode 100644 index 9d1f63a..0000000 --- a/public/index.php +++ /dev/null @@ -1,260 +0,0 @@ -bootstrap(); - -$session = new SessionMiddleware(); -$admin = new AdminMiddleware(); - -$db = getAppDb(); -$accountsDb = getAccountsDb(); -$notification = new NotificationService(); -$razorpay = new RazorpayApi( - $_ENV['RAZORPAY_KEY_ID'] ?? getenv('RAZORPAY_KEY_ID'), - $_ENV['RAZORPAY_KEY_SECRET'] ?? getenv('RAZORPAY_KEY_SECRET') -); - -$authController = new AuthController($db, $notification); -$portalController = new StudentPortalController($db); -$registrationController = new RegistrationController($db); -$verificationService = new PaymentVerificationService($db, $accountsDb, $razorpay, $notification); -$paymentController = new PaymentController($db, $verificationService, $razorpay); -$adminController = new AdminController($db, $verificationService, $notification); - -// ------------------------------------------------------------------------- -// Router -// ------------------------------------------------------------------------- -$method = $_SERVER['REQUEST_METHOD'] ?? 'GET'; -$uri = strtok($_SERVER['REQUEST_URI'] ?? '/', '?'); - -// Validate CSRF on all POST requests -if ($method === 'POST') { - $csrf->validate(); -} - -// Helper to render a view -function renderView(string $view, array $data = []): void -{ - extract($data, EXTR_SKIP); - require dirname(__DIR__) . '/app/Views/' . $view . '.php'; -} - -// ------------------------------------------------------------------------- -// Public routes (no session required) -// ------------------------------------------------------------------------- -match (true) { - - // Home - redirect to login - $uri === '/' && $method === 'GET' => (function () { - header('Location: /login', true, 302); - exit; - })(), - - // Sign-up - $uri === '/signup' && $method === 'GET' => renderView('auth/signup'), - $uri === '/signup' && $method === 'POST' => (function () use ($authController) { - $result = $authController->signUp($_POST); - if ($result['success']) { - header('Location: /login?registered=1', true, 302); - exit; - } - renderView('auth/signup', ['errors' => $result['errors']]); - })(), - - // Login - $uri === '/login' && $method === 'GET' => renderView('auth/login'), - $uri === '/login' && $method === 'POST' => (function () use ($authController) { - $result = $authController->login($_POST['roll_no'] ?? '', $_POST['mobile'] ?? ''); - if ($result['success']) { - $_SESSION['pending_roll_no'] = $_POST['roll_no'] ?? ''; - header('Location: /otp', true, 302); - exit; - } - renderView('auth/login', ['error' => $result['message']]); - })(), - - // OTP verification - $uri === '/otp' && $method === 'GET' => renderView('auth/otp'), - $uri === '/otp' && $method === 'POST' => (function () use ($authController) { - $rollNo = $_SESSION['pending_roll_no'] ?? ''; - $result = $authController->verifyOtp($rollNo, $_POST['otp'] ?? ''); - if ($result['success']) { - unset($_SESSION['pending_roll_no']); - header('Location: /portal', true, 302); - exit; - } - renderView('auth/otp', ['error' => $result['message']]); - })(), - - // Logout - $uri === '/logout' => (function () use ($authController) { - $authController->logout(); - header('Location: /login', true, 302); - exit; - })(), - - // ------------------------------------------------------------------------- - // Protected student routes - // ------------------------------------------------------------------------- - - // Student portal dashboard - $uri === '/portal' && $method === 'GET' => (function () use ($session, $portalController) { - $session->handle(); - $data = $portalController->dashboard((int) $_SESSION['student_id']); - renderView('portal/dashboard', $data); - })(), - - // Registration form - $uri === '/register' && $method === 'GET' => (function () use ($session, $registrationController) { - $session->handle(); - $studentId = (int) $_SESSION['student_id']; - $semesterId = (int) ($_GET['semester'] ?? 0); - $data = $registrationController->getRegistrationForm($studentId, $semesterId); - renderView('registration/form', $data); - })(), - - $uri === '/register' && $method === 'POST' => (function () use ($session, $registrationController) { - $session->handle(); - $result = $registrationController->submitRegistration((int) $_SESSION['student_id'], $_POST); - if ($result['success']) { - header('Location: /payment?reg=' . $result['registration_id'], true, 302); - exit; - } - renderView('registration/form', ['error' => $result['error']]); - })(), - - // Payment selection - $uri === '/payment' && $method === 'GET' => (function () use ($session, $paymentController) { - $session->handle(); - $registrationId = (int) ($_GET['reg'] ?? 0); - $status = $paymentController->getPaymentStatus($registrationId); - renderView('payment/select', ['registration_id' => $registrationId, 'status' => $status]); - })(), - - // Initiate Razorpay payment - $uri === '/payment/initiate' && $method === 'POST' => (function () use ($session, $paymentController) { - $session->handle(); - $result = $paymentController->initiatePayment( - (int) ($_POST['registration_id'] ?? 0), - $_POST['method'] ?? 'razorpay' - ); - header('Content-Type: application/json'); - echo json_encode($result); - exit; - })(), - - // Razorpay callback - $uri === '/payment/callback' && $method === 'POST' => (function () use ($paymentController) { - $result = $paymentController->handleGatewayCallback($_POST); - if ($result['success']) { - header('Location: /portal?payment=success', true, 302); - exit; - } - renderView('payment/select', ['error' => $result['error'] ?? 'Payment failed']); - })(), - - // Bank transfer submission - $uri === '/payment/bank-transfer' && $method === 'POST' => (function () use ($session, $paymentController) { - $session->handle(); - $result = $paymentController->submitBankTransfer( - (int) ($_POST['registration_id'] ?? 0), - $_POST, - $_FILES['receipt'] ?? null - ); - if ($result['success']) { - header('Location: /portal?payment=submitted', true, 302); - exit; - } - renderView('payment/bank_transfer', ['error' => $result['error'] ?? 'Submission failed']); - })(), - - // ------------------------------------------------------------------------- - // Admin routes - // ------------------------------------------------------------------------- - - $uri === '/admin' && $method === 'GET' => (function () use ($session, $admin, $adminController) { - $session->handle(); - $admin->handle(); - $registrations = $adminController->getPendingRegistrations(); - renderView('admin/dashboard', ['registrations' => $registrations]); - })(), - - $uri === '/admin/detail' && $method === 'GET' => (function () use ($session, $admin, $adminController) { - $session->handle(); - $admin->handle(); - $data = $adminController->getRegistrationDetail((int) ($_GET['id'] ?? 0)); - renderView('admin/detail', $data); - })(), - - $uri === '/admin/approve' && $method === 'POST' => (function () use ($session, $admin, $adminController) { - $session->handle(); - $admin->handle(); - $result = $adminController->approveRegistration( - (int) ($_POST['registration_id'] ?? 0), - (int) ($_SESSION['student_id'] ?? 0) - ); - header('Location: /admin?action=' . ($result['success'] ? 'approved' : 'error'), true, 302); - exit; - })(), - - $uri === '/admin/reject' && $method === 'POST' => (function () use ($session, $admin, $adminController) { - $session->handle(); - $admin->handle(); - $result = $adminController->rejectRegistration( - (int) ($_POST['registration_id'] ?? 0), - (int) ($_SESSION['student_id'] ?? 0), - trim($_POST['reason'] ?? '') - ); - header('Location: /admin?action=' . ($result['success'] ? 'rejected' : 'error'), true, 302); - exit; - })(), - - // 404 fallback - default => (function () { - http_response_code(404); - echo '

404 Not Found

'; - })(), -}; diff --git a/public/test.php b/public/test.php deleted file mode 100644 index b9df3d5..0000000 --- a/public/test.php +++ /dev/null @@ -1,3 +0,0 @@ - 'B.TECH', + 'Integrated B.tech + M.tech' => 'Integrated B.tech + M.tech', + 'BCA' => 'BCA', + 'M.tech' => 'M.tech', + 'MCA' => 'MCA', + 'PHD' => 'PHD' +]; + +// Branch list +$branches = [ + 'CSE' => 'CSE', + 'CSE (AI)' => 'CSE (AI)', + 'CSE (Cyber Security)' => 'CSE (Cyber Security)', + 'CSE (Data Science)' => 'CSE (Data Science)', + 'CSE (Internet of Things)' => 'CSE (IoT)', + 'CSE (Machine Learning)' => 'CSE (ML)', + 'ECE' => 'ECE', + 'ECE (AI/ML)' => 'ECE (AI/ML)', + 'IT' => 'IT', + 'Integrated B.tech + M.tech CSE' => 'Integrated B.tech + M.tech CSE', + 'BCA' => 'BCA', + 'MCA' => 'MCA', + 'PHD (CSE)' => 'PHD (CSE)', + 'PHD (ECE)' => 'PHD (ECE)' +]; + +// Hostel list +$hostels = [ + 'Sant Ravi Das Boys Hostel', 'Guru Ghasi Das Boys Hostel', 'Shri Narayan Guru Boys Hostel', + 'Birsa Munda Hostel Boys', 'Sant Kabir Das Hostel Boys', 'Shri Chatrapati Sahu Ji Maharaj Boys Hostel', + 'Malik Mohd. Jaysi Hostel Boys', 'Tulsidas Hostel Boys', 'Raheem Boys Hostel', 'Ram Sharan Das Boys Hostel', + 'Munshi Prem Chand Boys Hostel (New Hostel)', 'Maharshi Valmiki Boys Hostel', 'Rani Laxmi Bai Girls Hostel', + 'Mahamaya Girls Hostel', 'Rama Bai Ambedkar Girls Hostel', 'Savitri Bai Phule Girls Hostel', + 'Maha Devi Verma Girls Hostel', 'Ismat Chughtai Girls Hostel', 'Married Research Scholars Hostel', 'Day Scholar' +]; +?> + +
+

Registration Form (2024-25)

+

Please fill in all the details accurately. Fields marked with * are mandatory.

+ +
+ +
+

Personal Details

+
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+

Contact Details

+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+

Fee Details

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParticularsAmount PaidRemainingBank/Txn DetailsPlatformDate
Odd Semester + +
Even Semester + +
Hostel Fee + +
Mess Fee + +
+
+ + + + + + +
+ + + + +
+ +
+
+
+ + + + diff --git a/registrations.sql b/registrations.sql new file mode 100644 index 0000000..3223152 --- /dev/null +++ b/registrations.sql @@ -0,0 +1,102 @@ +-- phpMyAdmin SQL Dump +-- version 5.2.1 +-- https://www.phpmyadmin.net/ +-- +-- Host: localhost:3306 +-- Generation Time: Jan 03, 2025 at 02:21 AM +-- Server version: 10.6.20-MariaDB-cll-lve +-- PHP Version: 8.1.29 + +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +START TRANSACTION; +SET time_zone = "+00:00"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; + +-- +-- Database: `krishabhi_2003` +-- + +-- -------------------------------------------------------- + +-- +-- Table structure for table `registrations` +-- + +CREATE TABLE `registrations` ( + `id` int(11) NOT NULL, + `rollNumber` varchar(20) NOT NULL, + `fullName` varchar(255) NOT NULL, + `fathersName` varchar(255) NOT NULL, + `nameOfProgramme` varchar(255) NOT NULL, + `branchSpecialization` varchar(255) NOT NULL, + `year` int(11) NOT NULL, + `semester` int(11) NOT NULL, + `category` varchar(50) DEFAULT NULL, + `gender` varchar(20) DEFAULT NULL, + `stateDomicile` varchar(100) DEFAULT NULL, + `aadharCard` varchar(12) DEFAULT NULL, + `permanentAddress` text DEFAULT NULL, + `hostelAddress` text DEFAULT NULL, + `studentContact` varchar(15) DEFAULT NULL, + `studentEmail` varchar(100) DEFAULT NULL, + `fatherContact` varchar(15) DEFAULT NULL, + `fatherOccupation` varchar(100) NOT NULL, + `fatherEmail` varchar(100) DEFAULT NULL, + `oddSemesterAmount` decimal(10,2) DEFAULT NULL, + `oddSemesterRemaining` decimal(10,2) DEFAULT NULL, + `oddSemesterDetails` text DEFAULT NULL, + `oddSemesterTxnDetails` text DEFAULT NULL, + `oddSemesterPlatform` varchar(100) DEFAULT NULL, + `oddSemesterDate` date DEFAULT NULL, + `evenSemesterAmount` decimal(10,2) DEFAULT NULL, + `evenSemesterRemaining` decimal(10,2) DEFAULT NULL, + `evenSemesterDetails` text DEFAULT NULL, + `evenSemesterTxnDetails` text DEFAULT NULL, + `evenSemesterPlatform` varchar(100) DEFAULT NULL, + `evenSemesterDate` date DEFAULT NULL, + `hostelAmount` decimal(10,2) DEFAULT NULL, + `hostelRemaining` decimal(10,2) DEFAULT NULL, + `hostelPaymentMode` varchar(50) DEFAULT NULL, + `hostelTxnDetails` text DEFAULT NULL, + `hostelPlatform` varchar(100) DEFAULT NULL, + `hostelDate` date DEFAULT NULL, + `messAmount` decimal(10,2) DEFAULT NULL, + `messRemaining` decimal(10,2) DEFAULT NULL, + `messPaymentMode` varchar(50) DEFAULT NULL, + `messTxnDetails` text DEFAULT NULL, + `messPlatform` varchar(100) DEFAULT NULL, + `messDate` date DEFAULT NULL, + `motherContact` varchar(15) DEFAULT NULL, + `motherEmail` varchar(100) DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci; + +-- +-- Indexes for dumped tables +-- + +-- +-- Indexes for table `registrations` +-- +ALTER TABLE `registrations` + ADD PRIMARY KEY (`id`), + ADD UNIQUE KEY `rollNumber` (`rollNumber`); + +-- +-- AUTO_INCREMENT for dumped tables +-- + +-- +-- AUTO_INCREMENT for table `registrations` +-- +ALTER TABLE `registrations` + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=35; +COMMIT; + +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/scripts/.gitkeep b/scripts/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/scripts/purge_otp_tokens.php b/scripts/purge_otp_tokens.php deleted file mode 100644 index 74234f3..0000000 --- a/scripts/purge_otp_tokens.php +++ /dev/null @@ -1,23 +0,0 @@ -prepare('DELETE FROM otp_tokens WHERE expires_at < NOW()'); -$stmt->execute(); - -$deleted = $stmt->rowCount(); - -echo sprintf("[%s] Purged %d expired OTP token(s).\n", date('Y-m-d H:i:s'), $deleted); diff --git a/scripts/retry_verification.php b/scripts/retry_verification.php deleted file mode 100644 index 176eb32..0000000 --- a/scripts/retry_verification.php +++ /dev/null @@ -1,57 +0,0 @@ -prepare( - "SELECT r.id AS registration_id - FROM registrations r - JOIN payments p ON p.registration_id = r.id - WHERE p.verification_status = 'pending' - AND r.status = 'payment_submitted'" -); -$stmt->execute(); -$pending = $stmt->fetchAll(PDO::FETCH_ASSOC); - -$total = count($pending); -$verified = 0; - -foreach ($pending as $row) { - $result = $verificationService->pollVerification((int) $row['registration_id']); - if ($result['verified']) { - $verified++; - } - echo sprintf( - "[%s] Registration %d: %s\n", - date('Y-m-d H:i:s'), - $row['registration_id'], - $result['message'] - ); -} - -echo sprintf("\nDone. %d/%d verified.\n", $verified, $total); diff --git a/storage/uploads/.gitkeep b/storage/uploads/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/tests/.gitkeep b/tests/.gitkeep deleted file mode 100644 index e69de29..0000000