diff --git a/DATA_STRUCTURE.md b/DATA_STRUCTURE.md index ce36205..f972ead 100644 --- a/DATA_STRUCTURE.md +++ b/DATA_STRUCTURE.md @@ -1,4 +1,65 @@ -# Example Data Structure for Axia4 +# Data Architecture for Axia4 + +Axia4 uses a **SQLite database** (`/DATA/axia4.sqlite`) for all structured data, with the +filesystem reserved for binary assets (photos, uploaded files, project documents). + +## Database (`/DATA/axia4.sqlite`) + +The schema is defined in `public_html/_incl/migrations/001_initial_schema.sql` and +applied automatically on first boot via `db.php`. + +| Table | Replaces | +|----------------------|---------------------------------------------------| +| `config` | `/DATA/AuthConfig.json` | +| `users` | `/DATA/Usuarios/*.json` | +| `invitations` | `/DATA/Invitaciones_de_usuarios.json` | +| `centros` | Directory existence at `.../Centros/{id}/` | +| `user_centros` | `entreaulas.centro` + `entreaulas.aulas` in users | +| `aularios` | `.../Aularios/{id}.json` | +| `supercafe_menu` | `.../SuperCafe/Menu.json` | +| `supercafe_orders` | `.../SuperCafe/Comandas/*.json` | +| `comedor_menu_types` | `.../Comedor-MenuTypes.json` | +| `comedor_entries` | `.../Comedor/{ym}/{day}/_datos.json` | +| `club_events` | `/DATA/club/IMG/{date}/data.json` | +| `club_config` | `/DATA/club/config.json` | + +## Migrations + +Migrations live in `public_html/_incl/migrations/`: + +- `001_initial_schema.sql` — DDL for all tables. +- `002_import_json.php` — One-time importer: reads existing JSON files and + inserts them into the database. Run automatically on first boot if JSON files + exist and the DB is empty. + +## Filesystem (binary / large assets) + +``` +DATA/ +└── entreaulas/ + └── Centros/ + └── {centro_id}/ + ├── Aularios/ + │ └── {aulario_id}/ + │ ├── Alumnos/ # Student photo directories + │ │ └── {alumno}/photo.jpg + │ ├── Comedor/{ym}/{day}/ # Comedor pictogram images + │ └── Proyectos/ # Project binary files + └── Panel/ + └── Actividades/{name}/photo.jpg # Activity photos +└── club/ + └── IMG/{date}/ # Club event photos (still on filesystem) +``` + +## Multi-Tenant Support + +A user can belong to **multiple centros** (organizations). The active centro +is stored in `$_SESSION['active_centro']` and can be switched at any time via +`POST /_incl/switch_tenant.php`. + +The account page (`/account/`) shows all assigned organizations and lets the +user switch between them. + This directory contains example data files that demonstrate the structure needed for the Axia4 application. diff --git a/Dockerfile b/Dockerfile index a7d1076..659d075 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ FROM dunglas/frankenphp # && rm -rf /var/lib/apt/lists/* # Configure PHP extensions -RUN install-php-extensions gd opcache +RUN install-php-extensions gd opcache pdo pdo_sqlite # Set working directory WORKDIR /var/www/html diff --git a/Dockerfile.dev b/Dockerfile.dev index 2b975b6..10a1f71 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -8,7 +8,7 @@ FROM dunglas/frankenphp # && rm -rf /var/lib/apt/lists/* # Configure PHP extensions -RUN install-php-extensions gd opcache +RUN install-php-extensions gd opcache pdo pdo_sqlite # Set working directory WORKDIR /var/www/html diff --git a/README.md b/README.md index 3dccc19..bee20d3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Axia4 -Axia4 is a unified platform for EuskadiTech and Sketaria, providing various services including EntreAulas (connected classroom management system). +Axia4 is a unified platform for EuskadiTech and Sketaria, providing various services including AulaTek (connected classroom management system). ## Quick Start with Docker @@ -13,7 +13,7 @@ cd Axia4 # 2. Create the data directory structure mkdir -p DATA/entreaulas/Usuarios -mkdir -p DATA/entreaulas/Centros +mkdir -p DATA/entreaulas/Organizaciones # 3. Start the application docker compose up -d @@ -29,7 +29,7 @@ docker compose up -d ## Features -- **EntreAulas**: Management system for connected classrooms +- **AulaTek**: Management system for connected classrooms - **Aularios**: Centralized access to classroom resources - Integration with multiple external services diff --git a/public_html/_incl/db.php b/public_html/_incl/db.php new file mode 100644 index 0000000..be53e3a --- /dev/null +++ b/public_html/_incl/db.php @@ -0,0 +1,765 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); + $pdo->exec('PRAGMA journal_mode = WAL'); + $pdo->exec('PRAGMA foreign_keys = ON'); + $pdo->exec('PRAGMA synchronous = NORMAL'); + + db_migrate($pdo); + + return $pdo; +} + +// ── Migration runner ────────────────────────────────────────────────────────── + +function db_migrate(PDO $pdo): void +{ + $pdo->exec( + 'CREATE TABLE IF NOT EXISTS schema_migrations ( + version INTEGER PRIMARY KEY, + applied_at TEXT NOT NULL DEFAULT (datetime(\'now\')) + )' + ); + + $applied = $pdo->query('SELECT version FROM schema_migrations ORDER BY version') + ->fetchAll(PDO::FETCH_COLUMN); + + $files = glob(MIGRATIONS_DIR . '/*.{sql,php}', GLOB_BRACE) ?: []; + sort($files); + + foreach ($files as $file) { + if (!preg_match('/^(\d+)/', basename($file), $m)) { + continue; + } + $version = (int) $m[1]; + if (in_array($version, $applied, true)) { + continue; + } + + if (str_ends_with($file, '.sql')) { + $pdo->exec((string) file_get_contents($file)); + } elseif (str_ends_with($file, '.php')) { + // PHP migration receives the connection as $db + $db = $pdo; + require $file; + } + + $pdo->prepare('INSERT INTO schema_migrations (version) VALUES (?)')->execute([$version]); + } +} + +// ── Config helpers ──────────────────────────────────────────────────────────── + +function db_get_config(string $key, $default = null) +{ + $stmt = db()->prepare('SELECT value FROM config WHERE key = ?'); + $stmt->execute([$key]); + $row = $stmt->fetch(); + if ($row === false) { + return $default; + } + $decoded = json_decode($row['value'], true); + return $decoded !== null ? $decoded : $row['value']; +} + +function db_set_config(string $key, $value): void +{ + db()->prepare('INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)') + ->execute([$key, is_string($value) ? $value : json_encode($value)]); +} + +function db_get_all_config(): array +{ + $rows = db()->query('SELECT key, value FROM config')->fetchAll(); + $result = []; + foreach ($rows as $row) { + $decoded = json_decode($row['value'], true); + $result[$row['key']] = ($decoded !== null) ? $decoded : $row['value']; + } + return $result; +} + +// ── User helpers ────────────────────────────────────────────────────────────── + +/** Find a user by username (always lower-cased). Returns DB row or null. */ +function db_get_user(string $username): ?array +{ + $stmt = db()->prepare('SELECT * FROM users WHERE username = ?'); + $stmt->execute([strtolower($username)]); + $row = $stmt->fetch(); + return $row !== false ? $row : null; +} + +/** Return all user rows ordered by username. */ +function db_get_all_users(): array +{ + return db()->query('SELECT * FROM users ORDER BY username')->fetchAll(); +} + +/** + * Build the auth_data session array from a DB user row. + * Preserves the same format existing code expects: + * auth_data.permissions, auth_data.active_organizations auth_data.organizations. + */ +function db_build_auth_data(array $row): array +{ + $permissions = json_decode($row['permissions'] ?? '[]', true) ?: []; + $meta = json_decode($row['meta'] ?? '{}', true) ?: []; + $ea = [ + 'organization' => '', + 'organizations' => [], + 'role' => '', + 'aulas' => [], + 'organizations_data' => [], + ]; + + // Fetch all organization assignments for this user + $stmt = db()->prepare( + 'SELECT org_id, role, ea_aulas + FROM user_orgs + WHERE user_id = ? + ORDER BY org_id' + ); + $stmt->execute([$row['id']]); + $org_rows = $stmt->fetchAll(); + $orgs = []; + if (!empty($org_rows)) { + $first = $org_rows[0]; + foreach ($org_rows as $r) { + $orgs[] = $r['org_id']; + } + $ea['organization'] = $first['org_id']; + $ea['role'] = $first['role']; + $ea['aulas'] = json_decode($first['ea_aulas'] ?? '[]', true) ?: []; + $ea['organizations'] = $orgs; + $ea['organizations_data'] = $org_rows; + } + + $active_org = $ea['organization'] ?? ''; + $aulatek = [ + 'organizacion' => $active_org, + 'organizaciones' => $orgs, + 'organization' => $active_org, + 'organizations' => $orgs, + 'centro' => $active_org, + 'centros' => $orgs, + 'role' => $ea['role'] ?? '', + 'aulas' => $ea['aulas'] ?? [], + ]; + + return array_merge($meta, [ + 'display_name' => $row['display_name'], + 'email' => $row['email'], + 'password_hash' => $row['password_hash'], + 'permissions' => $permissions, + 'orgs' => $orgs, + 'organizations' => $orgs, + 'active_organization' => $active_org, + 'active_organizations' => $ea, + 'aulatek' => $aulatek, + 'entreaulas' => $aulatek, + 'google_auth' => (bool) $row['google_auth'], + ]); +} + +/** + * Create or update a user. + * $data keys: username, display_name, email, password_hash, permissions[], + * google_auth, entreaulas{organizacion,organizaciones[],role,aulas[]}, + any extra meta. + * Returns the user ID. + */ +function db_upsert_user(array $data): int +{ + $pdo = db(); + $username = strtolower((string) ($data['username'] ?? '')); + + $existing = $pdo->prepare('SELECT id FROM users WHERE username = ?'); + $existing->execute([$username]); + $existing_row = $existing->fetch(); + + $permissions = json_encode($data['permissions'] ?? []); + $meta_skip = ['username', 'display_name', 'email', 'password_hash', + 'permissions', 'entreaulas', 'google_auth', + 'orgs', 'organizations', 'organization', 'organizacion', + 'role', 'aulas']; + $meta = []; + foreach ($data as $k => $v) { + if (!in_array($k, $meta_skip, true)) { + $meta[$k] = $v; + } + } + + if ($existing_row) { + $user_id = (int) $existing_row['id']; + $upd = $pdo->prepare( + "UPDATE users SET + display_name = ?, + email = ?, + permissions = ?, + google_auth = ?, + meta = ?, + updated_at = datetime('now') + WHERE id = ?" + ); + $upd->execute([ + $data['display_name'] ?? '', + $data['email'] ?? '', + $permissions, + (int) ($data['google_auth'] ?? 0), + json_encode($meta), + $user_id, + ]); + if (!empty($data['password_hash'])) { + $pdo->prepare('UPDATE users SET password_hash = ? WHERE id = ?') + ->execute([$data['password_hash'], $user_id]); + } + } else { + $pdo->prepare( + 'INSERT INTO users (username, display_name, email, password_hash, permissions, google_auth, meta) + VALUES (?, ?, ?, ?, ?, ?, ?)' + )->execute([ + $username, + $data['display_name'] ?? '', + $data['email'] ?? '', + $data['password_hash'] ?? '', + $permissions, + (int) ($data['google_auth'] ?? 0), + json_encode($meta), + ]); + $user_id = (int) $pdo->lastInsertId(); + } + + // Update organization assignments if tenant data is provided. + $has_org_payload = array_key_exists('entreaulas', $data) + || array_key_exists('orgs', $data) + || array_key_exists('organizations', $data) + || array_key_exists('organization', $data) + || array_key_exists('organizacion', $data); + + if ($has_org_payload) { + $ea = $data['entreaulas'] ?? []; + + $organizations = []; + $candidate_lists = [ + $data['organizations'] ?? null, + $data['orgs'] ?? null, + $ea['organizaciones'] ?? null, + $ea['organizations'] ?? null, + $ea['centros'] ?? null, + ]; + foreach ($candidate_lists as $list) { + if (is_array($list) && !empty($list)) { + $organizations = $list; + break; + } + } + if (empty($organizations)) { + foreach ([ + $data['organization'] ?? null, + $data['organizacion'] ?? null, + $ea['organizacion'] ?? null, + $ea['organization'] ?? null, + $ea['centro'] ?? null, + ] as $single) { + if (!empty($single)) { + $organizations = [$single]; + break; + } + } + } + + $organizations = array_values(array_unique(array_filter(array_map( + static function ($value): string { + return preg_replace('/[^a-zA-Z0-9._-]/', '', (string) $value); + }, + $organizations + )))); + + $role = (string) ($data['role'] ?? $ea['role'] ?? ''); + $aulas_payload = $data['aulas'] ?? $ea['aulas'] ?? []; + if (!is_array($aulas_payload)) { + $aulas_payload = []; + } + $aulas = json_encode($aulas_payload, JSON_UNESCAPED_UNICODE); + + $pdo->prepare('DELETE FROM user_orgs WHERE user_id = ?')->execute([$user_id]); + + $ins_org = $pdo->prepare('INSERT OR IGNORE INTO organizaciones (org_id, org_name) VALUES (?, ?)'); + $ins_uo = $pdo->prepare( + 'INSERT OR REPLACE INTO user_orgs (user_id, org_id, role, ea_aulas) VALUES (?, ?, ?, ?)' + ); + foreach ($organizations as $org_id) { + if ($org_id === '') { + continue; + } + $ins_org->execute([$org_id, $org_id]); + $ins_uo->execute([$user_id, $org_id, $role, $aulas]); + } + } + + return $user_id; +} + +/** Delete a user and their organization assignments. */ +function db_delete_user(string $username): void +{ + db()->prepare('DELETE FROM users WHERE username = ?')->execute([strtolower($username)]); +} + +// ── Organization helpers ───────────────────────────────────────────────────── + +function db_get_organizations(): array +{ + return db()->query('SELECT org_id, org_name FROM organizaciones ORDER BY org_id')->fetchAll(); +} + +function db_get_organization_ids(): array +{ + return db()->query('SELECT org_id FROM organizaciones ORDER BY org_id')->fetchAll(PDO::FETCH_COLUMN); +} + +function db_get_organizaciones(): array +{ + return db_get_organizations(); +} + +function get_organizations(): array +{ + return db_get_organizations(); +} + +function db_get_centros(): array +{ + $rows = db_get_organizations(); + return array_map(static function (array $row): array { + return [ + 'centro_id' => $row['org_id'], + 'name' => $row['org_name'], + ]; + }, $rows); +} + +function db_get_centro_ids(): array +{ + return db_get_organization_ids(); +} + +// ── Aulario helpers ─────────────────────────────────────────────────────────── + +/** Get a single aulario config. Returns merged array (name, icon, + extra fields) or null. */ +function db_get_aulario(string $centro_id, string $aulario_id): ?array +{ + $stmt = db()->prepare( + 'SELECT name, icon, extra FROM aularios WHERE org_id = ? AND aulario_id = ?' + ); + $stmt->execute([$centro_id, $aulario_id]); + $row = $stmt->fetch(); + if ($row === false) { + return null; + } + $extra = json_decode($row['extra'] ?? '{}', true) ?: []; + return array_merge($extra, ['name' => $row['name'], 'icon' => $row['icon']]); +} + +/** Get all aularios for a centro as aulario_id → config array. */ +function db_get_aularios(string $centro_id): array +{ + $stmt = db()->prepare( + 'SELECT aulario_id, name, icon, extra FROM aularios WHERE org_id = ? ORDER BY aulario_id' + ); + $stmt->execute([$centro_id]); + $result = []; + foreach ($stmt->fetchAll() as $row) { + $extra = json_decode($row['extra'] ?? '{}', true) ?: []; + $result[$row['aulario_id']] = array_merge($extra, [ + 'name' => $row['name'], + 'icon' => $row['icon'], + ]); + } + return $result; +} + +// ── SuperCafe helpers ───────────────────────────────────────────────────────── + +function db_get_supercafe_menu(string $centro_id): array +{ + $stmt = db()->prepare('SELECT data FROM supercafe_menu WHERE org_id = ?'); + $stmt->execute([$centro_id]); + $row = $stmt->fetch(); + if ($row === false) { + return []; + } + return json_decode($row['data'], true) ?: []; +} + +function db_set_supercafe_menu(string $centro_id, array $menu): void +{ + db()->prepare('INSERT OR REPLACE INTO supercafe_menu (org_id, data, updated_at) VALUES (?, ?, datetime(\'now\'))') + ->execute([$centro_id, json_encode($menu, JSON_UNESCAPED_UNICODE)]); +} + +/** Return all SC orders for a centro as an array of rows. */ +function db_get_supercafe_orders(string $centro_id): array +{ + $stmt = db()->prepare( + 'SELECT * FROM supercafe_orders WHERE org_id = ? ORDER BY created_at DESC' + ); + $stmt->execute([$centro_id]); + return $stmt->fetchAll(); +} + +/** Return a single SC order by ref, or null. */ +function db_get_supercafe_order(string $centro_id, string $order_ref): ?array +{ + $stmt = db()->prepare( + 'SELECT * FROM supercafe_orders WHERE org_id = ? AND order_ref = ?' + ); + $stmt->execute([$centro_id, $order_ref]); + $row = $stmt->fetch(); + return $row !== false ? $row : null; +} + +/** Create or update an SC order. */ +function db_upsert_supercafe_order( + string $centro_id, + string $order_ref, + string $fecha, + string $persona, + string $comanda, + string $notas, + string $estado +): void { + db()->prepare( + 'INSERT INTO supercafe_orders (org_id, order_ref, fecha, persona, comanda, notas, estado) + VALUES (?, ?, ?, ?, ?, ?, ?) + ON CONFLICT(org_id, order_ref) DO UPDATE SET + fecha = excluded.fecha, + persona = excluded.persona, + comanda = excluded.comanda, + notas = excluded.notas, + estado = excluded.estado' + )->execute([$centro_id, $order_ref, $fecha, $persona, $comanda, $notas, $estado]); +} + +/** Generate the next order_ref for a centro (sc001, sc002, …). */ +function db_next_supercafe_ref(string $centro_id): string +{ + $stmt = db()->prepare( + "SELECT order_ref FROM supercafe_orders WHERE org_id = ? ORDER BY id DESC LIMIT 1" + ); + $stmt->execute([$centro_id]); + $last = $stmt->fetchColumn(); + $n = 0; + if ($last && preg_match('/^sc(\d+)$/', $last, $m)) { + $n = (int) $m[1]; + } + return 'sc' . str_pad($n + 1, 3, '0', STR_PAD_LEFT); +} + +/** Count 'Deuda' orders for a persona in a centro. */ +function db_supercafe_count_debts(string $centro_id, string $persona_key): int +{ + $stmt = db()->prepare( + "SELECT COUNT(*) FROM supercafe_orders WHERE org_id = ? AND persona = ? AND estado = 'Deuda'" + ); + $stmt->execute([$centro_id, $persona_key]); + return (int) $stmt->fetchColumn(); +} + +// ── Comedor helpers ─────────────────────────────────────────────────────────── + +function db_get_comedor_menu_types(string $centro_id, string $aulario_id): array +{ + $stmt = db()->prepare( + 'SELECT data FROM comedor_menu_types WHERE org_id = ? AND aulario_id = ?' + ); + $stmt->execute([$centro_id, $aulario_id]); + $row = $stmt->fetch(); + if ($row === false) { + return []; + } + return json_decode($row['data'], true) ?: []; +} + +function db_set_comedor_menu_types(string $centro_id, string $aulario_id, array $types): void +{ + db()->prepare( + 'INSERT OR REPLACE INTO comedor_menu_types (org_id, aulario_id, data) VALUES (?, ?, ?)' + )->execute([$centro_id, $aulario_id, json_encode($types, JSON_UNESCAPED_UNICODE)]); +} + +function db_get_comedor_entry(string $centro_id, string $aulario_id, string $ym, string $day): array +{ + $stmt = db()->prepare( + 'SELECT data FROM comedor_entries WHERE org_id = ? AND aulario_id = ? AND year_month = ? AND day = ?' + ); + $stmt->execute([$centro_id, $aulario_id, $ym, $day]); + $row = $stmt->fetch(); + if ($row === false) { + return []; + } + return json_decode($row['data'], true) ?: []; +} + +function db_set_comedor_entry(string $centro_id, string $aulario_id, string $ym, string $day, array $data): void +{ + db()->prepare( + 'INSERT OR REPLACE INTO comedor_entries (org_id, aulario_id, year_month, day, data) VALUES (?, ?, ?, ?, ?)' + )->execute([$centro_id, $aulario_id, $ym, $day, json_encode($data, JSON_UNESCAPED_UNICODE)]); +} + +// ── Diario helpers ──────────────────────────────────────────────────────────── + +function db_get_diario_entry(string $centro_id, string $aulario_id, string $entry_date): array +{ + $stmt = db()->prepare( + 'SELECT data FROM diario_entries WHERE org_id = ? AND aulario_id = ? AND entry_date = ?' + ); + $stmt->execute([$centro_id, $aulario_id, $entry_date]); + $row = $stmt->fetch(); + if ($row === false) { + return []; + } + return json_decode($row['data'], true) ?: []; +} + +function db_set_diario_entry(string $centro_id, string $aulario_id, string $entry_date, array $data): void +{ + db()->prepare( + 'INSERT OR REPLACE INTO diario_entries (org_id, aulario_id, entry_date, data) VALUES (?, ?, ?, ?)' + )->execute([$centro_id, $aulario_id, $entry_date, json_encode($data, JSON_UNESCAPED_UNICODE)]); +} + +// ── Panel alumno helpers ────────────────────────────────────────────────────── + +function db_get_panel_alumno(string $centro_id, string $aulario_id, string $alumno): array +{ + $stmt = db()->prepare( + 'SELECT data FROM panel_alumno WHERE org_id = ? AND aulario_id = ? AND alumno = ?' + ); + $stmt->execute([$centro_id, $aulario_id, $alumno]); + $row = $stmt->fetch(); + if ($row === false) { + return []; + } + return json_decode($row['data'], true) ?: []; +} + +function db_set_panel_alumno(string $centro_id, string $aulario_id, string $alumno, array $data): void +{ + db()->prepare( + 'INSERT OR REPLACE INTO panel_alumno (org_id, aulario_id, alumno, data) VALUES (?, ?, ?, ?)' + )->execute([$centro_id, $aulario_id, $alumno, json_encode($data, JSON_UNESCAPED_UNICODE)]); +} + +// ── Invitation helpers ──────────────────────────────────────────────────────── + +function db_get_all_invitations(): array +{ + return db()->query('SELECT * FROM invitations ORDER BY code')->fetchAll(); +} + +function db_get_invitation(string $code): ?array +{ + $stmt = db()->prepare('SELECT * FROM invitations WHERE code = ?'); + $stmt->execute([strtoupper($code)]); + $row = $stmt->fetch(); + return $row !== false ? $row : null; +} + +function db_upsert_invitation(string $code, bool $active, bool $single_use): void +{ + db()->prepare( + 'INSERT OR REPLACE INTO invitations (code, active, single_use) VALUES (?, ?, ?)' + )->execute([strtoupper($code), (int) $active, (int) $single_use]); +} + +function db_deactivate_invitation(string $code): void +{ + db()->prepare('UPDATE invitations SET active = 0 WHERE code = ?')->execute([strtoupper($code)]); +} + +function db_delete_invitation(string $code): void +{ + db()->prepare('DELETE FROM invitations WHERE code = ?')->execute([strtoupper($code)]); +} + +// ── Club helpers ────────────────────────────────────────────────────────────── + +function db_get_club_config(): array +{ + $stmt = db()->query('SELECT data FROM club_config WHERE id = 1'); + $row = $stmt->fetch(); + if ($row === false) { + return []; + } + return json_decode($row['data'], true) ?: []; +} + +function db_set_club_config(array $config): void +{ + db()->prepare('INSERT OR REPLACE INTO club_config (id, data) VALUES (1, ?)') + ->execute([json_encode($config, JSON_UNESCAPED_UNICODE)]); +} + +function db_get_all_club_events(): array +{ + return db()->query('SELECT date_ref, data FROM club_events ORDER BY date_ref DESC')->fetchAll(); +} + +function db_get_club_event(string $date_ref): array +{ + $stmt = db()->prepare('SELECT data FROM club_events WHERE date_ref = ?'); + $stmt->execute([$date_ref]); + $row = $stmt->fetch(); + if ($row === false) { + return []; + } + return json_decode($row['data'], true) ?: []; +} + +function db_set_club_event(string $date_ref, array $data): void +{ + db()->prepare('INSERT OR REPLACE INTO club_events (date_ref, data) VALUES (?, ?)') + ->execute([$date_ref, json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)]); +} + +// ── Multi-tenant helpers ────────────────────────────────────────────────────── + +/** Return all organization IDs the authenticated user belongs to. */ +function get_user_organizations(?array $auth_data = null): array +{ + $data = $auth_data ?? $_SESSION['auth_data'] ?? []; + $orgs = $data['organizations'] + ?? $data['orgs'] + ?? $data['aulatek']['organizaciones'] + ?? $data['aulatek']['organizations'] + ?? $data['aulatek']['centros'] + ?? $data['entreaulas']['organizaciones'] + ?? $data['entreaulas']['organizations'] + ?? $data['entreaulas']['centros'] + ?? []; + + if (!empty($orgs) && is_array($orgs)) { + return array_values(array_unique(array_filter($orgs, static function ($value): bool { + return is_string($value) && $value !== ''; + }))); + } + if (!empty($orgs)) { + return [(string) $orgs]; + } + + foreach ([ + $data['active_organization'] ?? null, + $data['aulatek']['organizacion'] ?? null, + $data['aulatek']['organization'] ?? null, + $data['aulatek']['centro'] ?? null, + $data['entreaulas']['organizacion'] ?? null, + $data['entreaulas']['organization'] ?? null, + $data['entreaulas']['centro'] ?? null, + ] as $single) { + if (is_string($single) && $single !== '') { + return [$single]; + } + } + + return []; +} + +/** Spanish alias used by pre-body.php menu rendering. */ +function get_user_organizaciones(?array $auth_data = null): array +{ + $org_ids = get_user_organizations($auth_data); + if (empty($org_ids)) { + return []; + } + $name_by_id = []; + foreach (db_get_organizations() as $org_row) { + $name_by_id[$org_row['org_id']] = $org_row['org_name']; + } + + $result = []; + foreach ($org_ids as $org_id) { + $result[$org_id] = $name_by_id[$org_id] ?? $org_id; + } + return $result; +} + +function get_user_centros(?array $auth_data = null): array +{ + return get_user_organizations($auth_data); +} + +/** Ensure active organization session keys are set and mirrored for legacy code. */ +function init_active_org(?array $auth_data = null): void +{ + $organizations = get_user_organizations($auth_data); + if (empty($organizations)) { + $_SESSION['active_organization'] = null; + $_SESSION['active_organizacion'] = null; + $_SESSION['active_centro'] = null; + return; + } + + $current = $_SESSION['active_organization'] + ?? $_SESSION['active_organizacion'] + ?? $_SESSION['active_centro'] + ?? null; + + if (!is_string($current) || !in_array($current, $organizations, true)) { + $current = $organizations[0]; + } + + $_SESSION['active_organization'] = $current; + $_SESSION['active_organizacion'] = $current; + $_SESSION['active_centro'] = $current; + + if (!isset($_SESSION['auth_data']) || !is_array($_SESSION['auth_data'])) { + $_SESSION['auth_data'] = []; + } + $_SESSION['auth_data']['active_organization'] = $current; + if (!isset($_SESSION['auth_data']['aulatek']) || !is_array($_SESSION['auth_data']['aulatek'])) { + $_SESSION['auth_data']['aulatek'] = []; + } + $_SESSION['auth_data']['aulatek']['organizacion'] = $current; + $_SESSION['auth_data']['aulatek']['organization'] = $current; + $_SESSION['auth_data']['aulatek']['centro'] = $current; + if (!isset($_SESSION['auth_data']['entreaulas']) || !is_array($_SESSION['auth_data']['entreaulas'])) { + $_SESSION['auth_data']['entreaulas'] = []; + } + $_SESSION['auth_data']['entreaulas']['organizacion'] = $current; + $_SESSION['auth_data']['entreaulas']['organization'] = $current; + $_SESSION['auth_data']['entreaulas']['centro'] = $current; +} + +function init_active_centro(?array $auth_data = null): void +{ + init_active_org($auth_data); +} diff --git a/public_html/_incl/migrations/001_initial_schema.sql b/public_html/_incl/migrations/001_initial_schema.sql new file mode 100644 index 0000000..a73a859 --- /dev/null +++ b/public_html/_incl/migrations/001_initial_schema.sql @@ -0,0 +1,144 @@ +-- Axia4 Migration 001: Initial Schema +-- Converts all JSON file-based storage to a proper relational schema. + +PRAGMA journal_mode = WAL; +PRAGMA foreign_keys = ON; + +-- ── Application configuration (replaces /DATA/AuthConfig.json) ───────────── +CREATE TABLE IF NOT EXISTS config ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL DEFAULT '' +); + +-- ── System installation flag (replaces /DATA/SISTEMA_INSTALADO.txt) ──────── +-- Stored as a config row: key='installed', value='1' + +-- ── Users (replaces /DATA/Usuarios/*.json) ───────────────────────────────── +CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT UNIQUE NOT NULL, + display_name TEXT NOT NULL DEFAULT '', + email TEXT NOT NULL DEFAULT '', + password_hash TEXT NOT NULL DEFAULT '', + permissions TEXT NOT NULL DEFAULT '[]', -- JSON array + google_auth INTEGER NOT NULL DEFAULT 0, + meta TEXT NOT NULL DEFAULT '{}', -- JSON for extra fields + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +-- ── Invitations (replaces /DATA/Invitaciones_de_usuarios.json) ───────────── +CREATE TABLE IF NOT EXISTS invitations ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + code TEXT UNIQUE NOT NULL, + active INTEGER NOT NULL DEFAULT 1, + single_use INTEGER NOT NULL DEFAULT 1, + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +-- ── Centros/Organizations ─────────────────────────────────────────────────── +-- Replaces directory existence at /DATA/entreaulas/Centros/{centro_id}/ +CREATE TABLE IF NOT EXISTS centros ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + centro_id TEXT UNIQUE NOT NULL, + name TEXT NOT NULL DEFAULT '', + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +-- ── User ↔ Centro assignments (many-to-many) ─────────────────────────────── +-- Replaces entreaulas.centro + entreaulas.aulas fields in user JSON. +-- A single user can belong to multiple centros (multi-tenant). +CREATE TABLE IF NOT EXISTS user_centros ( + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + centro_id TEXT NOT NULL REFERENCES centros(centro_id) ON DELETE CASCADE, + role TEXT NOT NULL DEFAULT '', + aulas TEXT NOT NULL DEFAULT '[]', -- JSON array of aulario_ids + PRIMARY KEY (user_id, centro_id) +); + +-- ── Aularios (replaces /DATA/entreaulas/Centros/{}/Aularios/{id}.json) ────── +CREATE TABLE IF NOT EXISTS aularios ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + centro_id TEXT NOT NULL REFERENCES centros(centro_id) ON DELETE CASCADE, + aulario_id TEXT NOT NULL, + name TEXT NOT NULL DEFAULT '', + icon TEXT NOT NULL DEFAULT '', + extra TEXT NOT NULL DEFAULT '{}', -- JSON for extra config + UNIQUE (centro_id, aulario_id) +); + +-- ── SuperCafe menu (replaces .../SuperCafe/Menu.json) ────────────────────── +CREATE TABLE IF NOT EXISTS supercafe_menu ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + centro_id TEXT NOT NULL REFERENCES centros(centro_id) ON DELETE CASCADE, + data TEXT NOT NULL DEFAULT '{}', -- JSON matching existing format + updated_at TEXT NOT NULL DEFAULT (datetime('now')), + UNIQUE (centro_id) +); + +-- ── SuperCafe orders (replaces .../SuperCafe/Comandas/*.json) ─────────────── +CREATE TABLE IF NOT EXISTS supercafe_orders ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + centro_id TEXT NOT NULL REFERENCES centros(centro_id) ON DELETE CASCADE, + order_ref TEXT NOT NULL, + fecha TEXT NOT NULL, + persona TEXT NOT NULL, + comanda TEXT NOT NULL DEFAULT '', + notas TEXT NOT NULL DEFAULT '', + estado TEXT NOT NULL DEFAULT 'Pedido', + created_at TEXT NOT NULL DEFAULT (datetime('now')), + UNIQUE (centro_id, order_ref) +); + +-- ── Comedor menu types (replaces .../Comedor-MenuTypes.json) ──────────────── +CREATE TABLE IF NOT EXISTS comedor_menu_types ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + centro_id TEXT NOT NULL REFERENCES centros(centro_id) ON DELETE CASCADE, + aulario_id TEXT NOT NULL, + data TEXT NOT NULL DEFAULT '[]', -- JSON array of menu type objs + UNIQUE (centro_id, aulario_id) +); + +-- ── Comedor daily entries (replaces .../Comedor/{ym}/{day}/_datos.json) ───── +CREATE TABLE IF NOT EXISTS comedor_entries ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + centro_id TEXT NOT NULL REFERENCES centros(centro_id) ON DELETE CASCADE, + aulario_id TEXT NOT NULL, + year_month TEXT NOT NULL, -- "2024-01" + day TEXT NOT NULL, -- "15" + data TEXT NOT NULL DEFAULT '{}', + UNIQUE (centro_id, aulario_id, year_month, day) +); + +-- ── Diary entries (replaces .../Diario/*.json) ────────────────────────────── +CREATE TABLE IF NOT EXISTS diario_entries ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + centro_id TEXT NOT NULL REFERENCES centros(centro_id) ON DELETE CASCADE, + aulario_id TEXT NOT NULL, + entry_date TEXT NOT NULL, + data TEXT NOT NULL DEFAULT '{}', + UNIQUE (centro_id, aulario_id, entry_date) +); + +-- ── Panel diario per-student data (replaces .../Alumnos/*/Panel.json) ─────── +CREATE TABLE IF NOT EXISTS panel_alumno ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + centro_id TEXT NOT NULL REFERENCES centros(centro_id) ON DELETE CASCADE, + aulario_id TEXT NOT NULL, + alumno TEXT NOT NULL, + data TEXT NOT NULL DEFAULT '{}', + UNIQUE (centro_id, aulario_id, alumno) +); + +-- ── Club event metadata (replaces /DATA/club/IMG/{date}/data.json) ────────── +CREATE TABLE IF NOT EXISTS club_events ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + date_ref TEXT UNIQUE NOT NULL, + data TEXT NOT NULL DEFAULT '{}' +); + +-- ── Club configuration (replaces /DATA/club/config.json) ──────────────────── +CREATE TABLE IF NOT EXISTS club_config ( + id INTEGER PRIMARY KEY CHECK (id = 1), + data TEXT NOT NULL DEFAULT '{}' +); diff --git a/public_html/_incl/migrations/002_import_json.php b/public_html/_incl/migrations/002_import_json.php new file mode 100644 index 0000000..869c674 --- /dev/null +++ b/public_html/_incl/migrations/002_import_json.php @@ -0,0 +1,256 @@ +prepare("INSERT OR IGNORE INTO config (key, value) VALUES (?, ?)"); + foreach ($auth_config as $k => $v) { + $ins->execute([$k, is_string($v) ? $v : json_encode($v)]); + } +} + +// ── SISTEMA_INSTALADO marker ───────────────────────────────────────────────── +if (file_exists('/DATA/SISTEMA_INSTALADO.txt')) { + $db->prepare("INSERT OR IGNORE INTO config (key, value) VALUES ('installed', '1')")->execute(); +} + +// ── Users (/DATA/Usuarios/*.json) ──────────────────────────────────────────── +$users_dir = '/DATA/Usuarios'; +if (is_dir($users_dir)) { + $ins_user = $db->prepare( + "INSERT OR IGNORE INTO users + (username, display_name, email, password_hash, permissions, google_auth, meta) + VALUES (?, ?, ?, ?, ?, ?, ?)" + ); + $ins_uc = $db->prepare( + "INSERT OR IGNORE INTO user_centros (user_id, centro_id, role, aulas) + VALUES (?, ?, ?, ?)" + ); + $ins_centro = $db->prepare("INSERT OR IGNORE INTO centros (centro_id) VALUES (?)"); + + foreach (glob("$users_dir/*.json") ?: [] as $user_file) { + $username = basename($user_file, '.json'); + $data = json_decode(file_get_contents($user_file), true); + if (!is_array($data)) { + continue; + } + $permissions = isset($data['permissions']) ? json_encode($data['permissions']) : '[]'; + // Store remaining non-standard keys in meta + $meta_keys = ['display_name', 'email', 'password_hash', 'permissions', 'entreaulas', 'google_auth']; + $meta = []; + foreach ($data as $k => $v) { + if (!in_array($k, $meta_keys, true)) { + $meta[$k] = $v; + } + } + $ins_user->execute([ + $username, + $data['display_name'] ?? '', + $data['email'] ?? '', + $data['password_hash'] ?? '', + $permissions, + (int) ($data['google_auth'] ?? 0), + json_encode($meta), + ]); + $user_id = (int) $db->lastInsertId(); + if ($user_id === 0) { + // Already existed – look it up + $stmt2 = $db->prepare("SELECT id FROM users WHERE username = ?"); + $stmt2->execute([$username]); + $user_id = (int) $stmt2->fetchColumn(); + } + + // Entreaulas centro assignment + $ea = $data['entreaulas'] ?? []; + // Support both old single "centro" and new "centros" array + $centros = []; + if (!empty($ea['centros']) && is_array($ea['centros'])) { + $centros = $ea['centros']; + } elseif (!empty($ea['centro'])) { + $centros = [$ea['centro']]; + } + $role = $ea['role'] ?? ''; + $aulas = json_encode($ea['aulas'] ?? []); + foreach ($centros as $cid) { + if ($cid === '') { + continue; + } + $ins_centro->execute([$cid]); + $ins_uc->execute([$user_id, $cid, $role, $aulas]); + } + } +} + +// ── Invitations (/DATA/Invitaciones_de_usuarios.json) ──────────────────────── +$inv_file = '/DATA/Invitaciones_de_usuarios.json'; +if (file_exists($inv_file)) { + $invs = json_decode(file_get_contents($inv_file), true) ?? []; + $ins = $db->prepare( + "INSERT OR IGNORE INTO invitations (code, active, single_use) VALUES (?, ?, ?)" + ); + foreach ($invs as $code => $inv) { + $ins->execute([ + strtoupper($code), + (int) ($inv['active'] ?? 1), + (int) ($inv['single_use'] ?? 1), + ]); + } +} + +// ── Centros & Aularios (directory structure) ────────────────────────────────── +$centros_base = '/DATA/entreaulas/Centros'; +if (is_dir($centros_base)) { + $ins_centro = $db->prepare("INSERT OR IGNORE INTO centros (centro_id) VALUES (?)"); + $ins_aulario = $db->prepare( + "INSERT OR IGNORE INTO aularios (centro_id, aulario_id, name, icon, extra) VALUES (?, ?, ?, ?, ?)" + ); + foreach (glob("$centros_base/*", GLOB_ONLYDIR) ?: [] as $centro_dir) { + $centro_id = basename($centro_dir); + $ins_centro->execute([$centro_id]); + + $aularios_dir = "$centro_dir/Aularios"; + foreach (glob("$aularios_dir/*.json") ?: [] as $aulario_file) { + $aulario_id = basename($aulario_file, '.json'); + $adata = json_decode(file_get_contents($aulario_file), true); + if (!is_array($adata)) { + continue; + } + $name = $adata['name'] ?? $aulario_id; + $icon = $adata['icon'] ?? ''; + $extra_keys = ['name', 'icon']; + $extra = []; + foreach ($adata as $k => $v) { + if (!in_array($k, $extra_keys, true)) { + $extra[$k] = $v; + } + } + $ins_aulario->execute([$centro_id, $aulario_id, $name, $icon, json_encode($extra)]); + } + + // SuperCafe menu + $menu_file = "$centro_dir/SuperCafe/Menu.json"; + if (file_exists($menu_file)) { + $menu_data = file_get_contents($menu_file); + $db->prepare("INSERT OR IGNORE INTO supercafe_menu (centro_id, data) VALUES (?, ?)") + ->execute([$centro_id, $menu_data]); + } + + // SuperCafe orders + $comandas_dir = "$centro_dir/SuperCafe/Comandas"; + if (is_dir($comandas_dir)) { + $ins_order = $db->prepare( + "INSERT OR IGNORE INTO supercafe_orders + (centro_id, order_ref, fecha, persona, comanda, notas, estado) + VALUES (?, ?, ?, ?, ?, ?, ?)" + ); + foreach (glob("$comandas_dir/*.json") ?: [] as $order_file) { + $order_ref = basename($order_file, '.json'); + $odata = json_decode(file_get_contents($order_file), true); + if (!is_array($odata)) { + continue; + } + $ins_order->execute([ + $centro_id, + $order_ref, + $odata['Fecha'] ?? '', + $odata['Persona'] ?? '', + $odata['Comanda'] ?? '', + $odata['Notas'] ?? '', + $odata['Estado'] ?? 'Pedido', + ]); + } + } + + // Comedor menu types & daily entries per aulario + foreach (glob("$aularios_dir/*.json") ?: [] as $aulario_file) { + $aulario_id = basename($aulario_file, '.json'); + + $menu_types_file = "$aularios_dir/$aulario_id/Comedor-MenuTypes.json"; + if (file_exists($menu_types_file)) { + $db->prepare( + "INSERT OR IGNORE INTO comedor_menu_types (centro_id, aulario_id, data) VALUES (?, ?, ?)" + )->execute([$centro_id, $aulario_id, file_get_contents($menu_types_file)]); + } + + $comedor_base = "$aularios_dir/$aulario_id/Comedor"; + if (is_dir($comedor_base)) { + $ins_centry = $db->prepare( + "INSERT OR IGNORE INTO comedor_entries (centro_id, aulario_id, year_month, day, data) VALUES (?, ?, ?, ?, ?)" + ); + foreach (glob("$comedor_base/*", GLOB_ONLYDIR) ?: [] as $ym_dir) { + $ym = basename($ym_dir); + foreach (glob("$ym_dir/*", GLOB_ONLYDIR) ?: [] as $day_dir) { + $day = basename($day_dir); + $data_file = "$day_dir/_datos.json"; + if (file_exists($data_file)) { + $ins_centry->execute([ + $centro_id, $aulario_id, $ym, $day, + file_get_contents($data_file), + ]); + } + } + } + } + + // Diario entries + $diario_base = "$aularios_dir/$aulario_id/Diario"; + if (is_dir($diario_base)) { + $ins_d = $db->prepare( + "INSERT OR IGNORE INTO diario_entries (centro_id, aulario_id, entry_date, data) VALUES (?, ?, ?, ?)" + ); + foreach (glob("$diario_base/*.json") ?: [] as $diario_file) { + $entry_date = basename($diario_file, '.json'); + $ins_d->execute([$centro_id, $aulario_id, $entry_date, file_get_contents($diario_file)]); + } + } + + // Panel alumno data + $alumnos_base = "$aularios_dir/$aulario_id/Alumnos"; + if (is_dir($alumnos_base)) { + $ins_pa = $db->prepare( + "INSERT OR IGNORE INTO panel_alumno (centro_id, aulario_id, alumno, data) VALUES (?, ?, ?, ?)" + ); + foreach (glob("$alumnos_base/*/", GLOB_ONLYDIR) ?: [] as $alumno_dir) { + $alumno = basename($alumno_dir); + // Look for Panel.json (used by paneldiario) + $panel_files = glob("$alumno_dir/Panel*.json") ?: []; + foreach ($panel_files as $pf) { + $ins_pa->execute([ + $centro_id, $aulario_id, $alumno, + file_get_contents($pf), + ]); + } + } + } + } + } +} + +// ── Club config (/DATA/club/config.json) ────────────────────────────────────── +$club_config_file = '/DATA/club/config.json'; +if (file_exists($club_config_file)) { + $db->prepare("INSERT OR IGNORE INTO club_config (id, data) VALUES (1, ?)") + ->execute([file_get_contents($club_config_file)]); +} + +// ── Club events (/DATA/club/IMG/{date}/data.json) ───────────────────────────── +$club_img_dir = '/DATA/club/IMG'; +if (is_dir($club_img_dir)) { + $ins_ev = $db->prepare("INSERT OR IGNORE INTO club_events (date_ref, data) VALUES (?, ?)"); + foreach (glob("$club_img_dir/*/", GLOB_ONLYDIR) ?: [] as $event_dir) { + $date_ref = basename($event_dir); + $event_data_file = "$event_dir/data.json"; + $ins_ev->execute([ + $date_ref, + file_exists($event_data_file) ? file_get_contents($event_data_file) : '{}', + ]); + } +} diff --git a/public_html/_incl/migrations/003_organizaciones.sql b/public_html/_incl/migrations/003_organizaciones.sql new file mode 100644 index 0000000..a526e72 --- /dev/null +++ b/public_html/_incl/migrations/003_organizaciones.sql @@ -0,0 +1,163 @@ +-- filepath: /workspaces/Axia4/public_html/_incl/migrations/003_organizaciones.sql +-- Axia4 Migration 003: Rename centros to organizaciones +-- Migrates the centros table to organizaciones with org_id and org_name columns. + +PRAGMA journal_mode = WAL; +PRAGMA foreign_keys = ON; + +-- ── Create new organizaciones table ────────────────────────────────────────── +CREATE TABLE IF NOT EXISTS organizaciones ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + org_id TEXT UNIQUE NOT NULL, + org_name TEXT NOT NULL DEFAULT '', + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +-- ── Migrate data from centros to organizaciones ────────────────────────────── +INSERT INTO organizaciones (org_id, org_name, created_at) +SELECT centro_id, COALESCE(name, centro_id), created_at +FROM centros +WHERE NOT EXISTS ( + SELECT 1 FROM organizaciones WHERE org_id = centros.centro_id +); + +-- ── Update foreign key references in user_centros ────────────────────────────── +-- user_centros.centro_id → user_centros.org_id (rename column if needed via recreation) +-- For SQLite, we need to recreate the table due to FK constraint changes + +CREATE TABLE user_centros_new ( + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + org_id TEXT NOT NULL REFERENCES organizaciones(org_id) ON DELETE CASCADE, + role TEXT NOT NULL DEFAULT '', + ea_aulas TEXT NOT NULL DEFAULT '[]', + PRIMARY KEY (user_id, org_id) +); + +INSERT INTO user_centros_new (user_id, org_id, role, ea_aulas) +SELECT user_id, centro_id, role, aulas FROM user_centros; + +DROP TABLE user_centros; +ALTER TABLE user_centros_new RENAME TO user_orgs; + +-- ── Update foreign key references in aularios ────────────────────────────────── +CREATE TABLE aularios_new ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + org_id TEXT NOT NULL REFERENCES organizaciones(org_id) ON DELETE CASCADE, + aulario_id TEXT NOT NULL, + name TEXT NOT NULL DEFAULT '', + icon TEXT NOT NULL DEFAULT '', + extra TEXT NOT NULL DEFAULT '{}', + UNIQUE (org_id, aulario_id) +); + +INSERT INTO aularios_new (id, org_id, aulario_id, name, icon, extra) +SELECT id, centro_id, aulario_id, name, icon, extra FROM aularios; + +DROP TABLE aularios; +ALTER TABLE aularios_new RENAME TO aularios; + +-- ── Update foreign key references in remaining tables ────────────────────────── +-- supercafe_menu +CREATE TABLE supercafe_menu_new ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + org_id TEXT NOT NULL REFERENCES organizaciones(org_id) ON DELETE CASCADE, + data TEXT NOT NULL DEFAULT '{}', + updated_at TEXT NOT NULL DEFAULT (datetime('now')), + UNIQUE (org_id) +); + +INSERT INTO supercafe_menu_new (id, org_id, data, updated_at) +SELECT id, centro_id, data, updated_at FROM supercafe_menu; + +DROP TABLE supercafe_menu; +ALTER TABLE supercafe_menu_new RENAME TO supercafe_menu; + +-- supercafe_orders +CREATE TABLE supercafe_orders_new ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + org_id TEXT NOT NULL REFERENCES organizaciones(org_id) ON DELETE CASCADE, + order_ref TEXT NOT NULL, + fecha TEXT NOT NULL, + persona TEXT NOT NULL, + comanda TEXT NOT NULL DEFAULT '', + notas TEXT NOT NULL DEFAULT '', + estado TEXT NOT NULL DEFAULT 'Pedido', + created_at TEXT NOT NULL DEFAULT (datetime('now')), + UNIQUE (org_id, order_ref) +); + +INSERT INTO supercafe_orders_new (id, org_id, order_ref, fecha, persona, comanda, notas, estado, created_at) +SELECT id, centro_id, order_ref, fecha, persona, comanda, notas, estado, created_at FROM supercafe_orders; + +DROP TABLE supercafe_orders; +ALTER TABLE supercafe_orders_new RENAME TO supercafe_orders; + +-- comedor_menu_types +CREATE TABLE comedor_menu_types_new ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + org_id TEXT NOT NULL REFERENCES organizaciones(org_id) ON DELETE CASCADE, + aulario_id TEXT NOT NULL, + data TEXT NOT NULL DEFAULT '[]', + UNIQUE (org_id, aulario_id) +); + +INSERT INTO comedor_menu_types_new (id, org_id, aulario_id, data) +SELECT id, centro_id, aulario_id, data FROM comedor_menu_types; + +DROP TABLE comedor_menu_types; +ALTER TABLE comedor_menu_types_new RENAME TO comedor_menu_types; + +-- comedor_entries +CREATE TABLE comedor_entries_new ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + org_id TEXT NOT NULL REFERENCES organizaciones(org_id) ON DELETE CASCADE, + aulario_id TEXT NOT NULL, + year_month TEXT NOT NULL, + day TEXT NOT NULL, + data TEXT NOT NULL DEFAULT '{}', + UNIQUE (org_id, aulario_id, year_month, day) +); + +INSERT INTO comedor_entries_new (id, org_id, aulario_id, year_month, day, data) +SELECT id, centro_id, aulario_id, year_month, day, data FROM comedor_entries; + +DROP TABLE comedor_entries; +ALTER TABLE comedor_entries_new RENAME TO comedor_entries; + +-- diario_entries +CREATE TABLE diario_entries_new ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + org_id TEXT NOT NULL REFERENCES organizaciones(org_id) ON DELETE CASCADE, + aulario_id TEXT NOT NULL, + entry_date TEXT NOT NULL, + data TEXT NOT NULL DEFAULT '{}', + UNIQUE (org_id, aulario_id, entry_date) +); + +INSERT INTO diario_entries_new (id, org_id, aulario_id, entry_date, data) +SELECT id, centro_id, aulario_id, entry_date, data FROM diario_entries; + +DROP TABLE diario_entries; +ALTER TABLE diario_entries_new RENAME TO diario_entries; + +-- panel_alumno +CREATE TABLE panel_alumno_new ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + org_id TEXT NOT NULL REFERENCES organizaciones(org_id) ON DELETE CASCADE, + aulario_id TEXT NOT NULL, + alumno TEXT NOT NULL, + data TEXT NOT NULL DEFAULT '{}', + UNIQUE (org_id, aulario_id, alumno) +); + +INSERT INTO panel_alumno_new (id, org_id, aulario_id, alumno, data) +SELECT id, centro_id, aulario_id, alumno, data FROM panel_alumno; + +DROP TABLE panel_alumno; +ALTER TABLE panel_alumno_new RENAME TO panel_alumno; + +-- ── Drop old centros table ───────────────────────────────────────────────────── +DROP TABLE IF EXISTS centros; + +-- ── Verify migration ─────────────────────────────────────────────────────────── +-- SELECT COUNT(*) as total_organizaciones FROM organizaciones; \ No newline at end of file diff --git a/public_html/_incl/pre-body.php b/public_html/_incl/pre-body.php index d7b6592..9f83c1b 100755 --- a/public_html/_incl/pre-body.php +++ b/public_html/_incl/pre-body.php @@ -27,6 +27,12 @@ if (!empty($displayName)) { $initials = mb_strtoupper($first . $last); } +// Tenant (organización) management +$userOrganizaciones = get_user_organizaciones($_SESSION["auth_data"] ?? []); +$activeOrganizacionId = $_SESSION['active_organizacion'] + ?? ($_SESSION["auth_data"]["aulatek"]["organizacion"] ?? ($_SESSION["auth_data"]["entreaulas"]["organizacion"] ?? '')); +$activeOrganizacionName = $userOrganizaciones[$activeOrganizacionId] ?? ''; + ?> @@ -38,412 +44,362 @@ if (!empty($displayName)) { " /> + + + @@ -548,96 +471,116 @@ if (!empty($displayName)) { }); })(); + + +
+ + + + + + +
+
+ + + + +
+ +
+
+ +
-
- - - - - - - -
-
300) { - $username = $_SESSION["auth_user"]; - $user_filename = safe_username_to_filename($username); - if ($user_filename !== "") { - $userdata = json_decode(file_get_contents("/DATA/Usuarios/" . $user_filename . ".json"), true); - $_SESSION["auth_data"] = $userdata; - } - $_SESSION["last_reload_time"] = time(); + } elseif ($load_mode !== "never") { + $last = $_SESSION["last_reload_time"] ?? 0; + if (time() - $last > 300) { + $row = db_get_user($_SESSION["auth_user"]); + if ($row) { + $_SESSION["auth_data"] = db_build_auth_data($row); + init_active_org($_SESSION["auth_data"]); } - } else { + $_SESSION["last_reload_time"] = time(); + } + if (!isset($_SESSION["last_reload_time"])) { $_SESSION["last_reload_time"] = time(); } } } - -function user_is_authenticated() +function user_is_authenticated(): bool { return isset($_SESSION["auth_ok"]) && $_SESSION["auth_ok"] === true; } -function user_has_permission($perm) + +function user_has_permission(string $perm): bool { - return in_array($perm, $_SESSION["auth_data"]["permissions"] ?? []); + return in_array($perm, $_SESSION["auth_data"]["permissions"] ?? [], true); } diff --git a/public_html/_incl/tools.security.php b/public_html/_incl/tools.security.php index dd80800..1d55a4d 100644 --- a/public_html/_incl/tools.security.php +++ b/public_html/_incl/tools.security.php @@ -108,12 +108,23 @@ function Sb($input) { } function get_user_file_path($username) { - return USERS_DIR . $username . '.json'; + $users_dir = defined('USERS_DIR') ? USERS_DIR : '/DATA/Usuarios/'; + return rtrim($users_dir, '/') . '/' . $username . '.json'; +} + +function safe_organization_id($value) +{ + return preg_replace('/[^a-zA-Z0-9._-]/', '', (string)$value); +} + +function safe_organizacion_id($value) +{ + return safe_organization_id($value); } function safe_centro_id($value) { - return preg_replace('/[^a-zA-Z0-9._-]/', '', (string)$value); + return safe_organization_id($value); } function safe_aulario_id($value) @@ -156,12 +167,42 @@ function path_is_within($real_base, $real_path) return strpos($real_path, $base_prefix) === 0 || $real_path === rtrim($real_base, DIRECTORY_SEPARATOR); } +function aulatek_orgs_base_path() +{ + $orgs_path = '/DATA/entreaulas/Organizaciones'; + $legacy_path = '/DATA/entreaulas/Centros'; + if (is_dir($orgs_path)) { + return $orgs_path; + } + if (is_dir($legacy_path)) { + return $legacy_path; + } + return $orgs_path; +} + +function entreaulas_orgs_base_path() +{ + return aulatek_orgs_base_path(); +} + function safe_aulario_config_path($centro_id, $aulario_id) { - $centro = safe_centro_id($centro_id); + $centro = safe_organization_id($centro_id); $aulario = safe_id_segment($aulario_id); if ($centro === '' || $aulario === '') { return null; } - return "/DATA/entreaulas/Centros/$centro/Aularios/$aulario.json"; + return aulatek_orgs_base_path() . "/$centro/Aularios/$aulario.json"; +} + +function safe_redir($url, $default = "/") +{ + if (empty($url) || !is_string($url)) { + return $default; + } + // Only allow relative URLs that start with / + if (str_starts_with($url, "/") && !str_contains($url, "\0")) { + return $url; + } + return $default; } \ No newline at end of file diff --git a/public_html/_install.php b/public_html/_install.php index 764e6ca..0f67c15 100644 --- a/public_html/_install.php +++ b/public_html/_install.php @@ -1,28 +1,25 @@ 'Administrador', - 'email' => "$admin_user@nomail.arpa", - 'permissions' => ['*', 'sysadmin:access', 'entreaulas:access'], - 'password_hash' => $password_hash - ]; - if (!is_dir("/DATA/Usuarios")) { - mkdir("/DATA/Usuarios", 0777, true); - } - file_put_contents("/DATA/Usuarios/$admin_user.json", json_encode($admin_userdata, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); - file_put_contents("/DATA/SISTEMA_INSTALADO.txt", "Sistema instalado el ".date("Y-m-d H:i:s")."\n"); + db_upsert_user([ + 'username' => $admin_user, + 'display_name' => 'Administrador', + 'email' => "$admin_user@nomail.arpa", + 'permissions' => ['*', 'sysadmin:access', 'aulatek:access'], + 'password_hash' => password_hash($admin_password, PASSWORD_DEFAULT), + ]); + db_set_config('installed', '1'); header("Location: /_login.php"); exit; break; diff --git a/public_html/_login.php b/public_html/_login.php index 324ac4e..853a30e 100755 --- a/public_html/_login.php +++ b/public_html/_login.php @@ -1,38 +1,27 @@ $name, - "email" => $email, - "permissions" => ["public"], - "password_hash" => password_hash($password, PASSWORD_DEFAULT), - "google_auth" => true, - "#" => "Este usuario fue creado automáticamente al iniciar sesión con Google por primera vez.", - ]; - file_put_contents($userfile, json_encode($userdata)); + db_upsert_user([ + 'username' => $username, + 'display_name' => $name, + 'email' => $email, + 'permissions' => ['public'], + 'password_hash' => password_hash($password, PASSWORD_DEFAULT), + 'google_auth' => true, + '#' => 'Este usuario fue creado automáticamente al iniciar sesión con Google por primera vez.', + ]); + $user_row = db_get_user($username); } - + session_regenerate_id(true); - $_SESSION['auth_user'] = $email; - $_SESSION['auth_data'] = $userdata; - $_SESSION['auth_ok'] = true; + $_SESSION['auth_user'] = $username; + $_SESSION['auth_data'] = db_build_auth_data($user_row); + $_SESSION['auth_ok'] = true; + init_active_org($_SESSION['auth_data']); $cookie_options = ["expires" => time() + (86400 * 30), "path" => "/", "httponly" => true, "secure" => true, "samesite" => "Lax"]; - setcookie("auth_user", $email, $cookie_options); - setcookie("auth_pass_b64", base64_encode($password), $cookie_options); + setcookie("auth_user", $username, $cookie_options); + setcookie("auth_pass_b64", base64_encode($password), $cookie_options); $redir = safe_redir($state["redir"] ?? "/"); header("Location: $redir"); die(); } -if ($_GET["google"] == "1") { +if (($_GET["google"] ?? "") === "1") { if (!isset($AuthConfig["google_client_id"]) || !isset($AuthConfig["google_client_secret"])) { die("Error: La autenticación de Google no está configurada."); } @@ -145,7 +136,7 @@ if ($_GET["google"] == "1") { header("Location: " . $request_to); die(); } -if ($_GET["logout"] == "1") { +if (($_GET["logout"] ?? "") === "1") { $redir = safe_redir($_GET["redir"] ?? "/"); $cookie_options_expired = ["expires" => time() - 3600, "path" => "/", "httponly" => true, "secure" => true, "samesite" => "Lax"]; setcookie("auth_user", "", $cookie_options_expired); @@ -154,27 +145,26 @@ if ($_GET["logout"] == "1") { header("Location: $redir"); die(); } -if ($_GET["clear_session"] == "1") { +if (($_GET["clear_session"] ?? "") === "1") { session_destroy(); $redir = safe_redir($_GET["redir"] ?? "/"); header("Location: $redir"); die(); } if (isset($_POST["user"])) { - $valid = ""; - $user = trim(strtolower($_POST["user"])); + $user = trim(strtolower($_POST["user"])); $password = $_POST["password"]; - $user_filename = safe_username_to_filename($user); - $userdata = ($user_filename !== "") ? json_decode(@file_get_contents("/DATA/Usuarios/" . $user_filename . ".json"), true) : null; - if (!is_array($userdata) || !isset($userdata["password_hash"])) { + $row = db_get_user($user); + if (!$row || !isset($row["password_hash"])) { $_GET["_result"] = "El usuario no existe."; - } elseif (password_verify($password, $userdata["password_hash"])) { + } elseif (password_verify($password, $row["password_hash"])) { session_regenerate_id(true); $_SESSION['auth_user'] = $user; - $_SESSION['auth_data'] = $userdata; - $_SESSION['auth_ok'] = true; + $_SESSION['auth_data'] = db_build_auth_data($row); + $_SESSION['auth_ok'] = true; + init_active_org($_SESSION['auth_data']); $cookie_options = ["expires" => time() + (86400 * 30), "path" => "/", "httponly" => true, "secure" => true, "samesite" => "Lax"]; - setcookie("auth_user", $user, $cookie_options); + setcookie("auth_user", $user, $cookie_options); setcookie("auth_pass_b64", base64_encode($password), $cookie_options); $redir = safe_redir($_GET["redir"] ?? "/"); header("Location: $redir"); @@ -182,9 +172,8 @@ if (isset($_POST["user"])) { } else { $_GET["_result"] = "La contraseña no es correcta."; } - } -if (!file_exists("/DATA/SISTEMA_INSTALADO.txt")) { +if (db_get_config('installed') !== '1') { header("Location: /_install.php"); die(); } diff --git a/public_html/account/index.php b/public_html/account/index.php index 0ea6b70..726b237 100644 --- a/public_html/account/index.php +++ b/public_html/account/index.php @@ -1,44 +1,132 @@ -
-
-

¡Hola, !

- Tu Email: - Tu Nombre de Usuario: -
-
- Código QR - " alt="QR Code de Nombre de Usuario" style="margin: 0 auto;"> - Escanea este código para iniciar sesión. Es como tu contraseña, pero más fácil. -
-
- - - \ No newline at end of file + + + + diff --git a/public_html/account/register.php b/public_html/account/register.php index 7dbc0f5..1752e34 100644 --- a/public_html/account/register.php +++ b/public_html/account/register.php @@ -1,36 +1,27 @@ $_POST['display_name'], - 'email' => $_POST['email'], - 'password_hash' => password_hash($_POST['password'], PASSWORD_DEFAULT), - '_meta_signup' => [ - 'invitation_code' => $invi_code - ], - 'permissions' => [] - ]; - if ($invitations[$invi_code]["active"] != true) { - header("Location: /?_resultcolor=red&_result=" . urlencode("Código de invitación no válido.")); - exit; - } - $username = $_POST['username']; - if (file_exists("/DATA/Usuarios/$username.json")) { + $username = strtolower(trim($_POST['username'] ?? '')); + if (db_get_user($username)) { header("Location: /?_resultcolor=red&_result=" . urlencode("El nombre de usuario ya existe. Por favor, elige otro.")); exit; } - file_put_contents("/DATA/Usuarios/$username.json", json_encode($userdata, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); - // Deactivate invitation code if it's single-use - if ($invitations[$invi_code]["single_use"] === true) { - $invitations[$invi_code]["active"] = false; - file_put_contents("/DATA/Invitaciones_de_usuarios.json", json_encode($invitations, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); + db_upsert_user([ + 'username' => $username, + 'display_name' => $_POST['display_name'] ?? '', + 'email' => $_POST['email'] ?? '', + 'password_hash' => password_hash($_POST['password'], PASSWORD_DEFAULT), + 'permissions' => [], + '_meta_signup' => ['invitation_code' => $invi_code], + ]); + if ($invitation['single_use']) { + db_deactivate_invitation($invi_code); } header("Location: /?_result=" . urlencode("Cuenta creada correctamente. Ya puedes iniciar sesión.")); exit; diff --git a/public_html/aulatek/__menu.php b/public_html/aulatek/__menu.php index e294b1d..a5dd4fd 100755 --- a/public_html/aulatek/__menu.php +++ b/public_html/aulatek/__menu.php @@ -1,11 +1,10 @@ - - - - - Mi aula - - - - SuperCafe - \ No newline at end of file + + \ No newline at end of file diff --git a/public_html/aulatek/_filefetch.php b/public_html/aulatek/_filefetch.php index 644baeb..b73b20e 100755 --- a/public_html/aulatek/_filefetch.php +++ b/public_html/aulatek/_filefetch.php @@ -10,9 +10,10 @@ header("Access-Control-Allow-Origin: *"); $type = $_GET["type"] ?? ""; +$orgs_base_dir = basename(aulatek_orgs_base_path()); switch ($type) { case "alumno_photo": - $centro = safe_centro_id($_GET["centro"] ?? ''); + $centro = safe_organization_id($_GET["organization"] ?? $_GET["organizacion"] ?? $_GET["org"] ?? $_GET["centro"] ?? ''); $aulario = safe_id_segment($_GET["aulario"] ?? ''); $alumno = safe_id_segment($_GET["alumno"] ?? ''); // Additional validation to prevent empty names @@ -20,19 +21,19 @@ switch ($type) { header("HTTP/1.1 403 Forbidden"); die("Invalid parameters"); } - $relpath = "entreaulas/Centros/$centro/Aularios/$aulario/Alumnos/$alumno/photo.jpg"; + $relpath = "entreaulas/$orgs_base_dir/$centro/Aularios/$aulario/Alumnos/$alumno/photo.jpg"; break; case "panel_actividades": - $centro = safe_centro_id($_GET["centro"] ?? ''); + $centro = safe_organization_id($_GET["organization"] ?? $_GET["organizacion"] ?? $_GET["org"] ?? $_GET["centro"] ?? ''); $activity = safe_id_segment($_GET["activity"] ?? ''); if (empty($centro) || empty($activity)) { header("HTTP/1.1 400 Bad Request"); die("Invalid parameters"); } - $relpath = "entreaulas/Centros/$centro/Panel/Actividades/$activity/photo.jpg"; + $relpath = "entreaulas/$orgs_base_dir/$centro/Panel/Actividades/$activity/photo.jpg"; break; case "comedor_image": - $centro = safe_centro_id($_GET["centro"] ?? ''); + $centro = safe_organization_id($_GET["organization"] ?? $_GET["organizacion"] ?? $_GET["org"] ?? $_GET["centro"] ?? ''); $aulario = safe_id_segment($_GET["aulario"] ?? ''); $date = preg_replace('/[^0-9-]/', '', $_GET["date"] ?? ''); $file = safe_filename($_GET["file"] ?? ''); @@ -46,10 +47,10 @@ switch ($type) { } $ym = substr($date, 0, 7); $day = substr($date, 8, 2); - $relpath = "entreaulas/Centros/$centro/Aularios/$aulario/Comedor/$ym/$day/$file"; + $relpath = "entreaulas/$orgs_base_dir/$centro/Aularios/$aulario/Comedor/$ym/$day/$file"; break; case "proyecto_file": - $centro = safe_centro_id($_GET["centro"] ?? ''); + $centro = safe_organization_id($_GET["organization"] ?? $_GET["organizacion"] ?? $_GET["org"] ?? $_GET["centro"] ?? ''); $project = safe_id_segment($_GET["project"] ?? ''); $file = safe_filename($_GET["file"] ?? ''); if (empty($centro) || empty($project) || empty($file)) { @@ -61,7 +62,7 @@ switch ($type) { header("HTTP/1.1 400 Bad Request"); die("Invalid file name"); } - $projects_base = "/DATA/entreaulas/Centros/$centro/Proyectos"; + $projects_base = aulatek_orgs_base_path() . "/$centro/Proyectos"; $project_dir = null; if (is_dir($projects_base)) { $iterator = new RecursiveIteratorIterator( diff --git a/public_html/aulatek/_incl/auth_redir.php b/public_html/aulatek/_incl/auth_redir.php index d6924c2..9f9ef06 100755 --- a/public_html/aulatek/_incl/auth_redir.php +++ b/public_html/aulatek/_incl/auth_redir.php @@ -1,5 +1,5 @@ Foto actual:
- Foto de <?= htmlspecialchars($nombre) ?>
@@ -351,7 +353,7 @@ switch ($_GET["action"] ?? '') { - Foto de <?= htmlspecialchars($nombre) ?> @@ -379,7 +381,7 @@ switch ($_GET["action"] ?? '') { - Volver al Aulario + Volver al Aulario
"Access denied", "code" => "FORBIDDEN"])); } -$centro_id = safe_centro_id($_SESSION["auth_data"]["entreaulas"]["centro"] ?? ""); +$tenant_data = $_SESSION["auth_data"]["aulatek"] ?? ($_SESSION["auth_data"]["entreaulas"] ?? []); +$centro_id = safe_organization_id($tenant_data["organizacion"] ?? ($tenant_data["centro"] ?? "")); if ($centro_id === "") { http_response_code(400); - die(json_encode(["error" => "Centro not found in session", "code" => "INVALID_SESSION"])); + die(json_encode(["error" => "Organizacion not found in session", "code" => "INVALID_SESSION"])); } -$action = $_GET["action"] ?? ($_POST["action"] ?? ""); +$action = $_GET["action"] ?? ($_POST["action"] ?? ""); $aulario_id = safe_id_segment($_GET["aulario"] ?? $_POST["aulario"] ?? ""); -// Validate aulario_id if ($aulario_id === "") { http_response_code(400); die(json_encode(["error" => "aulario parameter is required", "code" => "MISSING_PARAM"])); } -// Verify that the user has access to this aulario -$userAulas = $_SESSION["auth_data"]["entreaulas"]["aulas"] ?? []; -$userAulas = array_values(array_filter(array_map('safe_id_segment', $userAulas))); +$userAulas = array_values(array_filter(array_map('safe_id_segment', $tenant_data["aulas"] ?? []))); if (!in_array($aulario_id, $userAulas, true)) { http_response_code(403); die(json_encode(["error" => "Access denied to this aulario", "code" => "FORBIDDEN"])); } -$aulario_path = safe_aulario_config_path($centro_id, $aulario_id); -$aulario = ($aulario_path && file_exists($aulario_path)) ? json_decode(file_get_contents($aulario_path), true) : null; +$aulario = db_get_aulario($centro_id, $aulario_id); -// Handle shared comedor data $source_aulario_id = $aulario_id; $is_shared = false; if ($aulario && !empty($aulario["shared_comedor_from"])) { $shared_from = safe_id_segment($aulario["shared_comedor_from"]); - $shared_aulario_path = safe_aulario_config_path($centro_id, $shared_from); - if ($shared_aulario_path && file_exists($shared_aulario_path)) { + if (db_get_aulario($centro_id, $shared_from)) { $source_aulario_id = $shared_from; $is_shared = true; } } -// Check edit permissions (must be sysadmin and not shared) $canEdit = in_array("sysadmin:access", $_SESSION["auth_data"]["permissions"] ?? []) && !$is_shared; -// Helper functions -function get_menu_types($centro_id, $source_aulario_id) { - $menuTypesPath = menu_types_path($centro_id, $source_aulario_id); - $defaultMenuTypes = [ - ["id" => "basal", "label" => "Menú basal", "color" => "#0d6efd"], - ["id" => "vegetariano", "label" => "Menú vegetariano", "color" => "#198754"], - ["id" => "alergias", "label" => "Menú alergias", "color" => "#dc3545"], - ]; +$defaultMenuTypes = [ + ["id" => "basal", "label" => "Menú basal", "color" => "#0d6efd"], + ["id" => "vegetariano", "label" => "Menú vegetariano", "color" => "#198754"], + ["id" => "alergias", "label" => "Menú alergias", "color" => "#dc3545"], +]; - if ($menuTypesPath === null) { +function get_menu_types($centro_id, $source_aulario_id) { + global $defaultMenuTypes; + $types = db_get_comedor_menu_types($centro_id, $source_aulario_id); + if (empty($types)) { + db_set_comedor_menu_types($centro_id, $source_aulario_id, $defaultMenuTypes); return $defaultMenuTypes; } - - if (!file_exists($menuTypesPath)) { - if (!is_dir(dirname($menuTypesPath))) { - mkdir(dirname($menuTypesPath), 0777, true); - } - file_put_contents($menuTypesPath, json_encode($defaultMenuTypes, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); - return $defaultMenuTypes; - } - - $menuTypes = json_decode(@file_get_contents($menuTypesPath), true); - return (is_array($menuTypes) && count($menuTypes) > 0) ? $menuTypes : $defaultMenuTypes; + return $types; } function blank_menu() { @@ -98,7 +67,7 @@ function blank_menu() { "plates" => [ "primero" => ["name" => "", "pictogram" => ""], "segundo" => ["name" => "", "pictogram" => ""], - "postre" => ["name" => "", "pictogram" => ""], + "postre" => ["name" => "", "pictogram" => ""], ] ]; } @@ -113,43 +82,25 @@ switch ($action) { case "get_menu_types": handle_get_menu_types(); break; - case "get_menu": handle_get_menu(); break; - case "save_menu": - if (!$canEdit) { - http_response_code(403); - die(json_encode(["error" => "Insufficient permissions to edit", "code" => "FORBIDDEN"])); - } + if (!$canEdit) { http_response_code(403); die(json_encode(["error" => "Insufficient permissions to edit", "code" => "FORBIDDEN"])); } handle_save_menu(); break; - case "add_menu_type": - if (!$canEdit) { - http_response_code(403); - die(json_encode(["error" => "Insufficient permissions to edit", "code" => "FORBIDDEN"])); - } + if (!$canEdit) { http_response_code(403); die(json_encode(["error" => "Insufficient permissions to edit", "code" => "FORBIDDEN"])); } handle_add_menu_type(); break; - case "delete_menu_type": - if (!$canEdit) { - http_response_code(403); - die(json_encode(["error" => "Insufficient permissions to edit", "code" => "FORBIDDEN"])); - } + if (!$canEdit) { http_response_code(403); die(json_encode(["error" => "Insufficient permissions to edit", "code" => "FORBIDDEN"])); } handle_delete_menu_type(); break; - case "rename_menu_type": - if (!$canEdit) { - http_response_code(403); - die(json_encode(["error" => "Insufficient permissions to edit", "code" => "FORBIDDEN"])); - } + if (!$canEdit) { http_response_code(403); die(json_encode(["error" => "Insufficient permissions to edit", "code" => "FORBIDDEN"])); } handle_rename_menu_type(); break; - default: http_response_code(400); die(json_encode(["error" => "Invalid action", "code" => "INVALID_ACTION"])); @@ -157,298 +108,102 @@ switch ($action) { function handle_get_menu_types() { global $centro_id, $source_aulario_id; - - $menuTypes = get_menu_types($centro_id, $source_aulario_id); - echo json_encode([ - "success" => true, - "menu_types" => $menuTypes - ]); + echo json_encode(["success" => true, "menu_types" => get_menu_types($centro_id, $source_aulario_id)]); } function handle_get_menu() { global $centro_id, $source_aulario_id; - $date = $_GET["date"] ?? date("Y-m-d"); $menuTypeId = safe_id_segment($_GET["menu"] ?? ""); - - // Validate date $dateObj = DateTime::createFromFormat("Y-m-d", $date); - if (!$dateObj) { - http_response_code(400); - die(json_encode(["error" => "Invalid date format", "code" => "INVALID_FORMAT"])); - } + if (!$dateObj) { http_response_code(400); die(json_encode(["error" => "Invalid date format", "code" => "INVALID_FORMAT"])); } $date = $dateObj->format("Y-m-d"); - - // Get menu types - $menuTypes = get_menu_types($centro_id, $source_aulario_id); - $menuTypeIds = []; - foreach ($menuTypes as $t) { - if (!empty($t["id"])) { - $menuTypeIds[] = $t["id"]; - } - } - - if ($menuTypeId === "" || !in_array($menuTypeId, $menuTypeIds)) { - $menuTypeId = $menuTypeIds[0] ?? "basal"; - } - - // Get menu data - $ym = $dateObj->format("Y-m"); + $menuTypes = get_menu_types($centro_id, $source_aulario_id); + $menuTypeIds = array_column($menuTypes, "id"); + if ($menuTypeId === "" || !in_array($menuTypeId, $menuTypeIds)) { $menuTypeId = $menuTypeIds[0] ?? "basal"; } + $ym = $dateObj->format("Y-m"); $day = $dateObj->format("d"); - $baseDir = comedor_day_base_dir($centro_id, $source_aulario_id, $ym, $day); - if ($baseDir === null) { - http_response_code(400); - die(json_encode(["error" => "Invalid path parameters", "code" => "INVALID_PATH"])); - } - $dataPath = "$baseDir/_datos.json"; - - $menuData = [ - "date" => $date, - "menus" => [] - ]; - - if (file_exists($dataPath)) { - $existing = json_decode(file_get_contents($dataPath), true); - if (is_array($existing)) { - $menuData = array_merge($menuData, $existing); - } - } - - if (!isset($menuData["menus"][$menuTypeId])) { - $menuData["menus"][$menuTypeId] = blank_menu(); - } - - $menuForType = $menuData["menus"][$menuTypeId]; - - echo json_encode([ - "success" => true, - "date" => $date, - "menu_type" => $menuTypeId, - "menu_types" => $menuTypes, - "menu" => $menuForType - ]); + $menuData = ["date" => $date, "menus" => []]; + $existing = db_get_comedor_entry($centro_id, $source_aulario_id, $ym, $day); + if (!empty($existing)) { $menuData = array_merge($menuData, $existing); } + if (!isset($menuData["menus"][$menuTypeId])) { $menuData["menus"][$menuTypeId] = blank_menu(); } + echo json_encode(["success" => true, "date" => $date, "menu_type" => $menuTypeId, "menu_types" => $menuTypes, "menu" => $menuData["menus"][$menuTypeId]]); } function handle_save_menu() { global $centro_id, $source_aulario_id; - - // Parse JSON body - $input = json_decode(file_get_contents("php://input"), true); - if (!$input) { - $input = $_POST; - } - - $date = $input["date"] ?? date("Y-m-d"); + $input = json_decode(file_get_contents("php://input"), true) ?: $_POST; + $date = $input["date"] ?? date("Y-m-d"); $menuTypeId = safe_id_segment($input["menu_type"] ?? ""); - $plates = $input["plates"] ?? []; - - // Validate date + $plates = $input["plates"] ?? []; $dateObj = DateTime::createFromFormat("Y-m-d", $date); - if (!$dateObj) { - http_response_code(400); - die(json_encode(["error" => "Invalid date format", "code" => "INVALID_FORMAT"])); - } + if (!$dateObj) { http_response_code(400); die(json_encode(["error" => "Invalid date format", "code" => "INVALID_FORMAT"])); } $date = $dateObj->format("Y-m-d"); - - // Validate menu type - $menuTypes = get_menu_types($centro_id, $source_aulario_id); - $validMenuTypeIds = []; - foreach ($menuTypes as $t) { - if (!empty($t["id"])) { - $validMenuTypeIds[] = $t["id"]; - } - } - - if (!in_array($menuTypeId, $validMenuTypeIds)) { - http_response_code(400); - die(json_encode(["error" => "Invalid menu type", "code" => "INVALID_MENU_TYPE"])); - } - - // Get existing menu data - $ym = $dateObj->format("Y-m"); + $menuTypes = get_menu_types($centro_id, $source_aulario_id); + $validMenuTypeIds = array_column($menuTypes, "id"); + if (!in_array($menuTypeId, $validMenuTypeIds)) { http_response_code(400); die(json_encode(["error" => "Invalid menu type", "code" => "INVALID_MENU_TYPE"])); } + $ym = $dateObj->format("Y-m"); $day = $dateObj->format("d"); - $baseDir = comedor_day_base_dir($centro_id, $source_aulario_id, $ym, $day); - if ($baseDir === null) { - http_response_code(400); - die(json_encode(["error" => "Invalid path parameters", "code" => "INVALID_PATH"])); - } - $dataPath = "$baseDir/_datos.json"; - - $menuData = [ - "date" => $date, - "menus" => [] - ]; - - if (file_exists($dataPath)) { - $existing = json_decode(file_get_contents($dataPath), true); - if (is_array($existing)) { - $menuData = array_merge($menuData, $existing); + $menuData = ["date" => $date, "menus" => []]; + $existing = db_get_comedor_entry($centro_id, $source_aulario_id, $ym, $day); + if (!empty($existing)) { $menuData = array_merge($menuData, $existing); } + if (!isset($menuData["menus"][$menuTypeId])) { $menuData["menus"][$menuTypeId] = blank_menu(); } + foreach (["primero", "segundo", "postre"] as $plateKey) { + if (isset($plates[$plateKey]["name"])) { + $menuData["menus"][$menuTypeId]["plates"][$plateKey]["name"] = trim($plates[$plateKey]["name"]); } } - - if (!isset($menuData["menus"][$menuTypeId])) { - $menuData["menus"][$menuTypeId] = blank_menu(); - } - - // Update plates - $validPlates = ["primero", "segundo", "postre"]; - foreach ($validPlates as $plateKey) { - if (isset($plates[$plateKey])) { - if (isset($plates[$plateKey]["name"])) { - $menuData["menus"][$menuTypeId]["plates"][$plateKey]["name"] = trim($plates[$plateKey]["name"]); - } - // Note: pictogram upload not supported via JSON API - use form-data instead - } - } - - // Save menu - if (!is_dir($baseDir)) { - mkdir($baseDir, 0777, true); - } - file_put_contents($dataPath, json_encode($menuData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); - - echo json_encode([ - "success" => true, - "date" => $date, - "menu_type" => $menuTypeId, - "menu" => $menuData["menus"][$menuTypeId] - ]); + db_set_comedor_entry($centro_id, $source_aulario_id, $ym, $day, $menuData); + echo json_encode(["success" => true, "date" => $date, "menu_type" => $menuTypeId, "menu" => $menuData["menus"][$menuTypeId]]); } function handle_add_menu_type() { global $centro_id, $source_aulario_id; - - $input = json_decode(file_get_contents("php://input"), true); - if (!$input) { - $input = $_POST; - } - - $newId = safe_id_segment(strtolower(trim($input["id"] ?? ""))); + $input = json_decode(file_get_contents("php://input"), true) ?: $_POST; + $newId = safe_id_segment(strtolower(trim($input["id"] ?? ""))); $newLabel = trim($input["label"] ?? ""); $newColor = trim($input["color"] ?? "#0d6efd"); - - if ($newId === "" || $newLabel === "") { - http_response_code(400); - die(json_encode(["error" => "id and label are required", "code" => "MISSING_PARAM"])); - } - - $menuTypesPath = menu_types_path($centro_id, $source_aulario_id); - if ($menuTypesPath === null) { - http_response_code(400); - die(json_encode(["error" => "Invalid path parameters", "code" => "INVALID_PATH"])); - } + if ($newId === "" || $newLabel === "") { http_response_code(400); die(json_encode(["error" => "id and label are required", "code" => "MISSING_PARAM"])); } $menuTypes = get_menu_types($centro_id, $source_aulario_id); - - // Check if already exists foreach ($menuTypes as $t) { - if (($t["id"] ?? "") === $newId) { - http_response_code(400); - die(json_encode(["error" => "Menu type already exists", "code" => "DUPLICATE"])); - } + if (($t["id"] ?? "") === $newId) { http_response_code(400); die(json_encode(["error" => "Menu type already exists", "code" => "DUPLICATE"])); } } - $menuTypes[] = ["id" => $newId, "label" => $newLabel, "color" => $newColor]; - file_put_contents($menuTypesPath, json_encode($menuTypes, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); - - echo json_encode([ - "success" => true, - "menu_type" => ["id" => $newId, "label" => $newLabel, "color" => $newColor], - "message" => "Menu type added successfully" - ]); + db_set_comedor_menu_types($centro_id, $source_aulario_id, $menuTypes); + echo json_encode(["success" => true, "menu_type" => ["id" => $newId, "label" => $newLabel, "color" => $newColor], "message" => "Menu type added successfully"]); } function handle_delete_menu_type() { global $centro_id, $source_aulario_id; - - $input = json_decode(file_get_contents("php://input"), true); - if (!$input) { - $input = $_POST; - } - + $input = json_decode(file_get_contents("php://input"), true) ?: $_POST; $deleteId = safe_id_segment(trim($input["id"] ?? "")); - - if ($deleteId === "") { - http_response_code(400); - die(json_encode(["error" => "id is required", "code" => "MISSING_PARAM"])); - } - - $menuTypesPath = menu_types_path($centro_id, $source_aulario_id); - if ($menuTypesPath === null) { - http_response_code(400); - die(json_encode(["error" => "Invalid path parameters", "code" => "INVALID_PATH"])); - } - $menuTypes = get_menu_types($centro_id, $source_aulario_id); - - $deleted = false; - $newMenuTypes = []; - foreach ($menuTypes as $t) { - if (($t["id"] ?? "") === $deleteId) { - $deleted = true; - } else { - $newMenuTypes[] = $t; - } - } - - if (!$deleted) { - http_response_code(404); - die(json_encode(["error" => "Menu type not found", "code" => "NOT_FOUND"])); - } - - file_put_contents($menuTypesPath, json_encode($newMenuTypes, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); - - echo json_encode([ - "success" => true, - "message" => "Menu type deleted successfully" - ]); + if ($deleteId === "") { http_response_code(400); die(json_encode(["error" => "id is required", "code" => "MISSING_PARAM"])); } + $menuTypes = get_menu_types($centro_id, $source_aulario_id); + $newMenuTypes = array_values(array_filter($menuTypes, fn($t) => ($t["id"] ?? "") !== $deleteId)); + if (count($newMenuTypes) === count($menuTypes)) { http_response_code(404); die(json_encode(["error" => "Menu type not found", "code" => "NOT_FOUND"])); } + db_set_comedor_menu_types($centro_id, $source_aulario_id, $newMenuTypes); + echo json_encode(["success" => true, "message" => "Menu type deleted successfully"]); } function handle_rename_menu_type() { global $centro_id, $source_aulario_id; - - $input = json_decode(file_get_contents("php://input"), true); - if (!$input) { - $input = $_POST; - } - + $input = json_decode(file_get_contents("php://input"), true) ?: $_POST; $renameId = safe_id_segment(trim($input["id"] ?? "")); $newLabel = trim($input["label"] ?? ""); $newColor = trim($input["color"] ?? ""); - - if ($renameId === "" || $newLabel === "") { - http_response_code(400); - die(json_encode(["error" => "id and label are required", "code" => "MISSING_PARAM"])); - } - - $menuTypesPath = menu_types_path($centro_id, $source_aulario_id); - if ($menuTypesPath === null) { - http_response_code(400); - die(json_encode(["error" => "Invalid path parameters", "code" => "INVALID_PATH"])); - } + if ($renameId === "" || $newLabel === "") { http_response_code(400); die(json_encode(["error" => "id and label are required", "code" => "MISSING_PARAM"])); } $menuTypes = get_menu_types($centro_id, $source_aulario_id); - $found = false; foreach ($menuTypes as &$t) { if (($t["id"] ?? "") === $renameId) { $t["label"] = $newLabel; - if ($newColor !== "") { - $t["color"] = $newColor; - } + if ($newColor !== "") { $t["color"] = $newColor; } $found = true; break; } } unset($t); - - if (!$found) { - http_response_code(404); - die(json_encode(["error" => "Menu type not found", "code" => "NOT_FOUND"])); - } - - file_put_contents($menuTypesPath, json_encode($menuTypes, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); - - echo json_encode([ - "success" => true, - "menu_type" => ["id" => $renameId, "label" => $newLabel, "color" => $newColor], - "message" => "Menu type renamed successfully" - ]); + if (!$found) { http_response_code(404); die(json_encode(["error" => "Menu type not found", "code" => "NOT_FOUND"])); } + db_set_comedor_menu_types($centro_id, $source_aulario_id, $menuTypes); + echo json_encode(["success" => true, "message" => "Menu type renamed successfully"]); } diff --git a/public_html/aulatek/aulario.php b/public_html/aulatek/aulario.php index df80422..7ae8d6e 100644 --- a/public_html/aulatek/aulario.php +++ b/public_html/aulatek/aulario.php @@ -2,11 +2,12 @@ require_once "_incl/auth_redir.php"; require_once "_incl/pre-body.php"; require_once "../_incl/tools.security.php"; +require_once "../_incl/db.php"; $aulario_id = safe_id_segment($_GET["id"] ?? ""); -$centro_id = safe_centro_id($_SESSION["auth_data"]["entreaulas"]["centro"] ?? ""); -$aulario_path = safe_aulario_config_path($centro_id, $aulario_id); -$aulario = ($aulario_path && file_exists($aulario_path)) ? json_decode(file_get_contents($aulario_path), true) : null; +$tenant_data = $_SESSION["auth_data"]["aulatek"] ?? ($_SESSION["auth_data"]["entreaulas"] ?? []); +$centro_id = safe_organization_id($tenant_data["organizacion"] ?? ($tenant_data["centro"] ?? "")); +$aulario = db_get_aulario($centro_id, $aulario_id); if (!$aulario || !is_array($aulario)) { ?> @@ -26,13 +27,14 @@ if (!$aulario || !is_array($aulario)) {
- +
Panel Diario
- - + + + Icono de gestión de alumnos
Gestión de Alumnos @@ -46,13 +48,13 @@ if (!$aulario || !is_array($aulario)) {
- +
Menú del Comedor
- + folder-multiple diff --git a/public_html/aulatek/comedor.php b/public_html/aulatek/comedor.php index 3b47bdd..72734ff 100644 --- a/public_html/aulatek/comedor.php +++ b/public_html/aulatek/comedor.php @@ -1,14 +1,17 @@ "basal", "label" => "Menú basal", "color" => "#0d6efd"], + ["id" => "basal", "label" => "Menú basal", "color" => "#0d6efd"], ["id" => "vegetariano", "label" => "Menú vegetariano", "color" => "#198754"], - ["id" => "alergias", "label" => "Menú alergias", "color" => "#dc3545"], + ["id" => "alergias", "label" => "Menú alergias", "color" => "#dc3545"], ]; -if (!file_exists($menuTypesPath)) { - if (!is_dir(dirname($menuTypesPath))) { - mkdir(dirname($menuTypesPath), 0777, true); - } - file_put_contents($menuTypesPath, json_encode($defaultMenuTypes, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); -} -$menuTypes = json_decode(@file_get_contents($menuTypesPath), true); +$menuTypes = db_get_comedor_menu_types($centro_id, $source_aulario_id); if (!is_array($menuTypes) || count($menuTypes) === 0) { $menuTypes = $defaultMenuTypes; + db_set_comedor_menu_types($centro_id, $source_aulario_id, $menuTypes); } $menuTypeIds = []; @@ -71,10 +67,9 @@ if (!in_array($menuTypeId, $menuTypeIds, true)) { $menuTypeId = $menuTypeIds[0] ?? "basal"; } -$ym = $dateObj->format("Y-m"); +$ym = $dateObj->format("Y-m"); $day = $dateObj->format("d"); -$baseDir = "/DATA/entreaulas/Centros/$centro_id/Aularios/$source_aulario_id/Comedor/$ym/$day"; -$dataPath = "$baseDir/_datos.json"; + function blank_menu() { @@ -91,11 +86,9 @@ $menuData = [ "date" => $date, "menus" => [] ]; -if (file_exists($dataPath)) { - $existing = json_decode(file_get_contents($dataPath), true); - if (is_array($existing)) { - $menuData = array_merge($menuData, $existing); - } +$existing = db_get_comedor_entry($centro_id, $source_aulario_id, $ym, $day); +if (is_array($existing) && !empty($existing)) { + $menuData = array_merge($menuData, $existing); } if (!isset($menuData["menus"][$menuTypeId])) { $menuData["menus"][$menuTypeId] = blank_menu(); @@ -251,15 +244,12 @@ if ($_SERVER["REQUEST_METHOD"] === "POST" && $canEdit) { if ($newId !== "" && $newLabel !== "") { $exists = false; foreach ($menuTypes as $t) { - if (($t["id"] ?? "") === $newId) { - $exists = true; - break; - } + if (($t["id"] ?? "") === $newId) { $exists = true; break; } } if (!$exists) { $menuTypes[] = ["id" => $newId, "label" => $newLabel, "color" => $newColor]; - file_put_contents($menuTypesPath, json_encode($menuTypes, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); - header("Location: /entreaulas/comedor.php?aulario=" . urlencode($aulario_id) . "&date=" . urlencode($date) . "&menu=" . urlencode($newId)); + db_set_comedor_menu_types($centro_id, $source_aulario_id, $menuTypes); + header("Location: /aulatek/comedor.php?aulario=" . urlencode($aulario_id) . "&date=" . urlencode($date) . "&menu=" . urlencode($newId)); exit; } } @@ -268,21 +258,12 @@ if ($_SERVER["REQUEST_METHOD"] === "POST" && $canEdit) { if ($action === "delete_type") { $deleteId = safe_id_segment(trim($_POST["delete_type_id"] ?? "")); if ($deleteId !== "") { - $deleted = false; - $newMenuTypes = []; - foreach ($menuTypes as $t) { - if (($t["id"] ?? "") === $deleteId) { - $deleted = true; - } else { - $newMenuTypes[] = $t; - } - } - if ($deleted) { + $newMenuTypes = array_values(array_filter($menuTypes, fn($t) => ($t["id"] ?? "") !== $deleteId)); + if (count($newMenuTypes) !== count($menuTypes)) { $menuTypes = $newMenuTypes; - file_put_contents($menuTypesPath, json_encode($menuTypes, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); - // Redirect to the first available menu type or default + db_set_comedor_menu_types($centro_id, $source_aulario_id, $menuTypes); $redirectMenuId = !empty($menuTypes) ? $menuTypes[0]["id"] : "basal"; - header("Location: /entreaulas/comedor.php?aulario=" . urlencode($aulario_id) . "&date=" . urlencode($date) . "&menu=" . urlencode($redirectMenuId)); + header("Location: /aulatek/comedor.php?aulario=" . urlencode($aulario_id) . "&date=" . urlencode($date) . "&menu=" . urlencode($redirectMenuId)); exit; } } @@ -296,16 +277,13 @@ if ($_SERVER["REQUEST_METHOD"] === "POST" && $canEdit) { foreach ($menuTypes as &$t) { if (($t["id"] ?? "") === $renameId) { $t["label"] = $newLabel; - if ($newColor !== "") { - $t["color"] = $newColor; - } + if ($newColor !== "") { $t["color"] = $newColor; } break; } } - // Clean up the reference to avoid accidental usage after the loop unset($t); - file_put_contents($menuTypesPath, json_encode($menuTypes, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); - header("Location: /entreaulas/comedor.php?aulario=" . urlencode($aulario_id) . "&date=" . urlencode($date) . "&menu=" . urlencode($renameId)); + db_set_comedor_menu_types($centro_id, $source_aulario_id, $menuTypes); + header("Location: /aulatek/comedor.php?aulario=" . urlencode($aulario_id) . "&date=" . urlencode($date) . "&menu=" . urlencode($renameId)); exit; } } @@ -316,23 +294,19 @@ if ($_SERVER["REQUEST_METHOD"] === "POST" && $canEdit) { $menuData["menus"][$menuTypeId] = blank_menu(); } + // Pictogram images still stored on filesystem in Comedor dir + $baseDir = aulatek_orgs_base_path() . "/$centro_id/Aularios/$source_aulario_id/Comedor/$ym/$day"; $plates = ["primero", "segundo", "postre"]; foreach ($plates as $plate) { $name = trim($_POST["name_" . $plate] ?? ""); $menuData["menus"][$menuTypeId]["plates"][$plate]["name"] = $name; - $pictUpload = handle_image_upload("pictogram_file_" . $plate, $menuTypeId . "_" . $plate . "_pict", $baseDir, $uploadErrors); - if ($pictUpload !== null) { $menuData["menus"][$menuTypeId]["plates"][$plate]["pictogram"] = $pictUpload; } - } - if (!is_dir($baseDir)) { - mkdir($baseDir, 0777, true); - } - file_put_contents($dataPath, json_encode($menuData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); + db_set_comedor_entry($centro_id, $source_aulario_id, $ym, $day, $menuData); $saveNotice = "Menú guardado correctamente."; } } @@ -346,24 +320,23 @@ function image_src($value, $centro_id, $source_aulario_id, $date) if (filter_var($value, FILTER_VALIDATE_URL)) { return $value; } - return "/entreaulas/_filefetch.php?type=comedor_image¢ro=" . urlencode($centro_id) . "&aulario=" . urlencode($source_aulario_id) . "&date=" . urlencode($date) . "&file=" . urlencode($value); + return "/aulatek/_filefetch.php?type=comedor_image&org=" . urlencode($centro_id) . "&aulario=" . urlencode($source_aulario_id) . "&date=" . urlencode($date) . "&file=" . urlencode($value); } $prevDate = (clone $dateObj)->modify("-1 day")->format("Y-m-d"); $nextDate = (clone $dateObj)->modify("+1 day")->format("Y-m-d"); -$userAulas = $_SESSION["auth_data"]["entreaulas"]["aulas"] ?? []; +$userAulas = $tenant_data["aulas"] ?? []; $aulaOptions = []; foreach ($userAulas as $aulaId) { $aulaIdSafe = safe_id_segment($aulaId); if ($aulaIdSafe === "") { continue; } - $aulaPath = "/DATA/entreaulas/Centros/$centro_id/Aularios/$aulaIdSafe.json"; - $aulaData = file_exists($aulaPath) ? json_decode(file_get_contents($aulaPath), true) : null; + $aulaData = db_get_aulario($centro_id, $aulaIdSafe); $aulaOptions[] = [ - "id" => $aulaIdSafe, - "name" => $aulaData["name"] ?? $aulaIdSafe + "id" => $aulaIdSafe, + "name" => $aulaData["name"] ?? $aulaIdSafe, ]; } require_once "_incl/pre-body.php"; @@ -394,9 +367,9 @@ require_once "_incl/pre-body.php";
@@ -419,7 +392,7 @@ require_once "_incl/pre-body.php"; $isActive = ($type["id"] ?? "") === $menuTypeId; $color = $type["color"] ?? "#0d6efd"; ?> - " + " class="btn btn-lg" style="background: ; color: white; border: 3px solid ;"> @@ -644,7 +617,7 @@ require_once "_incl/pre-body.php"; params.set("date", dateValue); params.set("aulario", aularioValue); params.set("menu", ""); - window.location.href = "/entreaulas/comedor.php?" + params.toString(); + window.location.href = "/aulatek/comedor.php?" + params.toString(); } if (datePicker) { datePicker.addEventListener("change", goToSelection); diff --git a/public_html/aulatek/diario.php b/public_html/aulatek/diario.php index e0dbf9b..8d495f1 100644 --- a/public_html/aulatek/diario.php +++ b/public_html/aulatek/diario.php @@ -3,13 +3,15 @@ require_once "_incl/auth_redir.php"; require_once "../_incl/tools.security.php"; // Check if user has docente permission -if (!in_array("entreaulas:docente", $_SESSION["auth_data"]["permissions"] ?? [])) { +$permissions = $_SESSION["auth_data"]["permissions"] ?? []; +if (!in_array("aulatek:docente", $permissions, true) && !in_array("entreaulas:docente", $permissions, true)) { header("HTTP/1.1 403 Forbidden"); die("Acceso denegado"); } $aulario_id = safe_id_segment($_GET["aulario"] ?? ""); -$centro_id = safe_centro_id($_SESSION["auth_data"]["entreaulas"]["centro"] ?? ""); +$tenant_data = $_SESSION["auth_data"]["aulatek"] ?? ($_SESSION["auth_data"]["entreaulas"] ?? []); +$centro_id = safe_organization_id($tenant_data["organizacion"] ?? ($tenant_data["centro"] ?? "")); $alumno = safe_id_segment($_GET["alumno"] ?? ""); if (empty($aulario_id) || empty($centro_id)) { @@ -25,7 +27,7 @@ if (empty($aulario_id) || empty($centro_id)) { } // Validate paths with realpath -$base_path = "/DATA/entreaulas/Centros"; +$base_path = aulatek_orgs_base_path(); $real_base = realpath($base_path); if ($real_base === false) { @@ -77,7 +79,7 @@ if (empty($alumno)) {
- Foto de <?= htmlspecialchars($alumno_name) ?> diff --git a/public_html/aulatek/index.php b/public_html/aulatek/index.php old mode 100755 new mode 100644 index ce0271a..1b49523 --- a/public_html/aulatek/index.php +++ b/public_html/aulatek/index.php @@ -2,42 +2,42 @@ require_once "_incl/auth_redir.php"; require_once "_incl/pre-body.php"; require_once "../_incl/tools.security.php"; +require_once "../_incl/db.php"; ?>
-

¡Hola, !

+

¡Hola, !

Bienvenidx a la plataforma de gestión de aularios conectados. Desde aquí podrás administrar los aularios asociados a tu cuenta.
- + echo ' ' . htmlspecialchars($aulario_name) . ' Icono
' . htmlspecialchars($aulario_name) . '
'; } ?> - + Icono SuperCafe @@ -61,7 +61,6 @@ require_once "../_incl/tools.security.php"; } - - diff --git a/public_html/aulatek/paneldiario.php b/public_html/aulatek/paneldiario.php index e11a5f3..ed18c43 100755 --- a/public_html/aulatek/paneldiario.php +++ b/public_html/aulatek/paneldiario.php @@ -1,6 +1,7 @@ true]); die(); @@ -90,10 +93,12 @@ $form_action = $_GET["form"] ?? ""; switch ($form_action) { case "alumno_selected": $alumno = safe_id_segment($_GET["alumno"] ?? ""); - $centro_id = safe_centro_id($_SESSION["auth_data"]["entreaulas"]["centro"] ?? ""); + $centro_id = safe_organization_id($tenant_data["organizacion"] ?? ($tenant_data["centro"] ?? "")); $aulario_id = safe_id_segment($_GET["aulario"] ?? ''); $photo_url = $_GET["photo"] ?? ''; if ($alumno !== "" && $centro_id !== "" && $aulario_id !== "") { + $_SESSION["aulatek_selected_alumno"] = $alumno; + $_SESSION["aulatek_selected_aulario"] = $aulario_id; $_SESSION["entreaulas_selected_alumno"] = $alumno; $_SESSION["entreaulas_selected_aulario"] = $aulario_id; initDiario($alumno, $centro_id, $aulario_id); @@ -208,8 +213,8 @@ ini_set("display_errors", "0"); , "", "");' aria-label="Seleccionar alumno "> - Foto de <?php echo htmlspecialchars($alumno_name); ?> + Foto de <?php echo htmlspecialchars($alumno_name); ?>
? @@ -704,10 +709,10 @@ switch ($view_action) { { announceAndMaybeRedirect( actividad + ", Actividad seleccionada", - "/entreaulas/paneldiario.php?aulario=", + "/aulatek/paneldiario.php?aulario=", true ); }); @@ -745,7 +750,7 @@ switch ($view_action) {
@@ -779,17 +784,15 @@ switch ($view_action) { case "menu": // Menú del comedor (nuevo sistema, vista simplificada) $aulario_id = safe_id_segment($_GET["aulario"] ?? ''); - $centro_id = safe_centro_id($_SESSION["auth_data"]["entreaulas"]["centro"] ?? ""); + $centro_id = safe_organization_id($tenant_data["organizacion"] ?? ($tenant_data["centro"] ?? "")); $source_aulario_id = $aulario_id; $is_shared = false; if ($aulario_id !== "" && $centro_id !== "") { - $aulario_path = safe_aulario_config_path($centro_id, $aulario_id); - $aulario = ($aulario_path && file_exists($aulario_path)) ? json_decode(file_get_contents($aulario_path), true) : null; + $aulario = db_get_aulario($centro_id, $aulario_id); if ($aulario && !empty($aulario["shared_comedor_from"])) { $shared_from = safe_id_segment($aulario["shared_comedor_from"]); - $shared_aulario_path = safe_aulario_config_path($centro_id, $shared_from); - if ($shared_aulario_path && file_exists($shared_aulario_path)) { + if (db_get_aulario($centro_id, $shared_from)) { $source_aulario_id = $shared_from; $is_shared = true; } @@ -800,13 +803,12 @@ switch ($view_action) { $dateObj = DateTime::createFromFormat("Y-m-d", $dateParam) ?: new DateTime(); $date = $dateObj->format("Y-m-d"); - $menuTypesPath = ($centro_id !== '' && $source_aulario_id !== '') ? "/DATA/entreaulas/Centros/$centro_id/Aularios/$source_aulario_id/Comedor-MenuTypes.json" : ""; $defaultMenuTypes = [ ["id" => "basal", "label" => "Menú basal", "color" => "#0d6efd"], ["id" => "vegetariano", "label" => "Menú vegetariano", "color" => "#198754"], ["id" => "alergias", "label" => "Menú alergias", "color" => "#dc3545"], ]; - $menuTypes = ($menuTypesPath !== '' && file_exists($menuTypesPath)) ? json_decode(@file_get_contents($menuTypesPath), true) : null; + $menuTypes = ($centro_id !== '' && $source_aulario_id !== '') ? db_get_comedor_menu_types($centro_id, $source_aulario_id) : []; if (!is_array($menuTypes) || count($menuTypes) === 0) { $menuTypes = $defaultMenuTypes; } @@ -822,17 +824,13 @@ switch ($view_action) { $menuTypeId = $menuTypeIds[0] ?? "basal"; } - $ym = $dateObj->format("Y-m"); + $ym = $dateObj->format("Y-m"); $day = $dateObj->format("d"); - $dataPath = ($centro_id !== '' && $source_aulario_id !== '') ? "/DATA/entreaulas/Centros/$centro_id/Aularios/$source_aulario_id/Comedor/$ym/$day/_datos.json" : ""; - $menuData = [ - "date" => $date, - "menus" => [] - ]; - if ($dataPath !== '' && file_exists($dataPath)) { - $existing = json_decode(file_get_contents($dataPath), true); - if (is_array($existing)) { + $menuData = ["date" => $date, "menus" => []]; + if ($centro_id !== '' && $source_aulario_id !== '') { + $existing = db_get_comedor_entry($centro_id, $source_aulario_id, $ym, $day); + if (!empty($existing)) { $menuData = array_merge($menuData, $existing); } } @@ -843,7 +841,7 @@ switch ($view_action) { if (!$value) { return ""; } - return "/entreaulas/_filefetch.php?type=comedor_image¢ro=" . urlencode($centro_id) . "&aulario=" . urlencode($aulario_id) . "&date=" . urlencode($date) . "&file=" . urlencode($value); + return "/aulatek/_filefetch.php?type=comedor_image&org=" . urlencode($centro_id) . "&aulario=" . urlencode($aulario_id) . "&date=" . urlencode($date) . "&file=" . urlencode($value); } ?> diff --git a/public_html/club/cal.php b/public_html/club/cal.php index 97ba209..d67ebb4 100755 --- a/public_html/club/cal.php +++ b/public_html/club/cal.php @@ -1,10 +1,11 @@ $_POST["title"], - "note" => $_POST["note"], - "mapa" => [ - "url" => $_POST["mapa_url"] - ] - ]; + "title" => $_POST["title"], + "note" => $_POST["note"], + "mapa" => ["url" => $_POST["mapa_url"]], + ]; $file = $_POST["date"]; - $val = file_put_contents("/DATA/club/IMG/$file/data.json", json_encode($data, JSON_UNESCAPED_SLASHES)); + db_set_club_event($file, $data); header("Location: /club/"); die(); } diff --git a/public_html/club/index.php b/public_html/club/index.php index 167fd72..0dc861c 100755 --- a/public_html/club/index.php +++ b/public_html/club/index.php @@ -1,5 +1,6 @@
  • - diff --git a/public_html/club/upload/upload.php b/public_html/club/upload/upload.php index 920456a..0811646 100755 --- a/public_html/club/upload/upload.php +++ b/public_html/club/upload/upload.php @@ -1,7 +1,8 @@ - - \ No newline at end of file +header('Location: /aulatek/'); +exit; \ No newline at end of file diff --git a/public_html/index.php b/public_html/index.php index 489ea00..7f78ba7 100755 --- a/public_html/index.php +++ b/public_html/index.php @@ -1,15 +1,133 @@ + +

    Bienvenidx a Axia4

    -

    La plataforma unificada de EuskadiTech y Sketaria.

    +

    La plataforma unificada creada por EuskadiTech.


    -

    Versión 2.0.0

    -

    Con esta versión, cambiamos la interfaz a una mas sencilla.

    +

    Versión 2.1.0

    +

    Con esta versión, hacemos muchos cambios.

    +
    Accede a tu cuenta
    - +

    Aplicaciones

    @@ -19,14 +137,6 @@ Acceso público
    -
    - Logo TeleSec -
    TeleSec
    -
    Gestión de aularios conectados.
    - -
    Logo Account
    Mi Cuenta
    @@ -42,72 +152,13 @@
  • - Logo EntreAulas -
    EntreAulas
    -
    Recursos educativos digitales.
    + Logo AulaTek +
    AulaTek
    +
    Tu aula, digital.
    - -
    - Logo Arroz con leche -
    Arroz con leche
    -
    Compartiendo nuestros conocimientos.
    - -
    -
    -
    -
    - - \ No newline at end of file diff --git a/public_html/static/logo-aularios.png b/public_html/static/logo-aulatek.png similarity index 100% rename from public_html/static/logo-aularios.png rename to public_html/static/logo-aulatek.png diff --git a/public_html/sysadmin/__menu.php b/public_html/sysadmin/__menu.php index e69de29..02e5844 100755 --- a/public_html/sysadmin/__menu.php +++ b/public_html/sysadmin/__menu.php @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/public_html/sysadmin/aularios.php b/public_html/sysadmin/aularios.php index ba15ace..76616b3 100644 --- a/public_html/sysadmin/aularios.php +++ b/public_html/sysadmin/aularios.php @@ -1,10 +1,11 @@ prepare("DELETE FROM aularios WHERE org_id = ? AND aulario_id = ?") + ->execute([$centro_id, $aulario_id]); + // Remove comedor, diario, panel data + db()->prepare("DELETE FROM comedor_menu_types WHERE org_id = ? AND aulario_id = ?") + ->execute([$centro_id, $aulario_id]); + db()->prepare("DELETE FROM comedor_entries WHERE org_id = ? AND aulario_id = ?") + ->execute([$centro_id, $aulario_id]); + db()->prepare("DELETE FROM diario_entries WHERE org_id = ? AND aulario_id = ?") + ->execute([$centro_id, $aulario_id]); + db()->prepare("DELETE FROM panel_alumno WHERE org_id = ? AND aulario_id = ?") + ->execute([$centro_id, $aulario_id]); + // Remove filesystem directory with student photos + $aulario_dir = aulatek_orgs_base_path() . "/$centro_id/Aularios/$aulario_id"; + function rrmdir($dir) + { if (is_dir($dir)) { - $objects = scandir($dir); - foreach ($objects as $object) { - if ($object != "." && $object != "..") { - $obj_path = $dir . "/" . $object; - if (is_dir($obj_path)) { - rrmdir($obj_path); - } else { - unlink($obj_path); - } + foreach (scandir($dir) as $object) { + if ($object !== "." && $object !== "..") { + $p = "$dir/$object"; + is_dir($p) ? rrmdir($p) : unlink($p); } } rmdir($dir); } } rrmdir($aulario_dir); - // Remove aulario config file - unlink($aulario_file); header("Location: ?action=index"); exit(); break; case "create": - $user_data = $_SESSION["auth_data"]; - $centro_id = safe_path_segment(Sf($_POST["centro"] ?? "")); - if (empty($centro_id) || !is_dir("/DATA/entreaulas/Centros/$centro_id")) { + $centro_id = safe_path_segment(Sf($_POST["centro"] ?? ($_POST["org"] ?? ""))); + $aulario_id = strtolower(preg_replace("/[^a-zA-Z0-9_-]/", "_", Sf($_POST["name"] ?? ""))); + if (empty($centro_id) || empty($aulario_id)) { + die("Datos incompletos."); + } + // Ensure centro exists in DB + $stmt = db()->prepare("SELECT id FROM organizaciones WHERE org_id = ?"); + $stmt->execute([$centro_id]); + if (!$stmt->fetch()) { die("Centro no válido."); } - $aulario_id = strtolower(preg_replace("/[^a-zA-Z0-9_-]/", "_", Sf($_POST["name"] ?? ""))); - $aulario_data = [ - "name" => Sf($_POST["name"] ?? ""), - "icon" => Sf($_POST["icon"] ?? "/static/logo-entreaulas.png") - ]; - // Make path recursive (mkdir -p equivalent) - @mkdir("/DATA/entreaulas/Centros/$centro_id/Aularios/", 0777, true); - @mkdir("/DATA/entreaulas/Centros/$centro_id/Aularios/$aulario_id/Proyectos/", 0777, true); - file_put_contents("/DATA/entreaulas/Centros/$centro_id/Aularios/$aulario_id.json", json_encode($aulario_data)); - // Update user data - $_SESSION["auth_data"]["entreaulas"]["aulas"][] = $aulario_id; + db()->prepare( + "INSERT OR IGNORE INTO aularios (org_id, aulario_id, name, icon) VALUES (?, ?, ?, ?)" + )->execute([ + $centro_id, $aulario_id, + Sf($_POST["name"] ?? ""), + Sf($_POST["icon"] ?? "/static/logo-entreaulas.png"), + ]); + // Create Proyectos directory for project file storage + $proyectos_dir = aulatek_orgs_base_path() . "/$centro_id/Aularios/$aulario_id/Proyectos/"; + if (!is_dir($proyectos_dir)) { + mkdir($proyectos_dir, 0755, true); + } header("Location: ?action=index"); exit(); break; case "save_edit": $aulario_id = safe_path_segment(Sf($_POST["aulario_id"] ?? "")); - $centro_id = safe_path_segment(Sf($_POST["centro_id"] ?? "")); - $aulario_file = "/DATA/entreaulas/Centros/$centro_id/Aularios/$aulario_id.json"; - if (!file_exists($aulario_file)) { + $centro_id = safe_path_segment(Sf($_POST["centro_id"] ?? "")); + if ($aulario_id === "" || $centro_id === "") { + die("Parámetros inválidos."); + } + // Fetch existing extra data + $existing = db_get_aulario($centro_id, $aulario_id); + if ($existing === null) { die("Aulario no encontrado."); } - $aulario_data = json_decode(file_get_contents($aulario_file), true); - $aulario_data["name"] = Sf($_POST["name"] ?? ""); - $aulario_data["icon"] = Sf($_POST["icon"] ?? "/static/logo-entreaulas.png"); - - // Handle shared comedor configuration - $share_comedor_from = safe_path_segment(Sf($_POST["share_comedor_from"] ?? "")); - - if (!empty($share_comedor_from) && $share_comedor_from !== "none") { - $aulario_data["shared_comedor_from"] = $share_comedor_from; - } else { - unset($aulario_data["shared_comedor_from"]); - } - - // Handle linked projects configuration - $linked_projects = []; - $linked_aularios = $_POST["linked_aulario"] ?? []; - $linked_project_ids = $_POST["linked_project_id"] ?? []; - $linked_permissions = $_POST["linked_permission"] ?? []; - - for ($i = 0; $i < count($linked_aularios); $i++) { - $src_aul = safe_path_segment($linked_aularios[$i] ?? ""); - $proj_id = safe_path_segment($linked_project_ids[$i] ?? ""); - $perm = in_array(($linked_permissions[$i] ?? "read_only"), ["read_only", "request_edit", "full_edit"], true) - ? ($linked_permissions[$i] ?? "read_only") - : "read_only"; - if (!empty($src_aul) && !empty($proj_id)) { - $linked_projects[] = [ - "source_aulario" => $src_aul, - "project_id" => $proj_id, - "permission" => $perm - ]; + // Build extra JSON preserving any existing extra fields + $extra_skip = ['name', 'icon']; + $extra = []; + foreach ($existing as $k => $v) { + if (!in_array($k, $extra_skip, true)) { + $extra[$k] = $v; } } - - if (count($linked_projects) > 0) { - $aulario_data["linked_projects"] = $linked_projects; - } else { - unset($aulario_data["linked_projects"]); + // Update shared_comedor_from if posted + if (isset($_POST['shared_comedor_from'])) { + $extra['shared_comedor_from'] = Sf($_POST['shared_comedor_from']); } - @mkdir("/DATA/entreaulas/Centros/$centro_id/Aularios/$aulario_id/Proyectos/", 0777, true); - file_put_contents($aulario_file, json_encode($aulario_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); - header("Location: ?action=edit&aulario=" . urlencode($aulario_id) . "¢ro=" . urlencode($centro_id) . "&saved=1"); + db()->prepare( + "UPDATE aularios SET name = ?, icon = ?, extra = ? WHERE org_id = ? AND aulario_id = ?" + )->execute([ + Sf($_POST["name"] ?? ""), + Sf($_POST["icon"] ?? "/static/logo-entreaulas.png"), + json_encode($extra), + $centro_id, + $aulario_id, + ]); + header("Location: ?action=edit&aulario=" . urlencode($aulario_id) . "¢ro=" . urlencode($centro_id) . "&_result=" . urlencode("Cambios guardados.")); exit(); break; } -require_once "_incl/pre-body.php"; $view_action = $_GET["action"] ?? "index"; switch ($view_action) { case "new": - ?> + require_once "_incl/pre-body.php"; + $centro_id = safe_path_segment(Sf($_GET["centro"] ?? ($_GET["org"] ?? ""))); + $all_centros = db_get_centro_ids(); +?>
    -

    Nuevo Aulario

    - - Aquí puedes crear un nuevo aulario para el centro que administras. - +

    Nuevo Aulario

    - - + + + +
    - - + +
    - - + +
    - $aul_name) { - $proj_dir = "/DATA/entreaulas/Centros/$centro_id/Aularios/$aul_id/Proyectos"; - if (is_dir($proj_dir)) { - $projects = []; - $files = glob("$proj_dir/*.json"); - foreach ($files as $file) { - $proj_data = json_decode(file_get_contents($file), true); - // Only include root projects (no parent) - if ($proj_data && ($proj_data["parent_id"] ?? null) === null) { - $projects[] = [ - "id" => $proj_data["id"] ?? basename($file, ".json"), - "name" => $proj_data["name"] ?? "Sin nombre" - ]; - } - } - if (count($projects) > 0) { - $available_projects_by_aulario[$aul_id] = $projects; - } - } - } + $other_aularios = db_get_aularios($centro_id); ?> - -
    Cambios guardados correctamente.
    -
    -

    Editar Aulario:

    +

    Aulario:

    + +
    - - + +
    - - + +
    - -
    -

    Compartir Menú Comedor

    -

    Configura desde qué aulario compartir los datos del menú comedor. Si se selecciona un aulario origen, este aulario mostrará los menús del aulario seleccionado en lugar de los propios.

    -
    - - + + $adata): if ($aid === $aulario_id) continue; ?> +
    - -
    -

    Proyectos Enlazados

    -

    Selecciona proyectos raíz específicos de otros aularios para mostrarlos en este aulario. Puedes configurar el nivel de permisos: Solo lectura, Solicitar permiso para cambiar, o Cambiar sin solicitar.

    - -
    - "", "project_id" => "", "permission" => "read_only"]]; - } - foreach ($existing_links as $idx => $link): - $source_aul = $link['source_aulario'] ?? ''; - $proj_id = $link['project_id'] ?? ''; - $permission = $link['permission'] ?? 'read_only'; - ?> -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    - - - - - - -
    -
    - - - +
    + + + +
    -
    -

    Gestión de Aularios

    - - Desde esta sección puedes administrar los aularios asociados al centro que estás administrando. - - - - - - - - - - - '; - echo ''; - echo ''; - echo ''; - echo ''; - } - ?> - -
    IconoNombre - + Nuevo -
    Icono' . htmlspecialchars($aula_data['name'] ?? 'Sin Nombre') . '
    ' . $centro_id . '
    Gestionar
    +

    Gestión de Aularios

    + + +

    + + + + + + + + + $adata): ?> + + + + + + + +
    IconoNombre+ Nuevo
    Editar
    +
    - \ No newline at end of file diff --git a/public_html/sysadmin/centros.php b/public_html/sysadmin/centros.php deleted file mode 100644 index 5cb06dd..0000000 --- a/public_html/sysadmin/centros.php +++ /dev/null @@ -1,293 +0,0 @@ - -
    -
    -

    Gestión de la Actividad:

    - - Desde esta sección puedes administrar la actividad seleccionada del panel del centro . - -
    -
    - - -
    -
    -
    -
    - -
    -
    - -
    -
    -
    - -
    -
    -

    Nueva Actividad del Panel

    - - Aquí puedes crear una nueva actividad para el panel del centro . - -
    -
    - - -
    -
    - - -
    - -
    -
    -
    - -
    -
    -

    Nuevo Centro

    - - Aquí puedes crear un nuevo centro para el sistema. - -
    -
    - - -
    - -
    -
    -
    - - -
    -
    -

    Gestión del Centro:

    - - Desde esta sección puedes administrar el centro seleccionado. - -
    -
    -
    -
    -

    Aularios

    - - - - - - - - - - '; - echo ''; - echo ''; - echo ''; - echo ''; - } - ?> - -
    IconoNombre - + Nuevo -
    Icono' . htmlspecialchars($aula_data['name'] ?? 'Sin Nombre') . 'Gestionar
    -
    -
    -
    -
    -

    Actividades del panel

    - - - - - - - - - - '; - echo ''; - echo ''; - echo ''; - echo ''; - } - ?> - -
    FotoNombre - + Nuevo -
    Foto' . htmlspecialchars($activity_name) . 'Gestionar
    -
    -
    - - -
    -
    -

    Gestión de Centros

    - - Desde esta sección puedes administrar los centros asociados al sistema. - - - - - - - - - - '; - echo ''; - echo ''; - echo ''; - } - ?> - -
    Nombre - + Nuevo -
    ' . htmlspecialchars($centro_id) . 'Gestionar
    -
    -
    - \ No newline at end of file diff --git a/public_html/sysadmin/index.php b/public_html/sysadmin/index.php index 16a9260..ca1ae67 100644 --- a/public_html/sysadmin/index.php +++ b/public_html/sysadmin/index.php @@ -7,9 +7,9 @@ require_once "_incl/pre-body.php"; ?>
    diff --git a/public_html/sysadmin/invitations.php b/public_html/sysadmin/invitations.php index 2364f2e..50b1af6 100644 --- a/public_html/sysadmin/invitations.php +++ b/public_html/sysadmin/invitations.php @@ -1,100 +1,91 @@ true, - "single_use" => $single_use - ]; - file_put_contents("/DATA/Invitaciones_de_usuarios.json", json_encode($invitations, JSON_PRETTY_PRINT)); - header("Location: /sysadmin/invitations.php?_result=" . urlencode("Código $invitation_code creado correctamente.")); + db_upsert_invitation($code, true, $single_use); + header("Location: /sysadmin/invitations.php?_result=" . urlencode("Código $code creado correctamente.")); exit; break; case "delete": - // Handle deletion logic here - $invitations = json_decode(file_get_contents("/DATA/Invitaciones_de_usuarios.json"), true) ?? []; - $invitation_code = strtoupper($_POST['invitation_code'] ?? ''); - if (isset($invitations[$invitation_code])) { - unset($invitations[$invitation_code]); - file_put_contents("/DATA/Invitaciones_de_usuarios.json", json_encode($invitations, JSON_PRETTY_PRINT)); - } - header("Location: /sysadmin/invitations.php?_result=" . urlencode("Codigo $invitation_code borrado")); + $code = strtoupper(trim($_POST['invitation_code'] ?? '')); + db_delete_invitation($code); + header("Location: /sysadmin/invitations.php?_result=" . urlencode("Código $code borrado.")); exit; break; } require_once "_incl/pre-body.php"; -switch ($_GET['action']) { +switch ($_GET['action'] ?? 'index') { case "new": - ?> -
    -
    -

    Nueva invitación de usuario

    -
    -
    -
    -
    - - - Formato: 123456-ABCDEF -
    -
    - - -
    - -
    +?> +
    +
    +

    Nueva invitación de usuario

    + +
    +
    +
    + + + Formato: 123456-ABCDEF
    - +
    + + +
    + +
    -
    - +
    +
    + -
    -
    -

    Invitaciones de usuarios

    - Desde aquí puedes gestionar las invitaciones de usuarios. - - - - - - - $inv_data) { - echo ""; - echo ""; - echo ""; - echo ""; - } - ?> - -
    Codigo de invitación - + Nuevo -
    " . htmlspecialchars($inv_key) . ""; - echo '
    '; - echo ''; - echo ''; - echo '
    '; - echo "
    -
    -
    + $invitations = db_get_all_invitations(); +?> +
    +
    +

    Invitaciones de usuarios

    + + + + + + + + + + + + + + + + + +
    CódigoActivoUso único+ Nuevo
    +
    + + +
    +
    +
    +
    prepare("SELECT id FROM organizaciones WHERE org_id = ?"); + $existing->execute([$org_id]); + if ($existing->fetch()) { + die("La organización ya existe."); + } + // Create DB record + db()->prepare("INSERT INTO organizaciones (org_id, org_name) VALUES (?, ?)")->execute([$org_id, $org_name !== '' ? $org_name : $org_id]); + // Keep filesystem directory for activity photos (Panel/Actividades) + $org_path = aulatek_orgs_base_path() . "/$org_id"; + if (!is_dir($org_path) && !mkdir($org_path, 0755, true) && !is_dir($org_path)) { + error_log("orgs.php: failed to create directory $org_path"); + } + header("Location: ?action=index"); + exit(); + break; + case "edit": + $org_id = safe_path_segment(Sf($_GET['org'] ?? '')); + $org_name = Sf($_POST['org_name'] ?? ''); + if ($org_id === '' || $org_name === '') { + die("Datos inválidos para actualizar la organización."); + } + db()->prepare("UPDATE organizaciones SET org_name = ? WHERE org_id = ?")->execute([$org_name, $org_id]); + header("Location: ?action=edit&org=" . urlencode($org_id) . "&_result=" . urlencode("Cambios guardados.")); + exit(); + break; + case "create_activity": + ini_set('memory_limit', '512M'); + ini_set('upload_max_filesize', '256M'); + ini_set('post_max_size', '256M'); + $org_id = safe_path_segment(Sf($_GET['org'] ?? '')); + // Validate organization exists in DB + $stmt = db()->prepare("SELECT id FROM organizaciones WHERE org_id = ?"); + $stmt->execute([$org_id]); + if (!$stmt->fetch()) { + die("Organización no válida."); + } + $activity_name = safe_path_segment(Sf($_POST["name"] ?? '')); + if (empty($activity_name)) { + die("Nombre de la actividad no proporcionado."); + } + $activity_photo = $_FILES["photo"] ?? null; + if ($activity_photo === null || $activity_photo["error"] !== UPLOAD_ERR_OK) { + die("Error al subir la foto."); + } + $activity_path = aulatek_orgs_base_path() . "/$org_id/Panel/Actividades/$activity_name"; + if (is_dir($activity_path)) { + die("La actividad ya existe."); + } + mkdir($activity_path, 0755, true); + move_uploaded_file($activity_photo["tmp_name"], "$activity_path/photo.jpg"); + header("Location: ?action=edit&org=" . urlencode($org_id)); + exit(); + break; + case "edit_activity": + ini_set('memory_limit', '512M'); + ini_set('upload_max_filesize', '256M'); + ini_set('post_max_size', '256M'); + $org_id = safe_path_segment(Sf($_GET['org'] ?? '')); + $activity_name = safe_path_segment(Sf($_GET['activity'] ?? '')); + $activity_path = aulatek_orgs_base_path() . "/$org_id/Panel/Actividades/$activity_name"; + if (!is_dir($activity_path)) { + die("Actividad no válida."); + } + $activity_photo = $_FILES["file"] ?? null; + if ($activity_photo !== null && $activity_photo["error"] === UPLOAD_ERR_OK) { + move_uploaded_file($activity_photo["tmp_name"], "$activity_path/photo.jpg"); + } + $new_name = safe_path_segment(Sf($_POST['nombre'] ?? '')); + if ($new_name !== $activity_name && $new_name !== '') { + $new_path = aulatek_orgs_base_path() . "/$org_id/Panel/Actividades/$new_name"; + if (is_dir($new_path)) { + die("Ya existe una actividad con ese nombre."); + } + rename($activity_path, $new_path); + } + header("Location: ?action=edit&org=" . urlencode($org_id)); + exit(); + break; +} + +require_once "_incl/pre-body.php"; +$view_action = $_GET["action"] ?? "index"; +switch ($view_action) { + case "edit_activity": + $org_id = safe_path_segment(Sf($_GET['org'] ?? '')); + $activity_name = safe_path_segment(Sf($_GET['activity'] ?? '')); + $activity_path = aulatek_orgs_base_path() . "/$org_id/Panel/Actividades/$activity_name"; + if (!is_dir($activity_path)) { + die("Actividad no válida."); + } +?> +
    +
    +

    Gestión de la Actividad:

    +
    +
    + + +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +prepare("SELECT id FROM organizaciones WHERE org_id = ?"); + $stmt->execute([$org_id]); + if (!$stmt->fetch()) { + die("Organización no válida."); + } +?> +
    +
    +

    Nueva Actividad del Panel

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

    Nueva Organización

    +
    +
    + + +
    +
    + + +
    + +
    +
    +
    +prepare("SELECT org_name FROM organizaciones WHERE org_id = ?"); + $stmt->execute([$org_id]); + $org_row = $stmt->fetch(); + if (!$org_row) { + die("Organización no válida."); + } + $org_name = $org_row['org_name'] ?? $org_id; + $aularios = db_get_aularios($org_id); + $activities = glob(aulatek_orgs_base_path() . "/$org_id/Panel/Actividades/*", GLOB_ONLYDIR) ?: []; +?> +
    +
    +

    Gestión de la Organización:

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

    Aularios

    + + + + + + + + + $aula): ?> + + + + + + + +
    IconoNombre+ Nuevo
    IconoGestionar
    +
    +
    +
    +
    +

    Actividades del panel

    + + + + + + + + + + + + + + + + + +
    FotoNombre+ Nuevo
    Gestionar
    +
    +
    + +
    +
    +

    Gestión de Organizaciones

    + + + + + + + + + + + + + + + +
    Organización+ Nuevo

    Gestionar
    +
    +
    +prepare("UPDATE users SET password_hash = ?, updated_at = datetime('now') WHERE id = ?") + ->execute([password_hash($new_password, PASSWORD_DEFAULT), $row['id']]); - if (empty($new_password)) { - die("La contraseña no puede estar vacía."); - } - - if ($new_password !== $confirm_password) { - die("Las contraseñas no coinciden."); - } - - if (strlen($new_password) < 6) { - die("La contraseña debe tener al menos 6 caracteres."); - } - - $userfile = "/DATA/Usuarios/$username.json"; - if (!file_exists($userfile)) { - die("Usuario no encontrado."); - } - - $userdata = json_decode(file_get_contents($userfile), true); - $userdata['password_hash'] = password_hash($new_password, PASSWORD_DEFAULT); - - file_put_contents($userfile, json_encode($userdata, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); - - header("Location: users.php?action=edit&user=" . urlencode($username) . "&_result=" . urlencode("Contraseña restablecida correctamente a las " . date("H:i:s") . " (hora servidor).")); - exit; - break; + header("Location: users.php?action=edit&user=" . urlencode($username) . "&_result=" . urlencode("Contraseña restablecida correctamente a las " . date("H:i:s") . " (hora servidor).")); + exit; + break; } require_once "_incl/pre-body.php"; - $username = safe_username($_GET['user'] ?? ''); if (empty($username)) { - die("Usuario no especificado."); + die("Usuario no especificado."); } - -$userfile = "/DATA/Usuarios/$username.json"; -if (!file_exists($userfile)) { - die("Usuario no encontrado."); +$row = db_get_user($username); +if (!$row) { + die("Usuario no encontrado."); } - -$userdata = json_decode(file_get_contents($userfile), true); ?> -
    -

    Restablecer Contraseña:

    - +

    Restablecer Contraseña:

    Mínimo 6 caracteres
    -
    - - - + - Cancelar + Cancelar
    - diff --git a/public_html/sysadmin/users.php b/public_html/sysadmin/users.php index 5ab2696..ddb16b5 100644 --- a/public_html/sysadmin/users.php +++ b/public_html/sysadmin/users.php @@ -1,91 +1,108 @@ + + $_POST['display_name'] ?? '', - 'email' => $_POST['email'] ?? '', - 'permissions' => $permissions, - 'entreaulas' => [ - 'centro' => safe_centro_id($_POST['centro'] ?? ''), - 'role' => $_POST['role'] ?? '', - 'aulas' => $aulas - ] - ]; - // Merge old and new data to preserve any other fields, like password hashes or custom metadata. - $userdata = array_merge($userdata_old, $userdata_new); - $user_dir = rtrim(USERS_DIR, '/'); - $user_file = get_user_file_path($username); - if (!is_dir($user_dir) || !is_writable($user_dir)) { - die("No se puede guardar el usuario: directorio de datos no disponible."); - } - $json_data = json_encode($userdata, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); - if ($json_data === false) { - die("No se puede guardar el usuario: error al codificar los datos."); - } - $tmp_file = tempnam($user_dir, 'user_'); - if ($tmp_file === false) { - die("No se puede guardar el usuario: no se pudo crear un archivo temporal."); - } - $bytes_written = file_put_contents($tmp_file, $json_data, LOCK_EX); - if ($bytes_written === false) { - @unlink($tmp_file); - die("No se puede guardar el usuario: error al escribir en el disco."); - } - if (!rename($tmp_file, $user_file)) { - @unlink($tmp_file); - die("No se puede guardar el usuario: no se pudo finalizar la grabación del archivo."); - } - header("Location: ?action=edit&user=" . urlencode($username) . "&_result=" . urlencode("Cambios guardados correctamente a las ".date("H:i:s")." (hora servidor).")); - exit; - break; + $organization_input = $_POST['organization'] ?? []; + if (!is_array($organization_input)) { + $organization_input = [$organization_input]; + } + $organizations = array_values(array_unique(array_filter(array_map('safe_organization_id', $organization_input)))); + + db_upsert_user([ + 'username' => $username, + 'display_name' => $_POST['display_name'] ?? '', + 'email' => $_POST['email'] ?? '', + 'permissions' => $permissions, + 'orgs' => $organizations, + 'role' => $_POST['role'] ?? '', + 'aulas' => $aulas, + ]); + header("Location: ?action=edit&user=" . urlencode($username) . "&_result=" . urlencode("Cambios guardados correctamente a las " . date("H:i:s") . " (hora servidor).")); + exit; + break; } switch ($_GET['action'] ?? '') { - case 'add': - require_once "_incl/pre-body.php"; + case 'add': + require_once "_incl/pre-body.php"; + render_users_mobile_styles(); + $all_organizations = db_get_organizations(); ?> -
    +

    Agregar Nuevo Usuario

    @@ -104,70 +121,52 @@ switch ($_GET['action'] ?? '') { Permisos:
    -

    - +

    +

    -
    +
    - +
    -

    - +

    +

    -
    +
    - - + +
    - - + +
    - - + +
    -

    - +

    +

    -
    +
    - +
    - +
    @@ -178,22 +177,21 @@ switch ($_GET['action'] ?? '') {
    -

    EntreAulas: Configuración

    +

    AulaTek: Configuración

    - - + +
    + +
    + + +
    + +
    + Marca uno o varios tenants.
    - + +
    - + +
    +
    + +
    + +
    + > + +
    + +
    + Marca una o varias organizaciones.
    Permisos:
    -

    - +

    +

    -
    +
    - > - + > +
    -

    - +

    +

    -
    +
    - > - + > +
    - > - + > +
    - > - + > +
    -

    - +

    +

    -
    +
    - > - + > +
    - > - + > +
    - +
    -

    EntreAulas: Configuración

    +

    AulaTek: Configuración

    - - -
    -
    - +

    - '; - echo ''; - echo ''; - echo '
    '; - } - ?> +
    + + No hay organizaciones asociadas para mostrar aulas. + + $org_aularios): ?> +
    +
    + + Sin aulas en esta organización. + + $aula_data): ?> + +
    + > + +
    + + +
    + +

    Cambiar contraseña

    -

    Para cambiar la contraseña de este usuario, utiliza la herramienta de restablecimiento de contraseñas disponible en el siguiente enlace:

    - Restablecer Contraseña + Restablecer Contraseña
    - -
    -
    -

    Gestión de Usuarios

    -

    Desde esta sección puedes gestionar los usuarios del sistema. Puedes agregar, editar o eliminar usuarios según sea necesario.

    - - - - - - - - - - - "; - echo ""; - echo ""; - echo ""; - echo ""; - echo ""; - } - ?> - -
    UsuarioNombreCorreo - + Nuevo -
    " . htmlspecialchars($username) . "" . htmlspecialchars($userdata['display_name'] ?? 'N/A') . "" . htmlspecialchars($userdata['email'] ?? 'N/A') . ""; - echo 'Editar '; - echo 'Eliminar'; - echo "
    -
    -
    +
    +
    +
    +

    Gestión de Usuarios

    + + Nuevo +
    +

    Desde esta sección puedes gestionar los usuarios del sistema.

    +
    + + + + + + + + + + + + + + + + + + + +
    UsuarioNombreCorreo
    + Editar +
    +
    +
    + +
    +
    +
    +
    + Editar +
    + +
    +
    +
    + \ No newline at end of file