Compare commits
29 Commits
copilot/mi
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d82c100e19 | ||
|
|
704acf4cc3 | ||
|
|
7e7818f002 | ||
|
|
a0a304e8ec | ||
|
|
556ec6b838 | ||
|
|
e22a96ed8b | ||
|
|
3398bee812 | ||
|
|
36bacb1368 | ||
|
|
41db3bf422 | ||
|
|
ce120cbd4f | ||
|
|
39eda13d94 | ||
|
|
bef9babd0b | ||
|
|
6f0ada0713 | ||
|
|
868b8477e0 | ||
|
|
c21dfad437 | ||
|
|
b3a2795d66 | ||
|
|
7b226b4bb2 | ||
|
|
378515d28a | ||
|
|
f1ac55f359 | ||
|
|
51437cca18 | ||
|
|
b2c1314c69 | ||
|
|
9fed6f9af9 | ||
|
|
f7d60a3c2a | ||
|
|
a8936e55a2 | ||
|
|
6aaee59b3d | ||
|
|
0c362fd40b | ||
|
|
937a0f4083 | ||
|
|
c0a93ce109 | ||
|
|
7e85c2a1f2 |
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Axia4
|
||||
|
||||
Axia4 is a unified platform for EuskadiTech and Sketaria, providing various services including EntreAulas (connected classroom management system).
|
||||
Axia4 is a unified platform for EuskadiTech and Sketaria, providing various services including AulaTek (connected classroom management system).
|
||||
|
||||
## Quick Start with Docker
|
||||
|
||||
@@ -13,7 +13,7 @@ cd Axia4
|
||||
|
||||
# 2. Create the data directory structure
|
||||
mkdir -p DATA/entreaulas/Usuarios
|
||||
mkdir -p DATA/entreaulas/Centros
|
||||
mkdir -p DATA/entreaulas/Organizaciones
|
||||
|
||||
# 3. Start the application
|
||||
docker compose up -d
|
||||
@@ -29,7 +29,7 @@ docker compose up -d
|
||||
|
||||
## Features
|
||||
|
||||
- **EntreAulas**: Management system for connected classrooms
|
||||
- **AulaTek**: Management system for connected classrooms
|
||||
- **Aularios**: Centralized access to classroom resources
|
||||
- Integration with multiple external services
|
||||
|
||||
|
||||
883
public_html/_incl/db.php
Normal file
883
public_html/_incl/db.php
Normal file
@@ -0,0 +1,883 @@
|
||||
<?php
|
||||
/**
|
||||
* Axia4 Database Layer
|
||||
*
|
||||
* Provides a PDO SQLite connection and a lightweight migration runner.
|
||||
* All application data previously stored as JSON files under /DATA is now
|
||||
* persisted in /DATA/axia4.sqlite.
|
||||
*
|
||||
* Usage: db() → returns the shared PDO instance (auto-migrates on first call).
|
||||
*/
|
||||
|
||||
define('DB_PATH', '/DATA/axia4.sqlite');
|
||||
define('MIGRATIONS_DIR', __DIR__ . '/migrations');
|
||||
|
||||
// ── Connection ────────────────────────────────────────────────────────────────
|
||||
|
||||
function db(): PDO
|
||||
{
|
||||
static $pdo = null;
|
||||
if ($pdo !== null) {
|
||||
return $pdo;
|
||||
}
|
||||
|
||||
if (!is_dir('/DATA')) {
|
||||
mkdir('/DATA', 0755, true);
|
||||
}
|
||||
|
||||
$pdo = new PDO('sqlite:' . DB_PATH);
|
||||
$pdo->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 or email (always lower-cased). Returns DB row or null. */
|
||||
function db_get_user(string $username): ?array
|
||||
{
|
||||
if (str_contains($username, "@")) {
|
||||
$stmt = db()->prepare('SELECT * FROM users WHERE email = ?');
|
||||
$stmt->execute([strtolower($username)]);
|
||||
$row = $stmt->fetch();
|
||||
return $row !== false ? $row : null;
|
||||
}
|
||||
$stmt = db()->prepare('SELECT * FROM users WHERE username = ?');
|
||||
$stmt->execute([strtolower($username)]);
|
||||
$row = $stmt->fetch();
|
||||
return $row !== false ? $row : null;
|
||||
}
|
||||
|
||||
/** Return all user rows ordered by username. */
|
||||
function db_get_all_users(): array
|
||||
{
|
||||
return db()->query('SELECT * FROM users ORDER BY username')->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the auth_data session array from a DB user row.
|
||||
* Preserves the same format existing code expects:
|
||||
* auth_data.permissions, auth_data.active_organizations auth_data.organizations.
|
||||
*/
|
||||
function db_build_auth_data(array $row): array
|
||||
{
|
||||
$permissions = json_decode($row['permissions'] ?? '[]', true) ?: [];
|
||||
$meta = json_decode($row['meta'] ?? '{}', true) ?: [];
|
||||
$ea = [
|
||||
'organization' => '',
|
||||
'organizations' => [],
|
||||
'role' => '',
|
||||
'aulas' => [],
|
||||
'organizations_data' => [],
|
||||
];
|
||||
|
||||
// Fetch all organization assignments for this user
|
||||
$stmt = db()->prepare(
|
||||
'SELECT org_id, role, ea_aulas
|
||||
FROM user_orgs
|
||||
WHERE user_id = ?
|
||||
ORDER BY org_id'
|
||||
);
|
||||
$stmt->execute([$row['id']]);
|
||||
$org_rows = $stmt->fetchAll();
|
||||
$orgs = [];
|
||||
if (!empty($org_rows)) {
|
||||
$first = $org_rows[0];
|
||||
foreach ($org_rows as $r) {
|
||||
$orgs[] = $r['org_id'];
|
||||
}
|
||||
$ea['organization'] = $first['org_id'];
|
||||
$ea['role'] = $first['role'];
|
||||
$ea['aulas'] = json_decode($first['ea_aulas'] ?? '[]', true) ?: [];
|
||||
$ea['organizations'] = $orgs;
|
||||
$ea['organizations_data'] = $org_rows;
|
||||
}
|
||||
|
||||
$active_org = $ea['organization'] ?? '';
|
||||
$aulatek = [
|
||||
'organizacion' => $active_org,
|
||||
'organizaciones' => $orgs,
|
||||
'organization' => $active_org,
|
||||
'organizations' => $orgs,
|
||||
'centro' => $active_org,
|
||||
'centros' => $orgs,
|
||||
'role' => $ea['role'] ?? '',
|
||||
'aulas' => $ea['aulas'] ?? [],
|
||||
];
|
||||
|
||||
return array_merge($meta, [
|
||||
'display_name' => $row['display_name'],
|
||||
'email' => $row['email'],
|
||||
'password_hash' => $row['password_hash'],
|
||||
'permissions' => $permissions,
|
||||
'orgs' => $orgs,
|
||||
'organizations' => $orgs,
|
||||
'active_organization' => $active_org,
|
||||
'active_organizations' => $ea,
|
||||
'aulatek' => $aulatek,
|
||||
'entreaulas' => $aulatek,
|
||||
'google_auth' => (bool) $row['google_auth'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update a user.
|
||||
* $data keys: username, display_name, email, password_hash, permissions[],
|
||||
* google_auth, entreaulas{organizacion,organizaciones[],role,aulas[]}, + any extra meta.
|
||||
* Returns the user ID.
|
||||
*/
|
||||
function db_upsert_user(array $data): int
|
||||
{
|
||||
$pdo = db();
|
||||
$username = strtolower((string) ($data['username'] ?? ''));
|
||||
|
||||
$existing = $pdo->prepare('SELECT id FROM users WHERE username = ?');
|
||||
$existing->execute([$username]);
|
||||
$existing_row = $existing->fetch();
|
||||
|
||||
$permissions = json_encode($data['permissions'] ?? []);
|
||||
$meta_skip = ['username', 'display_name', 'email', 'password_hash',
|
||||
'permissions', 'entreaulas', 'google_auth',
|
||||
'orgs', 'organizations', 'organization', 'organizacion',
|
||||
'role', 'aulas'];
|
||||
$meta = [];
|
||||
foreach ($data as $k => $v) {
|
||||
if (!in_array($k, $meta_skip, true)) {
|
||||
$meta[$k] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
if ($existing_row) {
|
||||
$user_id = (int) $existing_row['id'];
|
||||
$upd = $pdo->prepare(
|
||||
"UPDATE users SET
|
||||
display_name = ?,
|
||||
email = ?,
|
||||
permissions = ?,
|
||||
google_auth = ?,
|
||||
meta = ?,
|
||||
updated_at = datetime('now')
|
||||
WHERE id = ?"
|
||||
);
|
||||
$upd->execute([
|
||||
$data['display_name'] ?? '',
|
||||
$data['email'] ?? '',
|
||||
$permissions,
|
||||
(int) ($data['google_auth'] ?? 0),
|
||||
json_encode($meta),
|
||||
$user_id,
|
||||
]);
|
||||
if (!empty($data['password_hash'])) {
|
||||
$pdo->prepare('UPDATE users SET password_hash = ? WHERE id = ?')
|
||||
->execute([$data['password_hash'], $user_id]);
|
||||
}
|
||||
} else {
|
||||
$pdo->prepare(
|
||||
'INSERT INTO users (username, display_name, email, password_hash, permissions, google_auth, meta)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)'
|
||||
)->execute([
|
||||
$username,
|
||||
$data['display_name'] ?? '',
|
||||
$data['email'] ?? '',
|
||||
$data['password_hash'] ?? '',
|
||||
$permissions,
|
||||
(int) ($data['google_auth'] ?? 0),
|
||||
json_encode($meta),
|
||||
]);
|
||||
$user_id = (int) $pdo->lastInsertId();
|
||||
}
|
||||
|
||||
// Update organization assignments if tenant data is provided.
|
||||
$has_org_payload = array_key_exists('entreaulas', $data)
|
||||
|| array_key_exists('orgs', $data)
|
||||
|| array_key_exists('organizations', $data)
|
||||
|| array_key_exists('organization', $data)
|
||||
|| array_key_exists('organizacion', $data);
|
||||
|
||||
if ($has_org_payload) {
|
||||
$ea = $data['entreaulas'] ?? [];
|
||||
|
||||
$organizations = [];
|
||||
$candidate_lists = [
|
||||
$data['organizations'] ?? null,
|
||||
$data['orgs'] ?? null,
|
||||
$ea['organizaciones'] ?? null,
|
||||
$ea['organizations'] ?? null,
|
||||
$ea['centros'] ?? null,
|
||||
];
|
||||
foreach ($candidate_lists as $list) {
|
||||
if (is_array($list) && !empty($list)) {
|
||||
$organizations = $list;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (empty($organizations)) {
|
||||
foreach ([
|
||||
$data['organization'] ?? null,
|
||||
$data['organizacion'] ?? null,
|
||||
$ea['organizacion'] ?? null,
|
||||
$ea['organization'] ?? null,
|
||||
$ea['centro'] ?? null,
|
||||
] as $single) {
|
||||
if (!empty($single)) {
|
||||
$organizations = [$single];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$organizations = array_values(array_unique(array_filter(array_map(
|
||||
static function ($value): string {
|
||||
return preg_replace('/[^a-zA-Z0-9._-]/', '', (string) $value);
|
||||
},
|
||||
$organizations
|
||||
))));
|
||||
|
||||
$role = (string) ($data['role'] ?? $ea['role'] ?? '');
|
||||
$aulas_payload = $data['aulas'] ?? $ea['aulas'] ?? [];
|
||||
if (!is_array($aulas_payload)) {
|
||||
$aulas_payload = [];
|
||||
}
|
||||
$aulas = json_encode($aulas_payload, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
$pdo->prepare('DELETE FROM user_orgs WHERE user_id = ?')->execute([$user_id]);
|
||||
|
||||
$ins_org = $pdo->prepare('INSERT OR IGNORE INTO organizaciones (org_id, org_name) VALUES (?, ?)');
|
||||
$ins_uo = $pdo->prepare(
|
||||
'INSERT OR REPLACE INTO user_orgs (user_id, org_id, role, ea_aulas) VALUES (?, ?, ?, ?)'
|
||||
);
|
||||
foreach ($organizations as $org_id) {
|
||||
if ($org_id === '') {
|
||||
continue;
|
||||
}
|
||||
$ins_org->execute([$org_id, $org_id]);
|
||||
$ins_uo->execute([$user_id, $org_id, $role, $aulas]);
|
||||
}
|
||||
}
|
||||
|
||||
return $user_id;
|
||||
}
|
||||
|
||||
/** Delete a user and their organization assignments. */
|
||||
function db_delete_user(string $username): void
|
||||
{
|
||||
db()->prepare('DELETE FROM users WHERE username = ?')->execute([strtolower($username)]);
|
||||
}
|
||||
|
||||
// ── Organization helpers ─────────────────────────────────────────────────────
|
||||
|
||||
function db_get_organizations(): array
|
||||
{
|
||||
return db()->query('SELECT org_id, org_name FROM organizaciones ORDER BY org_id')->fetchAll();
|
||||
}
|
||||
|
||||
function db_get_organization_ids(): array
|
||||
{
|
||||
return db()->query('SELECT org_id FROM organizaciones ORDER BY org_id')->fetchAll(PDO::FETCH_COLUMN);
|
||||
}
|
||||
|
||||
function db_get_organizaciones(): array
|
||||
{
|
||||
return db_get_organizations();
|
||||
}
|
||||
|
||||
function get_organizations(): array
|
||||
{
|
||||
return db_get_organizations();
|
||||
}
|
||||
|
||||
function db_get_centros(): array
|
||||
{
|
||||
$rows = db_get_organizations();
|
||||
return array_map(static function (array $row): array {
|
||||
return [
|
||||
'centro_id' => $row['org_id'],
|
||||
'name' => $row['org_name'],
|
||||
];
|
||||
}, $rows);
|
||||
}
|
||||
|
||||
function db_get_centro_ids(): array
|
||||
{
|
||||
return db_get_organization_ids();
|
||||
}
|
||||
|
||||
// ── Aulario helpers ───────────────────────────────────────────────────────────
|
||||
|
||||
/** Get a single aulario config. Returns merged array (name, icon, + extra fields) or null. */
|
||||
function db_get_aulario(string $centro_id, string $aulario_id): ?array
|
||||
{
|
||||
$stmt = db()->prepare(
|
||||
'SELECT name, icon, extra FROM aularios WHERE org_id = ? AND aulario_id = ?'
|
||||
);
|
||||
$stmt->execute([$centro_id, $aulario_id]);
|
||||
$row = $stmt->fetch();
|
||||
if ($row === false) {
|
||||
return null;
|
||||
}
|
||||
$extra = json_decode($row['extra'] ?? '{}', true) ?: [];
|
||||
return array_merge($extra, ['name' => $row['name'], 'icon' => $row['icon']]);
|
||||
}
|
||||
|
||||
/** Get all aularios for a centro as aulario_id → config array. */
|
||||
function db_get_aularios(string $centro_id): array
|
||||
{
|
||||
$stmt = db()->prepare(
|
||||
'SELECT aulario_id, name, icon, extra FROM aularios WHERE org_id = ? ORDER BY aulario_id'
|
||||
);
|
||||
$stmt->execute([$centro_id]);
|
||||
$result = [];
|
||||
foreach ($stmt->fetchAll() as $row) {
|
||||
$extra = json_decode($row['extra'] ?? '{}', true) ?: [];
|
||||
$result[$row['aulario_id']] = array_merge($extra, [
|
||||
'name' => $row['name'],
|
||||
'icon' => $row['icon'],
|
||||
]);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
// ── SuperCafe helpers ─────────────────────────────────────────────────────────
|
||||
|
||||
function db_get_supercafe_menu(string $centro_id): array
|
||||
{
|
||||
$stmt = db()->prepare('SELECT data FROM supercafe_menu WHERE org_id = ?');
|
||||
$stmt->execute([$centro_id]);
|
||||
$row = $stmt->fetch();
|
||||
if ($row === false) {
|
||||
return [];
|
||||
}
|
||||
return json_decode($row['data'], true) ?: [];
|
||||
}
|
||||
|
||||
function db_set_supercafe_menu(string $centro_id, array $menu): void
|
||||
{
|
||||
db()->prepare('INSERT OR REPLACE INTO supercafe_menu (org_id, data, updated_at) VALUES (?, ?, datetime(\'now\'))')
|
||||
->execute([$centro_id, json_encode($menu, JSON_UNESCAPED_UNICODE)]);
|
||||
}
|
||||
|
||||
/** Return all SC orders for a centro as an array of rows. */
|
||||
function db_get_supercafe_orders(string $centro_id): array
|
||||
{
|
||||
$stmt = db()->prepare(
|
||||
'SELECT * FROM supercafe_orders WHERE org_id = ? ORDER BY created_at DESC'
|
||||
);
|
||||
$stmt->execute([$centro_id]);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
/** Return a single SC order by ref, or null. */
|
||||
function db_get_supercafe_order(string $centro_id, string $order_ref): ?array
|
||||
{
|
||||
$stmt = db()->prepare(
|
||||
'SELECT * FROM supercafe_orders WHERE org_id = ? AND order_ref = ?'
|
||||
);
|
||||
$stmt->execute([$centro_id, $order_ref]);
|
||||
$row = $stmt->fetch();
|
||||
return $row !== false ? $row : null;
|
||||
}
|
||||
|
||||
/** Create or update an SC order. */
|
||||
function db_upsert_supercafe_order(
|
||||
string $centro_id,
|
||||
string $order_ref,
|
||||
string $fecha,
|
||||
string $persona,
|
||||
string $comanda,
|
||||
string $notas,
|
||||
string $estado
|
||||
): void {
|
||||
db()->prepare(
|
||||
'INSERT INTO supercafe_orders (org_id, order_ref, fecha, persona, comanda, notas, estado)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(org_id, order_ref) DO UPDATE SET
|
||||
fecha = excluded.fecha,
|
||||
persona = excluded.persona,
|
||||
comanda = excluded.comanda,
|
||||
notas = excluded.notas,
|
||||
estado = excluded.estado'
|
||||
)->execute([$centro_id, $order_ref, $fecha, $persona, $comanda, $notas, $estado]);
|
||||
}
|
||||
|
||||
/** Generate the next order_ref for a centro (sc001, sc002, …). */
|
||||
function db_next_supercafe_ref(string $centro_id): string
|
||||
{
|
||||
$stmt = db()->prepare(
|
||||
"SELECT order_ref FROM supercafe_orders WHERE org_id = ? ORDER BY id DESC LIMIT 1"
|
||||
);
|
||||
$stmt->execute([$centro_id]);
|
||||
$last = $stmt->fetchColumn();
|
||||
$n = 0;
|
||||
if ($last && preg_match('/^sc(\d+)$/', $last, $m)) {
|
||||
$n = (int) $m[1];
|
||||
}
|
||||
return 'sc' . str_pad($n + 1, 3, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
/** Count 'Deuda' orders for a persona in a centro. */
|
||||
function db_supercafe_count_debts(string $centro_id, string $persona_key): int
|
||||
{
|
||||
$stmt = db()->prepare(
|
||||
"SELECT COUNT(*) FROM supercafe_orders WHERE org_id = ? AND persona = ? AND estado = 'Deuda'"
|
||||
);
|
||||
$stmt->execute([$centro_id, $persona_key]);
|
||||
return (int) $stmt->fetchColumn();
|
||||
}
|
||||
|
||||
// ── Comedor helpers ───────────────────────────────────────────────────────────
|
||||
|
||||
function db_get_comedor_menu_types(string $centro_id, string $aulario_id): array
|
||||
{
|
||||
$stmt = db()->prepare(
|
||||
'SELECT data FROM comedor_menu_types WHERE org_id = ? AND aulario_id = ?'
|
||||
);
|
||||
$stmt->execute([$centro_id, $aulario_id]);
|
||||
$row = $stmt->fetch();
|
||||
if ($row === false) {
|
||||
return [];
|
||||
}
|
||||
return json_decode($row['data'], true) ?: [];
|
||||
}
|
||||
|
||||
function db_set_comedor_menu_types(string $centro_id, string $aulario_id, array $types): void
|
||||
{
|
||||
db()->prepare(
|
||||
'INSERT OR REPLACE INTO comedor_menu_types (org_id, aulario_id, data) VALUES (?, ?, ?)'
|
||||
)->execute([$centro_id, $aulario_id, json_encode($types, JSON_UNESCAPED_UNICODE)]);
|
||||
}
|
||||
|
||||
function db_get_comedor_entry(string $centro_id, string $aulario_id, string $ym, string $day): array
|
||||
{
|
||||
$stmt = db()->prepare(
|
||||
'SELECT data FROM comedor_entries WHERE org_id = ? AND aulario_id = ? AND year_month = ? AND day = ?'
|
||||
);
|
||||
$stmt->execute([$centro_id, $aulario_id, $ym, $day]);
|
||||
$row = $stmt->fetch();
|
||||
if ($row === false) {
|
||||
return [];
|
||||
}
|
||||
return json_decode($row['data'], true) ?: [];
|
||||
}
|
||||
|
||||
function db_set_comedor_entry(string $centro_id, string $aulario_id, string $ym, string $day, array $data): void
|
||||
{
|
||||
db()->prepare(
|
||||
'INSERT OR REPLACE INTO comedor_entries (org_id, aulario_id, year_month, day, data) VALUES (?, ?, ?, ?, ?)'
|
||||
)->execute([$centro_id, $aulario_id, $ym, $day, json_encode($data, JSON_UNESCAPED_UNICODE)]);
|
||||
}
|
||||
|
||||
// ── Diario helpers ────────────────────────────────────────────────────────────
|
||||
|
||||
function db_get_diario_entry(string $centro_id, string $aulario_id, string $entry_date): array
|
||||
{
|
||||
$stmt = db()->prepare(
|
||||
'SELECT data FROM diario_entries WHERE org_id = ? AND aulario_id = ? AND entry_date = ?'
|
||||
);
|
||||
$stmt->execute([$centro_id, $aulario_id, $entry_date]);
|
||||
$row = $stmt->fetch();
|
||||
if ($row === false) {
|
||||
return [];
|
||||
}
|
||||
return json_decode($row['data'], true) ?: [];
|
||||
}
|
||||
|
||||
function db_set_diario_entry(string $centro_id, string $aulario_id, string $entry_date, array $data): void
|
||||
{
|
||||
db()->prepare(
|
||||
'INSERT OR REPLACE INTO diario_entries (org_id, aulario_id, entry_date, data) VALUES (?, ?, ?, ?)'
|
||||
)->execute([$centro_id, $aulario_id, $entry_date, json_encode($data, JSON_UNESCAPED_UNICODE)]);
|
||||
}
|
||||
|
||||
// ── Panel alumno helpers ──────────────────────────────────────────────────────
|
||||
|
||||
function db_get_panel_alumno(string $centro_id, string $aulario_id, string $alumno): array
|
||||
{
|
||||
$stmt = db()->prepare(
|
||||
'SELECT data FROM panel_alumno WHERE org_id = ? AND aulario_id = ? AND alumno = ?'
|
||||
);
|
||||
$stmt->execute([$centro_id, $aulario_id, $alumno]);
|
||||
$row = $stmt->fetch();
|
||||
if ($row === false) {
|
||||
return [];
|
||||
}
|
||||
return json_decode($row['data'], true) ?: [];
|
||||
}
|
||||
|
||||
function db_set_panel_alumno(string $centro_id, string $aulario_id, string $alumno, array $data): void
|
||||
{
|
||||
db()->prepare(
|
||||
'INSERT OR REPLACE INTO panel_alumno (org_id, aulario_id, alumno, data) VALUES (?, ?, ?, ?)'
|
||||
)->execute([$centro_id, $aulario_id, $alumno, json_encode($data, JSON_UNESCAPED_UNICODE)]);
|
||||
}
|
||||
|
||||
// ── Invitation helpers ────────────────────────────────────────────────────────
|
||||
|
||||
function db_get_all_invitations(): array
|
||||
{
|
||||
return db()->query('SELECT * FROM invitations ORDER BY code')->fetchAll();
|
||||
}
|
||||
|
||||
function db_get_invitation(string $code): ?array
|
||||
{
|
||||
$stmt = db()->prepare('SELECT * FROM invitations WHERE code = ?');
|
||||
$stmt->execute([strtoupper($code)]);
|
||||
$row = $stmt->fetch();
|
||||
return $row !== false ? $row : null;
|
||||
}
|
||||
|
||||
function db_upsert_invitation(string $code, bool $active, bool $single_use): void
|
||||
{
|
||||
db()->prepare(
|
||||
'INSERT OR REPLACE INTO invitations (code, active, single_use) VALUES (?, ?, ?)'
|
||||
)->execute([strtoupper($code), (int) $active, (int) $single_use]);
|
||||
}
|
||||
|
||||
function db_deactivate_invitation(string $code): void
|
||||
{
|
||||
db()->prepare('UPDATE invitations SET active = 0 WHERE code = ?')->execute([strtoupper($code)]);
|
||||
}
|
||||
|
||||
function db_delete_invitation(string $code): void
|
||||
{
|
||||
db()->prepare('DELETE FROM invitations WHERE code = ?')->execute([strtoupper($code)]);
|
||||
}
|
||||
|
||||
// ── Club helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
function db_get_club_config(): array
|
||||
{
|
||||
$stmt = db()->query('SELECT data FROM club_config WHERE id = 1');
|
||||
$row = $stmt->fetch();
|
||||
if ($row === false) {
|
||||
return [];
|
||||
}
|
||||
return json_decode($row['data'], true) ?: [];
|
||||
}
|
||||
|
||||
function db_set_club_config(array $config): void
|
||||
{
|
||||
db()->prepare('INSERT OR REPLACE INTO club_config (id, data) VALUES (1, ?)')
|
||||
->execute([json_encode($config, JSON_UNESCAPED_UNICODE)]);
|
||||
}
|
||||
|
||||
function db_get_all_club_events(): array
|
||||
{
|
||||
return db()->query('SELECT date_ref, data FROM club_events ORDER BY date_ref DESC')->fetchAll();
|
||||
}
|
||||
|
||||
function db_get_club_event(string $date_ref): array
|
||||
{
|
||||
$stmt = db()->prepare('SELECT data FROM club_events WHERE date_ref = ?');
|
||||
$stmt->execute([$date_ref]);
|
||||
$row = $stmt->fetch();
|
||||
if ($row === false) {
|
||||
return [];
|
||||
}
|
||||
return json_decode($row['data'], true) ?: [];
|
||||
}
|
||||
|
||||
function db_set_club_event(string $date_ref, array $data): void
|
||||
{
|
||||
db()->prepare('INSERT OR REPLACE INTO club_events (date_ref, data) VALUES (?, ?)')
|
||||
->execute([$date_ref, json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)]);
|
||||
}
|
||||
|
||||
// ── Multi-tenant helpers ──────────────────────────────────────────────────────
|
||||
|
||||
/** Return all organization IDs the authenticated user belongs to. */
|
||||
function get_user_organizations(?array $auth_data = null): array
|
||||
{
|
||||
$data = $auth_data ?? $_SESSION['auth_data'] ?? [];
|
||||
$orgs = $data['organizations']
|
||||
?? $data['orgs']
|
||||
?? $data['aulatek']['organizaciones']
|
||||
?? $data['aulatek']['organizations']
|
||||
?? $data['aulatek']['centros']
|
||||
?? $data['entreaulas']['organizaciones']
|
||||
?? $data['entreaulas']['organizations']
|
||||
?? $data['entreaulas']['centros']
|
||||
?? [];
|
||||
|
||||
if (!empty($orgs) && is_array($orgs)) {
|
||||
return array_values(array_unique(array_filter($orgs, static function ($value): bool {
|
||||
return is_string($value) && $value !== '';
|
||||
})));
|
||||
}
|
||||
if (!empty($orgs)) {
|
||||
return [(string) $orgs];
|
||||
}
|
||||
|
||||
foreach ([
|
||||
$data['active_organization'] ?? null,
|
||||
$data['aulatek']['organizacion'] ?? null,
|
||||
$data['aulatek']['organization'] ?? null,
|
||||
$data['aulatek']['centro'] ?? null,
|
||||
$data['entreaulas']['organizacion'] ?? null,
|
||||
$data['entreaulas']['organization'] ?? null,
|
||||
$data['entreaulas']['centro'] ?? null,
|
||||
] as $single) {
|
||||
if (is_string($single) && $single !== '') {
|
||||
return [$single];
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/** Spanish alias used by pre-body.php menu rendering. */
|
||||
function get_user_organizaciones(?array $auth_data = null): array
|
||||
{
|
||||
$org_ids = get_user_organizations($auth_data);
|
||||
if (empty($org_ids)) {
|
||||
return [];
|
||||
}
|
||||
$name_by_id = [];
|
||||
foreach (db_get_organizations() as $org_row) {
|
||||
$name_by_id[$org_row['org_id']] = $org_row['org_name'];
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach ($org_ids as $org_id) {
|
||||
$result[$org_id] = $name_by_id[$org_id] ?? $org_id;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
function get_user_centros(?array $auth_data = null): array
|
||||
{
|
||||
return get_user_organizations($auth_data);
|
||||
}
|
||||
|
||||
/** Ensure active organization session keys are set and mirrored for legacy code. */
|
||||
function init_active_org(?array $auth_data = null): void
|
||||
{
|
||||
$organizations = get_user_organizations($auth_data);
|
||||
if (empty($organizations)) {
|
||||
$_SESSION['active_organization'] = null;
|
||||
$_SESSION['active_organizacion'] = null;
|
||||
$_SESSION['active_centro'] = null;
|
||||
return;
|
||||
}
|
||||
|
||||
$current = $_SESSION['active_organization']
|
||||
?? $_SESSION['active_organizacion']
|
||||
?? $_SESSION['active_centro']
|
||||
?? null;
|
||||
|
||||
if (!is_string($current) || !in_array($current, $organizations, true)) {
|
||||
$current = $organizations[0];
|
||||
}
|
||||
|
||||
$_SESSION['active_organization'] = $current;
|
||||
$_SESSION['active_organizacion'] = $current;
|
||||
$_SESSION['active_centro'] = $current;
|
||||
|
||||
if (!isset($_SESSION['auth_data']) || !is_array($_SESSION['auth_data'])) {
|
||||
$_SESSION['auth_data'] = [];
|
||||
}
|
||||
$_SESSION['auth_data']['active_organization'] = $current;
|
||||
if (!isset($_SESSION['auth_data']['aulatek']) || !is_array($_SESSION['auth_data']['aulatek'])) {
|
||||
$_SESSION['auth_data']['aulatek'] = [];
|
||||
}
|
||||
$_SESSION['auth_data']['aulatek']['organizacion'] = $current;
|
||||
$_SESSION['auth_data']['aulatek']['organization'] = $current;
|
||||
$_SESSION['auth_data']['aulatek']['centro'] = $current;
|
||||
if (!isset($_SESSION['auth_data']['entreaulas']) || !is_array($_SESSION['auth_data']['entreaulas'])) {
|
||||
$_SESSION['auth_data']['entreaulas'] = [];
|
||||
}
|
||||
$_SESSION['auth_data']['entreaulas']['organizacion'] = $current;
|
||||
$_SESSION['auth_data']['entreaulas']['organization'] = $current;
|
||||
$_SESSION['auth_data']['entreaulas']['centro'] = $current;
|
||||
}
|
||||
|
||||
function init_active_centro(?array $auth_data = null): void
|
||||
{
|
||||
init_active_org($auth_data);
|
||||
}
|
||||
|
||||
// ── User session helpers (Dispositivos conectados) ────────────────────────────
|
||||
|
||||
/**
|
||||
* Register a new session record in user_sessions.
|
||||
* $remember_token_hash is the SHA-256 hash of the raw remember-me cookie value.
|
||||
* Stores a SHA-256 hash of the PHP session_id so the raw token never reaches the DB.
|
||||
*/
|
||||
function db_register_session(string $username, string $remember_token_hash = ''): void
|
||||
{
|
||||
$token = hash('sha256', session_id());
|
||||
$ip = $_SERVER['HTTP_X_FORWARDED_FOR']
|
||||
?? $_SERVER['REMOTE_ADDR']
|
||||
?? '';
|
||||
$ip = trim(explode(',', $ip)[0]);
|
||||
$ua = substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 512);
|
||||
|
||||
db()->prepare(
|
||||
"INSERT INTO user_sessions (session_token, username, ip_address, user_agent, remember_token_hash)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
ON CONFLICT(session_token) DO UPDATE SET
|
||||
last_active = datetime('now'),
|
||||
remember_token_hash = COALESCE(excluded.remember_token_hash, remember_token_hash)"
|
||||
)->execute([$token, strtolower($username), $ip, $ua, $remember_token_hash ?: null]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore a session from a remember-me token hash.
|
||||
* Updates the session_token to the current PHP session_id so revocation
|
||||
* continues to work after the PHP session was re-created.
|
||||
* Returns the session row (including username) on success, or null if not found.
|
||||
*/
|
||||
function db_restore_session_by_remember_token(string $token_hash): ?array
|
||||
{
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare(
|
||||
'SELECT * FROM user_sessions WHERE remember_token_hash = ? LIMIT 1'
|
||||
);
|
||||
$stmt->execute([$token_hash]);
|
||||
$row = $stmt->fetch();
|
||||
if (!$row) {
|
||||
return null;
|
||||
}
|
||||
// Migrate the row to the new PHP session_id
|
||||
$new_session_token = hash('sha256', session_id());
|
||||
$pdo->prepare(
|
||||
"UPDATE user_sessions
|
||||
SET session_token = ?, last_active = datetime('now')
|
||||
WHERE remember_token_hash = ?"
|
||||
)->execute([$new_session_token, $token_hash]);
|
||||
return $row;
|
||||
}
|
||||
|
||||
/** Update last_active for the current session. */
|
||||
function db_touch_session(): void
|
||||
{
|
||||
$token = hash('sha256', session_id());
|
||||
db()->prepare(
|
||||
"UPDATE user_sessions SET last_active = datetime('now') WHERE session_token = ?"
|
||||
)->execute([$token]);
|
||||
}
|
||||
|
||||
/** Delete the current session record from the DB. */
|
||||
function db_delete_session(): void
|
||||
{
|
||||
$token = hash('sha256', session_id());
|
||||
db()->prepare('DELETE FROM user_sessions WHERE session_token = ?')->execute([$token]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a specific session by token for a given user.
|
||||
* Enforces that the session belongs to the requesting user.
|
||||
*/
|
||||
function db_revoke_session(string $token, string $username): void
|
||||
{
|
||||
db()->prepare('DELETE FROM user_sessions WHERE session_token = ? AND username = ?')
|
||||
->execute([$token, strtolower($username)]);
|
||||
}
|
||||
|
||||
/** Delete all session records for a user (e.g. on password change or account wipe). */
|
||||
function db_delete_user_sessions(string $username): void
|
||||
{
|
||||
db()->prepare('DELETE FROM user_sessions WHERE username = ?')
|
||||
->execute([strtolower($username)]);
|
||||
}
|
||||
|
||||
/** Return all session rows for a user, newest first. */
|
||||
function db_get_user_sessions(string $username): array
|
||||
{
|
||||
$stmt = db()->prepare(
|
||||
'SELECT session_token, ip_address, user_agent, created_at, last_active
|
||||
FROM user_sessions
|
||||
WHERE username = ?
|
||||
ORDER BY last_active DESC'
|
||||
);
|
||||
$stmt->execute([strtolower($username)]);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the current PHP session has a valid record in user_sessions.
|
||||
* Returns false if the session was revoked or was never registered.
|
||||
*/
|
||||
function db_session_is_valid(string $username): bool
|
||||
{
|
||||
$token = hash('sha256', session_id());
|
||||
$stmt = db()->prepare(
|
||||
'SELECT 1 FROM user_sessions WHERE session_token = ? AND username = ? LIMIT 1'
|
||||
);
|
||||
$stmt->execute([$token, strtolower($username)]);
|
||||
return $stmt->fetchColumn() !== false;
|
||||
}
|
||||
13
public_html/_incl/logout.php
Normal file
13
public_html/_incl/logout.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
require_once "tools.session.php";
|
||||
require_once "tools.security.php";
|
||||
require_once "db.php";
|
||||
|
||||
$redir = safe_redir($_GET["redir"] ?? "/");
|
||||
$cookie_options_expired = ["expires" => time() - 3600, "path" => "/", "httponly" => true, "secure" => true, "samesite" => "Lax"];
|
||||
setcookie("auth_token", "", $cookie_options_expired);
|
||||
db_delete_session();
|
||||
session_unset();
|
||||
session_destroy();
|
||||
header("Location: $redir");
|
||||
die();
|
||||
144
public_html/_incl/migrations/001_initial_schema.sql
Normal file
144
public_html/_incl/migrations/001_initial_schema.sql
Normal file
@@ -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 '{}'
|
||||
);
|
||||
256
public_html/_incl/migrations/002_import_json.php
Normal file
256
public_html/_incl/migrations/002_import_json.php
Normal file
@@ -0,0 +1,256 @@
|
||||
<?php
|
||||
/**
|
||||
* Migration 002: Import existing JSON data from the filesystem into the DB.
|
||||
* This runs once on first boot after the schema is created.
|
||||
* It is safe to run even if /DATA doesn't have all files – missing files are skipped.
|
||||
*
|
||||
* $db (PDO) is provided by the migration runner in db.php.
|
||||
*/
|
||||
|
||||
// ── AuthConfig → config table ────────────────────────────────────────────────
|
||||
$auth_config_file = '/DATA/AuthConfig.json';
|
||||
if (file_exists($auth_config_file)) {
|
||||
$auth_config = json_decode(file_get_contents($auth_config_file), true) ?? [];
|
||||
$ins = $db->prepare("INSERT OR IGNORE INTO config (key, value) VALUES (?, ?)");
|
||||
foreach ($auth_config as $k => $v) {
|
||||
$ins->execute([$k, is_string($v) ? $v : json_encode($v)]);
|
||||
}
|
||||
}
|
||||
|
||||
// ── SISTEMA_INSTALADO marker ─────────────────────────────────────────────────
|
||||
if (file_exists('/DATA/SISTEMA_INSTALADO.txt')) {
|
||||
$db->prepare("INSERT OR IGNORE INTO config (key, value) VALUES ('installed', '1')")->execute();
|
||||
}
|
||||
|
||||
// ── Users (/DATA/Usuarios/*.json) ────────────────────────────────────────────
|
||||
$users_dir = '/DATA/Usuarios';
|
||||
if (is_dir($users_dir)) {
|
||||
$ins_user = $db->prepare(
|
||||
"INSERT OR IGNORE INTO users
|
||||
(username, display_name, email, password_hash, permissions, google_auth, meta)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)"
|
||||
);
|
||||
$ins_uc = $db->prepare(
|
||||
"INSERT OR IGNORE INTO user_centros (user_id, centro_id, role, aulas)
|
||||
VALUES (?, ?, ?, ?)"
|
||||
);
|
||||
$ins_centro = $db->prepare("INSERT OR IGNORE INTO centros (centro_id) VALUES (?)");
|
||||
|
||||
foreach (glob("$users_dir/*.json") ?: [] as $user_file) {
|
||||
$username = basename($user_file, '.json');
|
||||
$data = json_decode(file_get_contents($user_file), true);
|
||||
if (!is_array($data)) {
|
||||
continue;
|
||||
}
|
||||
$permissions = isset($data['permissions']) ? json_encode($data['permissions']) : '[]';
|
||||
// Store remaining non-standard keys in meta
|
||||
$meta_keys = ['display_name', 'email', 'password_hash', 'permissions', 'entreaulas', 'google_auth'];
|
||||
$meta = [];
|
||||
foreach ($data as $k => $v) {
|
||||
if (!in_array($k, $meta_keys, true)) {
|
||||
$meta[$k] = $v;
|
||||
}
|
||||
}
|
||||
$ins_user->execute([
|
||||
$username,
|
||||
$data['display_name'] ?? '',
|
||||
$data['email'] ?? '',
|
||||
$data['password_hash'] ?? '',
|
||||
$permissions,
|
||||
(int) ($data['google_auth'] ?? 0),
|
||||
json_encode($meta),
|
||||
]);
|
||||
$user_id = (int) $db->lastInsertId();
|
||||
if ($user_id === 0) {
|
||||
// Already existed – look it up
|
||||
$stmt2 = $db->prepare("SELECT id FROM users WHERE username = ?");
|
||||
$stmt2->execute([$username]);
|
||||
$user_id = (int) $stmt2->fetchColumn();
|
||||
}
|
||||
|
||||
// Entreaulas centro assignment
|
||||
$ea = $data['entreaulas'] ?? [];
|
||||
// Support both old single "centro" and new "centros" array
|
||||
$centros = [];
|
||||
if (!empty($ea['centros']) && is_array($ea['centros'])) {
|
||||
$centros = $ea['centros'];
|
||||
} elseif (!empty($ea['centro'])) {
|
||||
$centros = [$ea['centro']];
|
||||
}
|
||||
$role = $ea['role'] ?? '';
|
||||
$aulas = json_encode($ea['aulas'] ?? []);
|
||||
foreach ($centros as $cid) {
|
||||
if ($cid === '') {
|
||||
continue;
|
||||
}
|
||||
$ins_centro->execute([$cid]);
|
||||
$ins_uc->execute([$user_id, $cid, $role, $aulas]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Invitations (/DATA/Invitaciones_de_usuarios.json) ────────────────────────
|
||||
$inv_file = '/DATA/Invitaciones_de_usuarios.json';
|
||||
if (file_exists($inv_file)) {
|
||||
$invs = json_decode(file_get_contents($inv_file), true) ?? [];
|
||||
$ins = $db->prepare(
|
||||
"INSERT OR IGNORE INTO invitations (code, active, single_use) VALUES (?, ?, ?)"
|
||||
);
|
||||
foreach ($invs as $code => $inv) {
|
||||
$ins->execute([
|
||||
strtoupper($code),
|
||||
(int) ($inv['active'] ?? 1),
|
||||
(int) ($inv['single_use'] ?? 1),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Centros & Aularios (directory structure) ──────────────────────────────────
|
||||
$centros_base = '/DATA/entreaulas/Centros';
|
||||
if (is_dir($centros_base)) {
|
||||
$ins_centro = $db->prepare("INSERT OR IGNORE INTO centros (centro_id) VALUES (?)");
|
||||
$ins_aulario = $db->prepare(
|
||||
"INSERT OR IGNORE INTO aularios (centro_id, aulario_id, name, icon, extra) VALUES (?, ?, ?, ?, ?)"
|
||||
);
|
||||
foreach (glob("$centros_base/*", GLOB_ONLYDIR) ?: [] as $centro_dir) {
|
||||
$centro_id = basename($centro_dir);
|
||||
$ins_centro->execute([$centro_id]);
|
||||
|
||||
$aularios_dir = "$centro_dir/Aularios";
|
||||
foreach (glob("$aularios_dir/*.json") ?: [] as $aulario_file) {
|
||||
$aulario_id = basename($aulario_file, '.json');
|
||||
$adata = json_decode(file_get_contents($aulario_file), true);
|
||||
if (!is_array($adata)) {
|
||||
continue;
|
||||
}
|
||||
$name = $adata['name'] ?? $aulario_id;
|
||||
$icon = $adata['icon'] ?? '';
|
||||
$extra_keys = ['name', 'icon'];
|
||||
$extra = [];
|
||||
foreach ($adata as $k => $v) {
|
||||
if (!in_array($k, $extra_keys, true)) {
|
||||
$extra[$k] = $v;
|
||||
}
|
||||
}
|
||||
$ins_aulario->execute([$centro_id, $aulario_id, $name, $icon, json_encode($extra)]);
|
||||
}
|
||||
|
||||
// SuperCafe menu
|
||||
$menu_file = "$centro_dir/SuperCafe/Menu.json";
|
||||
if (file_exists($menu_file)) {
|
||||
$menu_data = file_get_contents($menu_file);
|
||||
$db->prepare("INSERT OR IGNORE INTO supercafe_menu (centro_id, data) VALUES (?, ?)")
|
||||
->execute([$centro_id, $menu_data]);
|
||||
}
|
||||
|
||||
// SuperCafe orders
|
||||
$comandas_dir = "$centro_dir/SuperCafe/Comandas";
|
||||
if (is_dir($comandas_dir)) {
|
||||
$ins_order = $db->prepare(
|
||||
"INSERT OR IGNORE INTO supercafe_orders
|
||||
(centro_id, order_ref, fecha, persona, comanda, notas, estado)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)"
|
||||
);
|
||||
foreach (glob("$comandas_dir/*.json") ?: [] as $order_file) {
|
||||
$order_ref = basename($order_file, '.json');
|
||||
$odata = json_decode(file_get_contents($order_file), true);
|
||||
if (!is_array($odata)) {
|
||||
continue;
|
||||
}
|
||||
$ins_order->execute([
|
||||
$centro_id,
|
||||
$order_ref,
|
||||
$odata['Fecha'] ?? '',
|
||||
$odata['Persona'] ?? '',
|
||||
$odata['Comanda'] ?? '',
|
||||
$odata['Notas'] ?? '',
|
||||
$odata['Estado'] ?? 'Pedido',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Comedor menu types & daily entries per aulario
|
||||
foreach (glob("$aularios_dir/*.json") ?: [] as $aulario_file) {
|
||||
$aulario_id = basename($aulario_file, '.json');
|
||||
|
||||
$menu_types_file = "$aularios_dir/$aulario_id/Comedor-MenuTypes.json";
|
||||
if (file_exists($menu_types_file)) {
|
||||
$db->prepare(
|
||||
"INSERT OR IGNORE INTO comedor_menu_types (centro_id, aulario_id, data) VALUES (?, ?, ?)"
|
||||
)->execute([$centro_id, $aulario_id, file_get_contents($menu_types_file)]);
|
||||
}
|
||||
|
||||
$comedor_base = "$aularios_dir/$aulario_id/Comedor";
|
||||
if (is_dir($comedor_base)) {
|
||||
$ins_centry = $db->prepare(
|
||||
"INSERT OR IGNORE INTO comedor_entries (centro_id, aulario_id, year_month, day, data) VALUES (?, ?, ?, ?, ?)"
|
||||
);
|
||||
foreach (glob("$comedor_base/*", GLOB_ONLYDIR) ?: [] as $ym_dir) {
|
||||
$ym = basename($ym_dir);
|
||||
foreach (glob("$ym_dir/*", GLOB_ONLYDIR) ?: [] as $day_dir) {
|
||||
$day = basename($day_dir);
|
||||
$data_file = "$day_dir/_datos.json";
|
||||
if (file_exists($data_file)) {
|
||||
$ins_centry->execute([
|
||||
$centro_id, $aulario_id, $ym, $day,
|
||||
file_get_contents($data_file),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Diario entries
|
||||
$diario_base = "$aularios_dir/$aulario_id/Diario";
|
||||
if (is_dir($diario_base)) {
|
||||
$ins_d = $db->prepare(
|
||||
"INSERT OR IGNORE INTO diario_entries (centro_id, aulario_id, entry_date, data) VALUES (?, ?, ?, ?)"
|
||||
);
|
||||
foreach (glob("$diario_base/*.json") ?: [] as $diario_file) {
|
||||
$entry_date = basename($diario_file, '.json');
|
||||
$ins_d->execute([$centro_id, $aulario_id, $entry_date, file_get_contents($diario_file)]);
|
||||
}
|
||||
}
|
||||
|
||||
// Panel alumno data
|
||||
$alumnos_base = "$aularios_dir/$aulario_id/Alumnos";
|
||||
if (is_dir($alumnos_base)) {
|
||||
$ins_pa = $db->prepare(
|
||||
"INSERT OR IGNORE INTO panel_alumno (centro_id, aulario_id, alumno, data) VALUES (?, ?, ?, ?)"
|
||||
);
|
||||
foreach (glob("$alumnos_base/*/", GLOB_ONLYDIR) ?: [] as $alumno_dir) {
|
||||
$alumno = basename($alumno_dir);
|
||||
// Look for Panel.json (used by paneldiario)
|
||||
$panel_files = glob("$alumno_dir/Panel*.json") ?: [];
|
||||
foreach ($panel_files as $pf) {
|
||||
$ins_pa->execute([
|
||||
$centro_id, $aulario_id, $alumno,
|
||||
file_get_contents($pf),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Club config (/DATA/club/config.json) ──────────────────────────────────────
|
||||
$club_config_file = '/DATA/club/config.json';
|
||||
if (file_exists($club_config_file)) {
|
||||
$db->prepare("INSERT OR IGNORE INTO club_config (id, data) VALUES (1, ?)")
|
||||
->execute([file_get_contents($club_config_file)]);
|
||||
}
|
||||
|
||||
// ── Club events (/DATA/club/IMG/{date}/data.json) ─────────────────────────────
|
||||
$club_img_dir = '/DATA/club/IMG';
|
||||
if (is_dir($club_img_dir)) {
|
||||
$ins_ev = $db->prepare("INSERT OR IGNORE INTO club_events (date_ref, data) VALUES (?, ?)");
|
||||
foreach (glob("$club_img_dir/*/", GLOB_ONLYDIR) ?: [] as $event_dir) {
|
||||
$date_ref = basename($event_dir);
|
||||
$event_data_file = "$event_dir/data.json";
|
||||
$ins_ev->execute([
|
||||
$date_ref,
|
||||
file_exists($event_data_file) ? file_get_contents($event_data_file) : '{}',
|
||||
]);
|
||||
}
|
||||
}
|
||||
163
public_html/_incl/migrations/003_organizaciones.sql
Normal file
163
public_html/_incl/migrations/003_organizaciones.sql
Normal file
@@ -0,0 +1,163 @@
|
||||
-- filepath: /workspaces/Axia4/public_html/_incl/migrations/003_organizaciones.sql
|
||||
-- Axia4 Migration 003: Rename centros to organizaciones
|
||||
-- Migrates the centros table to organizaciones with org_id and org_name columns.
|
||||
|
||||
PRAGMA journal_mode = WAL;
|
||||
PRAGMA foreign_keys = ON;
|
||||
|
||||
-- ── Create new organizaciones table ──────────────────────────────────────────
|
||||
CREATE TABLE IF NOT EXISTS organizaciones (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
org_id TEXT UNIQUE NOT NULL,
|
||||
org_name TEXT NOT NULL DEFAULT '',
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
-- ── Migrate data from centros to organizaciones ──────────────────────────────
|
||||
INSERT INTO organizaciones (org_id, org_name, created_at)
|
||||
SELECT centro_id, COALESCE(name, centro_id), created_at
|
||||
FROM centros
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM organizaciones WHERE org_id = centros.centro_id
|
||||
);
|
||||
|
||||
-- ── Update foreign key references in user_centros ──────────────────────────────
|
||||
-- user_centros.centro_id → user_centros.org_id (rename column if needed via recreation)
|
||||
-- For SQLite, we need to recreate the table due to FK constraint changes
|
||||
|
||||
CREATE TABLE user_centros_new (
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
org_id TEXT NOT NULL REFERENCES organizaciones(org_id) ON DELETE CASCADE,
|
||||
role TEXT NOT NULL DEFAULT '',
|
||||
ea_aulas TEXT NOT NULL DEFAULT '[]',
|
||||
PRIMARY KEY (user_id, org_id)
|
||||
);
|
||||
|
||||
INSERT INTO user_centros_new (user_id, org_id, role, ea_aulas)
|
||||
SELECT user_id, centro_id, role, aulas FROM user_centros;
|
||||
|
||||
DROP TABLE user_centros;
|
||||
ALTER TABLE user_centros_new RENAME TO user_orgs;
|
||||
|
||||
-- ── Update foreign key references in aularios ──────────────────────────────────
|
||||
CREATE TABLE aularios_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
org_id TEXT NOT NULL REFERENCES organizaciones(org_id) ON DELETE CASCADE,
|
||||
aulario_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL DEFAULT '',
|
||||
icon TEXT NOT NULL DEFAULT '',
|
||||
extra TEXT NOT NULL DEFAULT '{}',
|
||||
UNIQUE (org_id, aulario_id)
|
||||
);
|
||||
|
||||
INSERT INTO aularios_new (id, org_id, aulario_id, name, icon, extra)
|
||||
SELECT id, centro_id, aulario_id, name, icon, extra FROM aularios;
|
||||
|
||||
DROP TABLE aularios;
|
||||
ALTER TABLE aularios_new RENAME TO aularios;
|
||||
|
||||
-- ── Update foreign key references in remaining tables ──────────────────────────
|
||||
-- supercafe_menu
|
||||
CREATE TABLE supercafe_menu_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
org_id TEXT NOT NULL REFERENCES organizaciones(org_id) ON DELETE CASCADE,
|
||||
data TEXT NOT NULL DEFAULT '{}',
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
UNIQUE (org_id)
|
||||
);
|
||||
|
||||
INSERT INTO supercafe_menu_new (id, org_id, data, updated_at)
|
||||
SELECT id, centro_id, data, updated_at FROM supercafe_menu;
|
||||
|
||||
DROP TABLE supercafe_menu;
|
||||
ALTER TABLE supercafe_menu_new RENAME TO supercafe_menu;
|
||||
|
||||
-- supercafe_orders
|
||||
CREATE TABLE supercafe_orders_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
org_id TEXT NOT NULL REFERENCES organizaciones(org_id) ON DELETE CASCADE,
|
||||
order_ref TEXT NOT NULL,
|
||||
fecha TEXT NOT NULL,
|
||||
persona TEXT NOT NULL,
|
||||
comanda TEXT NOT NULL DEFAULT '',
|
||||
notas TEXT NOT NULL DEFAULT '',
|
||||
estado TEXT NOT NULL DEFAULT 'Pedido',
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
UNIQUE (org_id, order_ref)
|
||||
);
|
||||
|
||||
INSERT INTO supercafe_orders_new (id, org_id, order_ref, fecha, persona, comanda, notas, estado, created_at)
|
||||
SELECT id, centro_id, order_ref, fecha, persona, comanda, notas, estado, created_at FROM supercafe_orders;
|
||||
|
||||
DROP TABLE supercafe_orders;
|
||||
ALTER TABLE supercafe_orders_new RENAME TO supercafe_orders;
|
||||
|
||||
-- comedor_menu_types
|
||||
CREATE TABLE comedor_menu_types_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
org_id TEXT NOT NULL REFERENCES organizaciones(org_id) ON DELETE CASCADE,
|
||||
aulario_id TEXT NOT NULL,
|
||||
data TEXT NOT NULL DEFAULT '[]',
|
||||
UNIQUE (org_id, aulario_id)
|
||||
);
|
||||
|
||||
INSERT INTO comedor_menu_types_new (id, org_id, aulario_id, data)
|
||||
SELECT id, centro_id, aulario_id, data FROM comedor_menu_types;
|
||||
|
||||
DROP TABLE comedor_menu_types;
|
||||
ALTER TABLE comedor_menu_types_new RENAME TO comedor_menu_types;
|
||||
|
||||
-- comedor_entries
|
||||
CREATE TABLE comedor_entries_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
org_id TEXT NOT NULL REFERENCES organizaciones(org_id) ON DELETE CASCADE,
|
||||
aulario_id TEXT NOT NULL,
|
||||
year_month TEXT NOT NULL,
|
||||
day TEXT NOT NULL,
|
||||
data TEXT NOT NULL DEFAULT '{}',
|
||||
UNIQUE (org_id, aulario_id, year_month, day)
|
||||
);
|
||||
|
||||
INSERT INTO comedor_entries_new (id, org_id, aulario_id, year_month, day, data)
|
||||
SELECT id, centro_id, aulario_id, year_month, day, data FROM comedor_entries;
|
||||
|
||||
DROP TABLE comedor_entries;
|
||||
ALTER TABLE comedor_entries_new RENAME TO comedor_entries;
|
||||
|
||||
-- diario_entries
|
||||
CREATE TABLE diario_entries_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
org_id TEXT NOT NULL REFERENCES organizaciones(org_id) ON DELETE CASCADE,
|
||||
aulario_id TEXT NOT NULL,
|
||||
entry_date TEXT NOT NULL,
|
||||
data TEXT NOT NULL DEFAULT '{}',
|
||||
UNIQUE (org_id, aulario_id, entry_date)
|
||||
);
|
||||
|
||||
INSERT INTO diario_entries_new (id, org_id, aulario_id, entry_date, data)
|
||||
SELECT id, centro_id, aulario_id, entry_date, data FROM diario_entries;
|
||||
|
||||
DROP TABLE diario_entries;
|
||||
ALTER TABLE diario_entries_new RENAME TO diario_entries;
|
||||
|
||||
-- panel_alumno
|
||||
CREATE TABLE panel_alumno_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
org_id TEXT NOT NULL REFERENCES organizaciones(org_id) ON DELETE CASCADE,
|
||||
aulario_id TEXT NOT NULL,
|
||||
alumno TEXT NOT NULL,
|
||||
data TEXT NOT NULL DEFAULT '{}',
|
||||
UNIQUE (org_id, aulario_id, alumno)
|
||||
);
|
||||
|
||||
INSERT INTO panel_alumno_new (id, org_id, aulario_id, alumno, data)
|
||||
SELECT id, centro_id, aulario_id, alumno, data FROM panel_alumno;
|
||||
|
||||
DROP TABLE panel_alumno;
|
||||
ALTER TABLE panel_alumno_new RENAME TO panel_alumno;
|
||||
|
||||
-- ── Drop old centros table ─────────────────────────────────────────────────────
|
||||
DROP TABLE IF EXISTS centros;
|
||||
|
||||
-- ── Verify migration ───────────────────────────────────────────────────────────
|
||||
-- SELECT COUNT(*) as total_organizaciones FROM organizaciones;
|
||||
17
public_html/_incl/migrations/004_user_sessions.sql
Normal file
17
public_html/_incl/migrations/004_user_sessions.sql
Normal file
@@ -0,0 +1,17 @@
|
||||
-- Axia4 Migration 004: User Sessions (Dispositivos conectados)
|
||||
-- Tracks active authenticated sessions so users can see and revoke connected devices.
|
||||
|
||||
PRAGMA journal_mode = WAL;
|
||||
PRAGMA foreign_keys = ON;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_sessions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
session_token TEXT UNIQUE NOT NULL, -- SHA-256 hash of the PHP session_id()
|
||||
username TEXT NOT NULL,
|
||||
ip_address TEXT NOT NULL DEFAULT '',
|
||||
user_agent TEXT NOT NULL DEFAULT '',
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
last_active TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_user_sessions_username ON user_sessions (username);
|
||||
12
public_html/_incl/migrations/005_remember_token.sql
Normal file
12
public_html/_incl/migrations/005_remember_token.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
-- Axia4 Migration 005: Add remember_token_hash to user_sessions
|
||||
-- Replaces the auth_user + auth_pass_b64 cookie pair with a secure opaque token.
|
||||
-- The raw token lives only in the browser cookie; only its SHA-256 hash is stored.
|
||||
|
||||
PRAGMA journal_mode = WAL;
|
||||
PRAGMA foreign_keys = ON;
|
||||
|
||||
ALTER TABLE user_sessions ADD COLUMN remember_token_hash TEXT DEFAULT NULL;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_user_sessions_remember
|
||||
ON user_sessions (remember_token_hash)
|
||||
WHERE remember_token_hash IS NOT NULL;
|
||||
753
public_html/_incl/pre-body.php
Executable file → Normal file
753
public_html/_incl/pre-body.php
Executable file → Normal file
@@ -27,6 +27,12 @@ if (!empty($displayName)) {
|
||||
$initials = mb_strtoupper($first . $last);
|
||||
}
|
||||
|
||||
// Tenant (organización) management
|
||||
$userOrganizaciones = get_user_organizaciones($_SESSION["auth_data"] ?? []);
|
||||
$activeOrganizacionId = $_SESSION['active_organizacion']
|
||||
?? ($_SESSION["auth_data"]["aulatek"]["organizacion"] ?? ($_SESSION["auth_data"]["entreaulas"]["organizacion"] ?? ''));
|
||||
$activeOrganizacionName = $userOrganizaciones[$activeOrganizacionId] ?? '';
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
@@ -38,412 +44,362 @@ if (!empty($displayName)) {
|
||||
<link rel="stylesheet" href="/static/bootstrap.min.css" />
|
||||
<link rel="icon" type="image/png" href="/static/<?php echo $APP_ICON ?? "logo.png"; ?>" />
|
||||
<link rel="manifest" href="/static/manifest.json">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&family=Google+Sans:wght@400;500;700&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<style>
|
||||
/* fieldset label {
|
||||
margin-bottom: 15px;
|
||||
/* ─── Google Workspace Design System ─────────────────────────────── */
|
||||
:root {
|
||||
--gw-font: 'Google Sans', 'Roboto', 'Arial', sans-serif;
|
||||
--gw-blue: #1a73e8;
|
||||
--gw-blue-hover: #1765cc;
|
||||
--gw-blue-light: #e8f0fe;
|
||||
--gw-text-primary: #202124;
|
||||
--gw-text-secondary: #5f6368;
|
||||
--gw-bg: #f0f4f9;
|
||||
--gw-surface: #ffffff;
|
||||
--gw-border: #dadce0;
|
||||
--gw-hover: #f1f3f4;
|
||||
--gw-header-h: 64px;
|
||||
--gw-sidebar-w: 256px;
|
||||
--gw-brand: #9013FE;
|
||||
--bs-btn-font-family: 'Google Sans', 'Roboto', Arial, sans-serif;
|
||||
--bs-body-font-family: 'Google Sans', 'Roboto', Arial, sans-serif;
|
||||
--bs-font-sans-serif: 'Google Sans', 'Roboto', Arial, sans-serif;
|
||||
--bs-link-color: var(--gw-blue);
|
||||
--bs-link-hover-color: var(--gw-blue-hover);
|
||||
}
|
||||
|
||||
.actbutton,
|
||||
.actbutton-half {
|
||||
padding: 5px 10px;
|
||||
padding-left: 5px;
|
||||
width: 200px;
|
||||
text-align: right;
|
||||
vertical-align: top;
|
||||
}
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
|
||||
.actbutton-half {
|
||||
width: 167.5px;
|
||||
|
||||
}
|
||||
|
||||
.actbutton img,
|
||||
.actbutton-half img {
|
||||
float: left;
|
||||
body {
|
||||
font-family: var(--gw-font);
|
||||
background: var(--gw-bg);
|
||||
color: var(--gw-text-primary);
|
||||
margin: 0;
|
||||
height: 55px;
|
||||
width: 55px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: 0.3em 0.6em;
|
||||
border-left: 1px solid #ccc;
|
||||
border-right: 1px solid #ccc;
|
||||
/* ─── Print ───────────────────────────────────────────────────────── */
|
||||
@media print { .no-print { display: none; } }
|
||||
|
||||
/* ─── Form helpers ────────────────────────────────────────────────── */
|
||||
input[readonly], textarea[readonly], .select select[readonly] {
|
||||
background-color: #f1f3f4;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: center;
|
||||
} */
|
||||
|
||||
@media print {
|
||||
.no-print {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
input[readonly],
|
||||
textarea[readonly],
|
||||
.select select[readonly] {
|
||||
background-color: lightgray;
|
||||
}
|
||||
|
||||
fieldset input,
|
||||
fieldset textarea,
|
||||
fieldset .select select {
|
||||
width: calc(100% - 1s5px);
|
||||
fieldset input, fieldset textarea, fieldset .select select {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
input.nonumscroll::-webkit-outer-spin-button,
|
||||
input.nonumscroll::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
input.nonumscroll::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
|
||||
input.nonumscroll[type=number] { appearance: textfield; -moz-appearance: textfield; }
|
||||
|
||||
input.nonumscroll[type=number] {
|
||||
appearance: textfield;
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
/*
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
a.grid-item {
|
||||
margin-bottom: 10px !important;
|
||||
padding: 15px;
|
||||
width: 250px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a.grid-item img {
|
||||
margin: 0 auto;
|
||||
} */
|
||||
.card.pad {
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
details summary {
|
||||
cursor: pointer;
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
.text-black {
|
||||
color: black !important;
|
||||
}
|
||||
|
||||
.btn {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.navbar-nav>a.btn {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.bg-custom {
|
||||
background-color: #9013FE;
|
||||
}
|
||||
/* ─── Utility ─────────────────────────────────────────────────────── */
|
||||
.card.pad { padding: 12px; margin-bottom: 12px; border: 1px solid var(--gw-border); border-radius: 8px; box-shadow: none; }
|
||||
details summary { cursor: pointer; display: list-item; }
|
||||
.text-black { color: black !important; }
|
||||
.bg-custom { background-color: var(--gw-brand); }
|
||||
.btn { margin-bottom: 4px; border-radius: 4px; font-family: var(--gw-font); font-weight: 500; letter-spacing: 0.01em; }
|
||||
.btn-primary { background-color: var(--gw-blue); border-color: var(--gw-blue); }
|
||||
.btn-primary:hover { background-color: var(--gw-blue-hover); border-color: var(--gw-blue-hover); }
|
||||
.navbar-nav > a.btn { margin-right: 10px; }
|
||||
|
||||
/* ─── App shell ───────────────────────────────────────────────────── */
|
||||
.app-shell {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
min-height: calc(100vh - var(--gw-header-h));
|
||||
background: var(--gw-bg);
|
||||
}
|
||||
|
||||
.sidebar-toggle-input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
/* ─── Sidebar toggle (hidden checkbox) ───────────────────────────── */
|
||||
.sidebar-toggle-input { position: absolute; opacity: 0; pointer-events: none; }
|
||||
|
||||
/* ─── Sidebar ─────────────────────────────────────────────────────── */
|
||||
.sidebar {
|
||||
width: 260px;
|
||||
background: #ffffff;
|
||||
border-right: 1px solid #e5e7eb;
|
||||
padding: 20px 16px;
|
||||
width: var(--gw-sidebar-w);
|
||||
background: var(--gw-surface);
|
||||
padding: 8px 0;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
top: var(--gw-header-h);
|
||||
height: calc(100vh - var(--gw-header-h));
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
transition: width 0.25s ease, transform 0.25s ease, padding 0.25s ease, opacity 0.2s ease;
|
||||
gap: 0;
|
||||
transition: width 0.3s cubic-bezier(0.4,0,0.2,1),
|
||||
padding 0.3s cubic-bezier(0.4,0,0.2,1),
|
||||
opacity 0.2s ease;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebar-toggle-input:not(:checked)~.app-shell .sidebar {
|
||||
.sidebar-toggle-input:not(:checked) ~ .app-shell .sidebar {
|
||||
width: 0;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
border-right: none;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.sidebar-brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-weight: 600;
|
||||
color: #202124;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.sidebar-brand img {
|
||||
height: 34px;
|
||||
.sidebar-section-label {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--gw-text-secondary);
|
||||
padding: 16px 16px 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
gap: 2px;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.sidebar-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 10px;
|
||||
border-radius: 12px;
|
||||
gap: 16px;
|
||||
padding: 0 16px;
|
||||
height: 48px;
|
||||
border-radius: 24px;
|
||||
text-decoration: none;
|
||||
color: #202124;
|
||||
background: #f8f9fa;
|
||||
outline: 1px solid grey;
|
||||
color: var(--gw-text-primary);
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
white-space: nowrap;
|
||||
transition: background 0.15s ease;
|
||||
border: 1px solid grey;
|
||||
}
|
||||
.sidebar-link:hover { background: var(--gw-hover); color: var(--gw-text-primary); text-decoration: none; }
|
||||
.sidebar-link.active, .sidebar-link:focus-visible { background: var(--gw-blue-light); color: var(--gw-blue); font-weight: 500; }
|
||||
|
||||
.sidebar-link img {
|
||||
height: 26px;
|
||||
}
|
||||
.sidebar-link img { height: 20px; width: 20px; object-fit: contain; flex-shrink: 0; }
|
||||
|
||||
.sidebar-note {
|
||||
font-size: 12px;
|
||||
color: #5f6368;
|
||||
}
|
||||
.sidebar-divider { height: 1px; background: var(--gw-border); margin: 8px 16px; }
|
||||
|
||||
.sidebar-backdrop {
|
||||
display: none;
|
||||
}
|
||||
.sidebar-backdrop { display: none; }
|
||||
|
||||
.app-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.axia-home {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 10px 16px 40px;
|
||||
}
|
||||
/* ─── App content ─────────────────────────────────────────────────── */
|
||||
.app-content { flex: 1; min-width: 0; }
|
||||
|
||||
/* ─── Top header ──────────────────────────────────────────────────── */
|
||||
.axia-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 18px;
|
||||
background: #ffffff;
|
||||
border-radius: 999px;
|
||||
padding: 10px 16px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
gap: 4px;
|
||||
background: var(--gw-surface);
|
||||
border-bottom: 1px solid var(--gw-border);
|
||||
padding: 0 8px;
|
||||
height: var(--gw-header-h);
|
||||
position: sticky;
|
||||
top: 10px;
|
||||
z-index: 5;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
box-shadow: 0 1px 2px 0 rgba(60,64,67,0.1);
|
||||
}
|
||||
|
||||
.logo-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-weight: 600;
|
||||
color: #202124;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.brand-logo {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.brand-text {
|
||||
gap: 6px;
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
color: var(--gw-text-secondary);
|
||||
text-decoration: none;
|
||||
padding: 0 8px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.logo-area:hover { color: var(--gw-text-primary); text-decoration: none; }
|
||||
|
||||
.brand-logo { height: 30px; }
|
||||
|
||||
.brand-text { font-size: 18px; font-weight: 400; letter-spacing: -0.01em; }
|
||||
|
||||
/* Sidebar toggle button */
|
||||
.sidebar-toggle {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
background: #f1f3f4;
|
||||
color: #5f6368;
|
||||
background: transparent;
|
||||
color: var(--gw-text-secondary);
|
||||
border: none;
|
||||
transition: background 0.15s ease;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar-toggle:hover { background: var(--gw-hover); }
|
||||
|
||||
.search-bar {
|
||||
flex: 1;
|
||||
}
|
||||
/* Search bar */
|
||||
.search-bar { flex: 1; max-width: 720px; margin: 0 auto; }
|
||||
|
||||
.search-bar form,
|
||||
.search-bar > form { display: flex; }
|
||||
|
||||
.search-bar input {
|
||||
width: 100%;
|
||||
border: none;
|
||||
background: #f1f3f4;
|
||||
padding: 10px 16px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--gw-border);
|
||||
background: var(--gw-hover);
|
||||
padding: 8px 20px;
|
||||
border-radius: 24px;
|
||||
outline: none;
|
||||
font-size: 15px;
|
||||
font-size: 16px;
|
||||
font-family: var(--gw-font);
|
||||
color: var(--gw-text-primary);
|
||||
transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
.search-bar input:focus {
|
||||
background: var(--gw-surface);
|
||||
border-color: var(--gw-blue);
|
||||
box-shadow: 0 1px 6px rgba(32,33,36,0.28);
|
||||
}
|
||||
.search-bar input::placeholder { color: var(--gw-text-secondary); }
|
||||
|
||||
.axia-header summary {
|
||||
list-style: none;
|
||||
}
|
||||
/* Header action area */
|
||||
.header-actions { display: flex; align-items: center; gap: 4px; margin-left: auto; }
|
||||
|
||||
.axia-header summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
.axia-header summary { list-style: none; }
|
||||
.axia-header summary::-webkit-details-marker { display: none; }
|
||||
|
||||
/* Icon button (for waffle, etc.) */
|
||||
.icon-button {
|
||||
list-style: none;
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 8px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
color: var(--gw-text-secondary);
|
||||
transition: background 0.15s ease;
|
||||
}
|
||||
.icon-button:hover { background: var(--gw-hover); }
|
||||
|
||||
/* Waffle 3×3 dot grid */
|
||||
.dot-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 4px);
|
||||
gap: 4px;
|
||||
padding: 2px;
|
||||
grid-template-columns: repeat(3, 5px);
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.dot-grid span {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
background: #5f6368;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
background: var(--gw-text-secondary);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
details {
|
||||
position: relative;
|
||||
}
|
||||
/* ─── Dropdown cards ──────────────────────────────────────────────── */
|
||||
details { position: relative; }
|
||||
|
||||
header .menu-card {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
margin-top: 10px;
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.12);
|
||||
padding: 16px;
|
||||
min-width: 240px;
|
||||
z-index: 10;
|
||||
top: calc(100% + 4px);
|
||||
background: var(--gw-surface);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.2), 0 0 0 1px rgba(0,0,0,0.06);
|
||||
padding: 12px 4px;
|
||||
min-width: 280px;
|
||||
z-index: 200;
|
||||
}
|
||||
|
||||
header .menu-card-title {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--gw-text-secondary);
|
||||
padding: 4px 16px 12px;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
header .menu-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
||||
gap: 12px;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
header .menu-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 10px;
|
||||
border-radius: 12px;
|
||||
gap: 6px;
|
||||
padding: 12px 8px;
|
||||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
color: #202124;
|
||||
background: #f8f9fa;
|
||||
outline: 1px solid grey;
|
||||
color: var(--gw-text-primary);
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
margin: 2px;
|
||||
transition: background 0.15s ease;
|
||||
}
|
||||
header .menu-item:hover { background: var(--gw-hover); text-decoration: none; color: var(--gw-text-primary); }
|
||||
|
||||
header .menu-item img {
|
||||
height: 28px;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border-radius: 8px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
header .menu-item span { line-height: 1.3; }
|
||||
|
||||
/* ─── Avatar ──────────────────────────────────────────────────────── */
|
||||
.avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background: #1a73e8;
|
||||
background: var(--gw-blue);
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
letter-spacing: 0.03em;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.avatar.big { width: 56px; height: 56px; font-size: 22px; }
|
||||
|
||||
.avatar.big {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.account-card {
|
||||
min-width: 280px;
|
||||
}
|
||||
/* ─── Account card ────────────────────────────────────────────────── */
|
||||
.account-card { min-width: 300px; }
|
||||
|
||||
.account-head {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.account-name {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.account-email {
|
||||
font-size: 13px;
|
||||
color: #5f6368;
|
||||
}
|
||||
|
||||
.account-actions .btn {
|
||||
gap: 8px;
|
||||
padding: 16px 16px 12px;
|
||||
border-bottom: 1px solid var(--gw-border);
|
||||
margin-bottom: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.account-name { font-weight: 500; font-size: 16px; }
|
||||
.account-email { font-size: 13px; color: var(--gw-text-secondary); }
|
||||
|
||||
.account-actions { padding: 0 12px 8px; }
|
||||
.account-actions .btn { margin-bottom: 6px; border-radius: 20px; font-size: 14px; }
|
||||
|
||||
/* ─── Main content ────────────────────────────────────────────────── */
|
||||
.axia-home {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 24px 24px 48px;
|
||||
}
|
||||
|
||||
/* ─── Mobile ──────────────────────────────────────────────────────── */
|
||||
@media (max-width: 768px) {
|
||||
.sidebar-toggle {
|
||||
display: inline-flex;
|
||||
/* make it more to the left */
|
||||
margin-left: -8px;
|
||||
}
|
||||
.axia-home { padding: 16px 12px 48px; }
|
||||
|
||||
.logo-area {
|
||||
gap: 6px;
|
||||
margin-left: -8px;
|
||||
margin-right: -8px;
|
||||
}
|
||||
|
||||
.axia-home {
|
||||
padding: 10px 8px 40px;
|
||||
}
|
||||
|
||||
.app-shell {
|
||||
display: block;
|
||||
}
|
||||
.app-shell { display: block; }
|
||||
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
@@ -451,19 +407,16 @@ if (!empty($displayName)) {
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.25s ease;
|
||||
z-index: 20;
|
||||
width: 260px;
|
||||
padding: 20px 16px;
|
||||
border-right: 1px solid #e5e7eb;
|
||||
transition: transform 0.25s cubic-bezier(0.4,0,0.2,1);
|
||||
z-index: 200;
|
||||
width: var(--gw-sidebar-w);
|
||||
padding: 8px 0;
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.sidebar-toggle-input:not(:checked)~.app-shell .sidebar {
|
||||
width: 260px;
|
||||
padding: 20px 16px;
|
||||
border-right: 1px solid #e5e7eb;
|
||||
.sidebar-toggle-input:not(:checked) ~ .app-shell .sidebar {
|
||||
width: var(--gw-sidebar-w);
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
@@ -471,52 +424,22 @@ if (!empty($displayName)) {
|
||||
.sidebar-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
background: rgba(0,0,0,0.32);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.25s ease;
|
||||
z-index: 15;
|
||||
z-index: 150;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sidebar-toggle-input:checked~.app-shell .sidebar {
|
||||
transform: translateX(0);
|
||||
}
|
||||
.sidebar-toggle-input:checked ~ .app-shell .sidebar { transform: translateX(0); }
|
||||
.sidebar-toggle-input:checked ~ .app-shell .sidebar-backdrop { opacity: 1; pointer-events: auto; }
|
||||
|
||||
.sidebar-toggle-input:checked~.app-shell .sidebar-backdrop {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.search-bar { display: none; }
|
||||
.header-actions { gap: 2px; }
|
||||
.hide-small { display: none; }
|
||||
|
||||
.axia-header {
|
||||
flex-wrap: wrap;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
width: 100%;
|
||||
order: 3;
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* make other buttons alinged to the right */
|
||||
.header-actions {
|
||||
margin-left: auto;
|
||||
margin-right: -8px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.hide-small {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
--bs-btn-font-family: Arial, Helvetica, sans-serif;
|
||||
--bs-body-font-family: Arial, Helvetica, sans-serif;
|
||||
--bs-font-sans-serif: Arial, Helvetica, sans-serif;
|
||||
--bs-font-family-base: Arial, Helvetica, sans-serif;
|
||||
--bs-heading-font-family: Arial, Helvetica, sans-serif;
|
||||
.logo-area { padding: 0 4px; }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -548,96 +471,116 @@ if (!empty($displayName)) {
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!-- ── Google Workspace-style top header ──────────────────── -->
|
||||
<header class="axia-header">
|
||||
<label for="sidebarToggle" class="sidebar-toggle" aria-label="Abrir menú">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/>
|
||||
</svg>
|
||||
</label>
|
||||
<a class="logo-area" href="<?= $APP_ROOT ?>">
|
||||
<img src="/static/<?= $APP_ICON ?>" alt="<?= htmlspecialchars($APP_NAME) ?>" class="brand-logo">
|
||||
<span class="brand-text"><?= $APP_NAME ?></span>
|
||||
</a>
|
||||
<div class="search-bar">
|
||||
<form action="https://search.tech.eus/s/" method="get">
|
||||
<input type="text" name="q" placeholder="Búsqueda global" aria-label="Buscar">
|
||||
</form>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<details class="app-menu">
|
||||
<summary class="icon-button" aria-label="Menú de aplicaciones">
|
||||
<span class="dot-grid" aria-hidden="true">
|
||||
<span></span><span></span><span></span>
|
||||
<span></span><span></span><span></span>
|
||||
<span></span><span></span><span></span>
|
||||
</span>
|
||||
</summary>
|
||||
<div class="menu-card">
|
||||
<div class="menu-card-title">Aplicaciones de Axia4</div>
|
||||
<div class="menu-grid">
|
||||
<a class="menu-item" href="/">
|
||||
<img src="/static/logo.png" alt="">
|
||||
<span>Axia4</span>
|
||||
</a>
|
||||
<a class="menu-item" href="/club/">
|
||||
<img src="/static/logo-club.png" alt="">
|
||||
<span>Club</span>
|
||||
</a>
|
||||
<a class="menu-item" href="/aulatek/">
|
||||
<img src="/static/logo-entreaulas.png" alt="">
|
||||
<span>AulaTek</span>
|
||||
</a>
|
||||
<a class="menu-item" href="/account/">
|
||||
<img src="/static/logo-account.png" alt="">
|
||||
<span>Cuenta</span>
|
||||
</a>
|
||||
<a class="menu-item" href="/sysadmin/">
|
||||
<img src="/static/logo-sysadmin.png" alt="">
|
||||
<span>SysAdmin</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
<details class="account-menu">
|
||||
<summary class="avatar" aria-label="Cuenta">
|
||||
<?php echo htmlspecialchars($initials); ?>
|
||||
</summary>
|
||||
<div class="menu-card account-card">
|
||||
<div class="account-head">
|
||||
<div class="avatar big"><?php echo htmlspecialchars($initials); ?></div>
|
||||
<div class="account-name"><?php echo htmlspecialchars($displayName); ?></div>
|
||||
<div class="account-email"><?php echo htmlspecialchars($email); ?></div>
|
||||
</div>
|
||||
<?php if (!empty($userOrganizaciones) && $_SESSION["auth_ok"]): ?>
|
||||
<div style="padding: 8px 16px;">
|
||||
<div style="font-size:.75rem;font-weight:600;color:#5f6368;text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px;">
|
||||
Organización activa
|
||||
</div>
|
||||
<div style="font-size:.9rem;font-weight:600;color:#1a73e8;margin-bottom:<?= count($userOrganizaciones) > 1 ? '8px' : '0' ?>;">
|
||||
<?= htmlspecialchars($activeOrganizacionName ?: '–') ?>
|
||||
</div>
|
||||
<?php if (count($userOrganizaciones) > 1): ?>
|
||||
<div style="font-size:.75rem;color:#5f6368;margin-bottom:4px;">Cambiar organización:</div>
|
||||
<?php foreach ($userOrganizaciones as $oid => $orgName): if ($oid === $activeOrganizacionId) continue; ?>
|
||||
<form method="post" action="/_incl/switch_tenant.php" style="margin:0 0 4px;">
|
||||
<input type="hidden" name="redir" value="<?= htmlspecialchars($_SERVER['REQUEST_URI'] ?? '/') ?>">
|
||||
<button type="submit" name="organization" value="<?= htmlspecialchars($oid) ?>"
|
||||
style="display:block;width:100%;text-align:left;padding:5px 8px;border:1px solid #e0e0e0;border-radius:6px;background:#f8f9fa;font-size:.85rem;cursor:pointer;">
|
||||
<?= htmlspecialchars($orgName) ?>
|
||||
</button>
|
||||
</form>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="account-actions">
|
||||
<?php if ($_SESSION["auth_ok"]) { ?>
|
||||
<a href="/account/" class="btn btn-outline-dark w-100">Gestionar cuenta</a>
|
||||
<a href="/_login.php?logout=1&redir=/" class="btn btn-outline-danger w-100">Cerrar sesión</a>
|
||||
<?php } else { ?>
|
||||
<a href="/_login.php?redir=/" class="btn btn-primary w-100">Iniciar sesión</a>
|
||||
<a href="/account/register.php" class="btn btn-outline-dark w-100">Crear cuenta</a>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- ── App shell (sidebar + content) ──────────────────────── -->
|
||||
<div class="app-shell">
|
||||
<aside class="sidebar">
|
||||
<b>Esta app</b>
|
||||
<nav class="sidebar-nav">
|
||||
<?php
|
||||
if (file_exists(__DIR__ . "/../$APP_CODE/__menu.php")) {
|
||||
include __DIR__ . "/../$APP_CODE/__menu.php";
|
||||
}
|
||||
?>
|
||||
</nav>
|
||||
<b>Axia4</b>
|
||||
<nav class="sidebar-nav">
|
||||
<a class="sidebar-link" href="/">
|
||||
<img src="/static/logo.png" alt="">
|
||||
<span>Inicio</span>
|
||||
</a>
|
||||
</nav>
|
||||
<?php
|
||||
if (file_exists(__DIR__ . "/../$APP_CODE/__menu.php")) {
|
||||
include __DIR__ . "/../$APP_CODE/__menu.php";
|
||||
}
|
||||
?>
|
||||
</aside>
|
||||
<label for="sidebarToggle" class="sidebar-backdrop" aria-hidden="true"></label>
|
||||
<div class="app-content">
|
||||
<main class="axia-home">
|
||||
<header class="axia-header">
|
||||
<label for="sidebarToggle" class="sidebar-toggle" aria-label="Abrir menú">☰</label>
|
||||
<a class="logo-area" href="<?= $APP_ROOT ?>">
|
||||
<img src="/static/<?= $APP_ICON ?>" alt="<?= htmlspecialchars($APP_NAME) ?>" class="brand-logo">
|
||||
<span class="brand-text"><?= $APP_NAME ?></span>
|
||||
</a>
|
||||
<form class="search-bar" action="https://search.tech.eus/s/" method="get">
|
||||
<input type="text" name="q" placeholder="Busqueda Global" aria-label="Buscar">
|
||||
</form>
|
||||
<div class="header-actions">
|
||||
<details class="app-menu">
|
||||
<summary class="icon-button" aria-label="Menú de aplicaciones">
|
||||
<span class="dot-grid" aria-hidden="true">
|
||||
<span></span><span></span><span></span>
|
||||
<span></span><span></span><span></span>
|
||||
<span></span><span></span><span></span>
|
||||
</span>
|
||||
</summary>
|
||||
<div class="menu-card">
|
||||
<div class="menu-grid">
|
||||
<a class="menu-item" href="/">
|
||||
<img src="/static/logo.png" alt="">
|
||||
<span>Axia4</span>
|
||||
</a>
|
||||
<a class="menu-item" href="/club/">
|
||||
<img src="/static/logo-club.png" alt="">
|
||||
<span>Club</span>
|
||||
</a>
|
||||
<a class="menu-item" href="/entreaulas/">
|
||||
<img src="/static/logo-entreaulas.png" alt="">
|
||||
<span>EntreAulas</span>
|
||||
</a>
|
||||
<a class="menu-item" href="/account/">
|
||||
<img src="/static/logo-account.png" alt="">
|
||||
<span>Cuenta</span>
|
||||
</a>
|
||||
<a class="menu-item" href="/sysadmin/">
|
||||
<img src="/static/logo-sysadmin.png" alt="">
|
||||
<span>SysAdmin</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
<details class="account-menu">
|
||||
<summary class="avatar" aria-label="Cuenta">
|
||||
<?php echo htmlspecialchars($initials); ?>
|
||||
</summary>
|
||||
<div class="menu-card account-card">
|
||||
<div class="account-head">
|
||||
<div class="avatar big"><?php echo htmlspecialchars($initials); ?></div>
|
||||
<div>
|
||||
<div class="account-name"><?php echo htmlspecialchars($displayName); ?></div>
|
||||
<div class="account-email"><?php echo htmlspecialchars($email); ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="account-actions">
|
||||
<?php if ($_SESSION["auth_ok"]) { ?>
|
||||
<a href="/account/" class="btn btn-primary w-100">Gestionar cuenta</a>
|
||||
<a href="/_login.php?logout=1&redir=/" class="btn btn-outline-secondary w-100">Cerrar sesión</a>
|
||||
<?php } else { ?>
|
||||
<a href="/_login.php?redir=/" class="btn btn-primary w-100">Iniciar sesión</a>
|
||||
<a href="/account/register.php" class="btn btn-outline-primary w-100">Crear cuenta</a>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</header>
|
||||
<div style="margin-top: 20px;">
|
||||
<?php } ?>
|
||||
<?php if (isset($_GET["_result"])) { ?>
|
||||
<div class="card pad"
|
||||
|
||||
42
public_html/_incl/switch_tenant.php
Normal file
42
public_html/_incl/switch_tenant.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* switch_organization.php
|
||||
* POST endpoint to switch the active organization for the current user session.
|
||||
* Validates the requested organization against the user's allowed organizations before applying.
|
||||
*/
|
||||
require_once "tools.session.php";
|
||||
require_once "tools.security.php";
|
||||
require_once "db.php";
|
||||
|
||||
if (!isset($_SESSION["auth_ok"]) || $_SESSION["auth_ok"] !== true) {
|
||||
header("HTTP/1.1 401 Unauthorized");
|
||||
die("No autenticado.");
|
||||
}
|
||||
|
||||
$requested = safe_organization_id(
|
||||
$_POST['organization']
|
||||
?? $_POST['organizacion']
|
||||
?? $_POST['org']
|
||||
?? $_POST['centro']
|
||||
?? ''
|
||||
);
|
||||
$redir = safe_redir($_POST['redir'] ?? '/');
|
||||
|
||||
$organizations = get_user_organizations($_SESSION['auth_data'] ?? []);
|
||||
|
||||
if ($requested !== '' && in_array($requested, $organizations, true)) {
|
||||
$_SESSION['active_organization'] = $requested;
|
||||
$_SESSION['active_organizacion'] = $requested;
|
||||
$_SESSION['active_centro'] = $requested;
|
||||
// Also update session auth_data so it reflects immediately
|
||||
$_SESSION['auth_data']['active_organization'] = $requested;
|
||||
$_SESSION['auth_data']['aulatek']['organizacion'] = $requested;
|
||||
$_SESSION['auth_data']['aulatek']['organization'] = $requested;
|
||||
$_SESSION['auth_data']['aulatek']['centro'] = $requested;
|
||||
$_SESSION['auth_data']['entreaulas']['organizacion'] = $requested;
|
||||
$_SESSION['auth_data']['entreaulas']['organization'] = $requested;
|
||||
$_SESSION['auth_data']['entreaulas']['centro'] = $requested;
|
||||
}
|
||||
|
||||
header("Location: $redir");
|
||||
exit;
|
||||
@@ -1,87 +1,106 @@
|
||||
<?php
|
||||
require_once "tools.session.php";
|
||||
require_once "tools.security.php";
|
||||
require_once __DIR__ . "/db.php";
|
||||
|
||||
// Load auth config from DB (replaces /DATA/AuthConfig.json)
|
||||
if (!isset($AuthConfig)) {
|
||||
$AuthConfig = json_decode(file_get_contents("/DATA/AuthConfig.json"), true);
|
||||
$AuthConfig = db_get_all_config();
|
||||
}
|
||||
$ua = $_SERVER['HTTP_USER_AGENT'];
|
||||
|
||||
// ── Header-based auth (Axia4Auth/{user}/{pass}) ───────────────────────────────
|
||||
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
|
||||
if (str_starts_with($ua, "Axia4Auth/")) {
|
||||
$username = explode("/", $ua)[1];
|
||||
$userpass = explode("/", $ua)[2];
|
||||
$user_filename = safe_username_to_filename($username);
|
||||
if ($user_filename === "") {
|
||||
$parts = explode("/", $ua);
|
||||
$username = $parts[1] ?? '';
|
||||
$userpass = $parts[2] ?? '';
|
||||
$row = db_get_user($username);
|
||||
if (!$row || !password_verify($userpass, $row['password_hash'])) {
|
||||
header("HTTP/1.1 403 Forbidden");
|
||||
die();
|
||||
}
|
||||
$userdata = json_decode(file_get_contents("/DATA/Usuarios/" . $user_filename . ".json"), true);
|
||||
if (!$userdata) {
|
||||
header("HTTP/1.1 403 Forbidden");
|
||||
die();
|
||||
}
|
||||
if (!password_verify($userpass, $userdata["password_hash"])) {
|
||||
header("HTTP/1.1 403 Forbidden");
|
||||
die();
|
||||
}
|
||||
$_SESSION["auth_user"] = $username;
|
||||
$_SESSION["auth_data"] = $userdata;
|
||||
$_SESSION["auth_ok"] = true;
|
||||
$_COOKIE["auth_user"] = $username;
|
||||
$_COOKIE["auth_pass_b64"] = base64_encode($userpass);
|
||||
$_SESSION["auth_external_lock"] = "header"; // Cannot logout because auth is done via header
|
||||
$_SESSION["auth_user"] = $username;
|
||||
$_SESSION["auth_data"] = db_build_auth_data($row);
|
||||
$_SESSION["auth_ok"] = true;
|
||||
$_SESSION["auth_external_lock"] = "header";
|
||||
init_active_org($_SESSION["auth_data"]);
|
||||
}
|
||||
|
||||
// If $_SESSION is empty, check for cookies "auth_user" and "auth_pass_b64"
|
||||
if ($_SESSION["auth_ok"] != true && isset($_COOKIE["auth_user"]) && isset($_COOKIE["auth_pass_b64"])) {
|
||||
$username = $_COOKIE["auth_user"];
|
||||
$userpass_b64 = $_COOKIE["auth_pass_b64"];
|
||||
$userpass = base64_decode($userpass_b64);
|
||||
$user_filename = safe_username_to_filename($username);
|
||||
if ($user_filename !== "") {
|
||||
$userdata = json_decode(file_get_contents("/DATA/Usuarios/" . $user_filename . ".json"), true);
|
||||
if ($userdata && password_verify($userpass, $userdata["password_hash"])) {
|
||||
// ── Remember-token auto-login ─────────────────────────────────────────────────
|
||||
// Restores the session from the opaque auth_token cookie (no password stored).
|
||||
if (($_SESSION["auth_ok"] ?? false) != true && isset($_COOKIE["auth_token"])) {
|
||||
$expired = ["expires" => time() - 3600, "path" => "/", "httponly" => true,
|
||||
"secure" => true, "samesite" => "Lax"];
|
||||
$raw_token = $_COOKIE["auth_token"];
|
||||
$token_hash = hash('sha256', $raw_token);
|
||||
$sess_row = db_restore_session_by_remember_token($token_hash);
|
||||
if ($sess_row) {
|
||||
$username = $sess_row['username'];
|
||||
$row = db_get_user($username);
|
||||
if ($row) {
|
||||
$_SESSION["auth_user"] = $username;
|
||||
$_SESSION["auth_data"] = $userdata;
|
||||
$_SESSION["auth_ok"] = true;
|
||||
$_SESSION["auth_data"] = db_build_auth_data($row);
|
||||
$_SESSION["auth_ok"] = true;
|
||||
if (empty($_SESSION["session_created"])) {
|
||||
$_SESSION["session_created"] = time();
|
||||
}
|
||||
init_active_org($_SESSION["auth_data"]);
|
||||
} else {
|
||||
// User no longer exists — clear the stale cookie
|
||||
setcookie("auth_token", "", $expired);
|
||||
}
|
||||
} else {
|
||||
// Token not found (revoked or expired) — clear the stale cookie
|
||||
setcookie("auth_token", "", $expired);
|
||||
}
|
||||
}
|
||||
|
||||
// If session is older than 5min, reload user data
|
||||
if (isset($_SESSION["auth_ok"]) && $_SESSION["auth_ok"] && isset($_SESSION["auth_user"])) {
|
||||
if (isset($AuthConfig["session_load_mode"]) && $AuthConfig["session_load_mode"] === "force") {
|
||||
$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;
|
||||
// ── Validate session is still active in user_sessions (enables revocation) ───
|
||||
if (!empty($_SESSION["auth_ok"]) && !empty($_SESSION["auth_user"])
|
||||
&& empty($_SESSION["auth_external_lock"])
|
||||
) {
|
||||
if (!db_session_is_valid($_SESSION["auth_user"])) {
|
||||
session_unset();
|
||||
session_destroy();
|
||||
header("Location: /_login.php?_result=" . urlencode("Tu sesión fue revocada. Inicia sesión de nuevo."));
|
||||
die();
|
||||
}
|
||||
}
|
||||
|
||||
// ── Periodic session reload from DB ──────────────────────────────────────────
|
||||
if (!empty($_SESSION["auth_ok"]) && !empty($_SESSION["auth_user"])) {
|
||||
$load_mode = $AuthConfig["session_load_mode"] ?? '';
|
||||
if ($load_mode === "force") {
|
||||
$row = db_get_user($_SESSION["auth_user"]);
|
||||
if ($row) {
|
||||
$_SESSION["auth_data"] = db_build_auth_data($row);
|
||||
init_active_org($_SESSION["auth_data"]);
|
||||
}
|
||||
$_SESSION["last_reload_time"] = time();
|
||||
} elseif (isset($AuthConfig["session_load_mode"]) && $AuthConfig["session_load_mode"] === "never") {
|
||||
// Do nothing, never reload session data
|
||||
} else {
|
||||
if (isset($_SESSION["last_reload_time"])) {
|
||||
$last_reload = $_SESSION["last_reload_time"];
|
||||
if (time() - $last_reload > 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();
|
||||
db_touch_session();
|
||||
} elseif ($load_mode !== "never") {
|
||||
$last = $_SESSION["last_reload_time"] ?? 0;
|
||||
if (time() - $last > 300) {
|
||||
$row = db_get_user($_SESSION["auth_user"]);
|
||||
if ($row) {
|
||||
$_SESSION["auth_data"] = db_build_auth_data($row);
|
||||
init_active_org($_SESSION["auth_data"]);
|
||||
}
|
||||
} else {
|
||||
$_SESSION["last_reload_time"] = time();
|
||||
db_touch_session();
|
||||
}
|
||||
if (!isset($_SESSION["last_reload_time"])) {
|
||||
$_SESSION["last_reload_time"] = time();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function user_is_authenticated()
|
||||
function user_is_authenticated(): bool
|
||||
{
|
||||
return isset($_SESSION["auth_ok"]) && $_SESSION["auth_ok"] === true;
|
||||
}
|
||||
function user_has_permission($perm)
|
||||
|
||||
function user_has_permission(string $perm): bool
|
||||
{
|
||||
return in_array($perm, $_SESSION["auth_data"]["permissions"] ?? []);
|
||||
return in_array($perm, $_SESSION["auth_data"]["permissions"] ?? [], true);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
function Ssql($string) {
|
||||
// Sanitize a SQL Parameter to be safe on html.
|
||||
return htmlspecialchars($string);
|
||||
}
|
||||
|
||||
function Sf($filename) {
|
||||
/**
|
||||
* Sanitize a filename by removing any path information, null bytes, and replacing any characters that are not alphanumeric, dot, hyphen, or underscore with an underscore.
|
||||
@@ -108,12 +113,23 @@ function Sb($input) {
|
||||
}
|
||||
function get_user_file_path($username)
|
||||
{
|
||||
return USERS_DIR . $username . '.json';
|
||||
$users_dir = defined('USERS_DIR') ? USERS_DIR : '/DATA/Usuarios/';
|
||||
return rtrim($users_dir, '/') . '/' . $username . '.json';
|
||||
}
|
||||
|
||||
function safe_organization_id($value)
|
||||
{
|
||||
return preg_replace('/[^a-zA-Z0-9._-]/', '', (string)$value);
|
||||
}
|
||||
|
||||
function safe_organizacion_id($value)
|
||||
{
|
||||
return safe_organization_id($value);
|
||||
}
|
||||
|
||||
function safe_centro_id($value)
|
||||
{
|
||||
return preg_replace('/[^a-zA-Z0-9._-]/', '', (string)$value);
|
||||
return safe_organization_id($value);
|
||||
}
|
||||
|
||||
function safe_aulario_id($value)
|
||||
@@ -124,9 +140,62 @@ function safe_aulario_id($value)
|
||||
|
||||
function safe_filename($name)
|
||||
{
|
||||
// Normalize to base name to avoid directory traversal
|
||||
$name = basename((string)$name);
|
||||
|
||||
// Best-effort normalize encoding to avoid odd Unicode tricks
|
||||
if (function_exists('mb_convert_encoding')) {
|
||||
$name = mb_convert_encoding($name, 'UTF-8', 'UTF-8');
|
||||
}
|
||||
|
||||
// Replace disallowed characters with underscore
|
||||
$name = preg_replace('/[^A-Za-z0-9._-]/', '_', $name);
|
||||
// Collapse multiple underscores introduced by replacement
|
||||
$name = preg_replace('/_+/', '_', $name);
|
||||
|
||||
// Remove leading dots to avoid hidden/special files like ".htaccess"
|
||||
$name = ltrim($name, '.');
|
||||
|
||||
// Ensure there is at most one dot in the filename to prevent extension confusion
|
||||
if (substr_count($name, '.') > 1) {
|
||||
$parts = explode('.', $name);
|
||||
$ext = array_pop($parts);
|
||||
$base = implode('_', $parts);
|
||||
// Ensure extension is not empty
|
||||
if ($ext === '') {
|
||||
$name = $base === '' ? 'file' : $base;
|
||||
} else {
|
||||
$name = ($base === '' ? 'file' : $base) . '.' . $ext;
|
||||
}
|
||||
}
|
||||
|
||||
// Trim stray dots/underscores from the start and end
|
||||
$name = trim($name, "._");
|
||||
|
||||
// Enforce a maximum length (common filesystem limit is 255 bytes)
|
||||
$maxLen = 255;
|
||||
if (strlen($name) > $maxLen) {
|
||||
$dotPos = strrpos($name, '.');
|
||||
if ($dotPos !== false) {
|
||||
$ext = substr($name, $dotPos);
|
||||
$base = substr($name, 0, $dotPos);
|
||||
$baseMaxLen = $maxLen - strlen($ext);
|
||||
if ($baseMaxLen < 1) {
|
||||
// Fallback if extension is unusually long
|
||||
$name = substr($name, 0, $maxLen);
|
||||
} else {
|
||||
$name = substr($base, 0, $baseMaxLen) . $ext;
|
||||
}
|
||||
} else {
|
||||
$name = substr($name, 0, $maxLen);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure we never return an empty or invalid filename
|
||||
if ($name === '' || $name === '.' || $name === '..') {
|
||||
$name = 'file';
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
function safe_id_segment($value)
|
||||
@@ -156,12 +225,42 @@ function path_is_within($real_base, $real_path)
|
||||
return strpos($real_path, $base_prefix) === 0 || $real_path === rtrim($real_base, DIRECTORY_SEPARATOR);
|
||||
}
|
||||
|
||||
function aulatek_orgs_base_path()
|
||||
{
|
||||
$orgs_path = '/DATA/entreaulas/Organizaciones';
|
||||
$legacy_path = '/DATA/entreaulas/Centros';
|
||||
if (is_dir($orgs_path)) {
|
||||
return $orgs_path;
|
||||
}
|
||||
if (is_dir($legacy_path)) {
|
||||
return $legacy_path;
|
||||
}
|
||||
return $orgs_path;
|
||||
}
|
||||
|
||||
function entreaulas_orgs_base_path()
|
||||
{
|
||||
return aulatek_orgs_base_path();
|
||||
}
|
||||
|
||||
function safe_aulario_config_path($centro_id, $aulario_id)
|
||||
{
|
||||
$centro = safe_centro_id($centro_id);
|
||||
$centro = safe_organization_id($centro_id);
|
||||
$aulario = safe_id_segment($aulario_id);
|
||||
if ($centro === '' || $aulario === '') {
|
||||
return null;
|
||||
}
|
||||
return "/DATA/entreaulas/Centros/$centro/Aularios/$aulario.json";
|
||||
return aulatek_orgs_base_path() . "/$centro/Aularios/$aulario.json";
|
||||
}
|
||||
|
||||
function safe_redir($url, $default = "/")
|
||||
{
|
||||
if (empty($url) || !is_string($url)) {
|
||||
return $default;
|
||||
}
|
||||
// Only allow relative URLs that start with /
|
||||
if (str_starts_with($url, "/") && !str_contains($url, "\0")) {
|
||||
return $url;
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
@@ -1,4 +1,9 @@
|
||||
<?php
|
||||
session_start([ 'cookie_lifetime' => 604800 ]);
|
||||
ini_set("session.use_only_cookies", "true");
|
||||
ini_set("session.use_trans_sid", "false");
|
||||
ini_set("session.use_only_cookies", "1");
|
||||
ini_set("session.use_trans_sid", "0");
|
||||
session_start([
|
||||
'cookie_lifetime' => 604800,
|
||||
'cookie_httponly' => true,
|
||||
'cookie_secure' => true,
|
||||
'cookie_samesite' => 'Lax',
|
||||
]);
|
||||
|
||||
@@ -1,28 +1,25 @@
|
||||
<?php
|
||||
if (file_exists("/DATA/SISTEMA_INSTALADO.txt")) {
|
||||
require_once "_incl/db.php";
|
||||
if (strval(db_get_config('installed')) === '1') {
|
||||
header("Location: /");
|
||||
die();
|
||||
}
|
||||
|
||||
switch ($_GET['form'] ?? '') {
|
||||
case 'create_admin':
|
||||
$admin_user = trim(strtolower($_POST['admin_user'] ?? ''));
|
||||
$admin_user = trim(strtolower($_POST['admin_user'] ?? ''));
|
||||
$admin_password = $_POST['admin_password'] ?? '';
|
||||
if (empty($admin_user) || empty($admin_password)) {
|
||||
die("El nombre de usuario y la contraseña son obligatorios.");
|
||||
}
|
||||
$password_hash = password_hash($admin_password, PASSWORD_DEFAULT);
|
||||
$admin_userdata = [
|
||||
'display_name' => 'Administrador',
|
||||
'email' => "$admin_user@nomail.arpa",
|
||||
'permissions' => ['*', 'sysadmin:access', 'entreaulas:access'],
|
||||
'password_hash' => $password_hash
|
||||
];
|
||||
if (!is_dir("/DATA/Usuarios")) {
|
||||
mkdir("/DATA/Usuarios", 0777, true);
|
||||
}
|
||||
file_put_contents("/DATA/Usuarios/$admin_user.json", json_encode($admin_userdata, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
file_put_contents("/DATA/SISTEMA_INSTALADO.txt", "Sistema instalado el ".date("Y-m-d H:i:s")."\n");
|
||||
db_upsert_user([
|
||||
'username' => $admin_user,
|
||||
'display_name' => 'Administrador',
|
||||
'email' => "$admin_user@nomail.arpa",
|
||||
'permissions' => ['*', 'sysadmin:access', 'aulatek:access'],
|
||||
'password_hash' => password_hash($admin_password, PASSWORD_DEFAULT),
|
||||
]);
|
||||
db_set_config('installed', '1');
|
||||
header("Location: /_login.php");
|
||||
exit;
|
||||
break;
|
||||
|
||||
123
public_html/_login.php
Executable file → Normal file
123
public_html/_login.php
Executable file → Normal file
@@ -1,38 +1,27 @@
|
||||
<?php
|
||||
require_once "_incl/tools.session.php";
|
||||
require_once "_incl/tools.security.php";
|
||||
require_once "_incl/db.php";
|
||||
if (!isset($AuthConfig)) {
|
||||
$AuthConfig = json_decode(file_get_contents("/DATA/AuthConfig.json"), true);
|
||||
$AuthConfig = db_get_all_config();
|
||||
}
|
||||
$DOMAIN = $_SERVER["HTTP_X_FORWARDED_HOST"] ?? $_SERVER["HTTP_HOST"];
|
||||
|
||||
/**
|
||||
* Return a safe redirect URL: only allow relative paths starting with a single slash.
|
||||
* Falls back to "/" for any external, protocol-relative, or otherwise unsafe URLs.
|
||||
*/
|
||||
function safe_redir($url) {
|
||||
$url = (string)$url;
|
||||
// Must start with a single "/" but not "//" (protocol-relative)
|
||||
if (preg_match('#^/[^/]#', $url) || $url === '/') {
|
||||
// Strip newlines to prevent header injection
|
||||
return preg_replace('/[\r\n]/', '', $url);
|
||||
}
|
||||
return '/';
|
||||
}
|
||||
// safe_redir() is provided by _incl/tools.security.php.
|
||||
|
||||
if ($_GET["reload_user"] == "1") {
|
||||
$user_filename = safe_username_to_filename($_SESSION["auth_user"] ?? "");
|
||||
if ($user_filename === "") {
|
||||
if (($_GET["reload_user"] ?? "") === "1") {
|
||||
$row = db_get_user($_SESSION["auth_user"] ?? "");
|
||||
if (!$row) {
|
||||
header("Location: /");
|
||||
die();
|
||||
}
|
||||
$userdata = json_decode(file_get_contents("/DATA/Usuarios/" . $user_filename . ".json"), true);
|
||||
$_SESSION['auth_data'] = $userdata;
|
||||
$_SESSION['auth_data'] = db_build_auth_data($row);
|
||||
init_active_org($_SESSION['auth_data']);
|
||||
$redir = safe_redir($_GET["redir"] ?? "/");
|
||||
header("Location: $redir");
|
||||
die();
|
||||
}
|
||||
if ($_GET["google_callback"] == "1") {
|
||||
if (($_GET["google_callback"] ?? "") === "1") {
|
||||
if (!isset($AuthConfig["google_client_id"]) || !isset($AuthConfig["google_client_secret"])) {
|
||||
die("Error: La autenticación de Google no está configurada.");
|
||||
}
|
||||
@@ -83,41 +72,46 @@ if ($_GET["google_callback"] == "1") {
|
||||
}
|
||||
|
||||
$email = $user_info["email"];
|
||||
$name = $user_info["name"] ?? explode("@", $email)[0];
|
||||
$user_filename = safe_username_to_filename($email);
|
||||
if ($user_filename === "") {
|
||||
$name = $user_info["name"] ?? explode("@", $email)[0];
|
||||
$username = strtolower($email);
|
||||
if ($username === "") {
|
||||
die("Error: Dirección de correo inválida.");
|
||||
}
|
||||
$userfile = "/DATA/Usuarios/" . $user_filename . ".json";
|
||||
$password = bin2hex(random_bytes(16)); // Generar una contraseña aleatoria para el usuario, aunque no se usará para iniciar sesión
|
||||
if (file_exists($userfile)) {
|
||||
$userdata = json_decode(file_get_contents($userfile), true);
|
||||
$password = bin2hex(random_bytes(16));
|
||||
$existing = db_get_user($username);
|
||||
if ($existing) {
|
||||
$user_row = $existing;
|
||||
} else {
|
||||
$userdata = [
|
||||
"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.",
|
||||
];
|
||||
file_put_contents($userfile, json_encode($userdata));
|
||||
db_upsert_user([
|
||||
'username' => explode("@", $email)[0] + uniqid(),
|
||||
'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;
|
||||
$_SESSION['session_created'] = time();
|
||||
init_active_org($_SESSION['auth_data']);
|
||||
$remember_token = bin2hex(random_bytes(32));
|
||||
$remember_token_hash = hash('sha256', $remember_token);
|
||||
db_register_session($username, $remember_token_hash);
|
||||
$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_token", $remember_token, $cookie_options);
|
||||
|
||||
$redir = safe_redir($state["redir"] ?? "/");
|
||||
|
||||
header("Location: $redir");
|
||||
die();
|
||||
}
|
||||
if ($_GET["google"] == "1") {
|
||||
if (($_GET["google"] ?? "") === "1") {
|
||||
if (!isset($AuthConfig["google_client_id"]) || !isset($AuthConfig["google_client_secret"])) {
|
||||
die("Error: La autenticación de Google no está configurada.");
|
||||
}
|
||||
@@ -145,53 +139,66 @@ if ($_GET["google"] == "1") {
|
||||
header("Location: " . $request_to);
|
||||
die();
|
||||
}
|
||||
if ($_GET["logout"] == "1") {
|
||||
if (($_GET["logout"] ?? "") === "1") {
|
||||
$redir = safe_redir($_GET["redir"] ?? "/");
|
||||
$cookie_options_expired = ["expires" => time() - 3600, "path" => "/", "httponly" => true, "secure" => true, "samesite" => "Lax"];
|
||||
setcookie("auth_user", "", $cookie_options_expired);
|
||||
setcookie("auth_pass_b64", "", $cookie_options_expired);
|
||||
setcookie("auth_token", "", $cookie_options_expired);
|
||||
db_delete_session();
|
||||
session_unset();
|
||||
session_destroy();
|
||||
header("Location: $redir");
|
||||
die();
|
||||
}
|
||||
if ($_GET["clear_session"] == "1") {
|
||||
if (($_GET["clear_session"] ?? "") === "1") {
|
||||
session_unset();
|
||||
session_destroy();
|
||||
$redir = safe_redir($_GET["redir"] ?? "/");
|
||||
header("Location: $redir");
|
||||
die();
|
||||
}
|
||||
if (isset($_POST["user"])) {
|
||||
$valid = "";
|
||||
$user = trim(strtolower($_POST["user"]));
|
||||
$user = trim(strtolower($_POST["user"]));
|
||||
$password = $_POST["password"];
|
||||
$user_filename = safe_username_to_filename($user);
|
||||
$userdata = ($user_filename !== "") ? json_decode(@file_get_contents("/DATA/Usuarios/" . $user_filename . ".json"), true) : null;
|
||||
if (!is_array($userdata) || !isset($userdata["password_hash"])) {
|
||||
// Validate CSRF token
|
||||
$csrf_token = $_POST["_csrf"] ?? "";
|
||||
if (!$csrf_token || !isset($_SESSION["login_csrf"]) || !hash_equals($_SESSION["login_csrf"], $csrf_token)) {
|
||||
$_GET["_result"] = "Token de seguridad inválido. Por favor, recarga la página e inténtalo de nuevo.";
|
||||
} else {
|
||||
$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"])) {
|
||||
$remember_token = bin2hex(random_bytes(32));
|
||||
$remember_token_hash = hash('sha256', $remember_token);
|
||||
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;
|
||||
$_SESSION['session_created'] = time();
|
||||
init_active_org($_SESSION['auth_data']);
|
||||
db_register_session($user, $remember_token_hash);
|
||||
$cookie_options = ["expires" => time() + (86400 * 30), "path" => "/", "httponly" => true, "secure" => true, "samesite" => "Lax"];
|
||||
setcookie("auth_user", $user, $cookie_options);
|
||||
setcookie("auth_pass_b64", base64_encode($password), $cookie_options);
|
||||
setcookie("auth_token", $remember_token, $cookie_options);
|
||||
$redir = safe_redir($_GET["redir"] ?? "/");
|
||||
header("Location: $redir");
|
||||
die();
|
||||
} else {
|
||||
$_GET["_result"] = "La contraseña no es correcta.";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (!file_exists("/DATA/SISTEMA_INSTALADO.txt")) {
|
||||
if (strval(db_get_config('installed')) !== '1') {
|
||||
header("Location: /_install.php");
|
||||
die();
|
||||
}
|
||||
if (empty($_SESSION["login_csrf"])) {
|
||||
$_SESSION["login_csrf"] = bin2hex(random_bytes(32));
|
||||
}
|
||||
require_once "_incl/pre-body.php";
|
||||
?>
|
||||
|
||||
<form method="post" action="?redir=<?= urlencode($_GET["redir"] ?? "/") ?>">
|
||||
<input type="hidden" name="_csrf" value="<?= htmlspecialchars($_SESSION["login_csrf"]) ?>">
|
||||
<div class="card pad" style="max-width: 500px;">
|
||||
<h1 style="text-align: center;">Iniciar sesión en Axia4</h1>
|
||||
<div>
|
||||
|
||||
@@ -1,44 +1,231 @@
|
||||
<?php
|
||||
require_once "_incl/auth_redir.php";
|
||||
require_once "../_incl/db.php";
|
||||
require_once "_incl/pre-body.php";
|
||||
?>
|
||||
<div id="grid">
|
||||
<div class="card pad grid-item" style="text-align: center;">
|
||||
<h2>¡Hola, <?php echo htmlspecialchars($_SESSION["auth_data"]["display_name"]); ?>!</h2>
|
||||
<span><b>Tu Email:</b> <?php echo htmlspecialchars($_SESSION["auth_data"]["email"]); ?></span>
|
||||
<span><b>Tu Nombre de Usuario:</b> <?php echo htmlspecialchars($_SESSION["auth_user"]); ?></span>
|
||||
</div>
|
||||
<div class="card pad grid-item" style="text-align: center;">
|
||||
<b>Código QR</b>
|
||||
<img src="https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=<?php echo urlencode($_SESSION["auth_user"]); ?>" alt="QR Code de Nombre de Usuario" style="margin: 0 auto;">
|
||||
<small>Escanea este código para iniciar sesión. Es como tu contraseña, pero más fácil.</small>
|
||||
</div>
|
||||
</div>
|
||||
<style>
|
||||
.grid-item {
|
||||
margin-bottom: 10px !important;
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.grid-item img {
|
||||
margin: 0 auto;
|
||||
height: 150px;
|
||||
}
|
||||
$authData = $_SESSION["auth_data"] ?? [];
|
||||
$username = $_SESSION["auth_user"] ?? '';
|
||||
$displayName = $authData["display_name"] ?? 'Invitado';
|
||||
$email = $authData["email"] ?? '';
|
||||
$permissions = $authData["permissions"] ?? [];
|
||||
|
||||
// Tenant / organization management
|
||||
$userOrganizations = get_user_organizations($authData);
|
||||
$activeOrganization = $_SESSION['active_organization']
|
||||
?? ($authData['aulatek']['organizacion'] ?? ($authData['aulatek']['centro'] ?? ($authData['entreaulas']['organizacion'] ?? ($authData['entreaulas']['centro'] ?? ''))));
|
||||
$aularios = ($activeOrganization !== '') ? db_get_aularios($activeOrganization) : [];
|
||||
$userAulas = $authData['aulatek']['aulas'] ?? ($authData['entreaulas']['aulas'] ?? []);
|
||||
$role = $authData['aulatek']['role'] ?? ($authData['entreaulas']['role'] ?? '');
|
||||
|
||||
// Initials for avatar
|
||||
$parts = preg_split('/\s+/', trim($displayName));
|
||||
$initials = mb_strtoupper(mb_substr($parts[0] ?? '', 0, 1) . mb_substr($parts[1] ?? '', 0, 1));
|
||||
if ($initials === '') {
|
||||
$initials = '?';
|
||||
}
|
||||
|
||||
// Connected devices
|
||||
$connectedSessions = db_get_user_sessions($username);
|
||||
$currentToken = hash('sha256', session_id());
|
||||
|
||||
/**
|
||||
* Parse a raw User-Agent string into a human-readable browser + OS label.
|
||||
* Returns an array ['browser' => string, 'os' => string, 'icon' => string].
|
||||
*/
|
||||
function parse_ua(string $ua): array
|
||||
{
|
||||
// OS
|
||||
$os = 'Desconocido';
|
||||
if (stripos($ua, 'Android') !== false) $os = 'Android';
|
||||
elseif (stripos($ua, 'iPhone') !== false
|
||||
|| stripos($ua, 'iPad') !== false) $os = 'iOS';
|
||||
elseif (stripos($ua, 'Windows') !== false) $os = 'Windows';
|
||||
elseif (stripos($ua, 'Macintosh') !== false
|
||||
|| stripos($ua, 'Mac OS') !== false) $os = 'macOS';
|
||||
elseif (stripos($ua, 'Linux') !== false) $os = 'Linux';
|
||||
elseif (stripos($ua, 'CrOS') !== false) $os = 'ChromeOS';
|
||||
|
||||
// Browser
|
||||
$browser = 'Desconocido';
|
||||
if (str_starts_with($ua, 'Axia4Auth/')) $browser = 'Axia4 App';
|
||||
elseif (stripos($ua, 'Edg/') !== false) $browser = 'Edge';
|
||||
elseif (stripos($ua, 'OPR/') !== false
|
||||
|| stripos($ua, 'Opera') !== false) $browser = 'Opera';
|
||||
elseif (stripos($ua, 'Chrome') !== false) $browser = 'Chrome';
|
||||
elseif (stripos($ua, 'Firefox') !== false) $browser = 'Firefox';
|
||||
elseif (stripos($ua, 'Safari') !== false) $browser = 'Safari';
|
||||
|
||||
// Emoji icon for a quick visual cue
|
||||
$icon = match ($browser) {
|
||||
'Chrome' => '🌐',
|
||||
'Firefox' => '🦊',
|
||||
'Safari' => '🧭',
|
||||
'Edge' => '🔷',
|
||||
'Opera' => '🔴',
|
||||
'Axia4 App' => '📱',
|
||||
default => '💻',
|
||||
};
|
||||
|
||||
return ['browser' => $browser, 'os' => $os, 'icon' => $icon];
|
||||
}
|
||||
?>
|
||||
<style>
|
||||
.account-grid { display: flex; flex-wrap: wrap; gap: 16px; padding: 16px; }
|
||||
.account-card { background: #fff; border: 1px solid #e0e0e0; border-radius: 12px; padding: 24px; min-width: 280px; flex: 1 1 280px; }
|
||||
.account-card h2 { font-size: 1rem; font-weight: 600; color: var(--gw-text-secondary, #5f6368); text-transform: uppercase; letter-spacing: .05em; margin: 0 0 16px; }
|
||||
.avatar-lg { width: 80px; height: 80px; border-radius: 50%; background: var(--gw-blue, #1a73e8); color: #fff; display: flex; align-items: center; justify-content: center; font-size: 2rem; font-weight: 700; margin: 0 auto 12px; }
|
||||
.info-row { display: flex; justify-content: space-between; padding: 6px 0; border-bottom: 1px solid #f1f3f4; font-size: .9rem; }
|
||||
.info-row:last-child { border-bottom: none; }
|
||||
.info-row .label { color: var(--gw-text-secondary, #5f6368); }
|
||||
.badge-pill { display: inline-block; padding: 2px 10px; border-radius: 99px; font-size: .78rem; font-weight: 600; margin: 2px; }
|
||||
.badge-active { background: #e6f4ea; color: #137333; }
|
||||
.badge-perm { background: #e8f0fe; color: #1a73e8; }
|
||||
.tenant-btn { display: block; width: 100%; text-align: left; padding: 8px 12px; border: 1px solid #e0e0e0; border-radius: 8px; margin-bottom: 8px; background: #f8f9fa; cursor: pointer; font-size: .9rem; transition: background .15s; }
|
||||
.tenant-btn:hover { background: #e8f0fe; }
|
||||
.tenant-btn.active-tenant { border-color: var(--gw-blue, #1a73e8); background: #e8f0fe; font-weight: 600; }
|
||||
.device-row { display: flex; align-items: center; gap: 12px; padding: 10px 0; border-bottom: 1px solid #f1f3f4; }
|
||||
.device-row:last-child { border-bottom: none; }
|
||||
.device-icon { font-size: 1.6rem; flex-shrink: 0; }
|
||||
.device-info { flex: 1; min-width: 0; }
|
||||
.device-name { font-weight: 600; font-size: .9rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.device-meta { font-size: .78rem; color: var(--gw-text-secondary, #5f6368); margin-top: 2px; }
|
||||
.device-current { font-size: .75rem; color: #137333; font-weight: 600; background: #e6f4ea; border-radius: 99px; padding: 1px 8px; }
|
||||
</style>
|
||||
<script>
|
||||
var msnry = new Masonry('#grid', {
|
||||
"columnWidth": 300,
|
||||
"itemSelector": ".grid-item",
|
||||
"gutter": 10,
|
||||
"transitionDuration": 0
|
||||
});
|
||||
setTimeout(() => {
|
||||
msnry.layout()
|
||||
}, 250)
|
||||
setInterval(() => {
|
||||
msnry.layout()
|
||||
}, 1000);
|
||||
</script>
|
||||
|
||||
<div class="account-grid">
|
||||
|
||||
<!-- Profile Card -->
|
||||
<div class="account-card" style="text-align:center;">
|
||||
<h2>Mi Perfil</h2>
|
||||
<div class="avatar-lg"><?= htmlspecialchars($initials) ?></div>
|
||||
<div style="font-size:1.2rem; font-weight:700; margin-bottom:4px;"><?= htmlspecialchars($displayName) ?></div>
|
||||
<div style="color:var(--gw-text-secondary,#5f6368); margin-bottom:16px;"><?= htmlspecialchars($email ?: 'Sin correo') ?></div>
|
||||
<div class="info-row"><span class="label">Usuario</span><span><?= htmlspecialchars($username) ?></span></div>
|
||||
<?php if ($role): ?>
|
||||
<div class="info-row"><span class="label">Rol</span><span><?= htmlspecialchars($role) ?></span></div>
|
||||
<?php endif; ?>
|
||||
<div style="margin-top:16px;">
|
||||
<a href="/account/change_password.php" class="btn btn-secondary btn-sm">Cambiar contraseña</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- QR Card -->
|
||||
<div class="account-card" style="text-align:center;">
|
||||
<h2>Código QR de Acceso</h2>
|
||||
<img src="https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=<?= urlencode($username) ?>"
|
||||
alt="QR Code" style="margin:0 auto 12px; display:block; width:150px; height:150px;">
|
||||
<small style="color:var(--gw-text-secondary,#5f6368);">Escanea este código para iniciar sesión rápidamente.</small>
|
||||
</div>
|
||||
|
||||
<!-- Tenant / Centro Card -->
|
||||
<?php if (!empty($userOrganizations)): ?>
|
||||
<div class="account-card">
|
||||
<h2>Organizaciones</h2>
|
||||
<?php foreach ($userOrganizations as $orgId): ?>
|
||||
<form method="post" action="/_incl/switch_tenant.php" style="margin:0;">
|
||||
<input type="hidden" name="redir" value="/account/">
|
||||
<button type="submit" name="organization" value="<?= htmlspecialchars($orgId) ?>"
|
||||
class="tenant-btn <?= ($activeOrganization === $orgId) ? 'active-tenant' : '' ?>">
|
||||
<?php if ($activeOrganization === $orgId): ?>
|
||||
<span style="color:var(--gw-blue,#1a73e8);">✓ </span>
|
||||
<?php endif; ?>
|
||||
<?= htmlspecialchars($orgId) ?>
|
||||
<?php if ($activeOrganization === $orgId): ?>
|
||||
<span class="badge-pill badge-active" style="float:right;">Activo</span>
|
||||
<?php endif; ?>
|
||||
</button>
|
||||
</form>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Aulas Card -->
|
||||
<?php if (!empty($userAulas)): ?>
|
||||
<div class="account-card">
|
||||
<h2>Mis Aulas (<?= htmlspecialchars($activeOrganization) ?>)</h2>
|
||||
<?php foreach ($userAulas as $aula_id): ?>
|
||||
<?php $aula = $aularios[$aula_id] ?? null; ?>
|
||||
<div class="info-row">
|
||||
<?php if ($aula && !empty($aula['icon'])): ?>
|
||||
<img src="<?= htmlspecialchars($aula['icon']) ?>" style="height:20px;vertical-align:middle;margin-right:6px;">
|
||||
<?php endif; ?>
|
||||
<span><?= htmlspecialchars($aula['name'] ?? $aula_id) ?></span>
|
||||
<span class="badge-pill badge-active">Asignada</span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Permissions Card -->
|
||||
<?php if (!empty($permissions)): ?>
|
||||
<div class="account-card">
|
||||
<h2>Permisos</h2>
|
||||
<div>
|
||||
<?php foreach ($permissions as $p): ?>
|
||||
<span class="badge-pill badge-perm"><?= htmlspecialchars($p) ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Session Info Card -->
|
||||
<div class="account-card">
|
||||
<h2>Sesión Activa</h2>
|
||||
<div class="info-row"><span class="label">ID Sesión</span><span style="font-family:monospace;font-size:.75rem;"><?= htmlspecialchars(substr(session_id(), 0, 12)) ?>…</span></div>
|
||||
<div class="info-row"><span class="label">Org. activa</span><span><?= htmlspecialchars($activeOrganization ?: '–') ?></span></div>
|
||||
<div class="info-row"><span class="label">Autenticación</span><span><?= empty($authData['google_auth']) ? 'Contraseña' : 'Google' ?></span></div>
|
||||
<?php if (!empty($_SESSION['session_created'])): ?>
|
||||
<div class="info-row"><span class="label">Sesión iniciada</span><span><?= htmlspecialchars(date('d/m/Y H:i', $_SESSION['session_created'])) ?></span></div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($_SESSION['last_reload_time'])): ?>
|
||||
<div class="info-row"><span class="label">Última actividad</span><span><?= htmlspecialchars(date('d/m/Y H:i', $_SESSION['last_reload_time'])) ?></span></div>
|
||||
<?php endif; ?>
|
||||
<div style="margin-top:16px;">
|
||||
<a href="/_incl/logout.php" class="btn btn-danger btn-sm">Cerrar sesión</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Connected Devices Card -->
|
||||
<?php if (!empty($connectedSessions)): ?>
|
||||
<div class="account-card" style="flex-basis:100%;">
|
||||
<h2>Dispositivos conectados</h2>
|
||||
<?php foreach ($connectedSessions as $sess):
|
||||
$isCurrent = hash_equals($currentToken, $sess['session_token']);
|
||||
$ua_info = parse_ua($sess['user_agent']);
|
||||
$label = $ua_info['browser'] . ' — ' . $ua_info['os'];
|
||||
?>
|
||||
<div class="device-row">
|
||||
<div class="device-icon"><?= $ua_info['icon'] ?></div>
|
||||
<div class="device-info">
|
||||
<div class="device-name">
|
||||
<?= htmlspecialchars($label) ?>
|
||||
<?php if ($isCurrent): ?>
|
||||
<span class="device-current">Este dispositivo</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="device-meta">
|
||||
IP: <?= htmlspecialchars($sess['ip_address'] ?: '–') ?>
|
||||
·
|
||||
Inicio: <?= htmlspecialchars(substr($sess['created_at'], 0, 16)) ?>
|
||||
·
|
||||
Activo: <?= htmlspecialchars(substr($sess['last_active'], 0, 16)) ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php if (!$isCurrent): ?>
|
||||
<form method="post" action="/account/revoke_session.php" style="margin:0;">
|
||||
<input type="hidden" name="token" value="<?= htmlspecialchars($sess['session_token']) ?>">
|
||||
<input type="hidden" name="redir" value="/account/">
|
||||
<button type="submit" class="btn btn-outline-danger btn-sm"
|
||||
onclick="return confirm('¿Cerrar sesión en este dispositivo?')">
|
||||
Revocar
|
||||
</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
|
||||
<?php require_once "_incl/post-body.php"; ?>
|
||||
@@ -1,36 +1,27 @@
|
||||
<?php
|
||||
require_once "_incl/pre-body.php";
|
||||
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
// Handle form submission
|
||||
$invitations = json_decode(file_get_contents("/DATA/Invitaciones_de_usuarios.json"), true);
|
||||
$invi_code = strtoupper($_POST['invitation_code'] ?? '');
|
||||
if (!isset($invitations[$invi_code])) {
|
||||
$invi_code = strtoupper(trim($_POST['invitation_code'] ?? ''));
|
||||
$invitation = db_get_invitation($invi_code);
|
||||
if (!$invitation || !$invitation['active']) {
|
||||
header("Location: /?_resultcolor=red&_result=" . urlencode("Código de invitación no válido."));
|
||||
exit;
|
||||
}
|
||||
$userdata = [
|
||||
'display_name' => $_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;
|
||||
|
||||
42
public_html/account/revoke_session.php
Normal file
42
public_html/account/revoke_session.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* Revoke a connected device session.
|
||||
* POST-only. Requires the user to be authenticated.
|
||||
* Accepts: token (session_token hash), redir (safe redirect URL).
|
||||
*/
|
||||
require_once "_incl/auth_redir.php";
|
||||
require_once "../_incl/db.php";
|
||||
require_once "../_incl/tools.security.php";
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
header('HTTP/1.1 405 Method Not Allowed');
|
||||
die();
|
||||
}
|
||||
|
||||
$username = $_SESSION['auth_user'] ?? '';
|
||||
if ($username === '') {
|
||||
header('HTTP/1.1 401 Unauthorized');
|
||||
die();
|
||||
}
|
||||
|
||||
$token = preg_replace('/[^a-f0-9]/', '', strtolower($_POST['token'] ?? ''));
|
||||
$redir = safe_redir($_POST['redir'] ?? '/account/');
|
||||
|
||||
if ($token === '') {
|
||||
header("Location: $redir");
|
||||
die();
|
||||
}
|
||||
|
||||
$current_token = hash('sha256', session_id());
|
||||
|
||||
// Prevent revoking the current session through this endpoint
|
||||
// (users should use the regular logout for that)
|
||||
if (hash_equals($current_token, $token)) {
|
||||
header("Location: $redir");
|
||||
die();
|
||||
}
|
||||
|
||||
db_revoke_session($token, $username);
|
||||
|
||||
header("Location: $redir");
|
||||
die();
|
||||
@@ -1,11 +1,10 @@
|
||||
<!-- <a href="/_login.php?reload_user=1" class="btn btn-secondary">Recargar Cuenta</a>
|
||||
<a href="/_login.php?logout=1" class="btn btn-secondary">Cerrar sesión</a> -->
|
||||
|
||||
|
||||
<a class="sidebar-link" href="/entreaulas/">
|
||||
<span>Mi aula</span>
|
||||
</a>
|
||||
<a class="sidebar-link" href="/entreaulas/supercafe.php">
|
||||
<img src="/static/iconexperience/purchase_order_cart.png" alt="">
|
||||
<span>SuperCafe</span>
|
||||
</a>
|
||||
<div class="sidebar-section-label">Atajos</div>
|
||||
<nav class="sidebar-nav">
|
||||
<a class="sidebar-link" href="/aulatek/">
|
||||
<span>Mi aula</span>
|
||||
</a>
|
||||
<a class="sidebar-link" href="/aulatek/supercafe.php">
|
||||
<img src="/static/iconexperience/purchase_order_cart.png" alt="">
|
||||
<span>SuperCafe</span>
|
||||
</a>
|
||||
</nav>
|
||||
@@ -10,9 +10,10 @@ header("Access-Control-Allow-Origin: *");
|
||||
|
||||
|
||||
$type = $_GET["type"] ?? "";
|
||||
$orgs_base_dir = basename(aulatek_orgs_base_path());
|
||||
switch ($type) {
|
||||
case "alumno_photo":
|
||||
$centro = safe_centro_id($_GET["centro"] ?? '');
|
||||
$centro = safe_organization_id($_GET["organization"] ?? $_GET["organizacion"] ?? $_GET["org"] ?? $_GET["centro"] ?? '');
|
||||
$aulario = safe_id_segment($_GET["aulario"] ?? '');
|
||||
$alumno = safe_id_segment($_GET["alumno"] ?? '');
|
||||
// Additional validation to prevent empty names
|
||||
@@ -20,19 +21,28 @@ switch ($type) {
|
||||
header("HTTP/1.1 403 Forbidden");
|
||||
die("Invalid parameters");
|
||||
}
|
||||
$relpath = "entreaulas/Centros/$centro/Aularios/$aulario/Alumnos/$alumno/photo.jpg";
|
||||
$relpath = "entreaulas/$orgs_base_dir/$centro/Aularios/$aulario/Alumnos/$alumno/photo.jpg";
|
||||
break;
|
||||
case "aulario_photo":
|
||||
$centro = safe_organization_id($_GET["organization"] ?? $_GET["organizacion"] ?? $_GET["org"] ?? $_GET["centro"] ?? '');
|
||||
$aulario = safe_id_segment($_GET["aulario"] ?? '');
|
||||
if (empty($centro) || empty($aulario)) {
|
||||
header("HTTP/1.1 400 Bad Request");
|
||||
die("Invalid parameters");
|
||||
}
|
||||
$relpath = "entreaulas/$orgs_base_dir/$centro/Aularios/$aulario/photo.jpg";
|
||||
break;
|
||||
case "panel_actividades":
|
||||
$centro = safe_centro_id($_GET["centro"] ?? '');
|
||||
$centro = safe_organization_id($_GET["organization"] ?? $_GET["organizacion"] ?? $_GET["org"] ?? $_GET["centro"] ?? '');
|
||||
$activity = safe_id_segment($_GET["activity"] ?? '');
|
||||
if (empty($centro) || empty($activity)) {
|
||||
header("HTTP/1.1 400 Bad Request");
|
||||
die("Invalid parameters");
|
||||
}
|
||||
$relpath = "entreaulas/Centros/$centro/Panel/Actividades/$activity/photo.jpg";
|
||||
$relpath = "entreaulas/$orgs_base_dir/$centro/Panel/Actividades/$activity/photo.jpg";
|
||||
break;
|
||||
case "comedor_image":
|
||||
$centro = safe_centro_id($_GET["centro"] ?? '');
|
||||
$centro = safe_organization_id($_GET["organization"] ?? $_GET["organizacion"] ?? $_GET["org"] ?? $_GET["centro"] ?? '');
|
||||
$aulario = safe_id_segment($_GET["aulario"] ?? '');
|
||||
$date = preg_replace('/[^0-9-]/', '', $_GET["date"] ?? '');
|
||||
$file = safe_filename($_GET["file"] ?? '');
|
||||
@@ -46,10 +56,10 @@ switch ($type) {
|
||||
}
|
||||
$ym = substr($date, 0, 7);
|
||||
$day = substr($date, 8, 2);
|
||||
$relpath = "entreaulas/Centros/$centro/Aularios/$aulario/Comedor/$ym/$day/$file";
|
||||
$relpath = "entreaulas/$orgs_base_dir/$centro/Aularios/$aulario/Comedor/$ym/$day/$file";
|
||||
break;
|
||||
case "proyecto_file":
|
||||
$centro = safe_centro_id($_GET["centro"] ?? '');
|
||||
$centro = safe_organization_id($_GET["organization"] ?? $_GET["organizacion"] ?? $_GET["org"] ?? $_GET["centro"] ?? '');
|
||||
$project = safe_id_segment($_GET["project"] ?? '');
|
||||
$file = safe_filename($_GET["file"] ?? '');
|
||||
if (empty($centro) || empty($project) || empty($file)) {
|
||||
@@ -61,7 +71,7 @@ switch ($type) {
|
||||
header("HTTP/1.1 400 Bad Request");
|
||||
die("Invalid file name");
|
||||
}
|
||||
$projects_base = "/DATA/entreaulas/Centros/$centro/Proyectos";
|
||||
$projects_base = aulatek_orgs_base_path() . "/$centro/Proyectos";
|
||||
$project_dir = null;
|
||||
if (is_dir($projects_base)) {
|
||||
$iterator = new RecursiveIteratorIterator(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
$APP_CODE = "entreaulas";
|
||||
$APP_NAME = "EntreAulas";
|
||||
$APP_TITLE = "EntreAulas";
|
||||
$APP_CODE = "aulatek";
|
||||
$APP_NAME = "AulaTek";
|
||||
$APP_TITLE = "AulaTek";
|
||||
require_once __DIR__ . "/../../_incl/auth_redir.php";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
$APP_CODE = "entreaulas";
|
||||
$APP_NAME = "EntreAulas";
|
||||
$APP_TITLE = "EntreAulas";
|
||||
$APP_CODE = "aulatek";
|
||||
$APP_NAME = "AulaTek";
|
||||
$APP_TITLE = "AulaTek";
|
||||
require_once __DIR__ . "/../../_incl/pre-body.php";
|
||||
|
||||
@@ -2,14 +2,16 @@
|
||||
require_once "_incl/auth_redir.php";
|
||||
require_once "../_incl/tools.security.php";
|
||||
// Check if user has docente permission
|
||||
if (!in_array("entreaulas:docente", $_SESSION["auth_data"]["permissions"] ?? [])) {
|
||||
$permissions = $_SESSION["auth_data"]["permissions"] ?? [];
|
||||
if (!in_array("aulatek:docente", $permissions, true) && !in_array("entreaulas:docente", $permissions, true)) {
|
||||
header("HTTP/1.1 403 Forbidden");
|
||||
die("Access denied");
|
||||
}
|
||||
|
||||
|
||||
$aulario_id = safe_id_segment($_GET["aulario"] ?? "");
|
||||
$centro_id = safe_centro_id($_SESSION["auth_data"]["entreaulas"]["centro"] ?? "");
|
||||
$tenant_data = $_SESSION["auth_data"]["aulatek"] ?? ($_SESSION["auth_data"]["entreaulas"] ?? []);
|
||||
$centro_id = safe_organization_id($tenant_data["organizacion"] ?? ($tenant_data["centro"] ?? ""));
|
||||
|
||||
if (empty($aulario_id) || empty($centro_id)) {
|
||||
require_once "_incl/pre-body.php";
|
||||
@@ -24,7 +26,7 @@ if (empty($aulario_id) || empty($centro_id)) {
|
||||
}
|
||||
|
||||
// Validate paths with realpath
|
||||
$base_path = "/DATA/entreaulas/Centros";
|
||||
$base_path = aulatek_orgs_base_path();
|
||||
$real_base = realpath($base_path);
|
||||
$alumnos_base_path = "$base_path/$centro_id/Aularios/$aulario_id/Alumnos";
|
||||
|
||||
@@ -250,7 +252,7 @@ switch ($_GET["action"] ?? '') {
|
||||
<label class="form-label">Foto actual:</label>
|
||||
<?php if ($photo_exists): ?>
|
||||
<div class="mb-2">
|
||||
<img src="_filefetch.php?type=alumno_photo&alumno=<?= urlencode($nombre) ?>¢ro=<?= urlencode($centro_id) ?>&aulario=<?= urlencode($aulario_id) ?>"
|
||||
<img src="_filefetch.php?type=alumno_photo&alumno=<?= urlencode($nombre) ?>&org=<?= urlencode($centro_id) ?>&aulario=<?= urlencode($aulario_id) ?>"
|
||||
alt="Foto de <?= htmlspecialchars($nombre) ?>"
|
||||
style="max-width: 200px; max-height: 200px; border: 2px solid #ddd; border-radius: 10px;">
|
||||
</div>
|
||||
@@ -351,7 +353,7 @@ switch ($_GET["action"] ?? '') {
|
||||
<tr>
|
||||
<td>
|
||||
<?php if ($photo_exists): ?>
|
||||
<img src="_filefetch.php?type=alumno_photo&alumno=<?= urlencode($nombre) ?>¢ro=<?= urlencode($centro_id) ?>&aulario=<?= urlencode($aulario_id) ?>"
|
||||
<img src="_filefetch.php?type=alumno_photo&alumno=<?= urlencode($nombre) ?>&org=<?= urlencode($centro_id) ?>&aulario=<?= urlencode($aulario_id) ?>"
|
||||
alt="Foto de <?= htmlspecialchars($nombre) ?>"
|
||||
style="width: 50px; height: 50px; object-fit: cover; border-radius: 5px;">
|
||||
<?php else: ?>
|
||||
@@ -379,7 +381,7 @@ switch ($_GET["action"] ?? '') {
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
|
||||
<a href="/entreaulas/aulario.php?id=<?= urlencode($aulario_id) ?>" class="btn btn-secondary mt-3">Volver al Aulario</a>
|
||||
<a href="/aulatek/aulario.php?id=<?= urlencode($aulario_id) ?>" class="btn btn-secondary mt-3">Volver al Aulario</a>
|
||||
</div>
|
||||
<?php
|
||||
require_once "_incl/post-body.php";
|
||||
|
||||
@@ -15,7 +15,7 @@ La API utiliza el mismo sistema de autenticación que el resto de la aplicación
|
||||
|
||||
### 1. Obtener tipos de menú
|
||||
|
||||
**GET** `/entreaulas/api/comedor.php?action=get_menu_types&aulario={aulario_id}`
|
||||
**GET** `/aulatek/api/comedor.php?action=get_menu_types&aulario={aulario_id}`
|
||||
|
||||
Devuelve todos los tipos de menú disponibles para un aulario.
|
||||
|
||||
@@ -89,7 +89,7 @@ Obtiene el menú de un día específico y tipo de menú.
|
||||
|
||||
### 3. Guardar menú
|
||||
|
||||
**POST** `/entreaulas/api/comedor.php?action=save_menu&aulario={aulario_id}`
|
||||
**POST** `/aulatek/api/comedor.php?action=save_menu&aulario={aulario_id}`
|
||||
|
||||
Guarda o actualiza un menú para un día específico.
|
||||
|
||||
@@ -114,7 +114,7 @@ Guarda o actualiza un menú para un día específico.
|
||||
|
||||
**Ejemplo de uso con curl:**
|
||||
```bash
|
||||
curl -X POST "http://localhost/entreaulas/api/comedor.php?action=save_menu&aulario=aulario_id" \
|
||||
curl -X POST "http://localhost/aulatek/api/comedor.php?action=save_menu&aulario=aulario_id" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"date": "2026-02-18",
|
||||
@@ -131,7 +131,7 @@ curl -X POST "http://localhost/entreaulas/api/comedor.php?action=save_menu&aular
|
||||
|
||||
### 4. Añadir nuevo tipo de menú
|
||||
|
||||
**POST** `/entreaulas/api/comedor.php?action=add_menu_type&aulario={aulario_id}`
|
||||
**POST** `/aulatek/api/comedor.php?action=add_menu_type&aulario={aulario_id}`
|
||||
|
||||
Crea un nuevo tipo de menú.
|
||||
|
||||
@@ -146,7 +146,7 @@ Crea un nuevo tipo de menú.
|
||||
|
||||
**Ejemplo de uso con curl:**
|
||||
```bash
|
||||
curl -X POST "http://localhost/entreaulas/api/comedor.php?action=add_menu_type&aulario=aulario_id" \
|
||||
curl -X POST "http://localhost/aulatek/api/comedor.php?action=add_menu_type&aulario=aulario_id" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"id": "celiaco",
|
||||
@@ -159,7 +159,7 @@ curl -X POST "http://localhost/entreaulas/api/comedor.php?action=add_menu_type&a
|
||||
|
||||
### 5. Renombrar tipo de menú
|
||||
|
||||
**POST** `/entreaulas/api/comedor.php?action=rename_menu_type&aulario={aulario_id}`
|
||||
**POST** `/aulatek/api/comedor.php?action=rename_menu_type&aulario={aulario_id}`
|
||||
|
||||
Cambia el nombre o color de un tipo de menú existente.
|
||||
|
||||
@@ -176,7 +176,7 @@ Cambia el nombre o color de un tipo de menú existente.
|
||||
|
||||
### 6. Eliminar tipo de menú
|
||||
|
||||
**POST** `/entreaulas/api/comedor.php?action=delete_menu_type&aulario={aulario_id}`
|
||||
**POST** `/aulatek/api/comedor.php?action=delete_menu_type&aulario={aulario_id}`
|
||||
|
||||
Elimina un tipo de menú.
|
||||
|
||||
@@ -211,7 +211,7 @@ Elimina un tipo de menú.
|
||||
```javascript
|
||||
async function obtenerMenu(aularioId) {
|
||||
const response = await fetch(
|
||||
`/entreaulas/api/comedor.php?action=get_menu&aulario=${aularioId}`
|
||||
`/aulatek/api/comedor.php?action=get_menu&aulario=${aularioId}`
|
||||
);
|
||||
const data = await response.json();
|
||||
return data.menu;
|
||||
@@ -223,7 +223,7 @@ async function obtenerMenu(aularioId) {
|
||||
```javascript
|
||||
async function guardarMenu(aularioId, fecha, tipoMenu, platos) {
|
||||
const response = await fetch(
|
||||
`/entreaulas/api/comedor.php?action=save_menu&aulario=${aularioId}`,
|
||||
`/aulatek/api/comedor.php?action=save_menu&aulario=${aularioId}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@@ -243,7 +243,7 @@ async function guardarMenu(aularioId, fecha, tipoMenu, platos) {
|
||||
```javascript
|
||||
async function obtenerTiposMenu(aularioId) {
|
||||
const response = await fetch(
|
||||
`/entreaulas/api/comedor.php?action=get_menu_types&aulario=${aularioId}`
|
||||
`/aulatek/api/comedor.php?action=get_menu_types&aulario=${aularioId}`
|
||||
);
|
||||
const data = await response.json();
|
||||
return data.menu_types;
|
||||
|
||||
@@ -1,96 +1,65 @@
|
||||
<?php
|
||||
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/auth_redir.php";
|
||||
require_once "../../_incl/tools.security.php";
|
||||
require_once "../../_incl/db.php";
|
||||
|
||||
// Check permissions
|
||||
if (!in_array("entreaulas:docente", $_SESSION["auth_data"]["permissions"] ?? [])) {
|
||||
$permissions = $_SESSION["auth_data"]["permissions"] ?? [];
|
||||
if (!in_array("aulatek:docente", $permissions, true) && !in_array("entreaulas:docente", $permissions, true)) {
|
||||
http_response_code(403);
|
||||
die(json_encode(["error" => "Access denied", "code" => "FORBIDDEN"]));
|
||||
}
|
||||
|
||||
$centro_id = safe_centro_id($_SESSION["auth_data"]["entreaulas"]["centro"] ?? "");
|
||||
$tenant_data = $_SESSION["auth_data"]["aulatek"] ?? ($_SESSION["auth_data"]["entreaulas"] ?? []);
|
||||
$centro_id = safe_organization_id($tenant_data["organizacion"] ?? ($tenant_data["centro"] ?? ""));
|
||||
if ($centro_id === "") {
|
||||
http_response_code(400);
|
||||
die(json_encode(["error" => "Centro not found in session", "code" => "INVALID_SESSION"]));
|
||||
die(json_encode(["error" => "Organizacion not found in session", "code" => "INVALID_SESSION"]));
|
||||
}
|
||||
|
||||
$action = $_GET["action"] ?? ($_POST["action"] ?? "");
|
||||
$action = $_GET["action"] ?? ($_POST["action"] ?? "");
|
||||
$aulario_id = safe_id_segment($_GET["aulario"] ?? $_POST["aulario"] ?? "");
|
||||
|
||||
// Validate aulario_id
|
||||
if ($aulario_id === "") {
|
||||
http_response_code(400);
|
||||
die(json_encode(["error" => "aulario parameter is required", "code" => "MISSING_PARAM"]));
|
||||
}
|
||||
|
||||
// Verify that the user has access to this aulario
|
||||
$userAulas = $_SESSION["auth_data"]["entreaulas"]["aulas"] ?? [];
|
||||
$userAulas = array_values(array_filter(array_map('safe_id_segment', $userAulas)));
|
||||
$userAulas = array_values(array_filter(array_map('safe_id_segment', $tenant_data["aulas"] ?? [])));
|
||||
if (!in_array($aulario_id, $userAulas, true)) {
|
||||
http_response_code(403);
|
||||
die(json_encode(["error" => "Access denied to this aulario", "code" => "FORBIDDEN"]));
|
||||
}
|
||||
|
||||
$aulario_path = safe_aulario_config_path($centro_id, $aulario_id);
|
||||
$aulario = ($aulario_path && file_exists($aulario_path)) ? json_decode(file_get_contents($aulario_path), true) : null;
|
||||
$aulario = db_get_aulario($centro_id, $aulario_id);
|
||||
|
||||
// Handle shared comedor data
|
||||
$source_aulario_id = $aulario_id;
|
||||
$is_shared = false;
|
||||
if ($aulario && !empty($aulario["shared_comedor_from"])) {
|
||||
$shared_from = safe_id_segment($aulario["shared_comedor_from"]);
|
||||
$shared_aulario_path = safe_aulario_config_path($centro_id, $shared_from);
|
||||
if ($shared_aulario_path && file_exists($shared_aulario_path)) {
|
||||
if (db_get_aulario($centro_id, $shared_from)) {
|
||||
$source_aulario_id = $shared_from;
|
||||
$is_shared = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check edit permissions (must be sysadmin and not shared)
|
||||
$canEdit = in_array("sysadmin:access", $_SESSION["auth_data"]["permissions"] ?? []) && !$is_shared;
|
||||
|
||||
// Helper functions
|
||||
$defaultMenuTypes = [
|
||||
["id" => "basal", "label" => "Menú basal", "color" => "#0d6efd"],
|
||||
["id" => "vegetariano", "label" => "Menú vegetariano", "color" => "#198754"],
|
||||
["id" => "alergias", "label" => "Menú alergias", "color" => "#dc3545"],
|
||||
];
|
||||
|
||||
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"],
|
||||
];
|
||||
|
||||
if ($menuTypesPath === null) {
|
||||
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,58 +67,35 @@ function blank_menu() {
|
||||
"plates" => [
|
||||
"primero" => ["name" => "", "pictogram" => ""],
|
||||
"segundo" => ["name" => "", "pictogram" => ""],
|
||||
"postre" => ["name" => "", "pictogram" => ""],
|
||||
"postre" => ["name" => "", "pictogram" => ""],
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
function safe_filename($name) {
|
||||
$name = basename($name);
|
||||
return preg_replace("/[^a-zA-Z0-9._-]/", "_", $name);
|
||||
}
|
||||
|
||||
// Routes
|
||||
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 +103,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"]);
|
||||
}
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
require_once "_incl/auth_redir.php";
|
||||
require_once "_incl/pre-body.php";
|
||||
require_once "../_incl/tools.security.php";
|
||||
require_once "../_incl/db.php";
|
||||
|
||||
$aulario_id = safe_id_segment($_GET["id"] ?? "");
|
||||
$centro_id = safe_centro_id($_SESSION["auth_data"]["entreaulas"]["centro"] ?? "");
|
||||
$aulario_path = safe_aulario_config_path($centro_id, $aulario_id);
|
||||
$aulario = ($aulario_path && file_exists($aulario_path)) ? json_decode(file_get_contents($aulario_path), true) : null;
|
||||
$tenant_data = $_SESSION["auth_data"]["aulatek"] ?? ($_SESSION["auth_data"]["entreaulas"] ?? []);
|
||||
$centro_id = safe_organization_id($tenant_data["organizacion"] ?? ($tenant_data["centro"] ?? ""));
|
||||
$aulario = db_get_aulario($centro_id, $aulario_id);
|
||||
|
||||
if (!$aulario || !is_array($aulario)) {
|
||||
?>
|
||||
@@ -26,13 +27,14 @@ if (!$aulario || !is_array($aulario)) {
|
||||
</div>
|
||||
|
||||
<div id="grid">
|
||||
<a href="/entreaulas/paneldiario.php?aulario=<?= urlencode($aulario_id) ?>" class="btn btn-primary grid-item">
|
||||
<a href="/aulatek/paneldiario.php?aulario=<?= urlencode($aulario_id) ?>" class="btn btn-primary grid-item">
|
||||
<img src="/static/arasaac/pdi.png" height="125" style="background: white; padding: 5px; border-radius: 10px;">
|
||||
</br>
|
||||
Panel Diario
|
||||
</a>
|
||||
<?php if (in_array("entreaulas:docente", $_SESSION["auth_data"]["permissions"] ?? [])): ?>
|
||||
<a href="/entreaulas/alumnos.php?aulario=<?= urlencode($aulario_id) ?>" class="btn btn-info grid-item">
|
||||
<?php $permissions = $_SESSION["auth_data"]["permissions"] ?? []; ?>
|
||||
<?php if (in_array("aulatek:docente", $permissions, true) || in_array("entreaulas:docente", $permissions, true)): ?>
|
||||
<a href="/aulatek/alumnos.php?aulario=<?= urlencode($aulario_id) ?>" class="btn btn-info grid-item">
|
||||
<img src="/static/arasaac/alumnos.png" height="125" style="background: white; padding: 5px; border-radius: 10px;" alt="Icono de gestión de alumnos">
|
||||
<br>
|
||||
Gestión de Alumnos
|
||||
@@ -46,13 +48,13 @@ if (!$aulario || !is_array($aulario)) {
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<!-- Menú del comedor -->
|
||||
<a href="/entreaulas/comedor.php?aulario=<?= urlencode($aulario_id) ?>" class="btn btn-success grid-item">
|
||||
<a href="/aulatek/comedor.php?aulario=<?= urlencode($aulario_id) ?>" class="btn btn-success grid-item">
|
||||
<img src="/static/arasaac/comedor.png" height="125" style="background: white; padding: 5px; border-radius: 10px;">
|
||||
<br>
|
||||
Menú del Comedor
|
||||
</a>
|
||||
<!-- Proyectos -->
|
||||
<a href="/entreaulas/proyectos.php?aulario=<?= urlencode($aulario_id) ?>" class="btn btn-warning grid-item">
|
||||
<a href="/aulatek/proyectos.php?aulario=<?= urlencode($aulario_id) ?>" class="btn btn-warning grid-item">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="125" fill="currentColor">
|
||||
<title>folder-multiple</title>
|
||||
<path d="M22,4H14L12,2H6A2,2 0 0,0 4,4V16A2,2 0 0,0 6,18H22A2,2 0 0,0 24,16V6A2,2 0 0,0 22,4M2,6H0V11H0V20A2,2 0 0,0 2,22H20V20H2V6Z" />
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
<?php
|
||||
require_once "_incl/auth_redir.php";
|
||||
require_once "../_incl/tools.security.php";
|
||||
require_once "../_incl/db.php";
|
||||
|
||||
if (in_array("entreaulas:docente", $_SESSION["auth_data"]["permissions"] ?? []) === false) {
|
||||
$permissions = $_SESSION["auth_data"]["permissions"] ?? [];
|
||||
if (!in_array("aulatek:docente", $permissions, true) && !in_array("entreaulas:docente", $permissions, true)) {
|
||||
header("HTTP/1.1 403 Forbidden");
|
||||
die("Access denied");
|
||||
}
|
||||
|
||||
$aulario_id = safe_id_segment($_GET["aulario"] ?? "");
|
||||
$centro_id = safe_centro_id($_SESSION["auth_data"]["entreaulas"]["centro"] ?? "");
|
||||
$tenant_data = $_SESSION["auth_data"]["aulatek"] ?? ($_SESSION["auth_data"]["entreaulas"] ?? []);
|
||||
$centro_id = safe_organization_id($tenant_data["organizacion"] ?? ($tenant_data["centro"] ?? ""));
|
||||
|
||||
if ($aulario_id === "" || $centro_id === "") {
|
||||
require_once "_incl/pre-body.php";
|
||||
@@ -22,38 +25,31 @@ if ($aulario_id === "" || $centro_id === "") {
|
||||
exit;
|
||||
}
|
||||
|
||||
$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);
|
||||
|
||||
// Check if this aulario shares comedor data from another aulario
|
||||
$source_aulario_id = $aulario_id; // Default to current aulario
|
||||
$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)) {
|
||||
$source_aulario_id = $shared_from;
|
||||
$source_aulario_name = json_decode(file_get_contents($shared_aulario_path), true)["name"] ?? $shared_from;
|
||||
$shared_aulario = db_get_aulario($centro_id, $shared_from);
|
||||
if ($shared_aulario) {
|
||||
$source_aulario_id = $shared_from;
|
||||
$source_aulario_name = $shared_aulario["name"] ?? $shared_from;
|
||||
$is_shared = true;
|
||||
}
|
||||
}
|
||||
|
||||
$menuTypesPath = "/DATA/entreaulas/Centros/$centro_id/Aularios/$source_aulario_id/Comedor-MenuTypes.json";
|
||||
$defaultMenuTypes = [
|
||||
["id" => "basal", "label" => "Menú basal", "color" => "#0d6efd"],
|
||||
["id" => "basal", "label" => "Menú basal", "color" => "#0d6efd"],
|
||||
["id" => "vegetariano", "label" => "Menú vegetariano", "color" => "#198754"],
|
||||
["id" => "alergias", "label" => "Menú alergias", "color" => "#dc3545"],
|
||||
["id" => "alergias", "label" => "Menú alergias", "color" => "#dc3545"],
|
||||
];
|
||||
if (!file_exists($menuTypesPath)) {
|
||||
if (!is_dir(dirname($menuTypesPath))) {
|
||||
mkdir(dirname($menuTypesPath), 0777, true);
|
||||
}
|
||||
file_put_contents($menuTypesPath, json_encode($defaultMenuTypes, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
|
||||
$menuTypes = json_decode(@file_get_contents($menuTypesPath), true);
|
||||
$menuTypes = db_get_comedor_menu_types($centro_id, $source_aulario_id);
|
||||
if (!is_array($menuTypes) || count($menuTypes) === 0) {
|
||||
$menuTypes = $defaultMenuTypes;
|
||||
db_set_comedor_menu_types($centro_id, $source_aulario_id, $menuTypes);
|
||||
}
|
||||
|
||||
$menuTypeIds = [];
|
||||
@@ -71,10 +67,9 @@ if (!in_array($menuTypeId, $menuTypeIds, true)) {
|
||||
$menuTypeId = $menuTypeIds[0] ?? "basal";
|
||||
}
|
||||
|
||||
$ym = $dateObj->format("Y-m");
|
||||
$ym = $dateObj->format("Y-m");
|
||||
$day = $dateObj->format("d");
|
||||
$baseDir = "/DATA/entreaulas/Centros/$centro_id/Aularios/$source_aulario_id/Comedor/$ym/$day";
|
||||
$dataPath = "$baseDir/_datos.json";
|
||||
|
||||
|
||||
function blank_menu()
|
||||
{
|
||||
@@ -91,11 +86,9 @@ $menuData = [
|
||||
"date" => $date,
|
||||
"menus" => []
|
||||
];
|
||||
if (file_exists($dataPath)) {
|
||||
$existing = json_decode(file_get_contents($dataPath), true);
|
||||
if (is_array($existing)) {
|
||||
$menuData = array_merge($menuData, $existing);
|
||||
}
|
||||
$existing = db_get_comedor_entry($centro_id, $source_aulario_id, $ym, $day);
|
||||
if (is_array($existing) && !empty($existing)) {
|
||||
$menuData = array_merge($menuData, $existing);
|
||||
}
|
||||
if (!isset($menuData["menus"][$menuTypeId])) {
|
||||
$menuData["menus"][$menuTypeId] = blank_menu();
|
||||
@@ -105,67 +98,6 @@ $canEdit = in_array("sysadmin:access", $_SESSION["auth_data"]["permissions"] ??
|
||||
$saveNotice = "";
|
||||
$uploadErrors = [];
|
||||
|
||||
function safe_filename($name)
|
||||
{
|
||||
// Normalize to base name to avoid directory traversal
|
||||
$name = basename($name);
|
||||
|
||||
// Best-effort normalize encoding to avoid odd Unicode tricks
|
||||
if (function_exists('mb_convert_encoding')) {
|
||||
$name = mb_convert_encoding($name, 'UTF-8', 'UTF-8');
|
||||
}
|
||||
|
||||
// Replace disallowed characters with underscore
|
||||
$name = preg_replace("/[^a-zA-Z0-9._-]/", "_", $name);
|
||||
// Collapse multiple underscores introduced by replacement
|
||||
$name = preg_replace('/_+/', '_', $name);
|
||||
|
||||
// Remove leading dots to avoid hidden/special files like ".htaccess"
|
||||
$name = ltrim($name, '.');
|
||||
|
||||
// Ensure there is at most one dot in the filename to prevent extension confusion
|
||||
if (substr_count($name, '.') > 1) {
|
||||
$parts = explode('.', $name);
|
||||
$ext = array_pop($parts);
|
||||
$base = implode('_', $parts);
|
||||
// Ensure extension is not empty
|
||||
if ($ext === '') {
|
||||
$name = $base === '' ? 'file' : $base;
|
||||
} else {
|
||||
$name = ($base === '' ? 'file' : $base) . '.' . $ext;
|
||||
}
|
||||
}
|
||||
|
||||
// Trim stray dots/underscores from the start and end
|
||||
$name = trim($name, "._");
|
||||
|
||||
// Enforce a maximum length (common filesystem limit is 255 bytes)
|
||||
$maxLen = 255;
|
||||
if (strlen($name) > $maxLen) {
|
||||
$dotPos = strrpos($name, '.');
|
||||
if ($dotPos !== false) {
|
||||
$ext = substr($name, $dotPos);
|
||||
$base = substr($name, 0, $dotPos);
|
||||
$baseMaxLen = $maxLen - strlen($ext);
|
||||
if ($baseMaxLen < 1) {
|
||||
// Fallback if extension is unusually long
|
||||
$name = substr($name, 0, $maxLen);
|
||||
} else {
|
||||
$name = substr($base, 0, $baseMaxLen) . $ext;
|
||||
}
|
||||
} else {
|
||||
$name = substr($name, 0, $maxLen);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure we never return an empty or invalid filename
|
||||
if ($name === '' || $name === '.' || $name === '..') {
|
||||
$name = 'file';
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
function handle_image_upload($fieldName, $targetBaseName, $baseDir, &$uploadErrors)
|
||||
{
|
||||
if (!isset($_FILES[$fieldName]) || $_FILES[$fieldName]["error"] !== UPLOAD_ERR_OK) {
|
||||
@@ -251,15 +183,12 @@ if ($_SERVER["REQUEST_METHOD"] === "POST" && $canEdit) {
|
||||
if ($newId !== "" && $newLabel !== "") {
|
||||
$exists = false;
|
||||
foreach ($menuTypes as $t) {
|
||||
if (($t["id"] ?? "") === $newId) {
|
||||
$exists = true;
|
||||
break;
|
||||
}
|
||||
if (($t["id"] ?? "") === $newId) { $exists = true; break; }
|
||||
}
|
||||
if (!$exists) {
|
||||
$menuTypes[] = ["id" => $newId, "label" => $newLabel, "color" => $newColor];
|
||||
file_put_contents($menuTypesPath, json_encode($menuTypes, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
header("Location: /entreaulas/comedor.php?aulario=" . urlencode($aulario_id) . "&date=" . urlencode($date) . "&menu=" . urlencode($newId));
|
||||
db_set_comedor_menu_types($centro_id, $source_aulario_id, $menuTypes);
|
||||
header("Location: /aulatek/comedor.php?aulario=" . urlencode($aulario_id) . "&date=" . urlencode($date) . "&menu=" . urlencode($newId));
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -268,21 +197,12 @@ if ($_SERVER["REQUEST_METHOD"] === "POST" && $canEdit) {
|
||||
if ($action === "delete_type") {
|
||||
$deleteId = safe_id_segment(trim($_POST["delete_type_id"] ?? ""));
|
||||
if ($deleteId !== "") {
|
||||
$deleted = false;
|
||||
$newMenuTypes = [];
|
||||
foreach ($menuTypes as $t) {
|
||||
if (($t["id"] ?? "") === $deleteId) {
|
||||
$deleted = true;
|
||||
} else {
|
||||
$newMenuTypes[] = $t;
|
||||
}
|
||||
}
|
||||
if ($deleted) {
|
||||
$newMenuTypes = array_values(array_filter($menuTypes, fn($t) => ($t["id"] ?? "") !== $deleteId));
|
||||
if (count($newMenuTypes) !== count($menuTypes)) {
|
||||
$menuTypes = $newMenuTypes;
|
||||
file_put_contents($menuTypesPath, json_encode($menuTypes, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
// Redirect to the first available menu type or default
|
||||
db_set_comedor_menu_types($centro_id, $source_aulario_id, $menuTypes);
|
||||
$redirectMenuId = !empty($menuTypes) ? $menuTypes[0]["id"] : "basal";
|
||||
header("Location: /entreaulas/comedor.php?aulario=" . urlencode($aulario_id) . "&date=" . urlencode($date) . "&menu=" . urlencode($redirectMenuId));
|
||||
header("Location: /aulatek/comedor.php?aulario=" . urlencode($aulario_id) . "&date=" . urlencode($date) . "&menu=" . urlencode($redirectMenuId));
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -296,16 +216,13 @@ if ($_SERVER["REQUEST_METHOD"] === "POST" && $canEdit) {
|
||||
foreach ($menuTypes as &$t) {
|
||||
if (($t["id"] ?? "") === $renameId) {
|
||||
$t["label"] = $newLabel;
|
||||
if ($newColor !== "") {
|
||||
$t["color"] = $newColor;
|
||||
}
|
||||
if ($newColor !== "") { $t["color"] = $newColor; }
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Clean up the reference to avoid accidental usage after the loop
|
||||
unset($t);
|
||||
file_put_contents($menuTypesPath, json_encode($menuTypes, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
header("Location: /entreaulas/comedor.php?aulario=" . urlencode($aulario_id) . "&date=" . urlencode($date) . "&menu=" . urlencode($renameId));
|
||||
db_set_comedor_menu_types($centro_id, $source_aulario_id, $menuTypes);
|
||||
header("Location: /aulatek/comedor.php?aulario=" . urlencode($aulario_id) . "&date=" . urlencode($date) . "&menu=" . urlencode($renameId));
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -316,23 +233,19 @@ if ($_SERVER["REQUEST_METHOD"] === "POST" && $canEdit) {
|
||||
$menuData["menus"][$menuTypeId] = blank_menu();
|
||||
}
|
||||
|
||||
// Pictogram images still stored on filesystem in Comedor dir
|
||||
$baseDir = aulatek_orgs_base_path() . "/$centro_id/Aularios/$source_aulario_id/Comedor/$ym/$day";
|
||||
$plates = ["primero", "segundo", "postre"];
|
||||
foreach ($plates as $plate) {
|
||||
$name = trim($_POST["name_" . $plate] ?? "");
|
||||
$menuData["menus"][$menuTypeId]["plates"][$plate]["name"] = $name;
|
||||
|
||||
$pictUpload = handle_image_upload("pictogram_file_" . $plate, $menuTypeId . "_" . $plate . "_pict", $baseDir, $uploadErrors);
|
||||
|
||||
if ($pictUpload !== null) {
|
||||
$menuData["menus"][$menuTypeId]["plates"][$plate]["pictogram"] = $pictUpload;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!is_dir($baseDir)) {
|
||||
mkdir($baseDir, 0777, true);
|
||||
}
|
||||
file_put_contents($dataPath, json_encode($menuData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
db_set_comedor_entry($centro_id, $source_aulario_id, $ym, $day, $menuData);
|
||||
$saveNotice = "Menú guardado correctamente.";
|
||||
}
|
||||
}
|
||||
@@ -346,24 +259,23 @@ function image_src($value, $centro_id, $source_aulario_id, $date)
|
||||
if (filter_var($value, FILTER_VALIDATE_URL)) {
|
||||
return $value;
|
||||
}
|
||||
return "/entreaulas/_filefetch.php?type=comedor_image¢ro=" . urlencode($centro_id) . "&aulario=" . urlencode($source_aulario_id) . "&date=" . urlencode($date) . "&file=" . urlencode($value);
|
||||
return "/aulatek/_filefetch.php?type=comedor_image&org=" . urlencode($centro_id) . "&aulario=" . urlencode($source_aulario_id) . "&date=" . urlencode($date) . "&file=" . urlencode($value);
|
||||
}
|
||||
|
||||
$prevDate = (clone $dateObj)->modify("-1 day")->format("Y-m-d");
|
||||
$nextDate = (clone $dateObj)->modify("+1 day")->format("Y-m-d");
|
||||
|
||||
$userAulas = $_SESSION["auth_data"]["entreaulas"]["aulas"] ?? [];
|
||||
$userAulas = $tenant_data["aulas"] ?? [];
|
||||
$aulaOptions = [];
|
||||
foreach ($userAulas as $aulaId) {
|
||||
$aulaIdSafe = safe_id_segment($aulaId);
|
||||
if ($aulaIdSafe === "") {
|
||||
continue;
|
||||
}
|
||||
$aulaPath = "/DATA/entreaulas/Centros/$centro_id/Aularios/$aulaIdSafe.json";
|
||||
$aulaData = file_exists($aulaPath) ? json_decode(file_get_contents($aulaPath), true) : null;
|
||||
$aulaData = db_get_aulario($centro_id, $aulaIdSafe);
|
||||
$aulaOptions[] = [
|
||||
"id" => $aulaIdSafe,
|
||||
"name" => $aulaData["name"] ?? $aulaIdSafe
|
||||
"id" => $aulaIdSafe,
|
||||
"name" => $aulaData["name"] ?? $aulaIdSafe,
|
||||
];
|
||||
}
|
||||
require_once "_incl/pre-body.php";
|
||||
@@ -394,9 +306,9 @@ require_once "_incl/pre-body.php";
|
||||
<!-- Navigation Buttons - Single row -->
|
||||
<div class="card pad">
|
||||
<div style="display: flex; gap: 10px; flex-wrap: wrap; align-items: center; justify-content: center; flex-direction: row;">
|
||||
<a class="btn btn-outline-dark" href="/entreaulas/comedor.php?aulario=<?= urlencode($aulario_id) ?>&date=<?= urlencode($prevDate) ?>&menu=<?= urlencode($menuTypeId) ?>">⟵ Día anterior</a>
|
||||
<a class="btn btn-outline-dark" href="/aulatek/comedor.php?aulario=<?= urlencode($aulario_id) ?>&date=<?= urlencode($prevDate) ?>&menu=<?= urlencode($menuTypeId) ?>">⟵ Día anterior</a>
|
||||
<input type="date" id="datePicker" class="form-control form-control-lg" value="<?= htmlspecialchars($date) ?>" style="max-width: 200px;">
|
||||
<a class="btn btn-outline-dark" href="/entreaulas/comedor.php?aulario=<?= urlencode($aulario_id) ?>&date=<?= urlencode($nextDate) ?>&menu=<?= urlencode($menuTypeId) ?>">Día siguiente ⟶</a>
|
||||
<a class="btn btn-outline-dark" href="/aulatek/comedor.php?aulario=<?= urlencode($aulario_id) ?>&date=<?= urlencode($nextDate) ?>&menu=<?= urlencode($menuTypeId) ?>">Día siguiente ⟶</a>
|
||||
</div>
|
||||
<div style="margin-top: 10px; text-align: center;">
|
||||
<label for="aularioPicker" class="form-label" style="margin-right: 10px;">Aulario:</label>
|
||||
@@ -419,7 +331,7 @@ require_once "_incl/pre-body.php";
|
||||
$isActive = ($type["id"] ?? "") === $menuTypeId;
|
||||
$color = $type["color"] ?? "#0d6efd";
|
||||
?>
|
||||
<a href="/entreaulas/comedor.php?aulario=<?= urlencode($aulario_id) ?>&date=<?= urlencode($date) ?>&menu=<?= urlencode($type["id"]) ?>"
|
||||
<a href="/aulatek/comedor.php?aulario=<?= urlencode($aulario_id) ?>&date=<?= urlencode($date) ?>&menu=<?= urlencode($type["id"]) ?>"
|
||||
class="btn btn-lg" style="background: <?= htmlspecialchars($color) ?>; color: white; border: 3px solid <?= $isActive ? "#000" : "transparent" ?>;">
|
||||
<?= htmlspecialchars($type["label"] ?? $type["id"]) ?>
|
||||
</a>
|
||||
@@ -644,7 +556,7 @@ require_once "_incl/pre-body.php";
|
||||
params.set("date", dateValue);
|
||||
params.set("aulario", aularioValue);
|
||||
params.set("menu", "<?= htmlspecialchars($menuTypeId) ?>");
|
||||
window.location.href = "/entreaulas/comedor.php?" + params.toString();
|
||||
window.location.href = "/aulatek/comedor.php?" + params.toString();
|
||||
}
|
||||
if (datePicker) {
|
||||
datePicker.addEventListener("change", goToSelection);
|
||||
|
||||
@@ -3,13 +3,15 @@ require_once "_incl/auth_redir.php";
|
||||
require_once "../_incl/tools.security.php";
|
||||
|
||||
// Check if user has docente permission
|
||||
if (!in_array("entreaulas:docente", $_SESSION["auth_data"]["permissions"] ?? [])) {
|
||||
$permissions = $_SESSION["auth_data"]["permissions"] ?? [];
|
||||
if (!in_array("aulatek:docente", $permissions, true) && !in_array("entreaulas:docente", $permissions, true)) {
|
||||
header("HTTP/1.1 403 Forbidden");
|
||||
die("Acceso denegado");
|
||||
}
|
||||
|
||||
$aulario_id = safe_id_segment($_GET["aulario"] ?? "");
|
||||
$centro_id = safe_centro_id($_SESSION["auth_data"]["entreaulas"]["centro"] ?? "");
|
||||
$tenant_data = $_SESSION["auth_data"]["aulatek"] ?? ($_SESSION["auth_data"]["entreaulas"] ?? []);
|
||||
$centro_id = safe_organization_id($tenant_data["organizacion"] ?? ($tenant_data["centro"] ?? ""));
|
||||
$alumno = safe_id_segment($_GET["alumno"] ?? "");
|
||||
|
||||
if (empty($aulario_id) || empty($centro_id)) {
|
||||
@@ -25,7 +27,7 @@ if (empty($aulario_id) || empty($centro_id)) {
|
||||
}
|
||||
|
||||
// Validate paths with realpath
|
||||
$base_path = "/DATA/entreaulas/Centros";
|
||||
$base_path = aulatek_orgs_base_path();
|
||||
$real_base = realpath($base_path);
|
||||
|
||||
if ($real_base === false) {
|
||||
@@ -77,7 +79,7 @@ if (empty($alumno)) {
|
||||
<div class="card" style="padding: 0; overflow: hidden;">
|
||||
<div style="background: #f8f9fa; padding: 15px; text-align: center;">
|
||||
<?php if ($photo_exists): ?>
|
||||
<img src="_filefetch.php?type=alumno_photo&alumno=<?= urlencode($alumno_name) ?>¢ro=<?= urlencode($centro_id) ?>&aulario=<?= urlencode($aulario_id) ?>"
|
||||
<img src="_filefetch.php?type=alumno_photo&alumno=<?= urlencode($alumno_name) ?>&org=<?= urlencode($centro_id) ?>&aulario=<?= urlencode($aulario_id) ?>"
|
||||
alt="Foto de <?= htmlspecialchars($alumno_name) ?>"
|
||||
style="width: 120px; height: 120px; object-fit: cover; border-radius: 10px; border: 3px solid #ddd;">
|
||||
<?php else: ?>
|
||||
|
||||
32
public_html/aulatek/index.php
Executable file → Normal file
32
public_html/aulatek/index.php
Executable file → Normal file
@@ -2,42 +2,42 @@
|
||||
require_once "_incl/auth_redir.php";
|
||||
require_once "_incl/pre-body.php";
|
||||
require_once "../_incl/tools.security.php";
|
||||
require_once "../_incl/db.php";
|
||||
|
||||
?>
|
||||
<div class="card pad">
|
||||
<div>
|
||||
<h1 class="card-title">¡Hola, <?php echo $_SESSION["auth_data"]["display_name"];?>!</h1>
|
||||
<h1 class="card-title">¡Hola, <?php echo htmlspecialchars($_SESSION["auth_data"]["display_name"]); ?>!</h1>
|
||||
<span>
|
||||
Bienvenidx a la plataforma de gestión de aularios conectados. Desde aquí podrás administrar los aularios asociados a tu cuenta.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="grid">
|
||||
<?php $user_data = $_SESSION["auth_data"];
|
||||
$centro_id = safe_centro_id($user_data["entreaulas"]["centro"] ?? "");
|
||||
foreach ($user_data["entreaulas"]["aulas"] as $aulario_id) {
|
||||
<?php
|
||||
$user_data = $_SESSION["auth_data"];
|
||||
$tenant_data = $user_data["aulatek"] ?? ($user_data["entreaulas"] ?? []);
|
||||
$centro_id = safe_organization_id($tenant_data["organizacion"] ?? ($tenant_data["centro"] ?? ""));
|
||||
$user_aulas = $tenant_data["aulas"] ?? [];
|
||||
foreach ($user_aulas as $aulario_id) {
|
||||
$aulario_id = safe_id_segment($aulario_id);
|
||||
if ($aulario_id === "") {
|
||||
continue;
|
||||
}
|
||||
$aulario_path = safe_aulario_config_path($centro_id, $aulario_id);
|
||||
if (!$aulario_path || !file_exists($aulario_path)) {
|
||||
continue;
|
||||
}
|
||||
$aulario = json_decode(file_get_contents($aulario_path), true);
|
||||
if (!is_array($aulario)) {
|
||||
$aulario = db_get_aulario($centro_id, $aulario_id);
|
||||
if (!$aulario) {
|
||||
continue;
|
||||
}
|
||||
$aulario_name = $aulario["name"] ?? $aulario_id;
|
||||
$aulario_icon = $aulario["icon"] ?? "/static/arasaac/aulario.png";
|
||||
echo '<a href="/entreaulas/aulario.php?id=' . urlencode($aulario_id) . '" class="btn btn-primary grid-item">
|
||||
echo '<a href="/aulatek/aulario.php?id=' . urlencode($aulario_id) . '" class="btn btn-primary grid-item">
|
||||
<img style="height: 125px;" src="' . htmlspecialchars($aulario_icon, ENT_QUOTES) . '" alt="' . htmlspecialchars($aulario_name) . ' Icono">
|
||||
<br>
|
||||
' . htmlspecialchars($aulario_name) . '
|
||||
</a>';
|
||||
} ?>
|
||||
<?php if (in_array('supercafe:access', $_SESSION['auth_data']['permissions'] ?? [])): ?>
|
||||
<a href="/entreaulas/supercafe.php" class="btn btn-warning grid-item">
|
||||
<a href="/aulatek/supercafe.php" class="btn btn-warning grid-item">
|
||||
<img src="/static/iconexperience/purchase_order_cart.png" height="125"
|
||||
style="background: white; padding: 5px; border-radius: 10px;"
|
||||
alt="Icono SuperCafe">
|
||||
@@ -61,7 +61,6 @@ require_once "../_incl/tools.security.php";
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<script>
|
||||
var msnry = new Masonry('#grid', {
|
||||
"columnWidth": 250,
|
||||
@@ -69,10 +68,7 @@ require_once "../_incl/tools.security.php";
|
||||
"gutter": 10,
|
||||
"transitionDuration": 0
|
||||
});
|
||||
setInterval(() => {
|
||||
msnry.layout()
|
||||
}, 1000);
|
||||
msnry.layout()
|
||||
setTimeout(() => { msnry.layout() }, 250);
|
||||
setInterval(() => { msnry.layout() }, 1000);
|
||||
</script>
|
||||
|
||||
<?php require_once "_incl/post-body.php"; ?>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
require_once "_incl/auth_redir.php";
|
||||
require_once "../_incl/tools.security.php";
|
||||
require_once "../_incl/db.php";
|
||||
ini_set("display_errors", "0");
|
||||
|
||||
// Funciones auxiliares para el diario
|
||||
@@ -8,8 +9,8 @@ function getDiarioPath($alumno, $centro_id, $aulario_id) {
|
||||
// Validate path components to avoid directory traversal or illegal characters
|
||||
// Allow only alphanumeric, underscore and dash for alumno and aulario_id
|
||||
$idPattern = '/^[A-Za-z0-9_-]+$/';
|
||||
// Typically centro_id is numeric; restrict it accordingly
|
||||
$centroPattern = '/^[0-9]+$/';
|
||||
// Organization id may include letters, numbers, dots, underscores and dashes.
|
||||
$centroPattern = '/^[A-Za-z0-9._-]+$/';
|
||||
|
||||
if (!preg_match($idPattern, (string)$alumno) ||
|
||||
!preg_match($idPattern, (string)$aulario_id) ||
|
||||
@@ -23,10 +24,12 @@ function getDiarioPath($alumno, $centro_id, $aulario_id) {
|
||||
$centro_safe = basename($centro_id);
|
||||
$aulario_safe = basename($aulario_id);
|
||||
|
||||
$base_path = "/DATA/entreaulas/Centros/$centro_safe/Aularios/$aulario_safe/Alumnos/$alumno_safe";
|
||||
$base_path = aulatek_orgs_base_path() . "/$centro_safe/Aularios/$aulario_safe/Alumnos/$alumno_safe";
|
||||
return $base_path . "/Diario/" . date("Y-m-d");
|
||||
}
|
||||
|
||||
$tenant_data = $_SESSION["auth_data"]["aulatek"] ?? ($_SESSION["auth_data"]["entreaulas"] ?? []);
|
||||
|
||||
function initDiario($alumno, $centro_id, $aulario_id) {
|
||||
$diario_path = getDiarioPath($alumno, $centro_id, $aulario_id);
|
||||
if ($diario_path) {
|
||||
@@ -72,14 +75,14 @@ function guardarPanelDiario($panel_name, $data, $alumno, $centro_id, $aulario_id
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_GET['api'])) {
|
||||
header('Content-Type: application/json');
|
||||
$api_action = $_GET['api'];
|
||||
$alumno = $_SESSION["entreaulas_selected_alumno"] ?? '';
|
||||
$centro_id = $_SESSION["auth_data"]["entreaulas"]["centro"] ?? '';
|
||||
$alumno = $_SESSION["aulatek_selected_alumno"] ?? ($_SESSION["entreaulas_selected_alumno"] ?? '');
|
||||
$centro_id = $tenant_data["organizacion"] ?? ($tenant_data["centro"] ?? '');
|
||||
|
||||
if ($api_action === 'guardar_panel' && $alumno && $centro_id) {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$panel_name = $input['panel'] ?? '';
|
||||
$panel_data = $input['data'] ?? [];
|
||||
$aulario_id = $_SESSION["entreaulas_selected_aulario"] ?? '';
|
||||
$aulario_id = $_SESSION["aulatek_selected_aulario"] ?? ($_SESSION["entreaulas_selected_aulario"] ?? '');
|
||||
guardarPanelDiario($panel_name, $panel_data, $alumno, $centro_id, $aulario_id);
|
||||
echo json_encode(['success' => true]);
|
||||
die();
|
||||
@@ -90,10 +93,12 @@ $form_action = $_GET["form"] ?? "";
|
||||
switch ($form_action) {
|
||||
case "alumno_selected":
|
||||
$alumno = safe_id_segment($_GET["alumno"] ?? "");
|
||||
$centro_id = safe_centro_id($_SESSION["auth_data"]["entreaulas"]["centro"] ?? "");
|
||||
$centro_id = safe_organization_id($tenant_data["organizacion"] ?? ($tenant_data["centro"] ?? ""));
|
||||
$aulario_id = safe_id_segment($_GET["aulario"] ?? '');
|
||||
$photo_url = $_GET["photo"] ?? '';
|
||||
if ($alumno !== "" && $centro_id !== "" && $aulario_id !== "") {
|
||||
$_SESSION["aulatek_selected_alumno"] = $alumno;
|
||||
$_SESSION["aulatek_selected_aulario"] = $aulario_id;
|
||||
$_SESSION["entreaulas_selected_alumno"] = $alumno;
|
||||
$_SESSION["entreaulas_selected_aulario"] = $aulario_id;
|
||||
initDiario($alumno, $centro_id, $aulario_id);
|
||||
@@ -208,8 +213,8 @@ ini_set("display_errors", "0");
|
||||
</script>
|
||||
<?php
|
||||
// Verificar si hay un alumno seleccionado y cargar su progreso
|
||||
$alumno_actual = $_SESSION["entreaulas_selected_alumno"] ?? '';
|
||||
$centro_id = safe_centro_id($_SESSION["auth_data"]["entreaulas"]["centro"] ?? '');
|
||||
$alumno_actual = $_SESSION["aulatek_selected_alumno"] ?? ($_SESSION["entreaulas_selected_alumno"] ?? '');
|
||||
$centro_id = safe_organization_id($tenant_data["organizacion"] ?? ($tenant_data["centro"] ?? ''));
|
||||
$aulario_id = safe_id_segment($_GET["aulario"] ?? '');
|
||||
|
||||
$diario_data = null;
|
||||
@@ -603,7 +608,7 @@ switch ($view_action) {
|
||||
case "quien_soy":
|
||||
// ¿Quién soy? - Identificación del alumno
|
||||
$aulario_id = safe_id_segment($_GET["aulario"] ?? '');
|
||||
$centro_id = safe_centro_id($_SESSION["auth_data"]["entreaulas"]["centro"] ?? '');
|
||||
$centro_id = safe_organization_id($tenant_data["organizacion"] ?? ($tenant_data["centro"] ?? ''));
|
||||
|
||||
// Validate parameters
|
||||
if (empty($aulario_id) || empty($centro_id)) {
|
||||
@@ -611,7 +616,7 @@ switch ($view_action) {
|
||||
break;
|
||||
}
|
||||
|
||||
$base_path = "/DATA/entreaulas/Centros";
|
||||
$base_path = aulatek_orgs_base_path();
|
||||
$alumnos_path = "$base_path/$centro_id/Aularios/$aulario_id/Alumnos";
|
||||
|
||||
// Validate the path is within the expected directory
|
||||
@@ -628,12 +633,12 @@ switch ($view_action) {
|
||||
element.style.backgroundColor = "#9cff9f"; // Verde
|
||||
let photoUrl = '';
|
||||
if (hasPhoto) {
|
||||
photoUrl = '/entreaulas/_filefetch.php?type=alumno_photo&alumno=' + encodeURIComponent(nombre) +
|
||||
photoUrl = '/aulatek/_filefetch.php?type=alumno_photo&alumno=' + encodeURIComponent(nombre) +
|
||||
'¢ro=' + encodeURIComponent(centro) + '&aulario=' + encodeURIComponent(aulario);
|
||||
}
|
||||
announceAndMaybeRedirect(
|
||||
"¡Hola " + nombre + "!",
|
||||
"/entreaulas/paneldiario.php?aulario=" + encodeURIComponent(aulario) + "&form=alumno_selected&alumno=" + encodeURIComponent(nombre) +
|
||||
"/aulatek/paneldiario.php?aulario=" + encodeURIComponent(aulario) + "&form=alumno_selected&alumno=" + encodeURIComponent(nombre) +
|
||||
(photoUrl ? "&photo=" + encodeURIComponent(photoUrl) : ''),
|
||||
true
|
||||
);
|
||||
@@ -661,7 +666,7 @@ switch ($view_action) {
|
||||
?>
|
||||
<a href="#" class="card grid-item" style="color: black;" onclick='seleccionarAlumno(this, "<?php echo htmlspecialchars($alumno_name, ENT_QUOTES); ?>", <?php echo $photo_exists ? 'true' : 'false'; ?>, "<?php echo htmlspecialchars($centro_id, ENT_QUOTES); ?>", "<?php echo htmlspecialchars($aulario_id, ENT_QUOTES); ?>");' aria-label="Seleccionar alumno <?php echo htmlspecialchars($alumno_name); ?>">
|
||||
<?php if ($photo_exists): ?>
|
||||
<img src="_filefetch.php?type=alumno_photo&alumno=<?php echo urlencode($alumno_name); ?>¢ro=<?php echo urlencode($centro_id); ?>&aulario=<?php echo urlencode($aulario_id); ?>" height="150" class="bg-white" alt="Foto de <?php echo htmlspecialchars($alumno_name); ?>">
|
||||
<img src="_filefetch.php?type=alumno_photo&alumno=<?php echo urlencode($alumno_name); ?>&org=<?php echo urlencode($centro_id); ?>&aulario=<?php echo urlencode($aulario_id); ?>" height="150" class="bg-white" alt="Foto de <?php echo htmlspecialchars($alumno_name); ?>">
|
||||
<?php else: ?>
|
||||
<div style="width: 150px; height: 150px; background: #f0f0f0; display: flex; align-items: center; justify-content: center; border-radius: 10px; border: 2px dashed #ccc;">
|
||||
<span style="font-size: 48px;">?</span>
|
||||
@@ -704,10 +709,10 @@ switch ($view_action) {
|
||||
<?php
|
||||
break;
|
||||
case "actividades":
|
||||
$centro_actividades = safe_centro_id($_SESSION["auth_data"]["entreaulas"]["centro"] ?? '');
|
||||
$centro_actividades = safe_organization_id($tenant_data["organizacion"] ?? ($tenant_data["centro"] ?? ''));
|
||||
$actividades = [];
|
||||
if ($centro_actividades !== '') {
|
||||
$actividades_path = "/DATA/entreaulas/Centros/$centro_actividades/Panel/Actividades";
|
||||
$actividades_path = aulatek_orgs_base_path() . "/$centro_actividades/Panel/Actividades";
|
||||
if (is_dir($actividades_path)) {
|
||||
$actividades = glob($actividades_path . "/*", GLOB_ONLYDIR) ?: [];
|
||||
}
|
||||
@@ -718,7 +723,7 @@ switch ($view_action) {
|
||||
element.style.backgroundColor = "#9cff9f"; // Verde
|
||||
|
||||
// Guardar al diario antes de redirigir
|
||||
fetch('/entreaulas/paneldiario.php?api=guardar_panel', {
|
||||
fetch('/aulatek/paneldiario.php?api=guardar_panel', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@@ -731,7 +736,7 @@ switch ($view_action) {
|
||||
}).finally(() => {
|
||||
announceAndMaybeRedirect(
|
||||
actividad + ", Actividad seleccionada",
|
||||
"/entreaulas/paneldiario.php?aulario=<?php echo urlencode($_GET['aulario'] ?? ''); ?>",
|
||||
"/aulatek/paneldiario.php?aulario=<?php echo urlencode($_GET['aulario'] ?? ''); ?>",
|
||||
true
|
||||
);
|
||||
});
|
||||
@@ -745,7 +750,7 @@ switch ($view_action) {
|
||||
<div class="grid">
|
||||
<?php foreach ($actividades as $actividad_path) {
|
||||
$actividad_name = basename($actividad_path);
|
||||
$pictogram_url = '/entreaulas/_filefetch.php?type=panel_actividades&activity=' . urlencode($actividad_name) . '¢ro=' . urlencode($centro_actividades);
|
||||
$pictogram_url = '/aulatek/_filefetch.php?type=panel_actividades&activity=' . urlencode($actividad_name) . '&org=' . urlencode($centro_actividades);
|
||||
?>
|
||||
<a class="card grid-item" style="color: black;" onclick="seleccionarActividad(this, '<?php echo htmlspecialchars($actividad_name); ?>', '<?php echo htmlspecialchars($pictogram_url); ?>');">
|
||||
<img src="<?php echo htmlspecialchars($pictogram_url); ?>" height="125" class="bg-white">
|
||||
@@ -779,17 +784,15 @@ switch ($view_action) {
|
||||
case "menu":
|
||||
// Menú del comedor (nuevo sistema, vista simplificada)
|
||||
$aulario_id = safe_id_segment($_GET["aulario"] ?? '');
|
||||
$centro_id = safe_centro_id($_SESSION["auth_data"]["entreaulas"]["centro"] ?? "");
|
||||
$centro_id = safe_organization_id($tenant_data["organizacion"] ?? ($tenant_data["centro"] ?? ""));
|
||||
|
||||
$source_aulario_id = $aulario_id;
|
||||
$is_shared = false;
|
||||
if ($aulario_id !== "" && $centro_id !== "") {
|
||||
$aulario_path = safe_aulario_config_path($centro_id, $aulario_id);
|
||||
$aulario = ($aulario_path && file_exists($aulario_path)) ? json_decode(file_get_contents($aulario_path), true) : null;
|
||||
$aulario = db_get_aulario($centro_id, $aulario_id);
|
||||
if ($aulario && !empty($aulario["shared_comedor_from"])) {
|
||||
$shared_from = safe_id_segment($aulario["shared_comedor_from"]);
|
||||
$shared_aulario_path = safe_aulario_config_path($centro_id, $shared_from);
|
||||
if ($shared_aulario_path && file_exists($shared_aulario_path)) {
|
||||
if (db_get_aulario($centro_id, $shared_from)) {
|
||||
$source_aulario_id = $shared_from;
|
||||
$is_shared = true;
|
||||
}
|
||||
@@ -800,13 +803,12 @@ switch ($view_action) {
|
||||
$dateObj = DateTime::createFromFormat("Y-m-d", $dateParam) ?: new DateTime();
|
||||
$date = $dateObj->format("Y-m-d");
|
||||
|
||||
$menuTypesPath = ($centro_id !== '' && $source_aulario_id !== '') ? "/DATA/entreaulas/Centros/$centro_id/Aularios/$source_aulario_id/Comedor-MenuTypes.json" : "";
|
||||
$defaultMenuTypes = [
|
||||
["id" => "basal", "label" => "Menú basal", "color" => "#0d6efd"],
|
||||
["id" => "vegetariano", "label" => "Menú vegetariano", "color" => "#198754"],
|
||||
["id" => "alergias", "label" => "Menú alergias", "color" => "#dc3545"],
|
||||
];
|
||||
$menuTypes = ($menuTypesPath !== '' && file_exists($menuTypesPath)) ? json_decode(@file_get_contents($menuTypesPath), true) : null;
|
||||
$menuTypes = ($centro_id !== '' && $source_aulario_id !== '') ? db_get_comedor_menu_types($centro_id, $source_aulario_id) : [];
|
||||
if (!is_array($menuTypes) || count($menuTypes) === 0) {
|
||||
$menuTypes = $defaultMenuTypes;
|
||||
}
|
||||
@@ -822,17 +824,13 @@ switch ($view_action) {
|
||||
$menuTypeId = $menuTypeIds[0] ?? "basal";
|
||||
}
|
||||
|
||||
$ym = $dateObj->format("Y-m");
|
||||
$ym = $dateObj->format("Y-m");
|
||||
$day = $dateObj->format("d");
|
||||
$dataPath = ($centro_id !== '' && $source_aulario_id !== '') ? "/DATA/entreaulas/Centros/$centro_id/Aularios/$source_aulario_id/Comedor/$ym/$day/_datos.json" : "";
|
||||
|
||||
$menuData = [
|
||||
"date" => $date,
|
||||
"menus" => []
|
||||
];
|
||||
if ($dataPath !== '' && file_exists($dataPath)) {
|
||||
$existing = json_decode(file_get_contents($dataPath), true);
|
||||
if (is_array($existing)) {
|
||||
$menuData = ["date" => $date, "menus" => []];
|
||||
if ($centro_id !== '' && $source_aulario_id !== '') {
|
||||
$existing = db_get_comedor_entry($centro_id, $source_aulario_id, $ym, $day);
|
||||
if (!empty($existing)) {
|
||||
$menuData = array_merge($menuData, $existing);
|
||||
}
|
||||
}
|
||||
@@ -843,7 +841,7 @@ switch ($view_action) {
|
||||
if (!$value) {
|
||||
return "";
|
||||
}
|
||||
return "/entreaulas/_filefetch.php?type=comedor_image¢ro=" . urlencode($centro_id) . "&aulario=" . urlencode($aulario_id) . "&date=" . urlencode($date) . "&file=" . urlencode($value);
|
||||
return "/aulatek/_filefetch.php?type=comedor_image&org=" . urlencode($centro_id) . "&aulario=" . urlencode($aulario_id) . "&date=" . urlencode($date) . "&file=" . urlencode($value);
|
||||
}
|
||||
?>
|
||||
<script>
|
||||
@@ -851,7 +849,7 @@ switch ($view_action) {
|
||||
element.style.backgroundColor = "#9cff9f"; // Verde
|
||||
|
||||
// Guardar al diario antes de redirigir
|
||||
fetch('/entreaulas/paneldiario.php?api=guardar_panel', {
|
||||
fetch('/aulatek/paneldiario.php?api=guardar_panel', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@@ -861,7 +859,7 @@ switch ($view_action) {
|
||||
}).finally(() => {
|
||||
announceAndMaybeRedirect(
|
||||
menu_ty + ", Menú seleccionado",
|
||||
"/entreaulas/paneldiario.php?aulario=<?php echo urlencode($_GET['aulario'] ?? ''); ?>",
|
||||
"/aulatek/paneldiario.php?aulario=<?php echo urlencode($_GET['aulario'] ?? ''); ?>",
|
||||
true
|
||||
);
|
||||
});
|
||||
@@ -1034,7 +1032,7 @@ switch ($view_action) {
|
||||
element.style.backgroundColor = "#9cff9f"; // Verde
|
||||
|
||||
// Guardar al diario antes de redirigir
|
||||
fetch('/entreaulas/paneldiario.php?api=guardar_panel', {
|
||||
fetch('/aulatek/paneldiario.php?api=guardar_panel', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@@ -1142,7 +1140,7 @@ switch ($view_action) {
|
||||
element.style.backgroundColor = "#9cff9f"; // Verde
|
||||
|
||||
// Guardar al diario antes de redirigir
|
||||
fetch('/entreaulas/paneldiario.php?api=guardar_panel', {
|
||||
fetch('/aulatek/paneldiario.php?api=guardar_panel', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@@ -1152,7 +1150,7 @@ switch ($view_action) {
|
||||
}).finally(() => {
|
||||
announceAndMaybeRedirect(
|
||||
dow[ds] + ", Correcto",
|
||||
"/entreaulas/paneldiario.php?action=calendario_mes&aulario=<?php echo urlencode($_GET['aulario'] ?? ''); ?>",
|
||||
"/aulatek/paneldiario.php?action=calendario_mes&aulario=<?php echo urlencode($_GET['aulario'] ?? ''); ?>",
|
||||
true
|
||||
);
|
||||
});
|
||||
@@ -1260,7 +1258,7 @@ switch ($view_action) {
|
||||
element.style.backgroundColor = "#9cff9f"; // Verde
|
||||
|
||||
// Guardar al diario antes de redirigir
|
||||
fetch('/entreaulas/paneldiario.php?api=guardar_panel', {
|
||||
fetch('/aulatek/paneldiario.php?api=guardar_panel', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@@ -1270,7 +1268,7 @@ switch ($view_action) {
|
||||
}).finally(() => {
|
||||
announceAndMaybeRedirect(
|
||||
meses[mes] + ", Correcto",
|
||||
"/entreaulas/paneldiario.php?aulario=<?php echo urlencode($_GET['aulario'] ?? ''); ?>",
|
||||
"/aulatek/paneldiario.php?aulario=<?php echo urlencode($_GET['aulario'] ?? ''); ?>",
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ $is_valid = false;
|
||||
if ($file !== "") {
|
||||
$parsed = parse_url($file);
|
||||
if (!isset($parsed["scheme"]) && !isset($parsed["host"])) {
|
||||
if (strpos($file, "/entreaulas/_filefetch.php") === 0) {
|
||||
if (strpos($file, "/aulatek/_filefetch.php") === 0) {
|
||||
$is_valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
<?php
|
||||
require_once "_incl/auth_redir.php";
|
||||
require_once "../_incl/tools.security.php";
|
||||
require_once "../_incl/db.php";
|
||||
|
||||
if (in_array("entreaulas:docente", $_SESSION["auth_data"]["permissions"] ?? []) === false) {
|
||||
$permissions = $_SESSION["auth_data"]["permissions"] ?? [];
|
||||
if (!in_array("aulatek:docente", $permissions, true) && !in_array("entreaulas:docente", $permissions, true)) {
|
||||
header("HTTP/1.1 403 Forbidden");
|
||||
die("Access denied");
|
||||
}
|
||||
|
||||
$aulario_id = safe_path_segment($_GET["aulario"] ?? "");
|
||||
$centro_id = safe_path_segment($_SESSION["auth_data"]["entreaulas"]["centro"] ?? "");
|
||||
$tenant_data = $_SESSION["auth_data"]["aulatek"] ?? ($_SESSION["auth_data"]["entreaulas"] ?? []);
|
||||
$centro_id = safe_path_segment($tenant_data["organizacion"] ?? ($tenant_data["centro"] ?? ""));
|
||||
|
||||
// Validate centro_id and aulario_id to prevent directory traversal
|
||||
if ($aulario_id === "" || $centro_id === "") {
|
||||
@@ -23,38 +26,14 @@ if ($aulario_id === "" || $centro_id === "") {
|
||||
exit;
|
||||
}
|
||||
|
||||
$aulario_path = "/DATA/entreaulas/Centros/$centro_id/Aularios/$aulario_id.json";
|
||||
$aulario = file_exists($aulario_path) ? json_decode(file_get_contents($aulario_path), true) : null;
|
||||
$aulario = db_get_aulario($centro_id, $aulario_id);
|
||||
|
||||
$proyectos_dir = "/DATA/entreaulas/Centros/$centro_id/Proyectos";
|
||||
$proyectos_dir = aulatek_orgs_base_path() . "/$centro_id/Proyectos";
|
||||
if (!is_dir($proyectos_dir)) {
|
||||
mkdir($proyectos_dir, 0755, true);
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
function safe_filename($name)
|
||||
{
|
||||
// Normalize to base name to avoid directory traversal
|
||||
$name = basename($name);
|
||||
// Replace disallowed characters with underscore
|
||||
$name = preg_replace("/[^a-zA-Z0-9._-]/", "_", $name);
|
||||
// Remove leading dots to avoid hidden/special files like ".htaccess"
|
||||
$name = ltrim($name, '.');
|
||||
// Ensure there is at most one dot in the filename to prevent extension confusion
|
||||
if (substr_count($name, '.') > 1) {
|
||||
$parts = explode('.', $name);
|
||||
$ext = array_pop($parts);
|
||||
$base = implode('_', $parts);
|
||||
// Ensure extension is not empty
|
||||
if ($ext === '') {
|
||||
$name = $base === '' ? 'file' : $base;
|
||||
} else {
|
||||
$name = ($base === '' ? 'file' : $base) . '.' . $ext;
|
||||
}
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
function safe_path_segment($value)
|
||||
{
|
||||
$value = basename((string)$value);
|
||||
@@ -71,16 +50,6 @@ function safe_join_file($base_dir, $filename)
|
||||
return rtrim($base_dir, '/') . '/' . $safe_name;
|
||||
}
|
||||
|
||||
function safe_aulario_config_path($centro_id, $aulario_id)
|
||||
{
|
||||
$safe_centro = safe_path_segment($centro_id);
|
||||
$safe_aulario = safe_path_segment($aulario_id);
|
||||
if ($safe_centro === '' || $safe_aulario === '') {
|
||||
return null;
|
||||
}
|
||||
return "/DATA/entreaulas/Centros/$safe_centro/Aularios/$safe_aulario.json";
|
||||
}
|
||||
|
||||
function sanitize_html($html)
|
||||
{
|
||||
$html = trim($html ?? "");
|
||||
@@ -367,7 +336,7 @@ function get_linked_projects($aulario, $centro_id)
|
||||
continue;
|
||||
}
|
||||
|
||||
$projects_base = "/DATA/entreaulas/Centros/$centro_id/Proyectos";
|
||||
$projects_base = aulatek_orgs_base_path() . "/$centro_id/Proyectos";
|
||||
$project = load_project($projects_base, $project_id);
|
||||
if ($project && ($project["parent_id"] ?? null) === null) {
|
||||
// Mark as linked and add source info
|
||||
@@ -450,7 +419,7 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
}
|
||||
}
|
||||
|
||||
header("Location: /entreaulas/proyectos.php?aulario=" . urlencode($aulario_id) . "&project=" . urlencode($project_id));
|
||||
header("Location: /aulatek/proyectos.php?aulario=" . urlencode($aulario_id) . "&project=" . urlencode($project_id));
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
@@ -459,23 +428,18 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
}
|
||||
|
||||
if ($action === "share_project") {
|
||||
$project_id = safe_path_segment($_POST["project_id"] ?? "");
|
||||
$project_id = safe_path_segment($_POST["project_id"] ?? "");
|
||||
$target_aulario = safe_path_segment($_POST["target_aulario"] ?? "");
|
||||
|
||||
if ($project_id !== "" && $target_aulario !== "" && $target_aulario !== $aulario_id) {
|
||||
// Only allow sharing local projects
|
||||
$is_local_project = (load_project($proyectos_dir, $project_id) !== null);
|
||||
if (!$is_local_project) {
|
||||
$error = "No se puede compartir un proyecto ajeno.";
|
||||
} else {
|
||||
$target_config_path = safe_aulario_config_path($centro_id, $target_aulario);
|
||||
if ($target_config_path === null || !file_exists($target_config_path)) {
|
||||
$target_config = db_get_aulario($centro_id, $target_aulario);
|
||||
if ($target_config === null) {
|
||||
$error = "Aulario de destino no encontrado.";
|
||||
} else {
|
||||
$target_config = json_decode(file_get_contents($target_config_path), true);
|
||||
if (!is_array($target_config)) {
|
||||
$target_config = [];
|
||||
}
|
||||
if (!isset($target_config["linked_projects"]) || !is_array($target_config["linked_projects"])) {
|
||||
$target_config["linked_projects"] = [];
|
||||
}
|
||||
@@ -494,20 +458,30 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
} else {
|
||||
$target_config["linked_projects"][] = [
|
||||
"source_aulario" => $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 org_id = ? AND aulario_id = ?"
|
||||
)->execute([json_encode($extra, JSON_UNESCAPED_UNICODE), $centro_id, $target_aulario]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($action === "delete_project") {
|
||||
if (in_array("entreaulas:proyectos:delete", $_SESSION["auth_data"]["permissions"] ?? []) === false) {
|
||||
if (!in_array("aulatek:proyectos:delete", $permissions, true) && !in_array("entreaulas:proyectos:delete", $permissions, true)) {
|
||||
$error = "No tienes permisos para borrar proyectos.";
|
||||
} else {
|
||||
$project_id = safe_path_segment($_POST["project_id"] ?? "");
|
||||
@@ -766,7 +740,7 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
if (!empty($source_aulario_param)) {
|
||||
$redirect_params .= "&source=" . urlencode($source_aulario_param);
|
||||
}
|
||||
header("Location: /entreaulas/proyectos.php?" . $redirect_params);
|
||||
header("Location: /aulatek/proyectos.php?" . $redirect_params);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -1030,7 +1004,7 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
if (!empty($source_aulario_param)) {
|
||||
$redirect_params .= "&source=" . urlencode($source_aulario_param);
|
||||
}
|
||||
header("Location: /entreaulas/proyectos.php?" . $redirect_params);
|
||||
header("Location: /aulatek/proyectos.php?" . $redirect_params);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -1060,7 +1034,7 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
if (!empty($source_aulario_param)) {
|
||||
$redirect_params .= "&source=" . urlencode($source_aulario_param);
|
||||
}
|
||||
header("Location: /entreaulas/proyectos.php?" . $redirect_params);
|
||||
header("Location: /aulatek/proyectos.php?" . $redirect_params);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -1180,7 +1154,7 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
if (!empty($source_aulario_param)) {
|
||||
$redirect_params .= "&source=" . urlencode($source_aulario_param);
|
||||
}
|
||||
header("Location: /entreaulas/proyectos.php?" . $redirect_params);
|
||||
header("Location: /aulatek/proyectos.php?" . $redirect_params);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -1382,11 +1356,11 @@ $view = $current_project ? "project" : "list";
|
||||
</p>
|
||||
<div class="d-flex gap-2">
|
||||
<?php if ($is_linked): ?>
|
||||
<a href="/entreaulas/proyectos.php?aulario=<?= urlencode($aulario_id) ?>&project=<?= urlencode($project["id"]) ?>&source=<?= urlencode($source_aulario) ?>" class="btn btn-primary">
|
||||
<a href="/aulatek/proyectos.php?aulario=<?= urlencode($aulario_id) ?>&project=<?= urlencode($project["id"]) ?>&source=<?= urlencode($source_aulario) ?>" class="btn btn-primary">
|
||||
Abrir
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<a href="/entreaulas/proyectos.php?aulario=<?= urlencode($aulario_id) ?>&project=<?= urlencode($project["id"]) ?>" class="btn btn-primary">
|
||||
<a href="/aulatek/proyectos.php?aulario=<?= urlencode($aulario_id) ?>&project=<?= urlencode($project["id"]) ?>" class="btn btn-primary">
|
||||
Abrir
|
||||
</a>
|
||||
<!-- Delete -->
|
||||
@@ -1476,7 +1450,7 @@ $view = $current_project ? "project" : "list";
|
||||
$linked_permission = $link["permission"] ?? "read_only";
|
||||
break;
|
||||
}
|
||||
$project_source_dir = "/DATA/entreaulas/Centros/$centro_id/Proyectos";
|
||||
$project_source_dir = aulatek_orgs_base_path() . "/$centro_id/Proyectos";
|
||||
$breadcrumb_check = get_project_breadcrumb($project_source_dir, $current_project);
|
||||
foreach ($breadcrumb_check as $crumb) {
|
||||
if (($crumb["id"] ?? "") === $link_root_id) {
|
||||
@@ -1489,7 +1463,7 @@ $view = $current_project ? "project" : "list";
|
||||
|
||||
if ($valid_link) {
|
||||
$is_linked_project = true;
|
||||
$project_source_dir = "/DATA/entreaulas/Centros/$centro_id/Proyectos";
|
||||
$project_source_dir = aulatek_orgs_base_path() . "/$centro_id/Proyectos";
|
||||
$project = load_project($project_source_dir, $current_project);
|
||||
} else {
|
||||
// Invalid link configuration, treat as local project
|
||||
@@ -1513,7 +1487,7 @@ $view = $current_project ? "project" : "list";
|
||||
<div class="card pad">
|
||||
<h1>Error</h1>
|
||||
<p>Proyecto no encontrado.</p>
|
||||
<a href="/entreaulas/proyectos.php?aulario=<?= urlencode($aulario_id) ?>" class="btn btn-primary">Volver a Proyectos</a>
|
||||
<a href="/aulatek/proyectos.php?aulario=<?= urlencode($aulario_id) ?>" class="btn btn-primary">Volver a Proyectos</a>
|
||||
</div>
|
||||
<?php
|
||||
require_once "_incl/post-body.php";
|
||||
@@ -1533,12 +1507,12 @@ $view = $current_project ? "project" : "list";
|
||||
<div class="card pad">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item">
|
||||
<a href="/entreaulas/proyectos.php?aulario=<?= urlencode($aulario_id) ?>">Proyectos</a>
|
||||
<a href="/aulatek/proyectos.php?aulario=<?= urlencode($aulario_id) ?>">Proyectos</a>
|
||||
</li>
|
||||
<?php foreach ($breadcrumb as $idx => $crumb): ?>
|
||||
<?php if ($idx < count($breadcrumb) - 1): ?>
|
||||
<li class="breadcrumb-item">
|
||||
<a href="/entreaulas/proyectos.php?aulario=<?= urlencode($aulario_id) ?>&project=<?= urlencode($crumb["id"]) ?><?= $is_linked_project ? "&source=" . urlencode($source_aulario_for_project) : "" ?>">
|
||||
<a href="/aulatek/proyectos.php?aulario=<?= urlencode($aulario_id) ?>&project=<?= urlencode($crumb["id"]) ?><?= $is_linked_project ? "&source=" . urlencode($source_aulario_for_project) : "" ?>">
|
||||
<?= htmlspecialchars($crumb["name"]) ?>
|
||||
</a>
|
||||
</li>
|
||||
@@ -1570,10 +1544,9 @@ $view = $current_project ? "project" : "list";
|
||||
<path d="M18,16.08C17.24,16.08 16.56,16.38 16.04,16.85L8.91,12.7C8.96,12.47 9,12.24 9,12C9,11.76 8.96,11.53 8.91,11.3L15.96,7.19C16.5,7.69 17.21,8 18,8A3,3 0 0,0 21,5A3,3 0 0,0 18,2A3,3 0 0,0 15,5C15,5.24 15.04,5.47 15.09,5.7L8.04,9.81C7.5,9.31 6.79,9 6,9A3,3 0 0,0 3,12A3,3 0 0,0 6,15C6.79,15 7.5,14.69 8.04,14.19L15.16,18.34C15.11,18.55 15.08,18.77 15.08,19C15.08,20.61 16.39,21.91 18,21.91C19.61,21.91 20.92,20.61 20.92,19A2.92,2.92 0 0,0 18,16.08Z" />
|
||||
</svg>
|
||||
<?php
|
||||
$source_aulario_path = safe_aulario_config_path($centro_id, $source_aulario_for_project);
|
||||
$source_aulario_name = "";
|
||||
if ($source_aulario_path && file_exists($source_aulario_path)) {
|
||||
$source_aulario_data = json_decode(file_get_contents($source_aulario_path), true);
|
||||
$source_aulario_data = db_get_aulario($centro_id, $source_aulario_for_project);
|
||||
if ($source_aulario_data) {
|
||||
$source_aulario_name = $source_aulario_data["name"] ?? "";
|
||||
}
|
||||
?>
|
||||
@@ -1587,11 +1560,11 @@ $view = $current_project ? "project" : "list";
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<?php if (!empty($project["parent_id"])): ?>
|
||||
<a href="/entreaulas/proyectos.php?aulario=<?= urlencode($aulario_id) ?>&project=<?= urlencode($project["parent_id"]) ?><?= $is_linked_project ? "&source=" . urlencode($source_aulario_for_project) : "" ?>" class="btn btn-secondary">
|
||||
<a href="/aulatek/proyectos.php?aulario=<?= urlencode($aulario_id) ?>&project=<?= urlencode($project["parent_id"]) ?><?= $is_linked_project ? "&source=" . urlencode($source_aulario_for_project) : "" ?>" class="btn btn-secondary">
|
||||
← Volver al Proyecto
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<a href="/entreaulas/proyectos.php?aulario=<?= urlencode($aulario_id) ?>" class="btn btn-secondary">
|
||||
<a href="/aulatek/proyectos.php?aulario=<?= urlencode($aulario_id) ?>" class="btn btn-secondary">
|
||||
← Volver al Listado
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
@@ -1636,27 +1609,14 @@ $view = $current_project ? "project" : "list";
|
||||
<?php
|
||||
function list_aularios($centro_id)
|
||||
{
|
||||
$aularios_dir = "/DATA/entreaulas/Centros/$centro_id/Aularios";
|
||||
$aularios_db = db_get_aularios($centro_id);
|
||||
$aularios = [];
|
||||
if (is_dir($aularios_dir)) {
|
||||
$entries = scandir($aularios_dir);
|
||||
foreach ($entries as $entry) {
|
||||
if ($entry === "." || $entry === "..") {
|
||||
continue;
|
||||
}
|
||||
$aulario_path = "$aularios_dir/$entry";
|
||||
if (is_dir($aulario_path)) {
|
||||
$config_file = "$aulario_path.json";
|
||||
if (file_exists($config_file)) {
|
||||
$config = json_decode(file_get_contents($config_file), true);
|
||||
$aularios[] = [
|
||||
"id" => $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;
|
||||
}
|
||||
@@ -1728,7 +1688,7 @@ $view = $current_project ? "project" : "list";
|
||||
</small>
|
||||
</p>
|
||||
<div class="d-flex gap-2">
|
||||
<a href="/entreaulas/proyectos.php?aulario=<?= urlencode($aulario_id) ?>&project=<?= urlencode($subproject["id"]) ?><?= $is_linked_project ? "&source=" . urlencode($source_aulario_for_project) : "" ?>" class="btn btn-primary">
|
||||
<a href="/aulatek/proyectos.php?aulario=<?= urlencode($aulario_id) ?>&project=<?= urlencode($subproject["id"]) ?><?= $is_linked_project ? "&source=" . urlencode($source_aulario_for_project) : "" ?>" class="btn btn-primary">
|
||||
Abrir
|
||||
</a>
|
||||
<?php if (!$is_linked_project || ($is_linked_project && $linked_permission === "full_edit")): ?>
|
||||
@@ -1833,7 +1793,7 @@ $view = $current_project ? "project" : "list";
|
||||
<?php elseif ($item["type"] === "pdf_secure"): ?>
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#viewPdfSecureModal"
|
||||
data-item-name="<?= htmlspecialchars($item["name"], ENT_QUOTES) ?>"
|
||||
data-file-url="/entreaulas/_filefetch.php?type=proyecto_file¢ro=<?= urlencode($centro_id) ?>&project=<?= urlencode($current_project) ?>&file=<?= urlencode($item["filename"]) ?>">
|
||||
data-file-url="/aulatek/_filefetch.php?type=proyecto_file&org=<?= urlencode($centro_id) ?>&project=<?= urlencode($current_project) ?>&file=<?= urlencode($item["filename"]) ?>">
|
||||
Abrir
|
||||
</button>
|
||||
<?php elseif ($item["type"] === "videocall"): ?>
|
||||
@@ -1847,7 +1807,7 @@ $view = $current_project ? "project" : "list";
|
||||
Abrir
|
||||
</button>
|
||||
<?php else: ?>
|
||||
<a href="/entreaulas/_filefetch.php?type=proyecto_file¢ro=<?= urlencode($centro_id) ?>&project=<?= urlencode($current_project) ?>&file=<?= urlencode($item["filename"]) ?>" target="_blank" class="btn btn-primary">
|
||||
<a href="/aulatek/_filefetch.php?type=proyecto_file&org=<?= urlencode($centro_id) ?>&project=<?= urlencode($current_project) ?>&file=<?= urlencode($item["filename"]) ?>" target="_blank" class="btn btn-primary">
|
||||
Abrir
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
@@ -1918,10 +1878,8 @@ $view = $current_project ? "project" : "list";
|
||||
<div id="grid">
|
||||
<?php foreach ($pending_changes as $change):
|
||||
$requesting_aulario = $change["requested_by_aulario"] ?? "Desconocido";
|
||||
// Get requesting aulario name
|
||||
$requesting_aulario = safe_path_segment($requesting_aulario);
|
||||
$req_aul_path = safe_aulario_config_path($centro_id, $requesting_aulario);
|
||||
$req_aul_data = ($req_aul_path && file_exists($req_aul_path)) ? json_decode(file_get_contents($req_aul_path), true) : null;
|
||||
$req_aul_data = db_get_aulario($centro_id, $requesting_aulario);
|
||||
$req_aul_name = $req_aul_data["name"] ?? $requesting_aulario;
|
||||
$req_persona_name = $change["requested_by_persona_name"] ?? "Desconocido";
|
||||
?>
|
||||
@@ -2579,7 +2537,7 @@ $view = $current_project ? "project" : "list";
|
||||
var title = button.getAttribute('data-item-name') || 'PDF Seguro';
|
||||
var url = button.getAttribute('data-file-url') || '';
|
||||
document.getElementById('viewPdfSecureModalLabel').textContent = title;
|
||||
document.getElementById('pdf_secure_frame').src = url ? ('/entreaulas/pdf_secure_viewer.php?file=' + encodeURIComponent(url)) : '';
|
||||
document.getElementById('pdf_secure_frame').src = url ? ('/aulatek/pdf_secure_viewer.php?file=' + encodeURIComponent(url)) : '';
|
||||
});
|
||||
viewPdfSecureModal.addEventListener('hidden.bs.modal', function() {
|
||||
document.getElementById('pdf_secure_frame').src = '';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
require_once "_incl/auth_redir.php";
|
||||
require_once "../_incl/tools.security.php";
|
||||
require_once "../_incl/db.php";
|
||||
|
||||
if (!in_array('supercafe:access', $_SESSION['auth_data']['permissions'] ?? [])) {
|
||||
header('HTTP/1.1 403 Forbidden');
|
||||
@@ -8,28 +9,23 @@ if (!in_array('supercafe:access', $_SESSION['auth_data']['permissions'] ?? []))
|
||||
}
|
||||
|
||||
/**
|
||||
* Load personas from the existing Alumnos system.
|
||||
* Returns array keyed by "{aulario_id}:{alumno_name}" with
|
||||
* ['Nombre', 'Region' (aulario display name), 'AularioID', 'HasPhoto'] entries.
|
||||
* Load personas from the Alumnos filesystem (photos still on disk).
|
||||
* Returns array keyed by "{aulario_id}:{alumno_name}".
|
||||
*/
|
||||
function sc_load_personas_from_alumnos($centro_id)
|
||||
{
|
||||
$aularios_path = "/DATA/entreaulas/Centros/$centro_id/Aularios";
|
||||
$personas = [];
|
||||
if (!is_dir($aularios_path)) {
|
||||
return $personas;
|
||||
}
|
||||
foreach (glob("$aularios_path/*.json") ?: [] as $aulario_file) {
|
||||
$aulario_id = basename($aulario_file, '.json');
|
||||
$aulario_data = json_decode(file_get_contents($aulario_file), true);
|
||||
$aularios = db_get_aularios($centro_id);
|
||||
$personas = [];
|
||||
$aularios_dir = aulatek_orgs_base_path() . "/$centro_id/Aularios";
|
||||
foreach ($aularios as $aulario_id => $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,
|
||||
@@ -54,15 +50,15 @@ function sc_persona_label($persona_key, $personas)
|
||||
return $persona_key;
|
||||
}
|
||||
|
||||
$centro_id = safe_centro_id($_SESSION['auth_data']['entreaulas']['centro'] ?? '');
|
||||
$tenant_data = $_SESSION['auth_data']['aulatek'] ?? ($_SESSION['auth_data']['entreaulas'] ?? []);
|
||||
$centro_id = safe_organization_id($tenant_data['organizacion'] ?? ($tenant_data['centro'] ?? ''));
|
||||
if ($centro_id === '') {
|
||||
require_once "_incl/pre-body.php";
|
||||
echo '<div class="card pad"><h1>SuperCafe</h1><p>No tienes un centro asignado.</p></div>';
|
||||
echo '<div class="card pad"><h1>SuperCafe</h1><p>No tienes una organizacion asignada.</p></div>';
|
||||
require_once "_incl/post-body.php";
|
||||
exit;
|
||||
}
|
||||
|
||||
define('SC_DATA_DIR', "/DATA/entreaulas/Centros/$centro_id/SuperCafe/Comandas");
|
||||
define('SC_MAX_DEBTS', 3);
|
||||
|
||||
$estados_colores = [
|
||||
@@ -81,57 +77,48 @@ 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');
|
||||
header('Location: /aulatek/supercafe.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
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 org_id = ? AND order_ref = ?')
|
||||
->execute([$centro_id, $order_id]);
|
||||
}
|
||||
header('Location: /entreaulas/supercafe.php');
|
||||
header('Location: /aulatek/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,11 +126,12 @@ $orders_deuda = array_filter($orders, fn($o) => ($o['Estado'] ?? '') === 'Deuda
|
||||
require_once "_incl/pre-body.php";
|
||||
?>
|
||||
|
||||
|
||||
<div class="card pad">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 10px;">
|
||||
<h1 style="margin: 0;">SuperCafe – Cafetería</h1>
|
||||
<?php if ($can_edit): ?>
|
||||
<a href="/entreaulas/supercafe_edit.php" class="btn btn-success">+ Nueva comanda</a>
|
||||
<a href="/aulatek/supercafe_edit.php" class="btn btn-success">+ Nueva comanda</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
@@ -183,7 +171,7 @@ require_once "_incl/pre-body.php";
|
||||
<td><strong><?= htmlspecialchars($estado) ?></strong></td>
|
||||
<?php if ($can_edit): ?>
|
||||
<td style="white-space: nowrap;">
|
||||
<a href="/entreaulas/supercafe_edit.php?id=<?= urlencode($order['_id']) ?>"
|
||||
<a href="/aulatek/supercafe_edit.php?id=<?= urlencode($order['_id']) ?>"
|
||||
class="btn btn-sm btn-primary">Editar</a>
|
||||
<form method="post" style="display: inline;">
|
||||
<input type="hidden" name="action" value="change_status">
|
||||
@@ -246,7 +234,7 @@ require_once "_incl/pre-body.php";
|
||||
<td><?= htmlspecialchars($order['Notas'] ?? '') ?></td>
|
||||
<?php if ($can_edit): ?>
|
||||
<td style="white-space: nowrap;">
|
||||
<a href="/entreaulas/supercafe_edit.php?id=<?= urlencode($order['_id']) ?>"
|
||||
<a href="/aulatek/supercafe_edit.php?id=<?= urlencode($order['_id']) ?>"
|
||||
class="btn btn-sm btn-primary">Editar</a>
|
||||
<form method="post" style="display: inline;">
|
||||
<input type="hidden" name="action" value="change_status">
|
||||
|
||||
@@ -1,56 +1,46 @@
|
||||
<?php
|
||||
require_once "_incl/auth_redir.php";
|
||||
require_once "../_incl/tools.security.php";
|
||||
require_once "../_incl/db.php";
|
||||
|
||||
if (!in_array('supercafe:edit', $_SESSION['auth_data']['permissions'] ?? [])) {
|
||||
header('HTTP/1.1 403 Forbidden');
|
||||
die('Acceso denegado');
|
||||
}
|
||||
|
||||
$centro_id = safe_centro_id($_SESSION['auth_data']['entreaulas']['centro'] ?? '');
|
||||
$tenant_data = $_SESSION['auth_data']['aulatek'] ?? ($_SESSION['auth_data']['entreaulas'] ?? []);
|
||||
$centro_id = safe_organization_id($tenant_data['organizacion'] ?? ($tenant_data['centro'] ?? ''));
|
||||
if ($centro_id === '') {
|
||||
require_once "_incl/pre-body.php";
|
||||
echo '<div class="card pad"><h1>SuperCafe</h1><p>No tienes un centro asignado.</p></div>';
|
||||
echo '<div class="card pad"><h1>SuperCafe</h1><p>No tienes una organizacion asignada.</p></div>';
|
||||
require_once "_incl/post-body.php";
|
||||
exit;
|
||||
}
|
||||
|
||||
$sc_base = "/DATA/entreaulas/Centros/$centro_id/SuperCafe";
|
||||
define('SC_DATA_DIR', "$sc_base/Comandas");
|
||||
define('SC_MAX_DEBTS', 3);
|
||||
|
||||
$valid_statuses = ['Pedido', 'En preparación', 'Listo', 'Entregado', 'Deuda'];
|
||||
|
||||
/**
|
||||
* Load personas from the existing Alumnos system (alumnos.php).
|
||||
* Returns array keyed by "{aulario_id}:{alumno_name}" with
|
||||
* ['Nombre', 'Region' (aulario display name), 'AularioID'] entries.
|
||||
* Groups are sorted by aulario name, alumnos sorted alphabetically.
|
||||
* Load personas from the Alumnos filesystem (photos still on disk).
|
||||
* Returns array keyed by "{aulario_id}:{alumno_name}".
|
||||
*/
|
||||
function sc_load_personas_from_alumnos($centro_id)
|
||||
{
|
||||
$aularios_path = "/DATA/entreaulas/Centros/$centro_id/Aularios";
|
||||
$personas = [];
|
||||
if (!is_dir($aularios_path)) {
|
||||
return $personas;
|
||||
}
|
||||
$aulario_files = glob("$aularios_path/*.json") ?: [];
|
||||
foreach ($aulario_files as $aulario_file) {
|
||||
$aulario_id = basename($aulario_file, '.json');
|
||||
$aulario_data = json_decode(file_get_contents($aulario_file), true);
|
||||
$aularios = db_get_aularios($centro_id);
|
||||
$personas = [];
|
||||
$aularios_dir = aulatek_orgs_base_path() . "/$centro_id/Aularios";
|
||||
foreach ($aularios as $aulario_id => $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 +52,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 +67,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 +100,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,52 +117,231 @@ 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: /aulatek/supercafe.php');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
require_once "_incl/pre-body.php";
|
||||
?>
|
||||
|
||||
<h1>Comanda <code><?= htmlspecialchars($order_id) ?></code></h1>
|
||||
<a href="/entreaulas/supercafe.php" class="btn btn-secondary">Salir</a>
|
||||
?>
|
||||
<style>
|
||||
.sc-legacy-wrap {
|
||||
max-width: 320px;
|
||||
}
|
||||
.sc-legacy-title {
|
||||
font-size: 1.9rem;
|
||||
margin: 0 0 0.2rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.sc-legacy-subtitle {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.35rem;
|
||||
}
|
||||
.sc-legacy-exit {
|
||||
padding: 2px 8px;
|
||||
border: 1px solid #111;
|
||||
border-radius: 2px;
|
||||
background: #fff;
|
||||
color: #111;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
margin-bottom: 0.35rem;
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
.sc-legacy-fieldset {
|
||||
border: 1px solid #bcbcbc;
|
||||
border-radius: 0;
|
||||
padding: 6px;
|
||||
background: #fff;
|
||||
}
|
||||
.sc-legacy-fieldset legend {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.sc-legacy-persona-photo img {
|
||||
height: 64px;
|
||||
border-radius: 10px;
|
||||
border: 2px solid #777;
|
||||
background: #fff;
|
||||
}
|
||||
.sc-persona-current {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
margin-top: 5px;
|
||||
min-height: 36px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.sc-persona-current img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #666;
|
||||
background: #fff;
|
||||
object-fit: cover;
|
||||
}
|
||||
.sc-persona-group {
|
||||
margin-top: 5px;
|
||||
padding-top: 3px;
|
||||
border-top: 1px dashed #ddd;
|
||||
}
|
||||
.sc-persona-group-label {
|
||||
font-size: 0.72rem;
|
||||
color: #555;
|
||||
margin-bottom: 3px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.sc-person-btn {
|
||||
border: 5px solid transparent;
|
||||
outline: 2px solid black;
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
color: #111;
|
||||
min-width: 86px;
|
||||
max-width: 125px;
|
||||
min-height: 54px;
|
||||
padding: 4px 6px;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.05;
|
||||
text-align: center;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 2px;
|
||||
}
|
||||
.sc-person-btn img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 7px;
|
||||
border: 1px solid #777;
|
||||
object-fit: cover;
|
||||
background: #fff;
|
||||
}
|
||||
.sc-person-btn.active {
|
||||
background: #d9ff1f;
|
||||
border: 5px dashed #000;
|
||||
outline: 2px solid black;
|
||||
}
|
||||
.sc-details {
|
||||
margin: 4px 0;
|
||||
border: 1px solid #444;
|
||||
border-radius: 3px;
|
||||
background: #fff;
|
||||
width: 100%;
|
||||
}
|
||||
.sc-details summary {
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
font-size: 0.86rem;
|
||||
padding: 4px 6px;
|
||||
background: #f3f3f3;
|
||||
border-bottom: 1px solid #ddd;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 6px;
|
||||
}
|
||||
.sc-details summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
.sc-summary-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
min-width: 0;
|
||||
}
|
||||
.sc-summary-left img {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
object-fit: contain;
|
||||
}
|
||||
.sc-summary-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sc-selected-val {
|
||||
max-width: 95px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 0.78rem;
|
||||
color: #444;
|
||||
}
|
||||
.sc-check {
|
||||
height: 15px;
|
||||
}
|
||||
.sc-category-body {
|
||||
padding: 4px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
}
|
||||
.sc-cat-btn {
|
||||
border: 5px solid transparent;
|
||||
outline: 2px solid black;
|
||||
border-radius: 9px;
|
||||
background: #d9ff1f;
|
||||
color: #111;
|
||||
min-width: 90px;
|
||||
min-height: 44px;
|
||||
padding: 3px 6px;
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.1;
|
||||
text-align: center;
|
||||
}
|
||||
.sc-cat-btn small {
|
||||
font-size: 0.68rem;
|
||||
}
|
||||
.sc-cat-btn.active {
|
||||
outline: 2px solid black;
|
||||
border: 5px dashed #000;
|
||||
}
|
||||
.sc-cat-btn.sc-size-btn {
|
||||
background: #ff3030;
|
||||
color: #fff;
|
||||
}
|
||||
.sc-notes {
|
||||
width: 100%;
|
||||
min-height: 78px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.sc-actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
justify-content: flex-start;
|
||||
margin-top: 6px;
|
||||
}
|
||||
.sc-actions .btn {
|
||||
border-radius: 2px;
|
||||
padding: 3px 9px;
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="sc-legacy-wrap">
|
||||
<h1 class="sc-legacy-title">Comanda <small style="font-size:.72rem;color:#666;"><?= htmlspecialchars($order_id) ?></small></h1>
|
||||
<a href="/aulatek/supercafe.php" class="sc-legacy-exit">Salir</a>
|
||||
|
||||
<?php if ($error !== ''): ?>
|
||||
<div class="card pad" style="background: #f8d7da; color: #842029;">
|
||||
@@ -206,56 +350,84 @@ require_once "_incl/pre-body.php";
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post">
|
||||
<fieldset class="card pad" style="text-align: center;">
|
||||
<legend>Rellenar comanda</legend>
|
||||
<fieldset class="sc-legacy-fieldset">
|
||||
<legend>Rellenar comanda</legend>
|
||||
|
||||
<label style="display: none;">
|
||||
Fecha<br>
|
||||
<input readonly disabled type="text" value="<?= htmlspecialchars($order_data['Fecha']) ?>"><br><br>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<label style="display:block; margin-bottom:6px;">
|
||||
Persona<br>
|
||||
<?php if (!empty($personas_by_aulario)): ?>
|
||||
<select name="Persona" class="form-select" required>
|
||||
<option value="">-- Selecciona una persona --</option>
|
||||
<?php foreach ($personas_by_aulario as $region_name => $group): ?>
|
||||
<optgroup label="<?= htmlspecialchars($region_name) ?>">
|
||||
<?php foreach ($group as $pkey => $pinfo): ?>
|
||||
<option value="<?= htmlspecialchars($pkey) ?>"
|
||||
<?= ($order_data['Persona'] === $pkey) ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($pinfo['Nombre']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</optgroup>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<?php
|
||||
$sel_key = $order_data['Persona'];
|
||||
$sel_info = $personas[$sel_key] ?? null;
|
||||
if ($sel_info && $sel_info['HasPhoto']):
|
||||
?>
|
||||
<div id="sc-persona-photo" style="margin-top: 8px;">
|
||||
<?php $photo_url = '/entreaulas/_filefetch.php?type=alumno_photo'
|
||||
. '¢ro=' . urlencode($centro_id)
|
||||
<details class="sc-details" open>
|
||||
<summary>
|
||||
<span class="sc-summary-left">
|
||||
<span>Persona</span>
|
||||
</span>
|
||||
<span class="sc-summary-right">
|
||||
<span class="sc-selected-val" id="sc-persona-selected-label"><?= htmlspecialchars($sel_info['Nombre'] ?? '') ?></span>
|
||||
<img class="sc-check" src="static/ico/<?= $sel_info ? 'checkbox.png' : 'checkbox_unchecked.png' ?>" id="sc-persona-check" alt="">
|
||||
</span>
|
||||
</summary>
|
||||
<div class="sc-category-body" id="sc-persona-panel">
|
||||
<?php foreach ($personas_by_aulario as $region_name => $group): ?>
|
||||
<div class="sc-persona-group" style="width:100%;">
|
||||
<div class="sc-persona-group-label"><?= htmlspecialchars($region_name) ?></div>
|
||||
<div style="display:flex; flex-wrap:wrap; gap:4px;">
|
||||
<?php foreach ($group as $pkey => $pinfo): ?>
|
||||
<?php
|
||||
$p_photo_url = '/aulatek/_filefetch.php?type=alumno_photo'
|
||||
. '&org=' . urlencode($centro_id)
|
||||
. '&aulario=' . urlencode($pinfo['AularioID'])
|
||||
. '&alumno=' . urlencode($pinfo['Nombre']);
|
||||
$is_person_active = ($order_data['Persona'] === $pkey);
|
||||
?>
|
||||
<button type="button"
|
||||
class="sc-person-btn<?= $is_person_active ? ' active' : '' ?>"
|
||||
data-person-key="<?= htmlspecialchars($pkey, ENT_QUOTES) ?>"
|
||||
data-person-name="<?= htmlspecialchars($pinfo['Nombre'], ENT_QUOTES) ?>"
|
||||
data-person-region="<?= htmlspecialchars($region_name, ENT_QUOTES) ?>"
|
||||
data-person-photo="<?= htmlspecialchars($pinfo['HasPhoto'] ? $p_photo_url : '/static/arasaac/alumnos.png', ENT_QUOTES) ?>"
|
||||
onclick="scSelectPersona(this)">
|
||||
<img src="<?= htmlspecialchars($pinfo['HasPhoto'] ? $p_photo_url : '/static/arasaac/alumnos.png') ?>" alt="">
|
||||
<span><?= htmlspecialchars($pinfo['Nombre']) ?></span>
|
||||
</button>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</details>
|
||||
<input type="hidden" name="Persona" id="sc-persona-input" value="<?= htmlspecialchars($order_data['Persona']) ?>">
|
||||
<div class="sc-persona-current" id="sc-persona-current" style="display: none;">
|
||||
<?php if ($sel_info): ?>
|
||||
<?php $photo_url = '/aulatek/_filefetch.php?type=alumno_photo'
|
||||
. '&org=' . urlencode($centro_id)
|
||||
. '&aulario=' . urlencode($sel_info['AularioID'])
|
||||
. '&alumno=' . urlencode($sel_info['Nombre']); ?>
|
||||
<img src="<?= htmlspecialchars($photo_url) ?>"
|
||||
alt="Foto de <?= htmlspecialchars($sel_info['Nombre']) ?>"
|
||||
style="height: 80px; border-radius: 8px; border: 2px solid #dee2e6;">
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<img src="<?= htmlspecialchars($sel_info['HasPhoto'] ? $photo_url : '/static/arasaac/alumnos.png') ?>" alt="Foto">
|
||||
<span><strong><?= htmlspecialchars($sel_info['Nombre']) ?></strong> (<?= htmlspecialchars($sel_info['Region']) ?>)</span>
|
||||
<?php else: ?>
|
||||
<span style="color:#666;">No hay persona seleccionada.</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<input type="text" name="Persona" class="form-control"
|
||||
value="<?= htmlspecialchars($order_data['Persona']) ?>"
|
||||
placeholder="Nombre de la persona" required>
|
||||
<small class="text-muted">
|
||||
No hay alumnos registrados en los aularios de este centro.
|
||||
No hay alumnos registrados en los aularios de esta organizacion.
|
||||
Añade alumnos desde
|
||||
<a href="/entreaulas/">EntreAulas</a>.
|
||||
<a href="/aulatek/">AulaTek</a>.
|
||||
</small>
|
||||
<?php endif; ?>
|
||||
<br><br>
|
||||
<br>
|
||||
</label>
|
||||
|
||||
<label style="display: none;">
|
||||
@@ -265,15 +437,6 @@ require_once "_incl/pre-body.php";
|
||||
|
||||
<div>
|
||||
<?php if (!empty($menu)): ?>
|
||||
<style>
|
||||
.sc-details { text-align: center; margin: 5px; padding: 5px; border: 2px solid black; border-radius: 5px; background: white; cursor: pointer; width: calc(100% - 25px); display: inline-block; }
|
||||
.sc-details summary { padding: 10px; background-size: contain; background-position: left; background-repeat: no-repeat; text-align: left; padding-left: 55px; font-size: 1.1em; }
|
||||
.sc-cat-btn { border-radius: 20px; font-size: 1.1em; margin: 6px; padding: 10px 18px; border: 2px solid #bbb; background: #f8f9fa; display: inline-block; min-width: 90px; min-height: 60px; vertical-align: top; transition: background 0.2s, border 0.2s; }
|
||||
.sc-cat-btn.active { background: #ffe066; border: 2px solid #222; }
|
||||
.sc-cat-btn img { height: 50px; padding: 5px; background: white; border-radius: 8px; }
|
||||
.sc-details .sc-summary-right { float: right; display: flex; align-items: center; gap: 6px; }
|
||||
.sc-details .sc-check { height: 30px; }
|
||||
</style>
|
||||
<?php
|
||||
// Iconos por categoría (puedes ampliar este array según tus iconos)
|
||||
$sc_actions_icons = [
|
||||
@@ -296,8 +459,13 @@ require_once "_incl/pre-body.php";
|
||||
?>
|
||||
<?php foreach ($menu as $category => $items): ?>
|
||||
<details class="sc-details">
|
||||
<summary style="background-image: url('<?= isset($sc_actions_icons[$category]) ? $sc_actions_icons[$category] : '' ?>');">
|
||||
<?= htmlspecialchars($category) ?>
|
||||
<summary>
|
||||
<span class="sc-summary-left">
|
||||
<?php if (isset($sc_actions_icons[$category])): ?>
|
||||
<img src="<?= htmlspecialchars($sc_actions_icons[$category]) ?>" alt="">
|
||||
<?php endif; ?>
|
||||
<span><?= htmlspecialchars($category) ?></span>
|
||||
</span>
|
||||
<span class="sc-summary-right">
|
||||
<span class="sc-selected-val" id="sc-val-<?= md5($category) ?>">
|
||||
<?= htmlspecialchars($selected[$category] ?? '') ?>
|
||||
@@ -305,12 +473,13 @@ require_once "_incl/pre-body.php";
|
||||
<img class="sc-check" src="static/ico/checkbox_unchecked.png" id="sc-check-<?= md5($category) ?>">
|
||||
</span>
|
||||
</summary>
|
||||
<div>
|
||||
<div class="sc-category-body">
|
||||
<?php foreach ($items as $item_name => $item_price):
|
||||
$btn_id = 'sc-btn-' . md5($category . '_' . $item_name);
|
||||
$is_active = ($selected[$category] ?? '') === $item_name;
|
||||
$btn_extra_class = ($category === 'Tamaño') ? ' sc-size-btn' : '';
|
||||
?>
|
||||
<button type="button" class="sc-cat-btn<?= $is_active ? ' active' : '' ?>" id="<?= $btn_id ?>" onclick="
|
||||
<button type="button" class="sc-cat-btn<?= $btn_extra_class ?><?= $is_active ? ' active' : '' ?>" id="<?= $btn_id ?>" onclick="
|
||||
document.getElementById('sc-val-<?= md5($category) ?>').innerText = '<?= htmlspecialchars($item_name) ?>';
|
||||
document.getElementById('sc-check-<?= md5($category) ?>').src = 'static/ico/checkbox.png';
|
||||
var btns = this.parentNode.querySelectorAll('.sc-cat-btn');
|
||||
@@ -322,7 +491,6 @@ require_once "_incl/pre-body.php";
|
||||
<?php if ($item_price > 0): ?>
|
||||
<br><small style="color: #6c757d;">(<?= number_format((float)$item_price, 2) ?>c)</small>
|
||||
<?php endif; ?>
|
||||
<!-- Aquí podrías poner una imagen si tienes -->
|
||||
</button>
|
||||
<?php endforeach; ?>
|
||||
<input type="hidden" name="<?= htmlspecialchars($category) ?>" id="input-<?= md5($category) ?>" value="<?= htmlspecialchars($selected[$category] ?? '') ?>">
|
||||
@@ -337,15 +505,15 @@ require_once "_incl/pre-body.php";
|
||||
placeholder="Ej. 1x Café, 1x Bocadillo">
|
||||
<small class="text-muted">
|
||||
No hay menú configurado en
|
||||
<code>/DATA/entreaulas/Centros/<?= htmlspecialchars($centro_id) ?>/SuperCafe/Menu.json</code>.
|
||||
<code><?= htmlspecialchars(aulatek_orgs_base_path() . "/" . $centro_id . "/SuperCafe/Menu.json") ?></code>.
|
||||
</small>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<label>
|
||||
<label style="display:block; margin-top:6px;">
|
||||
Notas<br>
|
||||
<textarea name="Notas" class="form-control" rows="2"><?= htmlspecialchars($order_data['Notas']) ?></textarea><br><br>
|
||||
<textarea name="Notas" class="form-control sc-notes" rows="2"><?= htmlspecialchars($order_data['Notas']) ?></textarea>
|
||||
</label>
|
||||
|
||||
<?php if (!$is_new): ?>
|
||||
@@ -359,14 +527,53 @@ require_once "_incl/pre-body.php";
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<br>Modificar en el listado de comandas<br>
|
||||
<br><small>Modificar en el listado de comandas</small><br>
|
||||
</label>
|
||||
<?php endif; ?>
|
||||
|
||||
<button type="submit" class="btn btn-success">Guardar</button>
|
||||
<a href="/entreaulas/supercafe.php" class="btn btn-secondary">Cancelar</a>
|
||||
<div class="sc-actions">
|
||||
<button type="submit" class="btn btn-success">Guardar</button>
|
||||
<a href="/aulatek/supercafe.php" class="btn btn-danger">Cancelar</a>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function scSelectPersona(button) {
|
||||
var key = button.getAttribute('data-person-key') || '';
|
||||
var name = button.getAttribute('data-person-name') || '';
|
||||
var region = button.getAttribute('data-person-region') || '';
|
||||
var photo = button.getAttribute('data-person-photo') || '/static/arasaac/alumnos.png';
|
||||
|
||||
var hiddenInput = document.getElementById('sc-persona-input');
|
||||
if (hiddenInput) {
|
||||
hiddenInput.value = key;
|
||||
}
|
||||
|
||||
var allButtons = document.querySelectorAll('.sc-person-btn');
|
||||
allButtons.forEach(function(btn) {
|
||||
btn.classList.remove('active');
|
||||
});
|
||||
button.classList.add('active');
|
||||
|
||||
var label = document.getElementById('sc-persona-selected-label');
|
||||
if (label) {
|
||||
label.innerText = name;
|
||||
}
|
||||
|
||||
var check = document.getElementById('sc-persona-check');
|
||||
if (check) {
|
||||
check.src = 'static/ico/checkbox.png';
|
||||
}
|
||||
|
||||
var current = document.getElementById('sc-persona-current');
|
||||
if (current) {
|
||||
current.innerHTML = '<img src="' + photo + '" alt="Foto">' +
|
||||
'<span><strong>' + name + '</strong>' + (region ? ' (' + region + ')' : '') + '</span>';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php require_once "_incl/post-body.php"; ?>
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<?php
|
||||
require_once "../_incl/tools.session.php";
|
||||
require_once "../_incl/tools.security.php";
|
||||
require_once "../_incl/db.php";
|
||||
ini_set("display_errors", 0);
|
||||
$file = Sf($_GET["f"]);
|
||||
$date = implode("/", array_reverse(explode("-", $file)));
|
||||
$val = json_decode(file_get_contents("/DATA/club/IMG/$file/data.json"), true);
|
||||
$val = db_get_club_event($file);
|
||||
|
||||
$fotos = glob("/DATA/club/IMG/$file/*/");
|
||||
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
<?php
|
||||
ini_set("display_errors", 0);
|
||||
require_once "../_incl/db.php";
|
||||
$file = Sf($_GET["f"]);
|
||||
$date = implode("/", array_reverse(explode("-", $file)));
|
||||
$val = json_decode(file_get_contents("/DATA/club/IMG/$file/data.json"), true);
|
||||
$config = json_decode(file_get_contents("/DATA/club/config.json"), true);
|
||||
if(strtoupper($_POST["adminpw"]) == strtoupper($config["adminpw"] ?? "")) {
|
||||
$val = db_get_club_event($file);
|
||||
$adminpw = db_get_config('club_adminpw', '');
|
||||
if (strtoupper($_POST["adminpw"] ?? '') === strtoupper($adminpw) && !empty($adminpw)) {
|
||||
$data = [
|
||||
"title" => $_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();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
ini_set("display_errors", 0);
|
||||
require_once "../_incl/db.php";
|
||||
$files = glob("/DATA/club/IMG/*/");
|
||||
sort($files);
|
||||
$files = array_reverse($files);
|
||||
@@ -17,7 +18,7 @@ require_once "../_incl/pre-body.php"; ?>
|
||||
<?php foreach ($files as $file) {
|
||||
$filenam = str_replace("/", "", str_replace("/DATA/club/IMG/", "", $file));
|
||||
$date = implode("/", array_reverse(explode("-", $filenam)));
|
||||
$val = json_decode(file_get_contents($file . "data.json"), true)
|
||||
$val = db_get_club_event($filenam);
|
||||
?>
|
||||
|
||||
<li><a class="btn btn-secondary" href="cal.php?f=<?php echo $filenam; ?>"><b><?php echo $date; ?></b></a> -
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<?php
|
||||
ini_set("display_errors", 1);
|
||||
$config = json_decode(file_get_contents("/DATA/club/config.json"), true);
|
||||
if (strtoupper($_GET["pw"]) != $config["uploadpw"]) {
|
||||
require_once "../../_incl/db.php";
|
||||
$uploadpw = db_get_config('club_uploadpw', '');
|
||||
if ($uploadpw === '' || strtoupper($_GET["pw"] ?? '') !== strtoupper($uploadpw)) {
|
||||
header("HTTP/1.1 401 Unauthorized");
|
||||
die();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
<?php
|
||||
ini_set("display_errors", 0);
|
||||
$PAGE_TITLE = "EntreAulas - Inicio";
|
||||
require_once "_incl/pre-body.php"; ?>
|
||||
<div class="card pad">
|
||||
<h2>Recursos:</h2>
|
||||
<ul>
|
||||
<li><a class="btn btn-secondary" href="/entreaulas/recursos/letras-a4.php">Generador de Letras A4 con varios estilos para imprimir</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<?php require_once "_incl/post-body.php"; ?>
|
||||
header('Location: /aulatek/');
|
||||
exit;
|
||||
@@ -1,15 +1,133 @@
|
||||
<?php require_once "_incl/pre-body.php"; ?>
|
||||
|
||||
<style>
|
||||
.hero {
|
||||
text-align: center;
|
||||
margin: 0 0 28px;
|
||||
background: linear-gradient(135deg, #e8f0fe 0%, #fce8ff 100%);
|
||||
padding: 48px 24px;
|
||||
border-radius: 12px;
|
||||
color: var(--gw-text-primary, #202124);
|
||||
border: 1px solid #dadce0;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: 36px;
|
||||
font-weight: 400;
|
||||
margin-bottom: 8px;
|
||||
color: #202124;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.hero p {
|
||||
color: #5f6368;
|
||||
font-size: 16px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.hero hr {
|
||||
border-color: #dadce0;
|
||||
margin: 20px auto;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.hero h3 {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #1a73e8;
|
||||
letter-spacing: 0.02em;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.hero .btn {
|
||||
border-radius: 20px;
|
||||
padding: 8px 24px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #5f6368;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
|
||||
.app-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.app-card {
|
||||
background: #ffffff;
|
||||
border-radius: 8px;
|
||||
padding: 20px 16px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
border: 1px solid #dadce0;
|
||||
transition: box-shadow 0.2s ease, border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.app-card:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
border-color: #bdc1c6;
|
||||
}
|
||||
|
||||
.app-card img {
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.app-title {
|
||||
font-weight: 500;
|
||||
font-size: 15px;
|
||||
color: #202124;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.app-desc,
|
||||
.app-note {
|
||||
color: #5f6368;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.app-actions {
|
||||
margin-top: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.app-actions .btn {
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.is-disabled {
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.app-card .btn.btn-outline-secondary.disabled {
|
||||
color: #5f6368;
|
||||
}
|
||||
</style>
|
||||
|
||||
<section class="hero">
|
||||
<h1>Bienvenidx a Axia4</h1>
|
||||
<p>La plataforma unificada de EuskadiTech y Sketaria.</p>
|
||||
<p>La plataforma unificada creada por EuskadiTech.</p>
|
||||
<hr>
|
||||
<h3>Versión 2.0.0</h3>
|
||||
<p>Con esta versión, cambiamos la interfaz a una mas sencilla.</p>
|
||||
<h3>Versión 2.1.0</h3>
|
||||
<p>Con esta versión, hacemos muchos cambios.</p>
|
||||
<br>
|
||||
<a class="btn btn-primary" href="/account/">Accede a tu cuenta</a>
|
||||
</section>
|
||||
|
||||
|
||||
<p class="section-title">Aplicaciones</p>
|
||||
|
||||
<div id="grid" class="app-grid">
|
||||
<div class="app-card">
|
||||
@@ -19,14 +137,6 @@
|
||||
<a href="/club/" class="btn btn-primary">Acceso público</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="app-card">
|
||||
<img src="/static/logo-telesec.png" alt="Logo TeleSec">
|
||||
<div class="app-title">TeleSec</div>
|
||||
<div class="app-desc">Gestión de aularios conectados.</div>
|
||||
<div class="app-actions">
|
||||
<a href="https://telesec.tech.eus/" target="_blank" class="btn btn-primary">Tengo cuenta</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="app-card">
|
||||
<img src="/static/logo-account.png" alt="Logo Account">
|
||||
<div class="app-title">Mi Cuenta</div>
|
||||
@@ -42,72 +152,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="app-card">
|
||||
<img src="/static/logo-entreaulas.png" alt="Logo EntreAulas">
|
||||
<div class="app-title">EntreAulas</div>
|
||||
<div class="app-desc">Recursos educativos digitales.</div>
|
||||
<img src="/static/logo-aulatek.png" alt="Logo AulaTek">
|
||||
<div class="app-title">AulaTek</div>
|
||||
<div class="app-desc">Tu aula, digital.</div>
|
||||
<div class="app-actions">
|
||||
<a href="/entreaulas/" target="_blank" class="btn btn-primary">Acceso publico</a>
|
||||
<a href="/aulatek/" target="_blank" class="btn btn-primary">Acceso público</a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Arroz con leche: Wiki publica -->
|
||||
<div class="app-card">
|
||||
<img src="/static/logo-arroz.png" alt="Logo Arroz con leche">
|
||||
<div class="app-title">Arroz con leche</div>
|
||||
<div class="app-desc">Compartiendo nuestros conocimientos.</div>
|
||||
<div class="app-actions">
|
||||
<a href="https://arroz.tech.eus/" target="_blank" class="btn btn-primary">Acceso público</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="app-grid" style="display: none;">
|
||||
<div class="app-card is-disabled">
|
||||
<img src="/static/logo-oscar.png" alt="Logo OSCAR">
|
||||
<div class="app-title">OSCAR</div>
|
||||
<div class="app-desc">Red de IA Absoluta.</div>
|
||||
<div class="app-note">Próximamente</div>
|
||||
</div>
|
||||
<div class="app-card is-disabled">
|
||||
<img src="/static/logo-media.png" alt="Logo ET Media">
|
||||
<div class="app-title">ET Media</div>
|
||||
<div class="app-desc">Streaming de pelis y series.</div>
|
||||
<div class="app-note">Próximamente</div>
|
||||
</div>
|
||||
<div class="app-card is-disabled">
|
||||
<img src="/static/logo-hyper.png" alt="Logo Hyper">
|
||||
<div class="app-title">Hyper</div>
|
||||
<div class="app-desc">Plataforma de gestión empresarial.</div>
|
||||
<div class="app-note">Próximamente</div>
|
||||
</div>
|
||||
<div class="app-card is-disabled">
|
||||
<img src="/static/logo-mail.png" alt="Logo Comunicaciones">
|
||||
<div class="app-title">Comunicaciones</div>
|
||||
<div class="app-desc">Correos electrónicos y mensajería.</div>
|
||||
<div class="app-note">Próximamente</div>
|
||||
</div>
|
||||
<div class="app-card is-disabled">
|
||||
<img src="/static/logo-malla.png" alt="Logo Malla">
|
||||
<div class="app-title">Malla Meshtastic</div>
|
||||
<div class="app-desc">Red de comunicación por radio.</div>
|
||||
<div class="app-note">Próximamente</div>
|
||||
</div>
|
||||
<div class="app-card is-disabled">
|
||||
<img src="/static/logo-aularios.png" alt="Logo Aularios">
|
||||
<div class="app-title">Aularios<sup>2</sup></div>
|
||||
<div class="app-desc">Visita virtual a los aularios.</div>
|
||||
<div class="app-note">Solo lectura · Migrando a Axia4</div>
|
||||
</div>
|
||||
<div class="app-card is-disabled">
|
||||
<img src="/static/logo-nube.png" alt="Logo Axia4 Cloud">
|
||||
<div class="app-title">Nube Axia4.NET</div>
|
||||
<div class="app-desc">Almacenamiento central de datos.</div>
|
||||
<div class="app-note">Cerrado por migración</div>
|
||||
</div>
|
||||
<div class="app-card is-disabled">
|
||||
<img src="/static/logo-nk4.png" alt="Logo Nube Kasa">
|
||||
<div class="app-title">Nube Kasa</div>
|
||||
<div class="app-desc">Nube personal con domótica.</div>
|
||||
<div class="app-note">Cerrado por mantenimiento</div>
|
||||
</div>
|
||||
<div class="app-card">
|
||||
<img src="/static/logo-sysadmin.png" alt="Logo SysAdmin">
|
||||
<div class="app-title">SysAdmin</div>
|
||||
@@ -121,95 +172,5 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
body {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.hero {
|
||||
text-align: center;
|
||||
margin: 32px 0 16px;
|
||||
background: url(/static/portugalete.jpg) #ffffffc2;
|
||||
padding: 25px 7px;
|
||||
padding-top: 50px;
|
||||
min-height: 350px;
|
||||
border-radius: 50px;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-blend-mode: lighten;
|
||||
color: black;
|
||||
/* -webkit-text-stroke: 0.5px #acacac; */
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: 42px;
|
||||
margin-bottom: 8px;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.hero p {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.notice-card {
|
||||
background: #e8f0fe;
|
||||
padding: 12px 16px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
color: #1a3c78;
|
||||
margin-bottom: 20px;
|
||||
outline: 1px solid #c2d1f0;
|
||||
}
|
||||
|
||||
.app-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.app-card {
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.app-card img {
|
||||
height: 64px;
|
||||
width: 64px;
|
||||
}
|
||||
|
||||
.app-title {
|
||||
font-weight: 600;
|
||||
color: #202124;
|
||||
}
|
||||
|
||||
.app-desc,
|
||||
.app-note {
|
||||
color: #5f6368;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.app-actions {
|
||||
margin-top: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.is-disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
.app-card .btn.btn-outline-secondary.disabled {
|
||||
color: black;
|
||||
}
|
||||
</style>
|
||||
|
||||
<?php require_once "_incl/post-body.php"; ?>
|
||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
@@ -0,0 +1,7 @@
|
||||
<div class="sidebar-section-label">Atajos</div>
|
||||
<nav class="sidebar-nav">
|
||||
<a class="sidebar-link" href="/sysadmin/users.php?action=add">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><title>account-plus</title><path d="M15,14C12.33,14 7,15.33 7,18V20H23V18C23,15.33 17.67,14 15,14M6,10V7H4V10H1V12H4V15H6V12H9V10M15,12A4,4 0 0,0 19,8A4,4 0 0,0 15,4A4,4 0 0,0 11,8A4,4 0 0,0 15,12Z" /></svg>
|
||||
<span>Nuevo usuario</span>
|
||||
</a>
|
||||
</nav>
|
||||
@@ -1,10 +1,11 @@
|
||||
<?php
|
||||
require_once "_incl/auth_redir.php";
|
||||
require_once "../_incl/tools.security.php";
|
||||
require_once "../_incl/db.php";
|
||||
|
||||
function safe_path_segment($value)
|
||||
{
|
||||
$value = trim((string)$value);
|
||||
$value = trim((string) $value);
|
||||
$value = str_replace(["\0", "/", "\\"], "", $value);
|
||||
$value = str_replace("..", "", $value);
|
||||
$value = basename($value);
|
||||
@@ -18,417 +19,241 @@ $form_action = $_GET["form"] ?? "";
|
||||
switch ($form_action) {
|
||||
case "delete":
|
||||
$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)) {
|
||||
die("Aulario no encontrado.");
|
||||
$centro_id = safe_path_segment(Sf($_POST["centro_id"] ?? ""));
|
||||
if ($aulario_id === "" || $centro_id === "") {
|
||||
die("Parámetros inválidos.");
|
||||
}
|
||||
// Remove aulario directory and contents
|
||||
$aulario_dir = "/DATA/entreaulas/Centros/$centro_id/Aularios/$aulario_id";
|
||||
function rrmdir($dir) {
|
||||
// Remove from DB
|
||||
db()->prepare("DELETE FROM aularios WHERE org_id = ? AND aulario_id = ?")
|
||||
->execute([$centro_id, $aulario_id]);
|
||||
// Remove comedor, diario, panel data
|
||||
db()->prepare("DELETE FROM comedor_menu_types WHERE org_id = ? AND aulario_id = ?")
|
||||
->execute([$centro_id, $aulario_id]);
|
||||
db()->prepare("DELETE FROM comedor_entries WHERE org_id = ? AND aulario_id = ?")
|
||||
->execute([$centro_id, $aulario_id]);
|
||||
db()->prepare("DELETE FROM diario_entries WHERE org_id = ? AND aulario_id = ?")
|
||||
->execute([$centro_id, $aulario_id]);
|
||||
db()->prepare("DELETE FROM panel_alumno WHERE org_id = ? AND aulario_id = ?")
|
||||
->execute([$centro_id, $aulario_id]);
|
||||
// Remove filesystem directory with student photos
|
||||
$aulario_dir = aulatek_orgs_base_path() . "/$centro_id/Aularios/$aulario_id";
|
||||
function rrmdir($dir)
|
||||
{
|
||||
if (is_dir($dir)) {
|
||||
$objects = scandir($dir);
|
||||
foreach ($objects as $object) {
|
||||
if ($object != "." && $object != "..") {
|
||||
$obj_path = $dir . "/" . $object;
|
||||
if (is_dir($obj_path)) {
|
||||
rrmdir($obj_path);
|
||||
} else {
|
||||
unlink($obj_path);
|
||||
}
|
||||
foreach (scandir($dir) as $object) {
|
||||
if ($object !== "." && $object !== "..") {
|
||||
$p = "$dir/$object";
|
||||
is_dir($p) ? rrmdir($p) : unlink($p);
|
||||
}
|
||||
}
|
||||
rmdir($dir);
|
||||
}
|
||||
}
|
||||
rrmdir($aulario_dir);
|
||||
// Remove aulario config file
|
||||
unlink($aulario_file);
|
||||
header("Location: ?action=index");
|
||||
exit();
|
||||
break;
|
||||
case "create":
|
||||
$user_data = $_SESSION["auth_data"];
|
||||
$centro_id = safe_path_segment(Sf($_POST["centro"] ?? ""));
|
||||
if (empty($centro_id) || !is_dir("/DATA/entreaulas/Centros/$centro_id")) {
|
||||
$centro_id = safe_path_segment(Sf($_POST["centro"] ?? ($_POST["org"] ?? "")));
|
||||
$aulario_id = strtolower(preg_replace("/[^a-zA-Z0-9_-]/", "_", Sf($_POST["name"] ?? "")));
|
||||
if (empty($centro_id) || empty($aulario_id)) {
|
||||
die("Datos incompletos.");
|
||||
}
|
||||
// Ensure centro exists in DB
|
||||
$stmt = db()->prepare("SELECT id FROM organizaciones WHERE org_id = ?");
|
||||
$stmt->execute([$centro_id]);
|
||||
if (!$stmt->fetch()) {
|
||||
die("Centro no válido.");
|
||||
}
|
||||
$aulario_id = strtolower(preg_replace("/[^a-zA-Z0-9_-]/", "_", Sf($_POST["name"] ?? "")));
|
||||
$aulario_data = [
|
||||
"name" => Sf($_POST["name"] ?? ""),
|
||||
"icon" => Sf($_POST["icon"] ?? "/static/logo-entreaulas.png")
|
||||
];
|
||||
// Make path recursive (mkdir -p equivalent)
|
||||
@mkdir("/DATA/entreaulas/Centros/$centro_id/Aularios/", 0777, true);
|
||||
@mkdir("/DATA/entreaulas/Centros/$centro_id/Aularios/$aulario_id/Proyectos/", 0777, true);
|
||||
file_put_contents("/DATA/entreaulas/Centros/$centro_id/Aularios/$aulario_id.json", json_encode($aulario_data));
|
||||
// Update user data
|
||||
$_SESSION["auth_data"]["entreaulas"]["aulas"][] = $aulario_id;
|
||||
db()->prepare(
|
||||
"INSERT OR IGNORE INTO aularios (org_id, aulario_id, name, icon) VALUES (?, ?, ?, ?)"
|
||||
)->execute([
|
||||
$centro_id, $aulario_id,
|
||||
Ssql($_POST["name"] ?? ""),
|
||||
Sf($_POST["icon"] ?? "/static/logo-entreaulas.png"),
|
||||
]);
|
||||
// Create Proyectos directory for project file storage
|
||||
$proyectos_dir = aulatek_orgs_base_path() . "/$centro_id/Aularios/$aulario_id/Proyectos/";
|
||||
if (!is_dir($proyectos_dir)) {
|
||||
mkdir($proyectos_dir, 0755, true);
|
||||
}
|
||||
header("Location: ?action=index");
|
||||
exit();
|
||||
break;
|
||||
case "save_edit":
|
||||
ini_set('memory_limit', '512M');
|
||||
ini_set('upload_max_filesize', '256M');
|
||||
ini_set('post_max_size', '256M');
|
||||
$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");
|
||||
// Determine icon: uploaded photo takes priority over text input
|
||||
$icon = $_POST["icon"] ?? "/static/logo-entreaulas.png";
|
||||
$aulario_photo = $_FILES["photo"] ?? null;
|
||||
if ($aulario_photo !== null && $aulario_photo["error"] === UPLOAD_ERR_OK) {
|
||||
$image_info = getimagesize($aulario_photo["tmp_name"]);
|
||||
if ($image_info !== false) {
|
||||
$aulario_dir = aulatek_orgs_base_path() . "/$centro_id/Aularios/$aulario_id";
|
||||
if (!is_dir($aulario_dir)) {
|
||||
mkdir($aulario_dir, 0755, true);
|
||||
}
|
||||
if (move_uploaded_file($aulario_photo["tmp_name"], "$aulario_dir/photo.jpg")) {
|
||||
$icon = "/aulatek/_filefetch.php?type=aulario_photo&org=" . urlencode($centro_id) . "&aulario=" . urlencode($aulario_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
db()->prepare(
|
||||
"UPDATE aularios SET name = ?, icon = ?, extra = ? WHERE org_id = ? AND aulario_id = ?"
|
||||
)->execute([
|
||||
Ssql($_POST["name"] ?? ""),
|
||||
$icon,
|
||||
json_encode($extra),
|
||||
$centro_id,
|
||||
$aulario_id,
|
||||
]);
|
||||
header("Location: ?action=edit&aulario=" . urlencode($aulario_id) . "¢ro=" . urlencode($centro_id) . "&_result=" . urlencode("Cambios guardados."));
|
||||
exit();
|
||||
break;
|
||||
}
|
||||
|
||||
require_once "_incl/pre-body.php";
|
||||
$view_action = $_GET["action"] ?? "index";
|
||||
switch ($view_action) {
|
||||
case "new":
|
||||
?>
|
||||
require_once "_incl/pre-body.php";
|
||||
$centro_id = safe_path_segment(Sf($_GET["centro"] ?? ($_GET["org"] ?? "")));
|
||||
$all_centros = db_get_centro_ids();
|
||||
?>
|
||||
<div class="card pad">
|
||||
<div>
|
||||
<h1 class="card-title">Nuevo Aulario</h1>
|
||||
<span>
|
||||
Aquí puedes crear un nuevo aulario para el centro que administras.
|
||||
</span>
|
||||
<h1>Nuevo Aulario</h1>
|
||||
<form method="post" action="?form=create">
|
||||
<div class="mb-3">
|
||||
<label for="centro" class="form-label"><b>Centro:</b></label>
|
||||
<select required id="centro" name="centro" class="form-select">
|
||||
<option value="">-- Selecciona un centro --</option>
|
||||
<?php
|
||||
foreach (glob("/DATA/entreaulas/Centros/*", GLOB_ONLYDIR) as $centro_folder) {
|
||||
$centro_id = basename($centro_folder);
|
||||
$selected = ($centro_id == $_SESSION["auth_data"]["entreaulas"]["centro"]) ? "selected" : "";
|
||||
echo '<option value="' . htmlspecialchars($centro_id) . '" ' . $selected . '>' . htmlspecialchars($centro_id) . '</option>';
|
||||
}
|
||||
?>
|
||||
<label for="centro" class="form-label">Organizacion:</label>
|
||||
<select id="centro" name="centro" class="form-select" required>
|
||||
<option value="">-- Selecciona una organizacion --</option>
|
||||
<?php foreach ($all_centros as $cid): ?>
|
||||
<option value="<?= htmlspecialchars($cid) ?>" <?= $cid === $centro_id ? 'selected' : '' ?>><?= htmlspecialchars($cid) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Nombre del Aulario:</label>
|
||||
<input required type="text" id="name" name="name" class="form-control" placeholder="Ej: Aulario Principal">
|
||||
<label for="name" class="form-label">Nombre:</label>
|
||||
<input required type="text" id="name" name="name" class="form-control" placeholder="Ej: Aula 1">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="icon" class="form-label">Icono del Aulario (URL):</label>
|
||||
<input type="text" id="icon" name="icon" class="form-control" placeholder="Ej: https://example.com/icon.png" value="/static/logo-entreaulas.png">
|
||||
<label for="icon" class="form-label">URL del icono:</label>
|
||||
<input type="text" id="icon" name="icon" class="form-control" value="/static/logo-entreaulas.png">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Crear Aulario</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
require_once "_incl/post-body.php";
|
||||
break;
|
||||
case "edit":
|
||||
require_once "_incl/pre-body.php";
|
||||
$aulario_id = safe_path_segment(Sf($_GET["aulario"] ?? ""));
|
||||
$centro_id = safe_path_segment(Sf($_GET["centro"] ?? ""));
|
||||
$aulario_file = "/DATA/entreaulas/Centros/$centro_id/Aularios/$aulario_id.json";
|
||||
if (!file_exists($aulario_file)) {
|
||||
$centro_id = safe_path_segment(Sf($_GET["centro"] ?? ($_GET["org"] ?? "")));
|
||||
$aulario = db_get_aulario($centro_id, $aulario_id);
|
||||
if (!$aulario) {
|
||||
die("Aulario no encontrado.");
|
||||
}
|
||||
$aulario_data = json_decode(file_get_contents($aulario_file), true);
|
||||
|
||||
// Get all aularios from the same centro for sharing options
|
||||
$available_aularios = [];
|
||||
$aularios_files = glob("/DATA/entreaulas/Centros/$centro_id/Aularios/*.json");
|
||||
foreach ($aularios_files as $aul_file) {
|
||||
$aul_id = basename($aul_file, ".json");
|
||||
if ($aul_id !== $aulario_id) { // Don't allow sharing from itself
|
||||
$aul_data = json_decode(file_get_contents($aul_file), true);
|
||||
$available_aularios[$aul_id] = $aul_data['name'] ?? $aul_id;
|
||||
}
|
||||
}
|
||||
|
||||
// Get available projects from other aularios
|
||||
$available_projects_by_aulario = [];
|
||||
foreach ($available_aularios as $aul_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);
|
||||
?>
|
||||
<?php if (isset($_GET['saved'])): ?>
|
||||
<div class="alert alert-success">Cambios guardados correctamente.</div>
|
||||
<?php endif; ?>
|
||||
<div class="card pad">
|
||||
<div>
|
||||
<h1 class="card-title">Editar Aulario: <?php echo htmlspecialchars($aulario_data['name'] ?? 'Sin Nombre'); ?></h1>
|
||||
<form method="post" action="?form=save_edit">
|
||||
<h1>Aulario: <?= htmlspecialchars($aulario['name'] ?? $aulario_id) ?></h1>
|
||||
<form method="post" action="?form=save_edit" enctype="multipart/form-data">
|
||||
<input type="hidden" name="aulario_id" value="<?= htmlspecialchars($aulario_id) ?>">
|
||||
<input type="hidden" name="centro_id" value="<?= htmlspecialchars($centro_id) ?>">
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Nombre del Aulario:</label>
|
||||
<input required type="text" id="name" name="name" class="form-control" value="<?php echo htmlspecialchars($aulario_data['name'] ?? ''); ?>">
|
||||
<label for="name" class="form-label">Nombre:</label>
|
||||
<input required type="text" id="name" name="name" class="form-control" value="<?= htmlspecialchars($aulario['name'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="icon" class="form-label">Icono del Aulario (URL):</label>
|
||||
<input type="text" id="icon" name="icon" class="form-control" value="<?php echo htmlspecialchars($aulario_data['icon'] ?? '/static/iconexperience/blackboard.png'); ?>">
|
||||
<label class="form-label">Foto actual:</label><br>
|
||||
<img src="<?= htmlspecialchars($aulario['icon'] ?: '/static/logo-entreaulas.png') ?>" style="height: 80px; border: 1px solid #ccc; border-radius: 6px; padding: 4px;">
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<h3>Compartir Menú Comedor</h3>
|
||||
<p class="text-muted">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.</p>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="share_comedor_from" class="form-label">Menú Comedor - Compartir desde:</label>
|
||||
<select id="share_comedor_from" name="share_comedor_from" class="form-select">
|
||||
<option value="none">No compartir (usar datos propios)</option>
|
||||
<?php foreach ($available_aularios as $aul_id => $aul_name): ?>
|
||||
<option value="<?php echo htmlspecialchars($aul_id); ?>"
|
||||
<?php echo ($aulario_data['shared_comedor_from'] ?? '') === $aul_id ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($aul_name); ?>
|
||||
</option>
|
||||
<label for="photo" class="form-label">Subir nueva foto del aulario:</label>
|
||||
<input type="file" id="photo" name="photo" class="form-control" accept="image/*">
|
||||
<small class="form-text text-muted">Dejar vacío para mantener la imagen actual.</small>
|
||||
</div>
|
||||
<input type="hidden" id="icon" name="icon" value="<?= htmlspecialchars($aulario['icon'] ?? '') ?>">
|
||||
<div class="mb-3">
|
||||
<label for="shared_comedor_from" class="form-label">Compartir comedor de:</label>
|
||||
<select id="shared_comedor_from" name="shared_comedor_from" class="form-select">
|
||||
<option value="">-- Sin compartir --</option>
|
||||
<?php foreach ($other_aularios as $aid => $adata): if ($aid === $aulario_id) continue; ?>
|
||||
<option value="<?= htmlspecialchars($aid) ?>" <?= ($aulario['shared_comedor_from'] ?? '') === $aid ? 'selected' : '' ?>><?= htmlspecialchars($adata['name'] ?? $aid) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<h3>Proyectos Enlazados</h3>
|
||||
<p class="text-muted">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.</p>
|
||||
|
||||
<div id="linked-projects-container">
|
||||
<?php
|
||||
$existing_links = $aulario_data['linked_projects'] ?? [];
|
||||
if (count($existing_links) === 0) {
|
||||
// Show one empty row
|
||||
$existing_links = [["source_aulario" => "", "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';
|
||||
?>
|
||||
<div class="row mb-2 linked-project-row">
|
||||
<div class="col-md-4">
|
||||
<select name="linked_aulario[]" class="form-select linked-aulario-select" data-row="<?php echo $idx; ?>">
|
||||
<option value="">-- Seleccionar aulario origen --</option>
|
||||
<?php foreach ($available_aularios as $aul_id => $aul_name): ?>
|
||||
<option value="<?php echo htmlspecialchars($aul_id); ?>"
|
||||
<?php echo $source_aul === $aul_id ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($aul_name); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select name="linked_project_id[]" class="form-select linked-project-select" data-row="<?php echo $idx; ?>">
|
||||
<option value="">-- Seleccionar proyecto --</option>
|
||||
<?php if (!empty($source_aul) && isset($available_projects_by_aulario[$source_aul])): ?>
|
||||
<?php foreach ($available_projects_by_aulario[$source_aul] as $proj): ?>
|
||||
<option value="<?php echo htmlspecialchars($proj['id']); ?>"
|
||||
<?php echo $proj_id === $proj['id'] ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($proj['name']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select name="linked_permission[]" class="form-select">
|
||||
<option value="read_only" <?php echo $permission === 'read_only' ? 'selected' : ''; ?>>Solo lectura</option>
|
||||
<option value="request_edit" <?php echo $permission === 'request_edit' ? 'selected' : ''; ?>>Solicitar permiso para cambiar</option>
|
||||
<option value="full_edit" <?php echo $permission === 'full_edit' ? 'selected' : ''; ?>>Cambiar sin solicitar</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button type="button" class="btn btn-danger remove-link-btn" onclick="removeLinkedProject(this)">Eliminar</button>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-secondary mb-3" onclick="addLinkedProject()">+ Añadir Proyecto Enlazado</button>
|
||||
|
||||
<script>
|
||||
// Store available projects data
|
||||
const availableProjects = <?php echo json_encode($available_projects_by_aulario); ?>;
|
||||
let rowCounter = <?php echo count($existing_links); ?>;
|
||||
|
||||
function addLinkedProject() {
|
||||
const container = document.getElementById('linked-projects-container');
|
||||
const newRow = document.createElement('div');
|
||||
newRow.className = 'row mb-2 linked-project-row';
|
||||
newRow.innerHTML = `
|
||||
<div class="col-md-4">
|
||||
<select name="linked_aulario[]" class="form-select linked-aulario-select" data-row="${rowCounter}">
|
||||
<option value="">-- Seleccionar aulario origen --</option>
|
||||
<?php foreach ($available_aularios as $aul_id => $aul_name): ?>
|
||||
<option value="<?php echo htmlspecialchars($aul_id); ?>">
|
||||
<?php echo htmlspecialchars($aul_name); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select name="linked_project_id[]" class="form-select linked-project-select" data-row="${rowCounter}">
|
||||
<option value="">-- Seleccionar proyecto --</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select name="linked_permission[]" class="form-select">
|
||||
<option value="read_only">Solo lectura</option>
|
||||
<option value="request_edit">Solicitar permiso para cambiar</option>
|
||||
<option value="full_edit">Cambiar sin solicitar</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button type="button" class="btn btn-danger remove-link-btn" onclick="removeLinkedProject(this)">Eliminar</button>
|
||||
</div>
|
||||
`;
|
||||
container.appendChild(newRow);
|
||||
|
||||
// Attach change event to new aulario select
|
||||
const newAularioSelect = newRow.querySelector('.linked-aulario-select');
|
||||
newAularioSelect.addEventListener('change', updateProjectOptions);
|
||||
|
||||
rowCounter++;
|
||||
}
|
||||
|
||||
function removeLinkedProject(btn) {
|
||||
btn.closest('.linked-project-row').remove();
|
||||
}
|
||||
|
||||
function updateProjectOptions(event) {
|
||||
const aularioSelect = event.target;
|
||||
const rowId = aularioSelect.dataset.row;
|
||||
const projectSelect = document.querySelector(`.linked-project-select[data-row="${rowId}"]`);
|
||||
const selectedAulario = aularioSelect.value;
|
||||
|
||||
// Clear project options
|
||||
projectSelect.innerHTML = '<option value="">-- Seleccionar proyecto --</option>';
|
||||
|
||||
// Add new options
|
||||
if (selectedAulario && availableProjects[selectedAulario]) {
|
||||
availableProjects[selectedAulario].forEach(proj => {
|
||||
const option = document.createElement('option');
|
||||
option.value = proj.id;
|
||||
option.textContent = proj.name;
|
||||
projectSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Attach event listeners to existing selects
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.querySelectorAll('.linked-aulario-select').forEach(select => {
|
||||
select.addEventListener('change', updateProjectOptions);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<input type="hidden" name="aulario_id" value="<?php echo htmlspecialchars($aulario_id); ?>">
|
||||
<input type="hidden" name="centro_id" value="<?php echo htmlspecialchars($centro_id); ?>">
|
||||
<button type="submit" class="btn btn-primary">Guardar Cambios</button>
|
||||
</form>
|
||||
<form method="post" action="?form=delete" style="display: inline;">
|
||||
<input type="hidden" name="aulario_id" value="<?php echo htmlspecialchars($aulario_id); ?>">
|
||||
<input type="hidden" name="centro_id" value="<?php echo htmlspecialchars($centro_id); ?>">
|
||||
<button type="submit" class="btn btn-danger" onclick="return confirm('¿Estás seguro de que deseas eliminar este aulario? Esta acción no se puede deshacer.')">Eliminar Aulario</button>
|
||||
<hr>
|
||||
<form method="post" action="?form=delete" onsubmit="return confirm('¿Eliminar este aulario? Se borrarán todos sus datos.')">
|
||||
<input type="hidden" name="aulario_id" value="<?= htmlspecialchars($aulario_id) ?>">
|
||||
<input type="hidden" name="centro_id" value="<?= htmlspecialchars($centro_id) ?>">
|
||||
<button type="submit" class="btn btn-danger">Eliminar Aulario</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
require_once "_incl/post-body.php";
|
||||
break;
|
||||
case "index":
|
||||
default:
|
||||
require_once "_incl/pre-body.php";
|
||||
$all_centros = db_get_centros();
|
||||
?>
|
||||
<div class="card pad">
|
||||
<div>
|
||||
<h1 class="card-title">Gestión de Aularios</h1>
|
||||
<span>
|
||||
Desde esta sección puedes administrar los aularios asociados al centro que estás administrando.
|
||||
</span>
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Icono</th>
|
||||
<th>Nombre</th>
|
||||
<th>
|
||||
<a href="?action=new" class="btn btn-success">+ Nuevo</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
$user_data = $_SESSION["auth_data"];
|
||||
$centro_filter = safe_path_segment(Sf($_GET['centro'] ?? ""));
|
||||
if ($centro_filter !== "") {
|
||||
$aulas_filelist = glob("/DATA/entreaulas/Centros/$centro_filter/Aularios/*.json") ?: [];
|
||||
} else {
|
||||
$aulas_filelist = glob("/DATA/entreaulas/Centros/*/Aularios/*.json") ?: [];
|
||||
}
|
||||
foreach ($aulas_filelist as $aula_file) {
|
||||
$aula_data = json_decode(file_get_contents($aula_file), true);
|
||||
$centro_id = basename(dirname(dirname($aula_file)));
|
||||
echo '<tr>';
|
||||
echo '<td><img src="' . htmlspecialchars($aula_data['icon'] ?? '/static/logo-entreaulas.png') . '" alt="Icono" style="height: 50px;"></td>';
|
||||
echo '<td>' . htmlspecialchars($aula_data['name'] ?? 'Sin Nombre') . '<br><small>' . $centro_id . '</small></td>';
|
||||
echo '<td><a href="?action=edit&aulario=' . urlencode(basename($aula_file, ".json")) . '¢ro=' . urlencode($centro_id) . '" class="btn btn-primary">Gestionar</a></td>';
|
||||
echo '</tr>';
|
||||
}
|
||||
?>
|
||||
</tbody>
|
||||
</table>
|
||||
<h1>Gestión de Aularios</h1>
|
||||
<?php foreach ($all_centros as $c): ?>
|
||||
<?php $aularios = db_get_aularios($c['centro_id']); ?>
|
||||
<h2><?= htmlspecialchars($c['centro_id']) ?></h2>
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Icono</th><th>Nombre</th>
|
||||
<th><a href="?action=new¢ro=<?= urlencode($c['centro_id']) ?>" class="btn btn-success">+ Nuevo</a></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($aularios as $aid => $adata): ?>
|
||||
<tr>
|
||||
<td><img src="<?= htmlspecialchars($adata['icon'] ?: '/static/logo-entreaulas.png') ?>" style="height: 50px;"></td>
|
||||
<td><?= htmlspecialchars($adata['name'] ?: $aid) ?></td>
|
||||
<td><a href="?action=edit&aulario=<?= urlencode($aid) ?>¢ro=<?= urlencode($c['centro_id']) ?>" class="btn btn-primary">Editar</a></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
require_once "_incl/post-body.php";
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
|
||||
require_once "_incl/post-body.php"; ?>
|
||||
@@ -1,293 +0,0 @@
|
||||
<?php
|
||||
require_once "_incl/auth_redir.php";
|
||||
require_once "../_incl/tools.security.php";
|
||||
|
||||
function safe_path_segment($value)
|
||||
{
|
||||
$value = trim((string)$value);
|
||||
$value = str_replace(["\0", "/", "\\"], "", $value);
|
||||
$value = str_replace("..", "", $value);
|
||||
$value = basename($value);
|
||||
if ($value === "." || $value === "..") {
|
||||
return "";
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
$form_action = $_GET["form"] ?? "";
|
||||
switch ($form_action) {
|
||||
case "create":
|
||||
$centro_id = safe_path_segment(Sf($_POST["name"] ?? ""));
|
||||
if (empty($centro_id)) {
|
||||
die("Nombre del centro no proporcionado.");
|
||||
}
|
||||
$centro_path = "/DATA/entreaulas/Centros/$centro_id";
|
||||
if (is_dir($centro_path)) {
|
||||
die("El centro ya existe.");
|
||||
}
|
||||
mkdir($centro_path, 0777, 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)) {
|
||||
die("Centro no válido.");
|
||||
}
|
||||
$activity_name = safe_path_segment(Sf($_POST["name"] ?? ''));
|
||||
if (empty($activity_name)) {
|
||||
die("Nombre de la actividad no proporcionado.");
|
||||
}
|
||||
$activity_photo = $_FILES["photo"] ?? null;
|
||||
if ($activity_photo === null || $activity_photo["error"] !== UPLOAD_ERR_OK) {
|
||||
die("Error al subir la foto.");
|
||||
}
|
||||
$activity_path = "$centro_path/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);
|
||||
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'] ?? ''));
|
||||
$activity_name = safe_path_segment(Sf($_GET['activity'] ?? ''));
|
||||
$activity_path = "/DATA/entreaulas/Centros/$centro_id/Panel/Actividades/$activity_name";
|
||||
if (!is_dir($activity_path)) {
|
||||
die("Actividad no válida.");
|
||||
}
|
||||
$activity_photo = $_FILES["file"] ?? null;
|
||||
if ($activity_photo !== null && $activity_photo["error"] === UPLOAD_ERR_OK) {
|
||||
$photo_path = "$activity_path/photo.jpg";
|
||||
move_uploaded_file($activity_photo["tmp_name"], $photo_path);
|
||||
}
|
||||
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)) {
|
||||
die("Ya existe una actividad con ese nombre.");
|
||||
}
|
||||
rename($activity_path, $new_activity_path);
|
||||
}
|
||||
header("Location: ?action=edit¢ro=" . urlencode($centro_id));;
|
||||
exit();
|
||||
break;
|
||||
}
|
||||
|
||||
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'] ?? ''));
|
||||
$activity_name = safe_path_segment(Sf($_GET['activity'] ?? ''));
|
||||
$activity_path = "/DATA/entreaulas/Centros/$centro_id/Panel/Actividades/$activity_name";
|
||||
if (!is_dir($activity_path)) {
|
||||
die("Actividad no válida.");
|
||||
}
|
||||
?>
|
||||
<div class="card pad">
|
||||
<div>
|
||||
<h1 class="card-title">Gestión de la Actividad: <?php echo htmlspecialchars($activity_name); ?></h1>
|
||||
<span>
|
||||
Desde esta sección puedes administrar la actividad seleccionada del panel del centro <?php echo htmlspecialchars($centro_id); ?>.
|
||||
</span>
|
||||
<form method="post" action="?form=edit_activity¢ro=<?php echo urlencode($centro_id); ?>&activity=<?php echo urlencode($activity_name); ?>" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<label for="nombre" class="form-label">Nombre de la actividad:</label>
|
||||
<input required type="text" id="nombre" name="nombre" class="form-control" value="<?php echo htmlspecialchars($activity_name); ?>">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Foto (pulsa para cambiarla):</label><br>
|
||||
<div style="width: 200px;">
|
||||
<label class="dropimage" style="background-image: url('<?php
|
||||
$image_path = "$activity_path/photo.jpg";
|
||||
$image_fetchpath = file_exists($image_path) ? "/entreaulas/_filefetch.php?type=panel_actividades¢ro=" . urlencode($centro_id) . "&activity=" . urlencode($activity_name) : '/static/logo-entreaulas.png';
|
||||
echo htmlspecialchars($image_fetchpath);
|
||||
?>');">
|
||||
<input title="Drop image or click me" type="file" name="file" accept="image/*">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Guardar Cambios</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
break;
|
||||
case "new_activity":
|
||||
$centro_id = safe_path_segment(Sf($_GET['centro'] ?? ''));
|
||||
$centro_path = "/DATA/entreaulas/Centros/$centro_id";
|
||||
if (!is_dir($centro_path)) {
|
||||
die("Centro no válido.");
|
||||
}
|
||||
?>
|
||||
<div class="card pad">
|
||||
<div>
|
||||
<h1 class="card-title">Nueva Actividad del Panel</h1>
|
||||
<span>
|
||||
Aquí puedes crear una nueva actividad para el panel del centro <?php echo htmlspecialchars($centro_id); ?>.
|
||||
</span>
|
||||
<form method="post" action="?form=create_activity¢ro=<?php echo urlencode($centro_id); ?>" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Nombre de la actividad:</label>
|
||||
<input required type="text" id="name" name="name" class="form-control" placeholder="Ej: Biblioteca">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="photo" class="form-label">Foto:</label>
|
||||
<input required type="file" id="photo" name="photo" class="form-control" accept="image/*">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Crear Actividad</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
break;
|
||||
case "new":
|
||||
?>
|
||||
<div class="card pad">
|
||||
<div>
|
||||
<h1 class="card-title">Nuevo Centro</h1>
|
||||
<span>
|
||||
Aquí puedes crear un nuevo centro para el sistema.
|
||||
</span>
|
||||
<form method="post" action="?form=create">
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">ID del centro:</label>
|
||||
<input required type="text" id="name" name="name" class="form-control" placeholder="Ej: Centro-Principal-001">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Crear Centro</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
break;
|
||||
case "edit":
|
||||
$centro_id = safe_path_segment(Sf($_GET['centro'] ?? ''));
|
||||
$centro_path = "/DATA/entreaulas/Centros/$centro_id";
|
||||
if (!is_dir($centro_path)) {
|
||||
die("Centro no válido.");
|
||||
}
|
||||
?>
|
||||
<div class="card pad">
|
||||
<div>
|
||||
<h1 class="card-title">Gestión del Centro: <?php echo htmlspecialchars($centro_id); ?></h1>
|
||||
<span>
|
||||
Desde esta sección puedes administrar el centro seleccionado.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card pad">
|
||||
<div>
|
||||
<h2>Aularios</h2>
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Icono</th>
|
||||
<th>Nombre</th>
|
||||
<th>
|
||||
<a href="/sysadmin/aularios.php?action=new¢ro=<?php echo urlencode($centro_id); ?>" class="btn btn-success">+ Nuevo</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
$aulas_filelist = glob("/DATA/entreaulas/Centros/$centro_id/Aularios/*.json");
|
||||
foreach ($aulas_filelist as $aula_file) {
|
||||
$aula_data = json_decode(file_get_contents($aula_file), true);
|
||||
echo '<tr>';
|
||||
echo '<td><img src="' . htmlspecialchars($aula_data['icon'] ?? '/static/logo-entreaulas.png') . '" alt="Icono" style="height: 50px;"></td>';
|
||||
echo '<td>' . htmlspecialchars($aula_data['name'] ?? 'Sin Nombre') . '</td>';
|
||||
echo '<td><a href="/sysadmin/aularios.php?action=edit&aulario=' . urlencode(basename($aula_file, ".json")) . '¢ro=' . urlencode($centro_id) . '" class="btn btn-primary">Gestionar</a></td>';
|
||||
echo '</tr>';
|
||||
}
|
||||
?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card pad">
|
||||
<div>
|
||||
<h2>Actividades del panel</h2>
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Foto</th>
|
||||
<th>Nombre</th>
|
||||
<th>
|
||||
<a href="?action=new_activity¢ro=<?php echo urlencode($centro_id); ?>" class="btn btn-success">+ Nuevo</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
$activities = glob("/DATA/entreaulas/Centros/$centro_id/Panel/Actividades/*", GLOB_ONLYDIR);
|
||||
foreach ($activities as $activity_path) {
|
||||
$activity_name = basename($activity_path);
|
||||
$image_path = "/DATA/entreaulas/Centros/$centro_id/Panel/Actividades/" . basename($activity_name) . "/photo.jpg";
|
||||
$image_fetchpath = file_exists($image_path) ? "/entreaulas/_filefetch.php?type=panel_actividades¢ro=" . urlencode($centro_id) . "&activity=" . urlencode($activity_name) : '/static/logo-entreaulas.png';
|
||||
echo '<tr>';
|
||||
echo '<td><img src="' . htmlspecialchars($image_fetchpath) . '" alt="Foto" style="height: 50px;"></td>';
|
||||
echo '<td>' . htmlspecialchars($activity_name) . '</td>';
|
||||
echo '<td><a href="?action=edit_activity¢ro=' . urlencode($centro_id) . '&activity=' . urlencode($activity_name) . '" class="btn btn-primary">Gestionar</a></td>';
|
||||
echo '</tr>';
|
||||
}
|
||||
?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
break;
|
||||
case "index":
|
||||
default:
|
||||
?>
|
||||
<div class="card pad">
|
||||
<div>
|
||||
<h1 class="card-title">Gestión de Centros</h1>
|
||||
<span>
|
||||
Desde esta sección puedes administrar los centros asociados al sistema.
|
||||
</span>
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Nombre</th>
|
||||
<th>
|
||||
<a href="?action=new" class="btn btn-success">+ Nuevo</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
$user_data = $_SESSION["auth_data"];
|
||||
$centros_filelist = glob("/DATA/entreaulas/Centros/*");
|
||||
foreach ($centros_filelist as $centro_folder) {
|
||||
$centro_id = basename($centro_folder);
|
||||
echo '<tr>';
|
||||
echo '<td>' . htmlspecialchars($centro_id) . '</td>';
|
||||
echo '<td><a href="?action=edit¢ro=' . urlencode($centro_id) . '" class="btn btn-primary">Gestionar</a></td>';
|
||||
echo '</tr>';
|
||||
}
|
||||
?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
break;
|
||||
}
|
||||
|
||||
require_once "_incl/post-body.php"; ?>
|
||||
@@ -7,9 +7,9 @@ require_once "_incl/pre-body.php"; ?>
|
||||
</div>
|
||||
<div id="grid">
|
||||
<div class="card grid-item">
|
||||
<img src="/static/logo-entreaulas.png" alt="Logo EntreAulas">
|
||||
<b>EntreAulas</b>
|
||||
<a href="/sysadmin/centros.php" class="btn btn-primary">Gestionar Centros</a>
|
||||
<img src="/static/logo-entreaulas.png" alt="Logo AulaTek">
|
||||
<b>AulaTek</b>
|
||||
<a href="/sysadmin/orgs.php" class="btn btn-primary">Gestionar Organizaciones</a>
|
||||
<a href="/sysadmin/aularios.php" class="btn btn-primary">Gestionar Aularios</a>
|
||||
</div>
|
||||
<div class="card grid-item">
|
||||
|
||||
@@ -1,99 +1,90 @@
|
||||
<?php
|
||||
require_once "_incl/auth_redir.php";
|
||||
require_once "../_incl/db.php";
|
||||
|
||||
switch ($_GET['form']) {
|
||||
switch ($_GET['form'] ?? '') {
|
||||
case "create":
|
||||
// Handle creation logic here
|
||||
$invitations = json_decode(file_get_contents("/DATA/Invitaciones_de_usuarios.json"), true) ?? [];
|
||||
$invitation_code = strtoupper($_POST['invitation_code'] ?? '');
|
||||
$single_use = isset($_POST['single_use']) ? true : false;
|
||||
if (isset($invitations[$invitation_code])) {
|
||||
$code = strtoupper(trim($_POST['invitation_code'] ?? ''));
|
||||
$single_use = isset($_POST['single_use']);
|
||||
if (empty($code)) {
|
||||
header("Location: /sysadmin/invitations.php?action=new&_resultcolor=red&_result=" . urlencode("Código de invitación vacío."));
|
||||
exit;
|
||||
}
|
||||
if (db_get_invitation($code)) {
|
||||
header("Location: /sysadmin/invitations.php?action=new&_resultcolor=red&_result=" . urlencode("El código de invitación ya existe."));
|
||||
exit;
|
||||
}
|
||||
$invitations[$invitation_code] = [
|
||||
"active" => 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":
|
||||
?>
|
||||
<div class="card pad">
|
||||
<div>
|
||||
<h1 class="card-title">Nueva invitación de usuario</h1>
|
||||
<form method="post" action="?form=create">
|
||||
<div class="card pad" style="max-width: 500px;">
|
||||
<div>
|
||||
<div class="mb-3">
|
||||
<label for="invitation_code" class="form-label"><b>Código de invitación:</b></label>
|
||||
<input type="text" id="invitation_code" name="invitation_code" class="form-control" required />
|
||||
<small>Formato: 123456-ABCDEF</small>
|
||||
</div>
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" name="single_use" id="single_use">
|
||||
<label class="form-check-label" for="single_use">
|
||||
Uso único
|
||||
</label>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Crear invitación</button>
|
||||
</div>
|
||||
?>
|
||||
<div class="card pad">
|
||||
<div>
|
||||
<h1 class="card-title">Nueva invitación de usuario</h1>
|
||||
<form method="post" action="?form=create">
|
||||
<div class="card pad" style="max-width: 500px;">
|
||||
<div>
|
||||
<div class="mb-3">
|
||||
<label for="invitation_code" class="form-label"><b>Código de invitación:</b></label>
|
||||
<input type="text" id="invitation_code" name="invitation_code" class="form-control" required />
|
||||
<small>Formato: 123456-ABCDEF</small>
|
||||
</div>
|
||||
</form>
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" name="single_use" id="single_use">
|
||||
<label class="form-check-label" for="single_use">Uso único</label>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Crear invitación</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
break;
|
||||
default:
|
||||
case "index":
|
||||
?>
|
||||
<div class="card pad">
|
||||
<div>
|
||||
<h1>Invitaciones de usuarios</h1>
|
||||
<span>Desde aquí puedes gestionar las invitaciones de usuarios.</span>
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="table-dark">
|
||||
<th>Codigo de invitación</th>
|
||||
<th>
|
||||
<a href="?action=new" class="btn btn-success">+ Nuevo</a>
|
||||
</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
$invitations = json_decode(file_get_contents("/DATA/Invitaciones_de_usuarios.json"), true);
|
||||
foreach ($invitations as $inv_key => $inv_data) {
|
||||
echo "<tr>";
|
||||
echo "<td>" . htmlspecialchars($inv_key) . "</td>";
|
||||
echo "<td>";
|
||||
echo '<form method="post" action="?form=delete" style="display:inline;">';
|
||||
echo '<input type="hidden" name="invitation_code" value="' . htmlspecialchars($inv_key) . '"/>';
|
||||
echo '<button type="submit" class="btn btn-danger" onclick="return confirm(\'¿Estás seguro de que deseas eliminar esta invitación?\');">Eliminar</button>';
|
||||
echo '</form>';
|
||||
echo "</td>";
|
||||
echo "</tr>";
|
||||
}
|
||||
?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
$invitations = db_get_all_invitations();
|
||||
?>
|
||||
<div class="card pad">
|
||||
<div>
|
||||
<h1>Invitaciones de usuarios</h1>
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="table-dark">
|
||||
<th>Código</th>
|
||||
<th>Activo</th>
|
||||
<th>Uso único</th>
|
||||
<th><a href="?action=new" class="btn btn-success">+ Nuevo</a></th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($invitations as $inv): ?>
|
||||
<tr>
|
||||
<td><?= htmlspecialchars($inv['code']) ?></td>
|
||||
<td><?= $inv['active'] ? 'Sí' : 'No' ?></td>
|
||||
<td><?= $inv['single_use'] ? 'Sí' : 'No' ?></td>
|
||||
<td>
|
||||
<form method="post" action="?form=delete" style="display:inline">
|
||||
<input type="hidden" name="invitation_code" value="<?= htmlspecialchars($inv['code']) ?>">
|
||||
<button type="submit" class="btn btn-danger btn-sm">Borrar</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
break;
|
||||
}
|
||||
|
||||
291
public_html/sysadmin/orgs.php
Normal file
291
public_html/sysadmin/orgs.php
Normal file
@@ -0,0 +1,291 @@
|
||||
<?php
|
||||
require_once "_incl/auth_redir.php";
|
||||
require_once "../_incl/tools.security.php";
|
||||
require_once "../_incl/db.php";
|
||||
|
||||
function safe_path_segment($value)
|
||||
{
|
||||
$value = trim((string) $value);
|
||||
$value = str_replace(["\0", "/", "\\"], "", $value);
|
||||
$value = str_replace("..", "", $value);
|
||||
$value = basename($value);
|
||||
if ($value === "." || $value === "..") {
|
||||
return "";
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
$form_action = $_GET["form"] ?? "";
|
||||
switch ($form_action) {
|
||||
case "create":
|
||||
$org_id = safe_path_segment(Sf($_POST["org_id"] ?? ""));
|
||||
$org_name = Ssql($_POST["org_name"] ?? "");
|
||||
if (empty($org_id)) {
|
||||
die("Nombre de la organización no proporcionado.");
|
||||
}
|
||||
// Check uniqueness in DB
|
||||
$existing = db()->prepare("SELECT id FROM organizaciones WHERE org_id = ?");
|
||||
$existing->execute([$org_id]);
|
||||
if ($existing->fetch()) {
|
||||
die("La organización ya existe.");
|
||||
}
|
||||
// Create DB record
|
||||
db()->prepare("INSERT INTO organizaciones (org_id, org_name) VALUES (?, ?)")->execute([$org_id, $org_name !== '' ? $org_name : $org_id]);
|
||||
// Keep filesystem directory for activity photos (Panel/Actividades)
|
||||
$org_path = aulatek_orgs_base_path() . "/$org_id";
|
||||
if (!is_dir($org_path) && !mkdir($org_path, 0755, true) && !is_dir($org_path)) {
|
||||
error_log("orgs.php: failed to create directory $org_path");
|
||||
}
|
||||
header("Location: ?action=index");
|
||||
exit();
|
||||
break;
|
||||
case "edit":
|
||||
$org_id = safe_path_segment(Sf($_GET['org'] ?? ''));
|
||||
$org_name = Ssql($_POST['org_name'] ?? '');
|
||||
if ($org_id === '' || $org_name === '') {
|
||||
die("Datos inválidos para actualizar la organización.");
|
||||
}
|
||||
db()->prepare("UPDATE organizaciones SET org_name = ? WHERE org_id = ?")->execute([$org_name, $org_id]);
|
||||
header("Location: ?action=edit&org=" . urlencode($org_id) . "&_result=" . urlencode("Cambios guardados."));
|
||||
exit();
|
||||
break;
|
||||
case "create_activity":
|
||||
ini_set('memory_limit', '512M');
|
||||
ini_set('upload_max_filesize', '256M');
|
||||
ini_set('post_max_size', '256M');
|
||||
$org_id = safe_path_segment(Sf($_GET['org'] ?? ''));
|
||||
// Validate organization exists in DB
|
||||
$stmt = db()->prepare("SELECT id FROM organizaciones WHERE org_id = ?");
|
||||
$stmt->execute([$org_id]);
|
||||
if (!$stmt->fetch()) {
|
||||
die("Organización no válida.");
|
||||
}
|
||||
$activity_name = safe_path_segment(Sf($_POST["name"] ?? ''));
|
||||
if (empty($activity_name)) {
|
||||
die("Nombre de la actividad no proporcionado.");
|
||||
}
|
||||
$activity_photo = $_FILES["photo"] ?? null;
|
||||
if ($activity_photo === null || $activity_photo["error"] !== UPLOAD_ERR_OK) {
|
||||
die("Error al subir la foto.");
|
||||
}
|
||||
$activity_path = aulatek_orgs_base_path() . "/$org_id/Panel/Actividades/$activity_name";
|
||||
if (is_dir($activity_path)) {
|
||||
die("La actividad ya existe.");
|
||||
}
|
||||
mkdir($activity_path, 0755, true);
|
||||
move_uploaded_file($activity_photo["tmp_name"], "$activity_path/photo.jpg");
|
||||
header("Location: ?action=edit&org=" . urlencode($org_id));
|
||||
exit();
|
||||
break;
|
||||
case "edit_activity":
|
||||
ini_set('memory_limit', '512M');
|
||||
ini_set('upload_max_filesize', '256M');
|
||||
ini_set('post_max_size', '256M');
|
||||
$org_id = safe_path_segment(Sf($_GET['org'] ?? ''));
|
||||
$activity_name = safe_path_segment(Sf($_GET['activity'] ?? ''));
|
||||
$activity_path = aulatek_orgs_base_path() . "/$org_id/Panel/Actividades/$activity_name";
|
||||
if (!is_dir($activity_path)) {
|
||||
die("Actividad no válida.");
|
||||
}
|
||||
$activity_photo = $_FILES["file"] ?? null;
|
||||
if ($activity_photo !== null && $activity_photo["error"] === UPLOAD_ERR_OK) {
|
||||
move_uploaded_file($activity_photo["tmp_name"], "$activity_path/photo.jpg");
|
||||
}
|
||||
$new_name = safe_path_segment(Sf($_POST['nombre'] ?? ''));
|
||||
if ($new_name !== $activity_name && $new_name !== '') {
|
||||
$new_path = aulatek_orgs_base_path() . "/$org_id/Panel/Actividades/$new_name";
|
||||
if (is_dir($new_path)) {
|
||||
die("Ya existe una actividad con ese nombre.");
|
||||
}
|
||||
rename($activity_path, $new_path);
|
||||
}
|
||||
header("Location: ?action=edit&org=" . urlencode($org_id));
|
||||
exit();
|
||||
break;
|
||||
}
|
||||
|
||||
require_once "_incl/pre-body.php";
|
||||
$view_action = $_GET["action"] ?? "index";
|
||||
switch ($view_action) {
|
||||
case "edit_activity":
|
||||
$org_id = safe_path_segment(Sf($_GET['org'] ?? ''));
|
||||
$activity_name = safe_path_segment(Sf($_GET['activity'] ?? ''));
|
||||
$activity_path = aulatek_orgs_base_path() . "/$org_id/Panel/Actividades/$activity_name";
|
||||
if (!is_dir($activity_path)) {
|
||||
die("Actividad no válida.");
|
||||
}
|
||||
?>
|
||||
<div class="card pad">
|
||||
<div>
|
||||
<h1 class="card-title">Gestión de la Actividad: <?= htmlspecialchars($activity_name) ?></h1>
|
||||
<form method="post" action="?form=edit_activity&org=<?= urlencode($org_id) ?>&activity=<?= urlencode($activity_name) ?>" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<label for="nombre" class="form-label">Nombre de la actividad:</label>
|
||||
<input required type="text" id="nombre" name="nombre" class="form-control" value="<?= htmlspecialchars($activity_name) ?>">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Foto (pulsa para cambiarla):</label><br>
|
||||
<div style="width: 200px;">
|
||||
<label class="dropimage" style="background-image: url('<?php
|
||||
$img = file_exists("$activity_path/photo.jpg")
|
||||
? "/aulatek/_filefetch.php?type=panel_actividades&org=" . urlencode($org_id) . "&activity=" . urlencode($activity_name)
|
||||
: '/static/logo-entreaulas.png';
|
||||
echo htmlspecialchars($img);
|
||||
?>');">
|
||||
<input title="Drop image or click me" type="file" name="file" accept="image/*">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Guardar Cambios</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
break;
|
||||
case "new_activity":
|
||||
$org_id = safe_path_segment(Sf($_GET['org'] ?? ''));
|
||||
$stmt = db()->prepare("SELECT id FROM organizaciones WHERE org_id = ?");
|
||||
$stmt->execute([$org_id]);
|
||||
if (!$stmt->fetch()) {
|
||||
die("Organización no válida.");
|
||||
}
|
||||
?>
|
||||
<div class="card pad">
|
||||
<div>
|
||||
<h1 class="card-title">Nueva Actividad del Panel</h1>
|
||||
<form method="post" action="?form=create_activity&org=<?= urlencode($org_id) ?>" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Nombre de la actividad:</label>
|
||||
<input required type="text" id="name" name="name" class="form-control" placeholder="Ej: Biblioteca">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="photo" class="form-label">Foto:</label>
|
||||
<input required type="file" id="photo" name="photo" class="form-control" accept="image/*">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Crear Actividad</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
break;
|
||||
case "new":
|
||||
?>
|
||||
<div class="card pad">
|
||||
<div>
|
||||
<h1 class="card-title">Nueva Organización</h1>
|
||||
<form method="post" action="?form=create">
|
||||
<div class="mb-3">
|
||||
<label for="org_id" class="form-label">ID de la organización:</label>
|
||||
<input required type="text" id="org_id" name="org_id" class="form-control" placeholder="Ej: Organizacion-Principal-001">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="org_name" class="form-label">Nombre de la organización:</label>
|
||||
<input required type="text" id="org_name" name="org_name" class="form-control" placeholder="Ej: Organización Principal">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Crear Organización</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
break;
|
||||
case "edit":
|
||||
$org_id = safe_path_segment(Sf($_GET['org'] ?? ''));
|
||||
$stmt = db()->prepare("SELECT org_name FROM organizaciones WHERE org_id = ?");
|
||||
$stmt->execute([$org_id]);
|
||||
$org_row = $stmt->fetch();
|
||||
if (!$org_row) {
|
||||
die("Organización no válida.");
|
||||
}
|
||||
$org_name = $org_row['org_name'] ?? $org_id;
|
||||
$aularios = db_get_aularios($org_id);
|
||||
$activities = glob(aulatek_orgs_base_path() . "/$org_id/Panel/Actividades/*", GLOB_ONLYDIR) ?: [];
|
||||
?>
|
||||
<div class="card pad">
|
||||
<div>
|
||||
<h1 class="card-title">Gestión de la Organización: <?= htmlspecialchars($org_name) ?></h1>
|
||||
</div>
|
||||
<form method="post" action="?form=edit&org=<?= urlencode($org_id) ?>">
|
||||
<div class="mb-3">
|
||||
<label for="org_name" class="form-label">Nombre de la organización:</label>
|
||||
<input required type="text" id="org_name" name="org_name" class="form-control" value="<?= htmlspecialchars($org_name) ?>">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Guardar Cambios</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="card pad">
|
||||
<div>
|
||||
<h2>Aularios</h2>
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Icono</th><th>Nombre</th>
|
||||
<th><a href="/sysadmin/aularios.php?action=new&org=<?= urlencode($org_id) ?>" class="btn btn-success">+ Nuevo</a></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($aularios as $aula_id => $aula): ?>
|
||||
<tr>
|
||||
<td><img src="<?= htmlspecialchars($aula['icon'] ?: '/static/logo-entreaulas.png') ?>" alt="Icono" style="height: 50px;"></td>
|
||||
<td><?= htmlspecialchars($aula['name'] ?: $aula_id) ?></td>
|
||||
<td><a href="/sysadmin/aularios.php?action=edit&aulario=<?= urlencode($aula_id) ?>&org=<?= urlencode($org_id) ?>" class="btn btn-primary">Gestionar</a></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card pad">
|
||||
<div>
|
||||
<h2>Actividades del panel</h2>
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Foto</th><th>Nombre</th>
|
||||
<th><a href="?action=new_activity&org=<?= urlencode($org_id) ?>" class="btn btn-success">+ Nuevo</a></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($activities as $ap): ?>
|
||||
<?php $an = basename($ap); $img_path = "$ap/photo.jpg"; ?>
|
||||
<tr>
|
||||
<td><img src="<?= file_exists($img_path) ? htmlspecialchars("/aulatek/_filefetch.php?type=panel_actividades&org=" . urlencode($org_id) . "&activity=" . urlencode($an)) : '/static/logo-entreaulas.png' ?>" style="height: 50px;"></td>
|
||||
<td><?= htmlspecialchars($an) ?></td>
|
||||
<td><a href="?action=edit_activity&org=<?= urlencode($org_id) ?>&activity=<?= urlencode($an) ?>" class="btn btn-primary">Gestionar</a></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
break;
|
||||
case "index":
|
||||
default:
|
||||
$all_organizaciones = db_get_organizaciones();
|
||||
?>
|
||||
<div class="card pad">
|
||||
<div>
|
||||
<h1 class="card-title">Gestión de Organizaciones</h1>
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Organización</th>
|
||||
<th style="text-align: right;"><a href="?action=new" class="btn btn-success">+ Nuevo</a></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($all_organizaciones as $o): ?>
|
||||
<tr>
|
||||
<td><?= htmlspecialchars($o['org_name']) ?><br><small><?= htmlspecialchars($o['org_id']) ?></small></td>
|
||||
<td><a href="?action=edit&org=<?= urlencode($o['org_id']) ?>" class="btn btn-primary">Gestionar</a></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
break;
|
||||
}
|
||||
|
||||
require_once "_incl/post-body.php";
|
||||
@@ -1,93 +1,75 @@
|
||||
<?php
|
||||
require_once "_incl/auth_redir.php";
|
||||
require_once "../_incl/tools.security.php";
|
||||
require_once "../_incl/db.php";
|
||||
|
||||
function safe_username($value)
|
||||
{
|
||||
$value = basename((string)$value);
|
||||
$value = preg_replace('/[^a-zA-Z0-9._-]/', '', $value);
|
||||
if (strpos($value, '..') !== false) {
|
||||
return '';
|
||||
}
|
||||
return $value;
|
||||
$value = strtolower(basename((string) $value));
|
||||
$value = preg_replace('/[^a-zA-Z0-9._@-]/', '', $value);
|
||||
if (strpos($value, '..') !== false) {
|
||||
return '';
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
switch ($_GET['form'] ?? '') {
|
||||
case 'save_password':
|
||||
$username = safe_username($_POST['username'] ?? '');
|
||||
$new_password = $_POST['new_password'] ?? '';
|
||||
$confirm_password = $_POST['confirm_password'] ?? '';
|
||||
case 'save_password':
|
||||
$username = safe_username($_POST['username'] ?? '');
|
||||
$new_password = $_POST['new_password'] ?? '';
|
||||
$confirm_password = $_POST['confirm_password'] ?? '';
|
||||
|
||||
if (empty($username)) {
|
||||
die("Nombre de usuario no proporcionado.");
|
||||
}
|
||||
if (empty($username)) {
|
||||
die("Nombre de usuario no proporcionado.");
|
||||
}
|
||||
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.");
|
||||
}
|
||||
$row = db_get_user($username);
|
||||
if (!$row) {
|
||||
die("Usuario no encontrado.");
|
||||
}
|
||||
db()->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);
|
||||
?>
|
||||
|
||||
<form method="post" action="?form=save_password">
|
||||
<div class="card pad">
|
||||
<div>
|
||||
<h1>Restablecer Contraseña: <?php echo htmlspecialchars($username); ?></h1>
|
||||
|
||||
<h1>Restablecer Contraseña: <?= htmlspecialchars($username) ?></h1>
|
||||
<div class="mb-3">
|
||||
<label for="new_password" class="form-label">Nueva Contraseña:</label>
|
||||
<input type="password" id="new_password" name="new_password" class="form-control" required minlength="6">
|
||||
<small class="form-text text-muted">Mínimo 6 caracteres</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="confirm_password" class="form-label">Confirmar Contraseña:</label>
|
||||
<input type="password" id="confirm_password" name="confirm_password" class="form-control" required minlength="6">
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="username" value="<?php echo htmlspecialchars($username); ?>">
|
||||
|
||||
<input type="hidden" name="username" value="<?= htmlspecialchars($username) ?>">
|
||||
<button type="submit" class="btn btn-primary">Restablecer Contraseña</button>
|
||||
<a href="users.php?action=edit&user=<?php echo urlencode($username); ?>" class="btn btn-secondary">Cancelar</a>
|
||||
<a href="users.php?action=edit&user=<?= urlencode($username) ?>" class="btn btn-secondary">Cancelar</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<?php
|
||||
require_once "_incl/post-body.php";
|
||||
?>
|
||||
|
||||
@@ -1,91 +1,118 @@
|
||||
<?php
|
||||
require_once "_incl/auth_redir.php";
|
||||
require_once "../_incl/tools.security.php";
|
||||
require_once "../_incl/db.php";
|
||||
|
||||
function safe_username($value)
|
||||
{
|
||||
$value = basename((string)$value);
|
||||
$value = preg_replace('/[^a-zA-Z0-9._-]/', '', $value);
|
||||
if (strpos($value, '..') !== false) {
|
||||
return '';
|
||||
}
|
||||
return $value;
|
||||
$value = strtolower(basename((string) $value));
|
||||
$value = preg_replace('/[^a-zA-Z0-9._@-]/', '', $value);
|
||||
if (strpos($value, '..') !== false) {
|
||||
return '';
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
define('USERS_DIR', '/DATA/Usuarios/');
|
||||
|
||||
switch ($_GET['form'] ?? '') {
|
||||
case 'save_edit':
|
||||
$username = safe_username($_POST['username'] ?? '');
|
||||
if (empty($username)) {
|
||||
die("Nombre de usuario no proporcionado.");
|
||||
}
|
||||
$user_file = get_user_file_path($username);
|
||||
$userdata_old = [];
|
||||
if (is_readable($user_file)) {
|
||||
$file_contents = file_get_contents($user_file);
|
||||
if ($file_contents !== false) {
|
||||
$decoded = json_decode($file_contents, true);
|
||||
if (is_array($decoded)) {
|
||||
$userdata_old = $decoded;
|
||||
function render_users_mobile_styles()
|
||||
{
|
||||
?>
|
||||
<style>
|
||||
.users-mobile-stack .btn {
|
||||
width: 100%;
|
||||
}
|
||||
.tenant-list {
|
||||
max-height: 210px;
|
||||
overflow: auto;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
}
|
||||
.tenant-list .form-check {
|
||||
margin-bottom: 0.45rem;
|
||||
}
|
||||
.aulas-list {
|
||||
max-height: 220px;
|
||||
overflow: auto;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
}
|
||||
.aulas-list .form-check {
|
||||
margin-right: 0.6rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
@media (max-width: 767.98px) {
|
||||
.card.pad {
|
||||
padding: 1rem !important;
|
||||
}
|
||||
.users-mobile-stack h1 {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
.users-mobile-stack .accordion-button {
|
||||
padding-top: 0.7rem;
|
||||
padding-bottom: 0.7rem;
|
||||
}
|
||||
.users-mobile-stack .btn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
$permissions = $_POST['permissions'] ?? [];
|
||||
if (!is_array($permissions)) {
|
||||
$permissions = [];
|
||||
}
|
||||
</style>
|
||||
<?php
|
||||
}
|
||||
|
||||
$aulas = $_POST['aulas'] ?? [];
|
||||
if (!is_array($aulas)) {
|
||||
$aulas = [];
|
||||
}
|
||||
$aulas = array_values(array_filter(array_map('safe_aulario_id', $aulas)));
|
||||
switch ($_GET['form'] ?? '') {
|
||||
case 'delete_user':
|
||||
$username = safe_username($_POST['username'] ?? '');
|
||||
if (empty($username)) {
|
||||
die("Nombre de usuario no proporcionado.");
|
||||
}
|
||||
db_delete_user($username);
|
||||
db_delete_user_sessions($username);
|
||||
header("Location: ?action=index&_result=" . urlencode("Usuario \"$username\" eliminado correctamente."));
|
||||
exit;
|
||||
|
||||
$userdata_new = [
|
||||
'display_name' => $_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;
|
||||
case 'save_edit':
|
||||
$username = safe_username($_POST['username'] ?? '');
|
||||
if (empty($username)) {
|
||||
die("Nombre de usuario no proporcionado.");
|
||||
}
|
||||
$permissions = $_POST['permissions'] ?? [];
|
||||
if (!is_array($permissions)) {
|
||||
$permissions = [];
|
||||
}
|
||||
$aulas = $_POST['aulas'] ?? [];
|
||||
if (!is_array($aulas)) {
|
||||
$aulas = [];
|
||||
}
|
||||
$aulas = array_values(array_filter(array_map('safe_aulario_id', $aulas)));
|
||||
|
||||
$organization_input = $_POST['organization'] ?? [];
|
||||
if (!is_array($organization_input)) {
|
||||
$organization_input = [$organization_input];
|
||||
}
|
||||
$organizations = array_values(array_unique(array_filter(array_map('safe_organization_id', $organization_input))));
|
||||
|
||||
db_upsert_user([
|
||||
'username' => $username,
|
||||
'display_name' => $_POST['display_name'] ?? '',
|
||||
'email' => $_POST['email'] ?? '',
|
||||
'permissions' => $permissions,
|
||||
'orgs' => $organizations,
|
||||
'role' => $_POST['role'] ?? '',
|
||||
'aulas' => $aulas,
|
||||
]);
|
||||
header("Location: ?action=edit&user=" . urlencode($username) . "&_result=" . urlencode("Cambios guardados correctamente a las " . date("H:i:s") . " (hora servidor)."));
|
||||
exit;
|
||||
break;
|
||||
}
|
||||
|
||||
switch ($_GET['action'] ?? '') {
|
||||
case 'add':
|
||||
require_once "_incl/pre-body.php";
|
||||
case 'add':
|
||||
require_once "_incl/pre-body.php";
|
||||
render_users_mobile_styles();
|
||||
$all_organizations = db_get_organizations();
|
||||
?>
|
||||
<form method="post" action="?form=save_edit">
|
||||
<form method="post" action="?form=save_edit" class="users-mobile-stack">
|
||||
<div class="card pad">
|
||||
<div>
|
||||
<h1 class="card-title">Agregar Nuevo Usuario</h1>
|
||||
@@ -104,70 +131,52 @@ switch ($_GET['action'] ?? '') {
|
||||
<b>Permisos:</b>
|
||||
<div class="accordion mt-3" id="permissionsAccordion">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="headingSysadmin">
|
||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseSysadmin" aria-expanded="true" aria-controls="collapseSysadmin">
|
||||
Administración del sistema
|
||||
</button>
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseSysadmin">Administración del sistema</button>
|
||||
</h2>
|
||||
<div id="collapseSysadmin" class="accordion-collapse collapse show" aria-labelledby="headingSysadmin" data-bs-parent="#permissionsAccordion">
|
||||
<div id="collapseSysadmin" class="accordion-collapse collapse show" data-bs-parent="#permissionsAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="permissions[]" value="sysadmin:access" id="sysadmin-access">
|
||||
<label class="form-check-label" for="sysadmin-access">
|
||||
Acceso
|
||||
</label>
|
||||
<label class="form-check-label" for="sysadmin-access">Acceso</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="headingEntreaulas">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseEntreaulas" aria-expanded="false" aria-controls="collapseEntreaulas">
|
||||
EntreAulas
|
||||
</button>
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseAulatek">AulaTek</button>
|
||||
</h2>
|
||||
<div id="collapseEntreaulas" class="accordion-collapse collapse" aria-labelledby="headingEntreaulas" data-bs-parent="#permissionsAccordion">
|
||||
<div id="collapseAulatek" class="accordion-collapse collapse" data-bs-parent="#permissionsAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="permissions[]" value="entreaulas:access" id="entreaulas-access">
|
||||
<label class="form-check-label" for="entreaulas-access">
|
||||
Acceso
|
||||
</label>
|
||||
<input class="form-check-input" type="checkbox" name="permissions[]" value="aulatek:access" id="aulatek-access">
|
||||
<label class="form-check-label" for="aulatek-access">Acceso</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="permissions[]" value="entreaulas:docente" id="entreaulas-docente">
|
||||
<label class="form-check-label" for="entreaulas-docente">
|
||||
Docente
|
||||
</label>
|
||||
<input class="form-check-input" type="checkbox" name="permissions[]" value="aulatek:docente" id="aulatek-docente">
|
||||
<label class="form-check-label" for="aulatek-docente">Docente</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="permissions[]" value="entreaulas:proyectos:delete" id="entreaulas-proyectos-delete">
|
||||
<label class="form-check-label" for="entreaulas-proyectos-delete">
|
||||
Eliminar Proyectos
|
||||
</label>
|
||||
<input class="form-check-input" type="checkbox" name="permissions[]" value="aulatek:proyectos:delete" id="aulatek-proyectos-delete">
|
||||
<label class="form-check-label" for="aulatek-proyectos-delete">Eliminar Proyectos</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="headingSupercafe">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseSupercafe" aria-expanded="false" aria-controls="collapseSupercafe">
|
||||
SuperCafe
|
||||
</button>
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseSupercafe">SuperCafe</button>
|
||||
</h2>
|
||||
<div id="collapseSupercafe" class="accordion-collapse collapse" aria-labelledby="headingSupercafe" data-bs-parent="#permissionsAccordion">
|
||||
<div id="collapseSupercafe" class="accordion-collapse collapse" data-bs-parent="#permissionsAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="permissions[]" value="supercafe:access" id="supercafe-access">
|
||||
<label class="form-check-label" for="supercafe-access">
|
||||
Acceso
|
||||
</label>
|
||||
<label class="form-check-label" for="supercafe-access">Acceso</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="permissions[]" value="supercafe:edit" id="supercafe-edit">
|
||||
<label class="form-check-label" for="supercafe-edit">
|
||||
Editar comandas
|
||||
</label>
|
||||
<label class="form-check-label" for="supercafe-edit">Editar comandas</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -178,22 +187,21 @@ switch ($_GET['action'] ?? '') {
|
||||
</div>
|
||||
<div class="card pad">
|
||||
<div>
|
||||
<h2>EntreAulas: Configuración</h2>
|
||||
<h2>AulaTek: Configuración</h2>
|
||||
<div class="mb-3">
|
||||
<label for="centro" class="form-label">Centro asociado:</label>
|
||||
<select id="centro" name="centro" class="form-select">
|
||||
<option value="">-- Selecciona un centro --</option>
|
||||
<?php
|
||||
$centros_folders_add = glob("/DATA/entreaulas/Centros/*", GLOB_ONLYDIR) ?: [];
|
||||
foreach ($centros_folders_add as $centro_folder) {
|
||||
$centro_id_opt = basename($centro_folder);
|
||||
echo '<option value="' . htmlspecialchars($centro_id_opt) . '">' . htmlspecialchars($centro_id_opt) . '</option>';
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<label class="form-label">Tenant asociado:</label>
|
||||
<div class="tenant-list">
|
||||
<?php foreach ($all_organizations as $orgRow): $cid = $orgRow['org_id']; ?>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="organization[]" value="<?= htmlspecialchars($cid) ?>" id="tenant-<?= htmlspecialchars($cid) ?>">
|
||||
<label class="form-check-label" for="tenant-<?= htmlspecialchars($cid) ?>"><?= htmlspecialchars($cid) ?></label>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<small class="text-muted">Marca uno o varios tenants.</small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="role" class="form-label">Rol en EntreAulas:</label>
|
||||
<label for="role" class="form-label">Rol en AulaTek:</label>
|
||||
<select id="role" name="role" class="form-select">
|
||||
<option value="">-- Selecciona un rol --</option>
|
||||
<option value="teacher">Profesor</option>
|
||||
@@ -205,219 +213,235 @@ switch ($_GET['action'] ?? '') {
|
||||
</div>
|
||||
</form>
|
||||
<?php
|
||||
require_once "_incl/post-body.php";
|
||||
break;
|
||||
case 'edit':
|
||||
require_once "_incl/pre-body.php";
|
||||
$username = safe_username($_GET['user'] ?? '');
|
||||
if (empty($username)) {
|
||||
die("Nombre de usuario inválido.");
|
||||
}
|
||||
$user_file = get_user_file_path($username);
|
||||
if (!file_exists($user_file) || !is_readable($user_file)) {
|
||||
die("Usuario no encontrado o datos no disponibles.");
|
||||
}
|
||||
$jsonContent = file_get_contents($user_file);
|
||||
if ($jsonContent === false) {
|
||||
die("Error al leer los datos del usuario.");
|
||||
}
|
||||
$userdata = json_decode($jsonContent, true);
|
||||
if (!is_array($userdata) || json_last_error() !== JSON_ERROR_NONE) {
|
||||
die("Datos de usuario corruptos o con formato inválido.");
|
||||
}
|
||||
require_once "_incl/post-body.php";
|
||||
break;
|
||||
|
||||
case 'edit':
|
||||
require_once "_incl/pre-body.php";
|
||||
render_users_mobile_styles();
|
||||
$username = safe_username($_GET['user'] ?? '');
|
||||
if (empty($username)) {
|
||||
die("Nombre de usuario inválido.");
|
||||
}
|
||||
$row = db_get_user($username);
|
||||
if (!$row) {
|
||||
die("Usuario no encontrado.");
|
||||
}
|
||||
$userdata = db_build_auth_data($row);
|
||||
$all_organizations = db_get_organizations();
|
||||
$user_organizations = $userdata['orgs'] ?? [];
|
||||
if (!is_array($user_organizations)) {
|
||||
$user_organizations = [];
|
||||
}
|
||||
$user_organizations = array_values(array_unique(array_filter(array_map('safe_organization_id', $user_organizations))));
|
||||
if (empty($user_organizations)) {
|
||||
$legacy_organization = safe_organization_id($userdata['orgs'] ?? '');
|
||||
if ($legacy_organization !== '') {
|
||||
$user_organizations = [$legacy_organization];
|
||||
}
|
||||
}
|
||||
|
||||
$aularios_by_organization = [];
|
||||
foreach ($user_organizations as $org_id) {
|
||||
$aularios_by_organization[$org_id] = db_get_aularios($org_id);
|
||||
}
|
||||
$assigned_aulas = $userdata['aulatek']['aulas'] ?? ($userdata['entreaulas']['aulas'] ?? []);
|
||||
?>
|
||||
<form method="post" action="?form=save_edit">
|
||||
<form method="post" action="?form=save_edit" class="users-mobile-stack">
|
||||
<div class="card pad">
|
||||
<div>
|
||||
<h1>Editar Usuario: <?php echo htmlspecialchars($username); ?></h1>
|
||||
<h1>Editar Usuario: <?= htmlspecialchars($username) ?></h1>
|
||||
<div class="mb-3">
|
||||
<label for="display_name" class="form-label">Nombre para mostrar:</label>
|
||||
<input type="text" id="display_name" name="display_name" value="<?php echo htmlspecialchars($userdata['display_name'] ?? ''); ?>" class="form-control" required>
|
||||
<input type="text" id="display_name" name="display_name" value="<?= htmlspecialchars($userdata['display_name'] ?? '') ?>" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">Correo electrónico:</label>
|
||||
<input type="email" id="email" name="email" value="<?php echo htmlspecialchars($userdata['email'] ?? ''); ?>" class="form-control" required>
|
||||
<input type="email" id="email" name="email" value="<?= htmlspecialchars($userdata['email'] ?? '') ?>" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Organizaciones asociadas:</label>
|
||||
<div class="organization-list">
|
||||
<?php foreach ($all_organizations as $orgRow): $org_id = $orgRow['org_id']; $org_name = $orgRow['org_name']; ?>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="organization[]" value="<?= htmlspecialchars($org_id) ?>" id="organization-<?= htmlspecialchars($org_id) ?>" <?= in_array($org_id, $user_organizations, true) ? 'checked' : '' ?>>
|
||||
<label class="form-check-label" for="organization-<?= htmlspecialchars($org_id) ?>"><?= htmlspecialchars($org_name) ?></label>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<small class="text-muted">Marca una o varias organizaciones.</small>
|
||||
</div>
|
||||
<b>Permisos:</b>
|
||||
<div class="accordion mt-3" id="permissionsAccordion">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="headingSysadmin">
|
||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseSysadmin" aria-expanded="true" aria-controls="collapseSysadmin">
|
||||
Administración del sistema
|
||||
</button>
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseSysadmin">Administración del sistema</button>
|
||||
</h2>
|
||||
<div id="collapseSysadmin" class="accordion-collapse collapse show" aria-labelledby="headingSysadmin" data-bs-parent="#permissionsAccordion">
|
||||
<div id="collapseSysadmin" class="accordion-collapse collapse show" data-bs-parent="#permissionsAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="permissions[]" value="sysadmin:access" id="sysadmin-access" <?php if (in_array('sysadmin:access', $userdata['permissions'] ?? [])) echo 'checked'; ?>>
|
||||
<label class="form-check-label" for="sysadmin-access">
|
||||
Acceso
|
||||
</label>
|
||||
<input class="form-check-input" type="checkbox" name="permissions[]" value="sysadmin:access" id="sysadmin-access" <?= in_array('sysadmin:access', $userdata['permissions'] ?? []) ? 'checked' : '' ?>>
|
||||
<label class="form-check-label" for="sysadmin-access">Acceso</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="headingEntreaulas">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseEntreaulas" aria-expanded="false" aria-controls="collapseEntreaulas">
|
||||
EntreAulas
|
||||
</button>
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseAulatek">AulaTek</button>
|
||||
</h2>
|
||||
<div id="collapseEntreaulas" class="accordion-collapse collapse" aria-labelledby="headingEntreaulas" data-bs-parent="#permissionsAccordion">
|
||||
<div id="collapseAulatek" class="accordion-collapse collapse" data-bs-parent="#permissionsAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="permissions[]" value="entreaulas:access" id="entreaulas-access" <?php if (in_array('entreaulas:access', $userdata['permissions'] ?? [])) echo 'checked'; ?>>
|
||||
<label class="form-check-label" for="entreaulas-access">
|
||||
Acceso
|
||||
</label>
|
||||
<input class="form-check-input" type="checkbox" name="permissions[]" value="aulatek:access" id="aulatek-access" <?= in_array('aulatek:access', $userdata['permissions'] ?? []) ? 'checked' : '' ?>>
|
||||
<label class="form-check-label" for="aulatek-access">Acceso</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="permissions[]" value="entreaulas:docente" id="entreaulas-docente" <?php if (in_array('entreaulas:docente', $userdata['permissions'] ?? [])) echo 'checked'; ?>>
|
||||
<label class="form-check-label" for="entreaulas-docente">
|
||||
Docente
|
||||
</label>
|
||||
<input class="form-check-input" type="checkbox" name="permissions[]" value="aulatek:docente" id="aulatek-docente" <?= in_array('aulatek:docente', $userdata['permissions'] ?? []) ? 'checked' : '' ?>>
|
||||
<label class="form-check-label" for="aulatek-docente">Docente</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="permissions[]" value="entreaulas:proyectos:delete" id="entreaulas-proyectos-delete" <?php if (in_array('entreaulas:proyectos:delete', $userdata['permissions'] ?? [])) echo 'checked'; ?>>
|
||||
<label class="form-check-label" for="entreaulas-proyectos-delete">
|
||||
Eliminar Proyectos
|
||||
</label>
|
||||
<input class="form-check-input" type="checkbox" name="permissions[]" value="aulatek:proyectos:delete" id="aulatek-proyectos-delete" <?= in_array('aulatek:proyectos:delete', $userdata['permissions'] ?? []) ? 'checked' : '' ?>>
|
||||
<label class="form-check-label" for="aulatek-proyectos-delete">Eliminar Proyectos</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="headingSupercafe">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseSupercafe" aria-expanded="false" aria-controls="collapseSupercafe">
|
||||
SuperCafe
|
||||
</button>
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseSupercafe">SuperCafe</button>
|
||||
</h2>
|
||||
<div id="collapseSupercafe" class="accordion-collapse collapse" aria-labelledby="headingSupercafe" data-bs-parent="#permissionsAccordion">
|
||||
<div id="collapseSupercafe" class="accordion-collapse collapse" data-bs-parent="#permissionsAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="permissions[]" value="supercafe:access" id="supercafe-access" <?php if (in_array('supercafe:access', $userdata['permissions'] ?? [])) echo 'checked'; ?>>
|
||||
<label class="form-check-label" for="supercafe-access">
|
||||
Acceso
|
||||
</label>
|
||||
<input class="form-check-input" type="checkbox" name="permissions[]" value="supercafe:access" id="supercafe-access" <?= in_array('supercafe:access', $userdata['permissions'] ?? []) ? 'checked' : '' ?>>
|
||||
<label class="form-check-label" for="supercafe-access">Acceso</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="permissions[]" value="supercafe:edit" id="supercafe-edit" <?php if (in_array('supercafe:edit', $userdata['permissions'] ?? [])) echo 'checked'; ?>>
|
||||
<label class="form-check-label" for="supercafe-edit">
|
||||
Editar comandas
|
||||
</label>
|
||||
<input class="form-check-input" type="checkbox" name="permissions[]" value="supercafe:edit" id="supercafe-edit" <?= in_array('supercafe:edit', $userdata['permissions'] ?? []) ? 'checked' : '' ?>>
|
||||
<label class="form-check-label" for="supercafe-edit">Editar comandas</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="username" value="<?php echo htmlspecialchars($username); ?>">
|
||||
<input type="hidden" name="username" value="<?= htmlspecialchars($username) ?>">
|
||||
<button type="submit" class="btn btn-primary mt-3">Guardar Cambios</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card pad">
|
||||
<div>
|
||||
<h2>EntreAulas: Configuración</h2>
|
||||
<h2>AulaTek: Configuración</h2>
|
||||
<div class="mb-3">
|
||||
<label for="centro" class="form-label">Centro asociado:</label>
|
||||
<select id="centro" name="centro" class="form-select" required>
|
||||
<option value="" <?php if (empty($userdata["entreaulas"]['centro'] ?? '')) echo 'selected'; ?>>-- Selecciona un centro --</option>
|
||||
<?php
|
||||
$centros_folders = glob("/DATA/entreaulas/Centros/*", GLOB_ONLYDIR);
|
||||
foreach ($centros_folders as $centro_folder) {
|
||||
$centro_id = basename($centro_folder);
|
||||
echo '<option value="' . htmlspecialchars($centro_id) . '"';
|
||||
if (($userdata["entreaulas"]['centro'] ?? '') === $centro_id) {
|
||||
echo ' selected';
|
||||
}
|
||||
echo '>' . htmlspecialchars($centro_id) . '</option>';
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="role" class="form-label">Rol en EntreAulas:</label>
|
||||
<label for="role" class="form-label">Rol en AulaTek:</label>
|
||||
<select id="role" name="role" class="form-select" required>
|
||||
<option value="" <?php if (empty($userdata["entreaulas"]['role'] ?? '')) echo 'selected'; ?>>-- Selecciona un rol --</option>
|
||||
<option value="teacher" <?php if (($userdata["entreaulas"]['role'] ?? '') === 'teacher') echo 'selected'; ?>>Profesor</option>
|
||||
<option value="student" <?php if (($userdata["entreaulas"]['role'] ?? '') === 'student') echo 'selected'; ?>>Estudiante</option>
|
||||
<option value="" <?= empty($userdata['aulatek']['role'] ?? '') ? 'selected' : '' ?>>-- Selecciona un rol --</option>
|
||||
<option value="teacher" <?= ($userdata['aulatek']['role'] ?? '') === 'teacher' ? 'selected' : '' ?>>Profesor</option>
|
||||
<option value="student" <?= ($userdata['aulatek']['role'] ?? '') === 'student' ? 'selected' : '' ?>>Estudiante</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Aulas asignadas: <small>(Guarda primero para actualizar la lista)</small></label><br>
|
||||
<?php
|
||||
$user_centro = safe_centro_id($userdata["entreaulas"]['centro'] ?? '');
|
||||
$aulas_filelist = $user_centro !== '' ? (glob("/DATA/entreaulas/Centros/" . $user_centro . "/Aularios/*.json") ?: []) : [];
|
||||
foreach ($aulas_filelist as $aula_file) {
|
||||
$aula_data = json_decode(file_get_contents($aula_file), true);
|
||||
$aula_id = safe_aulario_id(basename($aula_file, ".json"));
|
||||
$is_assigned = in_array($aula_id, $userdata["entreaulas"]['aulas'] ?? []);
|
||||
echo '<div class="form-check form-check-inline">';
|
||||
echo '<input class="form-check-input" type="checkbox" name="aulas[]" value="' . htmlspecialchars($aula_id) . '" id="aula-' . htmlspecialchars($aula_id) . '" ' . ($is_assigned ? 'checked' : '') . '>';
|
||||
echo '<label class="form-check-label" for="aula-' . htmlspecialchars($aula_id) . '">' . htmlspecialchars($aula_data['name'] ?? $aula_id) . '</label>';
|
||||
echo '</div>';
|
||||
}
|
||||
?>
|
||||
<div class="aulas-list">
|
||||
<?php if (empty($aularios_by_organization)): ?>
|
||||
<small class="text-muted">No hay organizaciones asociadas para mostrar aulas.</small>
|
||||
<?php endif; ?>
|
||||
<?php foreach ($aularios_by_organization as $org_id => $org_aularios): ?>
|
||||
<div style="margin-bottom: 0.75rem; padding-bottom: 0.5rem; border-bottom: 1px solid #e9ecef;">
|
||||
<div style="font-weight: 600; margin-bottom: 0.4rem;"><?= htmlspecialchars($org_id) ?></div>
|
||||
<?php if (empty($org_aularios)): ?>
|
||||
<small class="text-muted">Sin aulas en esta organización.</small>
|
||||
<?php else: ?>
|
||||
<?php foreach ($org_aularios as $aula_id => $aula_data): ?>
|
||||
<?php $checkbox_id = 'aula-' . md5($org_id . '-' . $aula_id); ?>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="checkbox" name="aulas[]"
|
||||
value="<?= htmlspecialchars($aula_id) ?>"
|
||||
id="<?= htmlspecialchars($checkbox_id) ?>"
|
||||
<?= in_array($aula_id, $assigned_aulas, true) ? 'checked' : '' ?>>
|
||||
<label class="form-check-label" for="<?= htmlspecialchars($checkbox_id) ?>">
|
||||
<?= htmlspecialchars($aula_data['name'] ?? $aula_id) ?>
|
||||
</label>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card pad">
|
||||
<div>
|
||||
<h2>Cambiar contraseña</h2>
|
||||
<p>Para cambiar la contraseña de este usuario, utiliza la herramienta de restablecimiento de contraseñas disponible en el siguiente enlace:</p>
|
||||
<a href="/sysadmin/reset_password.php?user=<?php echo urlencode($username); ?>" class="btn btn-secondary">Restablecer Contraseña</a>
|
||||
<a href="/sysadmin/reset_password.php?user=<?= urlencode($username) ?>" class="btn btn-secondary">Restablecer Contraseña</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<?php
|
||||
require_once "_incl/post-body.php";
|
||||
break;
|
||||
case "index":
|
||||
default:
|
||||
require_once "_incl/pre-body.php";
|
||||
?>
|
||||
<div class="card pad">
|
||||
<div>
|
||||
<h1>Gestión de Usuarios</h1>
|
||||
<p>Desde esta sección puedes gestionar los usuarios del sistema. Puedes agregar, editar o eliminar usuarios según sea necesario.</p>
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Usuario</th>
|
||||
<th>Nombre</th>
|
||||
<th>Correo</th>
|
||||
<th>
|
||||
<a href="?action=add" class="btn btn-success">+ Nuevo</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
$users_filelist = glob(USERS_DIR . '*.json') ?: [];
|
||||
foreach ($users_filelist as $user_file) {
|
||||
$userdata = json_decode(file_get_contents($user_file), true);
|
||||
if (!is_array($userdata)) {
|
||||
error_log("users.php: corrupted or unreadable user file: $user_file");
|
||||
$userdata = [];
|
||||
}
|
||||
// Username is the filename without path and extension
|
||||
$username = basename($user_file, ".json");
|
||||
echo "<tr>";
|
||||
echo "<td>" . htmlspecialchars($username) . "</td>";
|
||||
echo "<td>" . htmlspecialchars($userdata['display_name'] ?? 'N/A') . "</td>";
|
||||
echo "<td>" . htmlspecialchars($userdata['email'] ?? 'N/A') . "</td>";
|
||||
echo "<td>";
|
||||
echo '<a href="?action=edit&user=' . urlencode($username) . '" class="btn btn-primary">Editar</a> ';
|
||||
echo '<a href="?action=delete&user=' . urlencode($username) . '" class="btn btn-danger">Eliminar</a>';
|
||||
echo "</td>";
|
||||
echo "</tr>";
|
||||
}
|
||||
?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<form method="post" action="?form=delete_user" class="users-mobile-stack mt-2" onsubmit="return confirm('¿Seguro que quieres eliminar la cuenta de <?= htmlspecialchars($username, ENT_QUOTES) ?>? Esta acción no se puede deshacer.');">
|
||||
<div class="card pad border-danger">
|
||||
<div>
|
||||
<h2 class="text-danger">Zona de peligro</h2>
|
||||
<p>Eliminar la cuenta borrará permanentemente al usuario y todas sus sesiones activas.</p>
|
||||
<input type="hidden" name="username" value="<?= htmlspecialchars($username) ?>">
|
||||
<button type="submit" class="btn btn-danger">Eliminar cuenta</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<?php
|
||||
require_once "_incl/post-body.php";
|
||||
break;
|
||||
}
|
||||
require_once "_incl/post-body.php";
|
||||
break;
|
||||
|
||||
case 'index':
|
||||
default:
|
||||
require_once "_incl/pre-body.php";
|
||||
render_users_mobile_styles();
|
||||
$all_users = db_get_all_users();
|
||||
?>
|
||||
<div class="card pad users-mobile-stack">
|
||||
<div>
|
||||
<div class="d-flex flex-column flex-md-row align-items-md-center justify-content-md-between gap-2 mb-2">
|
||||
<h1 class="mb-0">Gestión de Usuarios</h1>
|
||||
<a href="?action=add" class="btn btn-success">+ Nuevo</a>
|
||||
</div>
|
||||
<p>Desde esta sección puedes gestionar los usuarios del sistema.</p>
|
||||
<div class="d-none d-md-block table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Usuario</th>
|
||||
<th>Nombre</th>
|
||||
<th>Correo</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($all_users as $u): ?>
|
||||
<tr>
|
||||
<td><?= htmlspecialchars($u['username']) ?></td>
|
||||
<td><?= htmlspecialchars($u['display_name'] ?: 'N/A') ?></td>
|
||||
<td><?= htmlspecialchars($u['email'] ?: 'N/A') ?></td>
|
||||
<td>
|
||||
<a href="?action=edit&user=<?= urlencode($u['username']) ?>" class="btn btn-primary btn-sm">Editar</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-md-none">
|
||||
<?php foreach ($all_users as $u): ?>
|
||||
<div class="border rounded p-3 mb-2 bg-white">
|
||||
<div><strong><?= htmlspecialchars($u['display_name'] ?: 'N/A') ?></strong></div>
|
||||
<div class="text-muted small"><?= htmlspecialchars($u['username']) ?></div>
|
||||
<div class="small"><?= htmlspecialchars($u['email'] ?: 'N/A') ?></div>
|
||||
<a href="?action=edit&user=<?= urlencode($u['username']) ?>" class="btn btn-primary btn-sm mt-2">Editar</a>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
require_once "_incl/post-body.php";
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user