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/public_html/_incl/db.php b/public_html/_incl/db.php new file mode 100644 index 0000000..5980588 --- /dev/null +++ b/public_html/_incl/db.php @@ -0,0 +1,589 @@ +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.entreaulas.centro, .role, .aulas, .centros + */ +function db_build_auth_data(array $row): array +{ + $permissions = json_decode($row['permissions'] ?? '[]', true) ?: []; + $meta = json_decode($row['meta'] ?? '{}', true) ?: []; + + // Fetch all centro assignments for this user + $stmt = db()->prepare( + 'SELECT centro_id, role, aulas + FROM user_centros + WHERE user_id = ? + ORDER BY centro_id' + ); + $stmt->execute([$row['id']]); + $centro_rows = $stmt->fetchAll(); + + $ea = ['centro' => '', 'centros' => [], 'role' => '', 'aulas' => []]; + if (!empty($centro_rows)) { + $first = $centro_rows[0]; + $ea['centro'] = $first['centro_id']; // legacy compat + $ea['role'] = $first['role']; + $ea['aulas'] = json_decode($first['aulas'] ?? '[]', true) ?: []; + $ea['centros'] = array_column($centro_rows, 'centro_id'); + $ea['centros_data'] = $centro_rows; + } + + return array_merge($meta, [ + 'display_name' => $row['display_name'], + 'email' => $row['email'], + 'password_hash' => $row['password_hash'], + 'permissions' => $permissions, + 'entreaulas' => $ea, + 'google_auth' => (bool) $row['google_auth'], + ]); +} + +/** + * Create or update a user. + * $data keys: username, display_name, email, password_hash, permissions[], + * google_auth, entreaulas{centro,centros[],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']; + $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 centro assignments when entreaulas data is provided + if (array_key_exists('entreaulas', $data)) { + $ea = $data['entreaulas'] ?? []; + $pdo->prepare('DELETE FROM user_centros WHERE user_id = ?')->execute([$user_id]); + + // Support both legacy single centro and new multi-centro + $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'] ?? []); + + $ins_centro = $pdo->prepare('INSERT OR IGNORE INTO centros (centro_id) VALUES (?)'); + $ins_uc = $pdo->prepare( + 'INSERT OR REPLACE INTO user_centros (user_id, centro_id, role, aulas) VALUES (?, ?, ?, ?)' + ); + foreach ($centros as $cid) { + if ($cid === '') { + continue; + } + $ins_centro->execute([$cid]); + $ins_uc->execute([$user_id, $cid, $role, $aulas]); + } + } + + return $user_id; +} + +/** Delete a user and their centro assignments. */ +function db_delete_user(string $username): void +{ + db()->prepare('DELETE FROM users WHERE username = ?')->execute([strtolower($username)]); +} + +// ── Centro helpers ──────────────────────────────────────────────────────────── + +function db_get_centros(): array +{ + return db()->query('SELECT centro_id, name FROM centros ORDER BY centro_id')->fetchAll(); +} + +function db_get_centro_ids(): array +{ + return db()->query('SELECT centro_id FROM centros ORDER BY centro_id')->fetchAll(PDO::FETCH_COLUMN); +} + +// ── 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 centro_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 centro_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 centro_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 (centro_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 centro_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 centro_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 (centro_id, order_ref, fecha, persona, comanda, notas, estado) + VALUES (?, ?, ?, ?, ?, ?, ?) + ON CONFLICT(centro_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 centro_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 centro_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 centro_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 (centro_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 centro_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 (centro_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 centro_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 (centro_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 centro_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 (centro_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 centro IDs the authenticated user belongs to. */ +function get_user_centros(?array $auth_data = null): array +{ + $data = $auth_data ?? $_SESSION['auth_data'] ?? []; + $ea = $data['entreaulas'] ?? []; + + if (!empty($ea['centros']) && is_array($ea['centros'])) { + return array_values($ea['centros']); + } + if (!empty($ea['centro'])) { + return [$ea['centro']]; + } + return []; +} + +/** Ensure $_SESSION['active_centro'] is set to a valid centro. */ +function init_active_centro(?array $auth_data = null): void +{ + $centros = get_user_centros($auth_data); + if (empty($centros)) { + $_SESSION['active_centro'] = null; + return; + } + if (!empty($_SESSION['active_centro']) && in_array($_SESSION['active_centro'], $centros, true)) { + return; + } + $_SESSION['active_centro'] = $centros[0]; +} 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..cbc5e9c --- /dev/null +++ b/public_html/_incl/migrations/002_import_json.php @@ -0,0 +1,255 @@ +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 + $row = $db->prepare("SELECT id FROM users WHERE username = ?")->execute([$username]); + $user_id = (int) $db->query("SELECT id FROM users WHERE username = " . $db->quote($username))->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/pre-body.php b/public_html/_incl/pre-body.php index 9a39d47..0c4cc49 100755 --- a/public_html/_incl/pre-body.php +++ b/public_html/_incl/pre-body.php @@ -27,7 +27,11 @@ if (!empty($displayName)) { $initials = mb_strtoupper($first . $last); } -?> +// Tenant (centro) management +$userCentros = get_user_centros($_SESSION["auth_data"] ?? []); +$activeCentro = $_SESSION['active_centro'] ?? ($_SESSION["auth_data"]["entreaulas"]["centro"] ?? ''); + + @@ -526,6 +530,28 @@ if (!empty($displayName)) {
+ +
+
+ Organización activa +
+
+ +
+ 1): ?> +
Cambiar organización:
+ +
+ + +
+ + +
+
Gestionar cuenta diff --git a/public_html/_incl/switch_tenant.php b/public_html/_incl/switch_tenant.php new file mode 100644 index 0000000..65e6891 --- /dev/null +++ b/public_html/_incl/switch_tenant.php @@ -0,0 +1,28 @@ + 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_centro($_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) -{ - return in_array($perm, $_SESSION["auth_data"]["permissions"] ?? []); -} -/** - * Returns all centro/tenant IDs the authenticated user belongs to. - * Supports both the legacy single-centro format (entreaulas.centro = "string") - * and the new multi-tenant format (entreaulas.centros = ["a", "b"]). - */ -function get_user_centros($auth_data = null) +function user_has_permission(string $perm): bool { - $data = $auth_data ?? $_SESSION["auth_data"] ?? []; - $ea = $data["entreaulas"] ?? []; - - if (!empty($ea["centros"]) && is_array($ea["centros"])) { - return array_values($ea["centros"]); - } - if (!empty($ea["centro"])) { - return [$ea["centro"]]; - } - return []; -} - -/** - * Ensures $_SESSION['active_centro'] is set to a valid centro for the user. - * Call after user data is loaded/reloaded. - */ -function init_active_centro($auth_data = null) -{ - $centros = get_user_centros($auth_data); - if (empty($centros)) { - $_SESSION['active_centro'] = null; - return; - } - // Keep existing selection only if it is still valid - if (!empty($_SESSION['active_centro']) && in_array($_SESSION['active_centro'], $centros, true)) { - return; - } - $_SESSION['active_centro'] = $centros[0]; + return in_array($perm, $_SESSION["auth_data"]["permissions"] ?? [], true); } diff --git a/public_html/_install.php b/public_html/_install.php index 764e6ca..c9eb756 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', 'entreaulas: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..98d36e3 100755 --- a/public_html/_login.php +++ b/public_html/_login.php @@ -1,8 +1,9 @@ $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_centro($_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"] ?? "/"); @@ -161,20 +164,19 @@ if ($_GET["clear_session"] == "1") { 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_centro($_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 +184,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..aa9838b 100644 --- a/public_html/account/index.php +++ b/public_html/account/index.php @@ -1,44 +1,131 @@ -
-
-

¡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/api/comedor.php b/public_html/aulatek/api/comedor.php index 320ca98..36aa8d9 100644 --- a/public_html/aulatek/api/comedor.php +++ b/public_html/aulatek/api/comedor.php @@ -2,24 +2,7 @@ header("Content-Type: application/json; charset=utf-8"); require_once "_incl/auth_redir.php"; require_once "../_incl/tools.security.php"; - -function menu_types_path($centro_id, $aulario_id) { - $centro = safe_centro_id($centro_id); - $aulario = safe_id_segment($aulario_id); - if ($centro === '' || $aulario === '') { - return null; - } - return "/DATA/entreaulas/Centros/$centro/Aularios/$aulario/Comedor-MenuTypes.json"; -} - -function comedor_day_base_dir($centro_id, $aulario_id, $ym, $day) { - $centro = safe_centro_id($centro_id); - $aulario = safe_id_segment($aulario_id); - if ($centro === '' || $aulario === '') { - return null; - } - return "/DATA/entreaulas/Centros/$centro/Aularios/$aulario/Comedor/$ym/$day"; -} +require_once "../../_incl/db.php"; // Check permissions if (!in_array("entreaulas:docente", $_SESSION["auth_data"]["permissions"] ?? [])) { @@ -33,64 +16,48 @@ if ($centro_id === "") { die(json_encode(["error" => "Centro 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', $_SESSION["auth_data"]["entreaulas"]["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 +65,7 @@ function blank_menu() { "plates" => [ "primero" => ["name" => "", "pictogram" => ""], "segundo" => ["name" => "", "pictogram" => ""], - "postre" => ["name" => "", "pictogram" => ""], + "postre" => ["name" => "", "pictogram" => ""], ] ]; } @@ -113,43 +80,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 +106,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..9259f3f 100644 --- a/public_html/aulatek/aulario.php +++ b/public_html/aulatek/aulario.php @@ -2,11 +2,11 @@ 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; +$centro_id = safe_centro_id($_SESSION["auth_data"]["entreaulas"]["centro"] ?? ""); +$aulario = db_get_aulario($centro_id, $aulario_id); if (!$aulario || !is_array($aulario)) { ?> diff --git a/public_html/aulatek/comedor.php b/public_html/aulatek/comedor.php index 3b47bdd..32e7d0d 100644 --- a/public_html/aulatek/comedor.php +++ b/public_html/aulatek/comedor.php @@ -1,6 +1,7 @@ "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 +65,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 +84,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,14 +242,11 @@ 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)); + db_set_comedor_menu_types($centro_id, $source_aulario_id, $menuTypes); header("Location: /entreaulas/comedor.php?aulario=" . urlencode($aulario_id) . "&date=" . urlencode($date) . "&menu=" . urlencode($newId)); exit; } @@ -268,19 +256,10 @@ 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)); exit; @@ -296,15 +275,12 @@ 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)); + db_set_comedor_menu_types($centro_id, $source_aulario_id, $menuTypes); header("Location: /entreaulas/comedor.php?aulario=" . urlencode($aulario_id) . "&date=" . urlencode($date) . "&menu=" . urlencode($renameId)); exit; } @@ -316,23 +292,19 @@ if ($_SERVER["REQUEST_METHOD"] === "POST" && $canEdit) { $menuData["menus"][$menuTypeId] = blank_menu(); } + // Pictogram images still stored on filesystem in Comedor dir + $baseDir = "/DATA/entreaulas/Centros/$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."; } } @@ -359,11 +331,10 @@ foreach ($userAulas as $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"; diff --git a/public_html/aulatek/index.php b/public_html/aulatek/index.php old mode 100755 new mode 100644 index ce0271a..09e12d7 --- a/public_html/aulatek/index.php +++ b/public_html/aulatek/index.php @@ -2,30 +2,29 @@ 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.
- - - diff --git a/public_html/aulatek/paneldiario.php b/public_html/aulatek/paneldiario.php index e11a5f3..056229a 100755 --- a/public_html/aulatek/paneldiario.php +++ b/public_html/aulatek/paneldiario.php @@ -1,6 +1,7 @@ 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 +820,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); } } diff --git a/public_html/aulatek/proyectos.php b/public_html/aulatek/proyectos.php index 7ff347c..d68606b 100644 --- a/public_html/aulatek/proyectos.php +++ b/public_html/aulatek/proyectos.php @@ -1,6 +1,7 @@ $aulario_id, - "project_id" => $project_id, - "permission" => "request_edit" + "project_id" => $project_id, + "permission" => "request_edit", ]; $message = "Proyecto compartido correctamente."; } - file_put_contents($target_config_path, json_encode($target_config, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); + // Save back: build extra JSON excluding standard fields + $extra_skip = ['name', 'icon']; + $extra = []; + foreach ($target_config as $k => $v) { + if (!in_array($k, $extra_skip, true)) { + $extra[$k] = $v; + } + } + db()->prepare( + "UPDATE aularios SET extra = ? WHERE centro_id = ? AND aulario_id = ?" + )->execute([json_encode($extra, JSON_UNESCAPED_UNICODE), $centro_id, $target_aulario]); } } } @@ -1570,10 +1565,9 @@ $view = $current_project ? "project" : "list"; @@ -1636,27 +1630,14 @@ $view = $current_project ? "project" : "list"; $entry, - "name" => $config["name"] ?? "Aulario Desconocido", - "linked_projects" => $config["linked_projects"] ?? [] - ]; - } - } - } + foreach ($aularios_db as $aid => $adata) { + $aularios[] = [ + "id" => $aid, + "name" => $adata["name"] ?? "Aulario Desconocido", + "linked_projects" => $adata["linked_projects"] ?? [], + ]; } return $aularios; } @@ -1918,10 +1899,8 @@ $view = $current_project ? "project" : "list";
diff --git a/public_html/aulatek/supercafe.php b/public_html/aulatek/supercafe.php index e1d0355..1b53a4a 100644 --- a/public_html/aulatek/supercafe.php +++ b/public_html/aulatek/supercafe.php @@ -1,6 +1,7 @@ $aulario_data) { $aulario_name = $aulario_data['name'] ?? $aulario_id; - $alumnos_path = "$aularios_path/$aulario_id/Alumnos"; + $alumnos_path = "$aularios_dir/$aulario_id/Alumnos"; if (!is_dir($alumnos_path)) { continue; } foreach (glob("$alumnos_path/*/", GLOB_ONLYDIR) ?: [] as $alumno_dir) { $alumno_name = basename($alumno_dir); - $key = $aulario_id . ':' . $alumno_name; + $key = $aulario_id . ':' . $alumno_name; $personas[$key] = [ 'Nombre' => $alumno_name, 'Region' => $aulario_name, @@ -62,7 +58,6 @@ if ($centro_id === '') { exit; } -define('SC_DATA_DIR', "/DATA/entreaulas/Centros/$centro_id/SuperCafe/Comandas"); define('SC_MAX_DEBTS', 3); $estados_colores = [ @@ -81,20 +76,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && $can_edit) { $action = $_POST['action'] ?? ''; if ($action === 'change_status') { - $order_id = safe_id($_POST['order_id'] ?? ''); + $order_id = safe_id($_POST['order_id'] ?? ''); $new_status = $_POST['status'] ?? ''; if ($order_id !== '' && array_key_exists($new_status, $estados_colores)) { - $order_file = SC_DATA_DIR . '/' . $order_id . '.json'; - if (is_readable($order_file)) { - $data = json_decode(file_get_contents($order_file), true); - if (is_array($data)) { - $data['Estado'] = $new_status; - file_put_contents( - $order_file, - json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE), - LOCK_EX - ); - } + $row = db_get_supercafe_order($centro_id, $order_id); + if ($row) { + db_upsert_supercafe_order( + $centro_id, $order_id, + $row['fecha'], $row['persona'], $row['comanda'], $row['notas'], $new_status + ); } } header('Location: /entreaulas/supercafe.php'); @@ -104,34 +94,30 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && $can_edit) { if ($action === 'delete') { $order_id = safe_id($_POST['order_id'] ?? ''); if ($order_id !== '') { - $order_file = SC_DATA_DIR . '/' . $order_id . '.json'; - if (is_file($order_file)) { - unlink($order_file); - } + db()->prepare('DELETE FROM supercafe_orders WHERE centro_id = ? AND order_ref = ?') + ->execute([$centro_id, $order_id]); } header('Location: /entreaulas/supercafe.php'); exit; } } -// Load all orders +// Load all orders from DB +$db_orders = db_get_supercafe_orders($centro_id); $orders = []; -if (is_dir(SC_DATA_DIR)) { - $files = glob(SC_DATA_DIR . '/*.json') ?: []; - foreach ($files as $file) { - $data = json_decode(file_get_contents($file), true); - if (!is_array($data)) { - continue; - } - $data['_id'] = basename($file, '.json'); - $orders[] = $data; - } +foreach ($db_orders as $row) { + $orders[] = [ + '_id' => $row['order_ref'], + 'Fecha' => $row['fecha'], + 'Persona'=> $row['persona'], + 'Comanda'=> $row['comanda'], + 'Notas' => $row['notas'], + 'Estado' => $row['estado'], + ]; } // Sort newest first (by Fecha desc) -usort($orders, function ($a, $b) { - return strcmp($b['Fecha'] ?? '', $a['Fecha'] ?? ''); -}); +usort($orders, fn($a, $b) => strcmp($b['Fecha'] ?? '', $a['Fecha'] ?? '')); $orders_active = array_filter($orders, fn($o) => ($o['Estado'] ?? '') !== 'Deuda'); $orders_deuda = array_filter($orders, fn($o) => ($o['Estado'] ?? '') === 'Deuda'); @@ -139,6 +125,7 @@ $orders_deuda = array_filter($orders, fn($o) => ($o['Estado'] ?? '') === 'Deuda require_once "_incl/pre-body.php"; ?> +

SuperCafe – Cafetería

diff --git a/public_html/aulatek/supercafe_edit.php b/public_html/aulatek/supercafe_edit.php index ded87ea..2e187b7 100644 --- a/public_html/aulatek/supercafe_edit.php +++ b/public_html/aulatek/supercafe_edit.php @@ -1,6 +1,7 @@ $aulario_data) { $aulario_name = $aulario_data['name'] ?? $aulario_id; - $alumnos_path = "$aularios_path/$aulario_id/Alumnos"; + $alumnos_path = "$aularios_dir/$aulario_id/Alumnos"; if (!is_dir($alumnos_path)) { continue; } $alumno_dirs = glob("$alumnos_path/*/", GLOB_ONLYDIR) ?: []; - usort($alumno_dirs, function ($a, $b) { - return strcasecmp(basename($a), basename($b)); - }); + usort($alumno_dirs, fn($a, $b) => strcasecmp(basename($a), basename($b))); foreach ($alumno_dirs as $alumno_dir) { $alumno_name = basename($alumno_dir); - // Key uses ':' as separator; safe_id_segment chars [A-Za-z0-9_-] exclude ':' - $key = $aulario_id . ':' . $alumno_name; + $key = $aulario_id . ':' . $alumno_name; $personas[$key] = [ 'Nombre' => $alumno_name, 'Region' => $aulario_name, @@ -62,43 +51,14 @@ function sc_load_personas_from_alumnos($centro_id) return $personas; } -function sc_load_menu($sc_base) -{ - $path = "$sc_base/Menu.json"; - if (!file_exists($path)) { - return []; - } - $data = json_decode(file_get_contents($path), true); - return is_array($data) ? $data : []; -} - -function sc_count_debts($persona_key) -{ - if (!is_dir(SC_DATA_DIR)) { - return 0; - } - $count = 0; - foreach (glob(SC_DATA_DIR . '/*.json') ?: [] as $file) { - $data = json_decode(file_get_contents($file), true); - if (is_array($data) - && ($data['Persona'] ?? '') === $persona_key - && ($data['Estado'] ?? '') === 'Deuda') { - $count++; - } - } - return $count; -} - // Determine if creating or editing $order_id = safe_id($_GET['id'] ?? ''); $is_new = $order_id === ''; if ($is_new) { - $raw_id = uniqid('sc', true); - $order_id = preg_replace('/[^a-zA-Z0-9_-]/', '', $raw_id); + $order_id = db_next_supercafe_ref($centro_id); } -$order_file = SC_DATA_DIR . '/' . $order_id . '.json'; - +// Load existing order from DB (or defaults) $order_data = [ 'Fecha' => date('Y-m-d'), 'Persona' => '', @@ -106,15 +66,21 @@ $order_data = [ 'Notas' => '', 'Estado' => 'Pedido', ]; -if (!$is_new && is_readable($order_file)) { - $existing = json_decode(file_get_contents($order_file), true); - if (is_array($existing)) { - $order_data = array_merge($order_data, $existing); +if (!$is_new) { + $existing = db_get_supercafe_order($centro_id, $order_id); + if ($existing) { + $order_data = [ + 'Fecha' => $existing['fecha'], + 'Persona' => $existing['persona'], + 'Comanda' => $existing['comanda'], + 'Notas' => $existing['notas'], + 'Estado' => $existing['estado'], + ]; } } $personas = sc_load_personas_from_alumnos($centro_id); -$menu = sc_load_menu($sc_base); +$menu = db_get_supercafe_menu($centro_id); // Group personas by aulario for the optgroup picker $personas_by_aulario = []; @@ -133,11 +99,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $estado = 'Pedido'; } - // Validar persona if ($persona_key === '' || (!empty($personas) && !array_key_exists($persona_key, $personas))) { $error = '¡Hay que elegir una persona válida!'; } else { - // Construir comanda desde los campos de categoría visual $comanda_parts = []; if (!empty($menu)) { foreach ($menu as $category => $items) { @@ -152,48 +116,28 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $comanda_parts[] = $manual; } } - $comanda_str = implode(', ', $comanda_parts); - - // Comprobar deudas - $prev_persona = $order_data['Persona'] ?? ''; + $comanda_str = implode(', ', $comanda_parts); + $prev_persona = $order_data['Persona']; if ($is_new || $prev_persona !== $persona_key) { - $debt_count = sc_count_debts($persona_key); + $debt_count = db_supercafe_count_debts($centro_id, $persona_key); if ($debt_count >= SC_MAX_DEBTS) { $error = 'Esta persona tiene ' . $debt_count . ' comandas en deuda. No se puede realizar el pedido.'; } } - if ($error === '') { - $new_data = [ - 'Fecha' => date('Y-m-d'), - 'Persona' => $persona_key, - 'Comanda' => $comanda_str, - 'Notas' => $notas, - 'Estado' => $is_new ? 'Pedido' : $estado, - ]; - - if (!is_dir(SC_DATA_DIR)) { - mkdir(SC_DATA_DIR, 0755, true); - } - - $tmp = SC_DATA_DIR . '/.' . $order_id . '.tmp'; - $bytes = file_put_contents( - $tmp, - json_encode($new_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE), - LOCK_EX + db_upsert_supercafe_order( + $centro_id, $order_id, + date('Y-m-d'), $persona_key, $comanda_str, $notas, + $is_new ? 'Pedido' : $estado ); - if ($bytes === false || !rename($tmp, $order_file)) { - @unlink($tmp); - $error = 'Error al guardar la comanda.'; - } else { - header('Location: /entreaulas/supercafe.php'); - exit; - } + header('Location: /entreaulas/supercafe.php'); + exit; } } } require_once "_incl/pre-body.php"; + ?>

Comanda

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 @@ prepare("DELETE FROM aularios WHERE centro_id = ? AND aulario_id = ?") + ->execute([$centro_id, $aulario_id]); + // Remove comedor, diario, panel data + db()->prepare("DELETE FROM comedor_menu_types WHERE centro_id = ? AND aulario_id = ?") + ->execute([$centro_id, $aulario_id]); + db()->prepare("DELETE FROM comedor_entries WHERE centro_id = ? AND aulario_id = ?") + ->execute([$centro_id, $aulario_id]); + db()->prepare("DELETE FROM diario_entries WHERE centro_id = ? AND aulario_id = ?") + ->execute([$centro_id, $aulario_id]); + db()->prepare("DELETE FROM panel_alumno WHERE centro_id = ? AND aulario_id = ?") + ->execute([$centro_id, $aulario_id]); + // Remove filesystem directory with student photos $aulario_dir = "/DATA/entreaulas/Centros/$centro_id/Aularios/$aulario_id"; - function rrmdir($dir) { + 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"] ?? "")); + $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 centros WHERE centro_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 (centro_id, aulario_id, name, icon) VALUES (?, ?, ?, ?)" + )->execute([ + $centro_id, $aulario_id, + Sf($_POST["name"] ?? ""), + Sf($_POST["icon"] ?? "/static/logo-entreaulas.png"), + ]); + // Create Alumnos directory for photo-based features + @mkdir("/DATA/entreaulas/Centros/$centro_id/Aularios/$aulario_id/Proyectos/", 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 centro_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"] ?? "")); + $all_centros = db_get_centro_ids(); +?>
    -

    Nuevo Aulario

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

    Nuevo Aulario

    - - - ' . htmlspecialchars($centro_id) . ''; - } - ?> + + +
    - - + +
    - - + +
    - $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 index 5cb06dd..2aa6be5 100644 --- a/public_html/sysadmin/centros.php +++ b/public_html/sysadmin/centros.php @@ -1,10 +1,11 @@ prepare("SELECT id FROM centros WHERE centro_id = ?"); + $existing->execute([$centro_id]); + if ($existing->fetch()) { die("El centro ya existe."); } - mkdir($centro_path, 0777, true); + // Create DB record + db()->prepare("INSERT INTO centros (centro_id) VALUES (?)")->execute([$centro_id]); + // Keep filesystem directory for activity photos (Panel/Actividades) + $centro_path = "/DATA/entreaulas/Centros/$centro_id"; + if (!is_dir($centro_path)) { + mkdir($centro_path, 0755, true); + } header("Location: ?action=index"); exit(); break; case "create_activity": ini_set('memory_limit', '512M'); - ini_set("display_errors", 1); ini_set('upload_max_filesize', '256M'); ini_set('post_max_size', '256M'); $centro_id = safe_path_segment(Sf($_GET['centro'] ?? '')); - $centro_path = "/DATA/entreaulas/Centros/$centro_id"; - if (!is_dir($centro_path)) { + // Validate centro exists in DB + $stmt = db()->prepare("SELECT id FROM centros WHERE centro_id = ?"); + $stmt->execute([$centro_id]); + if (!$stmt->fetch()) { die("Centro no válido."); } - $activity_name = safe_path_segment(Sf($_POST["name"] ?? '')); + $activity_name = safe_path_segment(Sf($_POST["name"] ?? '')); if (empty($activity_name)) { die("Nombre de la actividad no proporcionado."); } @@ -47,22 +57,20 @@ switch ($form_action) { if ($activity_photo === null || $activity_photo["error"] !== UPLOAD_ERR_OK) { die("Error al subir la foto."); } - $activity_path = "$centro_path/Panel/Actividades/$activity_name"; + $activity_path = "/DATA/entreaulas/Centros/$centro_id/Panel/Actividades/$activity_name"; if (is_dir($activity_path)) { die("La actividad ya existe."); } - mkdir($activity_path, 0777, true); - $photo_path = "$activity_path/photo.jpg"; - move_uploaded_file($activity_photo["tmp_name"], $photo_path); + mkdir($activity_path, 0755, true); + move_uploaded_file($activity_photo["tmp_name"], "$activity_path/photo.jpg"); header("Location: ?action=edit¢ro=" . urlencode($centro_id)); exit(); break; case "edit_activity": ini_set('memory_limit', '512M'); - ini_set("display_errors", 1); ini_set('upload_max_filesize', '256M'); ini_set('post_max_size', '256M'); - $centro_id = safe_path_segment(Sf($_GET['centro'] ?? '')); + $centro_id = safe_path_segment(Sf($_GET['centro'] ?? '')); $activity_name = safe_path_segment(Sf($_GET['activity'] ?? '')); $activity_path = "/DATA/entreaulas/Centros/$centro_id/Panel/Actividades/$activity_name"; if (!is_dir($activity_path)) { @@ -70,27 +78,26 @@ switch ($form_action) { } $activity_photo = $_FILES["file"] ?? null; if ($activity_photo !== null && $activity_photo["error"] === UPLOAD_ERR_OK) { - $photo_path = "$activity_path/photo.jpg"; - move_uploaded_file($activity_photo["tmp_name"], $photo_path); + move_uploaded_file($activity_photo["tmp_name"], "$activity_path/photo.jpg"); } - if (safe_path_segment(Sf($_POST['nombre'] ?? '')) != $activity_name) { - $new_activity_name = safe_path_segment(Sf($_POST['nombre'] ?? '')); - $new_activity_path = "/DATA/entreaulas/Centros/$centro_id/Panel/Actividades/$new_activity_name"; - if (is_dir($new_activity_path)) { + $new_name = safe_path_segment(Sf($_POST['nombre'] ?? '')); + if ($new_name !== $activity_name && $new_name !== '') { + $new_path = "/DATA/entreaulas/Centros/$centro_id/Panel/Actividades/$new_name"; + if (is_dir($new_path)) { die("Ya existe una actividad con ese nombre."); } - rename($activity_path, $new_activity_path); + rename($activity_path, $new_path); } - header("Location: ?action=edit¢ro=" . urlencode($centro_id));; + header("Location: ?action=edit¢ro=" . urlencode($centro_id)); exit(); break; } -require_once "_incl/pre-body.php"; +require_once "_incl/pre-body.php"; $view_action = $_GET["action"] ?? "index"; switch ($view_action) { case "edit_activity": - $centro_id = safe_path_segment(Sf($_GET['centro'] ?? '')); + $centro_id = safe_path_segment(Sf($_GET['centro'] ?? '')); $activity_name = safe_path_segment(Sf($_GET['activity'] ?? '')); $activity_path = "/DATA/entreaulas/Centros/$centro_id/Panel/Actividades/$activity_name"; if (!is_dir($activity_path)) { @@ -99,22 +106,20 @@ switch ($view_action) { ?>
    -

    Gestión de la Actividad:

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

    Gestión de la Actividad:

    +
    - +

    @@ -128,18 +133,16 @@ switch ($view_action) { break; case "new_activity": $centro_id = safe_path_segment(Sf($_GET['centro'] ?? '')); - $centro_path = "/DATA/entreaulas/Centros/$centro_id"; - if (!is_dir($centro_path)) { + $stmt = db()->prepare("SELECT id FROM centros WHERE centro_id = ?"); + $stmt->execute([$centro_id]); + if (!$stmt->fetch()) { die("Centro no válido."); } ?>

    Nueva Actividad del Panel

    - - Aquí puedes crear una nueva actividad para el panel del centro . - - +
    @@ -155,13 +158,10 @@ switch ($view_action) { +?>

    Nuevo Centro

    - - Aquí puedes crear un nuevo centro para el sistema. -
    @@ -171,22 +171,21 @@ switch ($view_action) {
    - prepare("SELECT id FROM centros WHERE centro_id = ?"); + $stmt->execute([$centro_id]); + if (!$stmt->fetch()) { die("Centro no válido."); } + $aularios = db_get_aularios($centro_id); + $activities = glob("/DATA/entreaulas/Centros/$centro_id/Panel/Actividades/*", GLOB_ONLYDIR) ?: []; ?>
    -

    Gestión del Centro:

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

    Gestión del Centro:

    @@ -195,25 +194,18 @@ switch ($view_action) { - - - + + - '; - echo ''; - echo ''; - echo ''; - echo ''; - } - ?> + $aula): ?> + + + + + +
    IconoNombre - + Nuevo - IconoNombre+ Nuevo
    Icono' . htmlspecialchars($aula_data['name'] ?? 'Sin Nombre') . 'Gestionar
    IconoGestionar
    @@ -224,70 +216,52 @@ switch ($view_action) { - - - + + - '; - echo ''; - echo ''; - echo ''; - echo ''; - } - ?> + + + + + + + +
    FotoNombre - + Nuevo - FotoNombre+ Nuevo
    Foto' . htmlspecialchars($activity_name) . 'Gestionar
    Gestionar
    - -

    Gestión de Centros

    - - Desde esta sección puedes administrar los centros asociados al sistema. - - - + + - '; - echo ''; - echo ''; - echo ''; - } - ?> + + + + + +
    Nombre - + Nuevo - Centro+ Nuevo
    ' . htmlspecialchars($centro_id) . 'Gestionar
    Gestionar
    - \ No newline at end of file +require_once "_incl/post-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("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..3aa49e5 100644 --- a/public_html/sysadmin/users.php +++ b/public_html/sysadmin/users.php @@ -1,89 +1,54 @@ $_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; + db_upsert_user([ + 'username' => $username, + 'display_name' => $_POST['display_name'] ?? '', + 'email' => $_POST['email'] ?? '', + 'permissions' => $permissions, + 'entreaulas' => [ + 'centro' => safe_centro_id($_POST['centro'] ?? ''), + '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"; + $all_centros = db_get_centro_ids(); ?>
    @@ -104,70 +69,52 @@ switch ($_GET['action'] ?? '') { Permisos:
    -

    - +

    +

    -
    +
    - +
    -

    - +

    +

    -
    +
    - +
    - +
    - +
    -

    - +

    +

    -
    +
    - +
    - +
    @@ -183,13 +130,9 @@ switch ($_GET['action'] ?? '') {
    @@ -205,112 +148,91 @@ switch ($_GET['action'] ?? '') {
    -

    Editar Usuario:

    +

    Editar Usuario:

    - +
    - +
    Permisos:
    -

    - +

    +

    -
    +
    - > - + > +
    -

    - +

    +

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

    - +

    +

    -
    +
    - > - + > +
    - > - + > +
    - +
    @@ -320,104 +242,81 @@ switch ($_GET['action'] ?? '') {

    - '; - echo ''; - echo ''; - echo '
    '; - } - ?> + $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

    +

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

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