diff --git a/center_subjects.php b/center_subjects.php
new file mode 100644
index 0000000..98f36a7
--- /dev/null
+++ b/center_subjects.php
@@ -0,0 +1,124 @@
+ 0 ? get_application($applicationId) : null;
+$isApproved = $application && (string) $application['status'] === 'approved';
+
+$selectedCycleId = 0;
+if ($isApproved) {
+ $cycleContext = resolve_school_cycle_context((int) $application['id'], $application, $requestedCycleId);
+ $selectedCycle = $cycleContext['selected'];
+ $selectedCycleId = $selectedCycle ? (int) ($selectedCycle['id'] ?? 0) : 0;
+}
+
+if (!$application || !$isApproved) {
+ http_response_code(404);
+ render_page_start('المركز غير موجود', 'profile');
+ render_flash($flash);
+ ?>
+
+
+
+
+
+
+
+
+
المركز غير موجود
+
العودة
+
+
+
+
+
+ 0 ? '&cycle=' . $selectedCycleId : '';
+ header('Location: center_subjects.php?id=' . $application['id'] . $selectedCycleIdStr);
+ exit;
+ } catch (Throwable $e) {
+ $errors['form'] = 'تعذر حفظ البيانات. يرجى المحاولة لاحقاً.';
+ }
+}
+
+render_page_start('إدارة المواد الدراسية: ' . (string) $application['center_name'], 'profile', 'تحديد المواد الدراسية التي يتم تدريسها في المركز.');
+render_flash($flash);
+?>
+
+
+
+
+
+
+
+
+
المواد الدراسية للمركز
+
تحديد وتحديث المواد الدراسية التي يتم تقديمها في هذا المركز. سيتم توفير هذه المواد لاختيارها في التقييمات والجداول.
+
+
+
+
+
+
+
+
+ 0,
+ "SELECT 1",
+ CONCAT("ALTER TABLE ", @tablename, " ADD ", @columnname, " INT UNSIGNED NULL AFTER center_application_id;")
+));
+PREPARE alterIfNotExists FROM @preparedStatement;
+EXECUTE alterIfNotExists;
+DEALLOCATE PREPARE alterIfNotExists;
+
+-- Safely add foreign key
+SET @fkname = 'fk_school_cycles_global_cycle';
+SET @preparedStatement = (SELECT IF(
+ (
+ SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
+ WHERE
+ (table_name = @tablename)
+ AND (table_schema = @dbname)
+ AND (constraint_name = @fkname)
+ ) > 0,
+ "SELECT 1",
+ CONCAT("ALTER TABLE ", @tablename, " ADD CONSTRAINT ", @fkname, " FOREIGN KEY (", @columnname, ") REFERENCES global_cycles(id) ON DELETE SET NULL;")
+));
+PREPARE alterIfNotExists FROM @preparedStatement;
+EXECUTE alterIfNotExists;
+DEALLOCATE PREPARE alterIfNotExists;
+
+-- We can make season/year nullable or give defaults, since we will use global cycles instead.
+-- We'll just alter them to allow NULL
+ALTER TABLE school_cycles MODIFY season VARCHAR(20) NULL;
+ALTER TABLE school_cycles MODIFY year SMALLINT UNSIGNED NULL;
diff --git a/db/migrations/20260416_global_cycles.sql b/db/migrations/20260416_global_cycles.sql
new file mode 100644
index 0000000..ae036ae
--- /dev/null
+++ b/db/migrations/20260416_global_cycles.sql
@@ -0,0 +1,45 @@
+CREATE TABLE IF NOT EXISTS global_cycles (
+ id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
+ cycle_name VARCHAR(150) NOT NULL,
+ start_date DATE NOT NULL,
+ end_date DATE NOT NULL,
+ is_active TINYINT(1) NOT NULL DEFAULT 1,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- We need to safely add the column.
+SET @dbname = DATABASE();
+SET @tablename = 'center_applications';
+SET @columnname = 'global_cycle_id';
+SET @preparedStatement = (SELECT IF(
+ (
+ SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
+ WHERE
+ (table_name = @tablename)
+ AND (table_schema = @dbname)
+ AND (column_name = @columnname)
+ ) > 0,
+ "SELECT 1",
+ CONCAT("ALTER TABLE ", @tablename, " ADD ", @columnname, " INT UNSIGNED NULL AFTER expected_students;")
+));
+PREPARE alterIfNotExists FROM @preparedStatement;
+EXECUTE alterIfNotExists;
+DEALLOCATE PREPARE alterIfNotExists;
+
+-- Safely add foreign key
+SET @fkname = 'fk_center_applications_global_cycle';
+SET @preparedStatement = (SELECT IF(
+ (
+ SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
+ WHERE
+ (table_name = @tablename)
+ AND (table_schema = @dbname)
+ AND (constraint_name = @fkname)
+ ) > 0,
+ "SELECT 1",
+ CONCAT("ALTER TABLE ", @tablename, " ADD CONSTRAINT ", @fkname, " FOREIGN KEY (", @columnname, ") REFERENCES global_cycles(id) ON DELETE SET NULL;")
+));
+PREPARE alterIfNotExists FROM @preparedStatement;
+EXECUTE alterIfNotExists;
+DEALLOCATE PREPARE alterIfNotExists;
diff --git a/fix_app.php b/fix_app.php
new file mode 100644
index 0000000..55f20e7
--- /dev/null
+++ b/fix_app.php
@@ -0,0 +1,49 @@
+exec(\"
+ CREATE TABLE IF NOT EXISTS app_settings (
+ id INT PRIMARY KEY DEFAULT 1,
+ app_name VARCHAR(190) NOT NULL DEFAULT 'Central Admin',
+ app_email VARCHAR(190) DEFAULT NULL,
+ app_telephone VARCHAR(60) DEFAULT NULL,
+ app_logo VARCHAR(255) DEFAULT NULL,
+ app_favicon VARCHAR(255) DEFAULT NULL,
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+ \");
+ \$pdo->exec(\"INSERT IGNORE INTO app_settings (id, app_name) VALUES (1, 'Central Admin')\");
+}
+
+function get_app_settings(): array
+{
+ \$pdo = db_connection();
+ \$stmt = \$pdo->query('SELECT * FROM app_settings WHERE id = 1');
+ \$res = \$stmt->fetch(PDO::FETCH_ASSOC);
+ if (!\$res) {
+ return [
+ 'app_name' => 'Central Admin',
+ 'app_email' => '',
+ 'app_telephone' => '',
+ 'app_logo' => '',
+ 'app_favicon' => ''
+ ];
+ }
+ return \$res;
+}
+
+function ensure_center_application_schema(PDO \$pdo): void";
+
+$content = str_replace($search2, $replace2, $content);
+
+file_put_contents($file, $content);
+echo "Done\n";
+
diff --git a/fix_app_settings.py b/fix_app_settings.py
new file mode 100644
index 0000000..0101dee
--- /dev/null
+++ b/fix_app_settings.py
@@ -0,0 +1,52 @@
+import re
+
+with open('includes/app.php', 'r') as f:
+ content = f.read()
+
+# Add ensure_app_settings_schema call
+content = content.replace(
+ 'ensure_center_application_schema($pdo);',
+ 'ensure_app_settings_schema($pdo);
+ ensure_center_application_schema($pdo);'
+)
+
+# Add function definitions before ensure_center_application_schema definition
+new_funcs = """function ensure_app_settings_schema(PDO $pdo): void
+{
+ $pdo->exec("
+ CREATE TABLE IF NOT EXISTS app_settings (
+ id INT PRIMARY KEY DEFAULT 1,
+ app_name VARCHAR(190) NOT NULL DEFAULT 'Central Admin',
+ app_email VARCHAR(190) DEFAULT NULL,
+ app_telephone VARCHAR(60) DEFAULT NULL,
+ app_logo VARCHAR(255) DEFAULT NULL,
+ app_favicon VARCHAR(255) DEFAULT NULL,
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+ ");
+ $pdo->exec("INSERT IGNORE INTO app_settings (id, app_name) VALUES (1, 'Central Admin')");
+}
+
+function get_app_settings(): array
+{
+ $pdo = db_connection();
+ $stmt = $pdo->query('SELECT * FROM app_settings WHERE id = 1');
+ $res = $stmt->fetch(PDO::FETCH_ASSOC);
+ if (!$res) {
+ return [
+ 'app_name' => 'Central Admin',
+ 'app_email' => '',
+ 'app_telephone' => '',
+ 'app_logo' => '',
+ 'app_favicon' => ''
+ ];
+ }
+ return $res;
+}
+
+function ensure_center_application_schema(PDO $pdo): void"""
+
+content = content.replace('function ensure_center_application_schema(PDO $pdo): void', new_funcs)
+
+with open('includes/app.php', 'w') as f:
+ f.write(content)
\ No newline at end of file
diff --git a/global_cycles.php b/global_cycles.php
new file mode 100644
index 0000000..a4c1edc
--- /dev/null
+++ b/global_cycles.php
@@ -0,0 +1,297 @@
+prepare('INSERT INTO global_cycles (cycle_name, start_date, end_date) VALUES (?, ?, ?)');
+ $stmt->execute([$cycleName, $startDate, $endDate]);
+ set_flash('success', 'تم إضافة الدورة بنجاح.');
+ header('Location: global_cycles.php');
+ exit;
+ } catch (Throwable $e) {
+ $errors['form'] = 'حدث خطأ أثناء حفظ الدورة.';
+ }
+ }
+ } elseif ($action === 'toggle') {
+ $cycleId = filter_input(INPUT_POST, 'cycle_id', FILTER_VALIDATE_INT);
+ if ($cycleId) {
+ try {
+ $stmt = db()->prepare('UPDATE global_cycles SET is_active = NOT is_active WHERE id = ?');
+ $stmt->execute([$cycleId]);
+ set_flash('success', 'تم تغيير حالة الدورة.');
+ } catch (Throwable $e) {
+ set_flash('error', 'حدث خطأ.');
+ }
+ }
+ header('Location: global_cycles.php');
+ exit;
+ } elseif ($action === 'delete') {
+ $cycleId = filter_input(INPUT_POST, 'cycle_id', FILTER_VALIDATE_INT);
+ if ($cycleId) {
+ try {
+ $stmt = db()->prepare('DELETE FROM global_cycles WHERE id = ?');
+ $stmt->execute([$cycleId]);
+ set_flash('success', 'تم حذف الدورة بنجاح.');
+ } catch (Throwable $e) {
+ set_flash('error', 'لا يمكن حذف الدورة، ربما تكون مرتبطة بطلبات أخرى.');
+ }
+ }
+ header('Location: global_cycles.php');
+ exit;
+ } elseif ($action === 'edit') {
+ $cycleId = filter_input(INPUT_POST, 'cycle_id', FILTER_VALIDATE_INT);
+ $cycleName = clean_text($_POST['cycle_name'] ?? '');
+ $startDate = $_POST['start_date'] ?? '';
+ $endDate = $_POST['end_date'] ?? '';
+
+ if ($cycleId && $cycleName && $startDate && $endDate) {
+ try {
+ $stmt = db()->prepare('UPDATE global_cycles SET cycle_name = ?, start_date = ?, end_date = ? WHERE id = ?');
+ $stmt->execute([$cycleName, $startDate, $endDate, $cycleId]);
+ set_flash('success', 'تم تعديل الدورة بنجاح.');
+ } catch (Throwable $e) {
+ set_flash('error', 'حدث خطأ أثناء التعديل.');
+ }
+ }
+ header('Location: global_cycles.php');
+ exit;
+ }
+}
+
+// Fetch cycles
+$search = clean_text($_GET['search'] ?? '', 255);
+$page = filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT) ?: 1;
+$limit = 10;
+$offset = ($page - 1) * $limit;
+
+$cycles = [];
+$totalItems = 0;
+
+try {
+ $query = 'SELECT * FROM global_cycles';
+ $countQuery = 'SELECT COUNT(*) FROM global_cycles';
+ $params = [];
+
+ if ($search !== '') {
+ $where = ' WHERE cycle_name LIKE ?';
+ $query .= $where;
+ $countQuery .= $where;
+ $params[] = "%$search%";
+ }
+
+ $stmtCount = db()->prepare($countQuery);
+ $stmtCount->execute($params);
+ $totalItems = (int)$stmtCount->fetchColumn();
+
+ $query .= ' ORDER BY start_date DESC LIMIT ' . $limit . ' OFFSET ' . $offset;
+ $stmt = db()->prepare($query);
+ $stmt->execute($params);
+ $cycles = $stmt->fetchAll(PDO::FETCH_ASSOC);
+} catch (Throwable $e) {}
+
+render_page_start('إدارة الدورات الموسمية', 'cycles', 'إضافة وإدارة الدورات التي تتقدم لها المراكز.');
+render_flash($flash);
+?>
+
+
+
+
+
+
+
+
+
+
+
الدورات الموسمية
+
يمكنك هنا تعريف الدورات الجديدة ليتاح للمراكز التقديم عليها.
+
+
+
+
+
+
+
+
+
= e($errors['form']) ?>
+
+
+
+
+
+
+
+ | اسم الدورة |
+ الفترة الزمنية |
+ الحالة |
+ الإجراءات |
+
+
+
+
+
+ | لا توجد دورات مسجلة حالياً. |
+
+
+
+
+ | = e((string)$cycle['cycle_name']) ?> |
+
+ من: = e((string)$cycle['start_date']) ?>
+ إلى: = e((string)$cycle['end_date']) ?>
+ |
+
+
+ متاحة للتقديم
+
+ مغلقة
+
+ |
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/includes/app.php b/includes/app.php
index a57e7f0..5d97d29 100644
--- a/includes/app.php
+++ b/includes/app.php
@@ -96,6 +96,7 @@ function db_connection(): PDO
}
if (!$bootstrapped) {
+ ensure_app_settings_schema($pdo);
ensure_center_application_schema($pdo);
seed_center_application_demo_data($pdo);
ensure_school_module_schema($pdo);
@@ -107,6 +108,39 @@ function db_connection(): PDO
return $pdo;
}
+function ensure_app_settings_schema(PDO $pdo): void
+{
+ $pdo->exec("
+ CREATE TABLE IF NOT EXISTS app_settings (
+ id INT PRIMARY KEY DEFAULT 1,
+ app_name VARCHAR(190) NOT NULL DEFAULT 'Central Admin',
+ app_email VARCHAR(190) DEFAULT NULL,
+ app_telephone VARCHAR(60) DEFAULT NULL,
+ app_logo VARCHAR(255) DEFAULT NULL,
+ app_favicon VARCHAR(255) DEFAULT NULL,
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+ ");
+ $pdo->exec("INSERT IGNORE INTO app_settings (id, app_name) VALUES (1, 'Central Admin')");
+}
+
+function get_app_settings(): array
+{
+ $pdo = db_connection();
+ $stmt = $pdo->query('SELECT * FROM app_settings WHERE id = 1');
+ $res = $stmt->fetch(PDO::FETCH_ASSOC);
+ if (!$res) {
+ return [
+ 'app_name' => 'Central Admin',
+ 'app_email' => '',
+ 'app_telephone' => '',
+ 'app_logo' => '',
+ 'app_favicon' => ''
+ ];
+ }
+ return $res;
+}
+
function ensure_center_application_schema(PDO $pdo): void
{
static $done = false;
@@ -406,7 +440,7 @@ function application_defaults(): array
'phone' => '',
'email' => '',
'expected_students' => '',
- 'start_date' => '',
+ 'start_date' => '', 'global_cycle_id' => '',
'end_date' => '',
'notes' => '',
'subjects' => [],
@@ -1294,6 +1328,75 @@ function render_page_start(string $pageTitle, string $active = 'home', string $p
';
+ echo '';
+ echo '';
+}
+
+function render_search_bar(string $currentSearch, string $placeholder = "بحث...", string $action = "", array $hiddenParams = []): void {
+ echo '
';
+}
+
function render_flash(?array $flash): void
{
if (!$flash || empty($flash['message'])) {
@@ -1368,3 +1471,14 @@ function get_enabled_subjects(): array
return [];
}
}
+
+
+function get_active_global_cycles(): array {
+ try {
+ $stmt = db()->query('SELECT * FROM global_cycles WHERE is_active = 1 ORDER BY start_date DESC');
+ return $stmt->fetchAll(PDO::FETCH_ASSOC);
+ } catch (Throwable $e) {
+ return [];
+ }
+}
+
diff --git a/includes/center_sidebar.php b/includes/center_sidebar.php
new file mode 100644
index 0000000..dc29627
--- /dev/null
+++ b/includes/center_sidebar.php
@@ -0,0 +1,73 @@
+ 0, 'center_name' => 'المركز غير متوفر'];
+}
+
+// Same for selected cycle
+$selectedCycleIdStr = isset($selectedCycleId) && $selectedCycleId > 0 ? '&cycle=' . $selectedCycleId : '';
+$baseQuery = '?id=' . e((string) $application['id']) . $selectedCycleIdStr;
+
+?>
+
diff --git a/includes/cycles.php b/includes/cycles.php
index b93480f..34a84d2 100644
--- a/includes/cycles.php
+++ b/includes/cycles.php
@@ -203,30 +203,26 @@ function validate_school_cycle_input(array $input, ?array $application = null):
{
$defaults = school_cycle_defaults($application);
$data = $defaults;
- $data['season'] = clean_text((string) ($input['season'] ?? $defaults['season']), 20);
- $data['year'] = clean_text((string) ($input['year'] ?? $defaults['year']), 4);
- $data['start_date'] = clean_text((string) ($input['start_date'] ?? $defaults['start_date']), 20);
- $data['end_date'] = clean_text((string) ($input['end_date'] ?? $defaults['end_date']), 20);
$data['status'] = clean_text((string) ($input['status'] ?? 'active'), 20);
+ $data['global_cycle_id'] = filter_var($input['global_cycle_id'] ?? null, FILTER_VALIDATE_INT) ?: null;
$errors = [];
- if (!array_key_exists($data['season'], school_cycle_season_options())) {
- $errors['season'] = 'يرجى اختيار موسم صحيح.';
- }
-
- $year = (int) $data['year'];
- if ((string) $year !== $data['year'] || $year < 2020 || $year > 2100) {
- $errors['year'] = 'يرجى إدخال سنة صحيحة مثل 2026.';
- }
-
- if ($data['start_date'] === '' || strtotime($data['start_date']) === false) {
- $errors['start_date'] = 'يرجى إدخال تاريخ بداية صحيح.';
- }
- if ($data['end_date'] === '' || strtotime($data['end_date']) === false) {
- $errors['end_date'] = 'يرجى إدخال تاريخ نهاية صحيح.';
- }
- if (!isset($errors['start_date'], $errors['end_date']) && strtotime($data['end_date']) < strtotime($data['start_date'])) {
- $errors['end_date'] = 'تاريخ النهاية يجب أن يكون بعد البداية أو مساوياً لها.';
+ if (empty($data['global_cycle_id'])) {
+ $errors['global_cycle_id'] = 'يرجى اختيار الدورة.';
+ } else {
+ try {
+ $stmt = db_connection()->prepare('SELECT * FROM global_cycles WHERE id = ?');
+ $stmt->execute([$data['global_cycle_id']]);
+ if ($cycle = $stmt->fetch(PDO::FETCH_ASSOC)) {
+ $data['cycle_name'] = $cycle['cycle_name'];
+ $data['start_date'] = $cycle['start_date'];
+ $data['end_date'] = $cycle['end_date'];
+ $data['season'] = null; // No longer used
+ $data['year'] = null; // No longer used
+ } else {
+ $errors['global_cycle_id'] = 'الدورة غير صالحة';
+ }
+ } catch (Throwable $e) {}
}
if (!in_array($data['status'], ['active', 'upcoming'], true)) {
@@ -260,20 +256,32 @@ function ensure_default_school_cycle_record(PDO $pdo, array $application): int
return (int) $existing['id'];
}
- $season = detect_school_cycle_season((string) ($application['start_date'] ?? ''));
- $year = (int) date('Y', strtotime((string) ($application['start_date'] ?? 'now')) ?: time());
+ $season = null;
+ $year = null;
$startDate = (string) ($application['start_date'] ?? date('Y-m-d'));
$endDate = (string) ($application['end_date'] ?? $startDate);
- $cycleName = format_school_cycle_name($season, $year);
+ $cycleName = 'الدورة الأساسية';
+ $globalCycleId = $application['global_cycle_id'] ?? null;
+
+ if ($globalCycleId) {
+ $gcStmt = $pdo->prepare('SELECT * FROM global_cycles WHERE id = ?');
+ $gcStmt->execute([$globalCycleId]);
+ if ($gc = $gcStmt->fetch()) {
+ $cycleName = $gc['cycle_name'];
+ $startDate = $gc['start_date'];
+ $endDate = $gc['end_date'];
+ }
+ }
+
$status = ((string) ($application['status'] ?? '') === 'approved') ? 'active' : 'upcoming';
$insert = $pdo->prepare(
'INSERT INTO school_cycles (
center_application_id, season, year, cycle_name, start_date, end_date,
- status, archived_at, created_at, updated_at
+ status, archived_at, created_at, updated_at, global_cycle_id
) VALUES (
:center_application_id, :season, :year, :cycle_name, :start_date, :end_date,
- :status, NULL, NOW(), NOW()
+ :status, NULL, NOW(), NOW(), :global_cycle_id
)'
);
$insert->execute([
@@ -284,6 +292,7 @@ function ensure_default_school_cycle_record(PDO $pdo, array $application): int
':start_date' => $startDate,
':end_date' => $endDate,
':status' => $status,
+ ':global_cycle_id' => $globalCycleId
]);
return (int) $pdo->lastInsertId();
@@ -420,9 +429,10 @@ function copy_school_cycle_rollover(PDO $pdo, int $centerApplicationId, int $sou
function create_school_cycle(int $centerApplicationId, array $data, array $rollover = []): array
{
$pdo = db_connection();
- $season = $data['season'];
- $year = (int) $data['year'];
- $cycleName = format_school_cycle_name($season, $year);
+ $season = $data['season'] ?? null;
+ $year = isset($data['year']) ? (int) $data['year'] : null;
+ $cycleName = $data['cycle_name'] ?? format_school_cycle_name($season ?? 'summer', $year ?? (int)date('Y'));
+ $globalCycleId = $data['global_cycle_id'] ?? null;
$rollover = array_merge(school_cycle_rollover_defaults(), $rollover);
$sourceCycleId = (int) ($rollover['source_cycle_id'] ?? 0);
@@ -455,10 +465,10 @@ function create_school_cycle(int $centerApplicationId, array $data, array $rollo
$insert = $pdo->prepare(
'INSERT INTO school_cycles (
center_application_id, season, year, cycle_name, start_date, end_date,
- status, archived_at, created_at, updated_at
+ status, archived_at, created_at, updated_at, global_cycle_id
) VALUES (
:center_application_id, :season, :year, :cycle_name, :start_date, :end_date,
- :status, :archived_at, NOW(), NOW()
+ :status, :archived_at, NOW(), NOW(), :global_cycle_id
)'
);
$insert->execute([
@@ -470,6 +480,7 @@ function create_school_cycle(int $centerApplicationId, array $data, array $rollo
':end_date' => $data['end_date'],
':status' => $data['status'],
':archived_at' => $data['status'] === 'archived' ? date('Y-m-d H:i:s') : null,
+ ':global_cycle_id' => $globalCycleId,
]);
$cycleId = (int) $pdo->lastInsertId();
@@ -581,21 +592,54 @@ function create_student_in_cycle(int $centerApplicationId, int $cycleId, array $
return (int) $pdo->lastInsertId();
}
-function list_school_students_by_cycle(int $centerApplicationId, int $cycleId): array
+function list_school_students_by_cycle(int $centerApplicationId, int $cycleId, string $search = '', int $limit = 0, int $offset = 0): array
{
$pdo = db_connection();
- $stmt = $pdo->prepare(
- 'SELECT * FROM school_students
- WHERE center_application_id = :center_application_id AND cycle_id = :cycle_id
- ORDER BY created_at DESC, id DESC'
- );
- $stmt->execute([
+ $query = 'SELECT * FROM school_students WHERE center_application_id = :center_application_id AND cycle_id = :cycle_id';
+ $params = [
':center_application_id' => $centerApplicationId,
':cycle_id' => $cycleId,
- ]);
+ ];
+
+ if ($search !== '') {
+ $query .= ' AND (student_code LIKE :search1 OR full_name LIKE :search2 OR guardian_phone LIKE :search3)';
+ $params[':search1'] = "%$search%";
+ $params[':search2'] = "%$search%";
+ $params[':search3'] = "%$search%";
+ }
+
+ $query .= ' ORDER BY created_at DESC, id DESC';
+
+ if ($limit > 0) {
+ $query .= ' LIMIT ' . (int)$limit . ' OFFSET ' . (int)$offset;
+ }
+
+ $stmt = $pdo->prepare($query);
+ $stmt->execute($params);
return $stmt->fetchAll();
}
+function count_school_students_by_cycle(int $centerApplicationId, int $cycleId, string $search = ''): int
+{
+ $pdo = db_connection();
+ $query = 'SELECT COUNT(*) FROM school_students WHERE center_application_id = :center_application_id AND cycle_id = :cycle_id';
+ $params = [
+ ':center_application_id' => $centerApplicationId,
+ ':cycle_id' => $cycleId,
+ ];
+
+ if ($search !== '') {
+ $query .= ' AND (student_code LIKE :search1 OR full_name LIKE :search2 OR guardian_phone LIKE :search3)';
+ $params[':search1'] = "%$search%";
+ $params[':search2'] = "%$search%";
+ $params[':search3'] = "%$search%";
+ }
+
+ $stmt = $pdo->prepare($query);
+ $stmt->execute($params);
+ return (int)$stmt->fetchColumn();
+}
+
function school_student_metrics_by_cycle(int $centerApplicationId, int $cycleId): array
{
$pdo = db_connection();
@@ -654,21 +698,54 @@ function create_teacher_in_cycle(int $centerApplicationId, int $cycleId, array $
return (int) $pdo->lastInsertId();
}
-function list_school_teachers_by_cycle(int $centerApplicationId, int $cycleId): array
+function list_school_teachers_by_cycle(int $centerApplicationId, int $cycleId, string $search = '', int $limit = 0, int $offset = 0): array
{
$pdo = db_connection();
- $stmt = $pdo->prepare(
- 'SELECT * FROM school_teachers
- WHERE center_application_id = :center_application_id AND cycle_id = :cycle_id
- ORDER BY created_at DESC, id DESC'
- );
- $stmt->execute([
+ $query = 'SELECT * FROM school_teachers WHERE center_application_id = :center_application_id AND cycle_id = :cycle_id';
+ $params = [
':center_application_id' => $centerApplicationId,
':cycle_id' => $cycleId,
- ]);
+ ];
+
+ if ($search !== '') {
+ $query .= ' AND (full_name LIKE :search1 OR email LIKE :search2 OR phone LIKE :search3)';
+ $params[':search1'] = "%$search%";
+ $params[':search2'] = "%$search%";
+ $params[':search3'] = "%$search%";
+ }
+
+ $query .= ' ORDER BY created_at DESC, id DESC';
+
+ if ($limit > 0) {
+ $query .= ' LIMIT ' . (int)$limit . ' OFFSET ' . (int)$offset;
+ }
+
+ $stmt = $pdo->prepare($query);
+ $stmt->execute($params);
return $stmt->fetchAll();
}
+function count_school_teachers_by_cycle(int $centerApplicationId, int $cycleId, string $search = ''): int
+{
+ $pdo = db_connection();
+ $query = 'SELECT COUNT(*) FROM school_teachers WHERE center_application_id = :center_application_id AND cycle_id = :cycle_id';
+ $params = [
+ ':center_application_id' => $centerApplicationId,
+ ':cycle_id' => $cycleId,
+ ];
+
+ if ($search !== '') {
+ $query .= ' AND (full_name LIKE :search1 OR email LIKE :search2 OR phone LIKE :search3)';
+ $params[':search1'] = "%$search%";
+ $params[':search2'] = "%$search%";
+ $params[':search3'] = "%$search%";
+ }
+
+ $stmt = $pdo->prepare($query);
+ $stmt->execute($params);
+ return (int)$stmt->fetchColumn();
+}
+
function school_teacher_metrics_by_cycle(int $centerApplicationId, int $cycleId): array
{
$pdo = db_connection();
@@ -729,21 +806,52 @@ function create_assessment_type_in_cycle(int $centerApplicationId, int $cycleId,
return (int) $pdo->lastInsertId();
}
-function list_school_assessments_by_cycle(int $centerApplicationId, int $cycleId): array
+function list_school_assessments_by_cycle(int $centerApplicationId, int $cycleId, string $search = '', int $limit = 0, int $offset = 0): array
{
$pdo = db_connection();
- $stmt = $pdo->prepare(
- 'SELECT * FROM school_assessment_types
- WHERE center_application_id = :center_application_id AND cycle_id = :cycle_id
- ORDER BY is_active DESC, created_at DESC, id DESC'
- );
- $stmt->execute([
+ $query = 'SELECT * FROM school_assessment_types WHERE center_application_id = :center_application_id AND cycle_id = :cycle_id';
+ $params = [
':center_application_id' => $centerApplicationId,
':cycle_id' => $cycleId,
- ]);
+ ];
+
+ if ($search !== '') {
+ $query .= ' AND (assessment_name LIKE :search1 OR assessment_category LIKE :search2)';
+ $params[':search1'] = "%$search%";
+ $params[':search2'] = "%$search%";
+ }
+
+ $query .= ' ORDER BY is_active DESC, created_at DESC, id DESC';
+
+ if ($limit > 0) {
+ $query .= ' LIMIT ' . (int)$limit . ' OFFSET ' . (int)$offset;
+ }
+
+ $stmt = $pdo->prepare($query);
+ $stmt->execute($params);
return $stmt->fetchAll();
}
+function count_school_assessments_by_cycle(int $centerApplicationId, int $cycleId, string $search = ''): int
+{
+ $pdo = db_connection();
+ $query = 'SELECT COUNT(*) FROM school_assessment_types WHERE center_application_id = :center_application_id AND cycle_id = :cycle_id';
+ $params = [
+ ':center_application_id' => $centerApplicationId,
+ ':cycle_id' => $cycleId,
+ ];
+
+ if ($search !== '') {
+ $query .= ' AND (assessment_name LIKE :search1 OR assessment_category LIKE :search2)';
+ $params[':search1'] = "%$search%";
+ $params[':search2'] = "%$search%";
+ }
+
+ $stmt = $pdo->prepare($query);
+ $stmt->execute($params);
+ return (int)$stmt->fetchColumn();
+}
+
function school_assessment_metrics_by_cycle(int $centerApplicationId, int $cycleId): array
{
$pdo = db_connection();
@@ -860,23 +968,58 @@ function create_attendance_record_in_cycle(int $centerApplicationId, int $cycleI
return (int) $pdo->lastInsertId();
}
-function list_school_attendance_records_by_cycle(int $centerApplicationId, int $cycleId): array
+function list_school_attendance_records_by_cycle(int $centerApplicationId, int $cycleId, string $search = '', int $limit = 0, int $offset = 0): array
{
$pdo = db_connection();
- $stmt = $pdo->prepare(
- 'SELECT ar.*, s.student_code, s.full_name, s.grade_level, s.guardian_phone
+ $query = 'SELECT ar.*, s.student_code, s.full_name, s.grade_level, s.guardian_phone
FROM school_attendance_records ar
INNER JOIN school_students s ON s.id = ar.student_id
- WHERE ar.center_application_id = :center_application_id AND ar.cycle_id = :cycle_id
- ORDER BY ar.attendance_date DESC, ar.created_at DESC, ar.id DESC'
- );
- $stmt->execute([
+ WHERE ar.center_application_id = :center_application_id AND ar.cycle_id = :cycle_id';
+ $params = [
':center_application_id' => $centerApplicationId,
':cycle_id' => $cycleId,
- ]);
+ ];
+
+ if ($search !== '') {
+ $query .= ' AND (s.full_name LIKE :search1 OR s.student_code LIKE :search2)';
+ $params[':search1'] = "%$search%";
+ $params[':search2'] = "%$search%";
+ }
+
+ $query .= ' ORDER BY ar.attendance_date DESC, ar.created_at DESC, ar.id DESC';
+
+ if ($limit > 0) {
+ $query .= ' LIMIT ' . (int)$limit . ' OFFSET ' . (int)$offset;
+ }
+
+ $stmt = $pdo->prepare($query);
+ $stmt->execute($params);
return $stmt->fetchAll();
}
+function count_school_attendance_records_by_cycle(int $centerApplicationId, int $cycleId, string $search = ''): int
+{
+ $pdo = db_connection();
+ $query = 'SELECT COUNT(*)
+ FROM school_attendance_records ar
+ INNER JOIN school_students s ON s.id = ar.student_id
+ WHERE ar.center_application_id = :center_application_id AND ar.cycle_id = :cycle_id';
+ $params = [
+ ':center_application_id' => $centerApplicationId,
+ ':cycle_id' => $cycleId,
+ ];
+
+ if ($search !== '') {
+ $query .= ' AND (s.full_name LIKE :search1 OR s.student_code LIKE :search2)';
+ $params[':search1'] = "%$search%";
+ $params[':search2'] = "%$search%";
+ }
+
+ $stmt = $pdo->prepare($query);
+ $stmt->execute($params);
+ return (int)$stmt->fetchColumn();
+}
+
function school_attendance_metrics_by_cycle(int $centerApplicationId, int $cycleId): array
{
$pdo = db_connection();
diff --git a/includes/sidebar.php b/includes/sidebar.php
index 63f80b3..93cb17c 100644
--- a/includes/sidebar.php
+++ b/includes/sidebar.php
@@ -5,6 +5,7 @@ $statusQuery = $_GET['status'] ?? '';
$activePage = 'admin';
if ($script === 'app_settings.php') $activePage = 'app_settings';
if ($script === 'subjects.php') $activePage = 'subjects';
+if ($script === 'global_cycles.php') $activePage = 'global_cycles';
if ($script === 'dashboard.php') $activePage = 'dashboard';
if ($script === 'applications.php') $activePage = ($statusQuery === 'approved') ? 'approved' : 'applications';
if (in_array($script, ['approved_school.php', 'center_profile.php', 'students.php', 'teachers.php', 'assessments.php', 'attendance.php'], true)) $activePage = 'approved';
@@ -38,6 +39,10 @@ if (!isset($recentApproved)) {
+