";
$clean = strip_tags($html, $allowed);
// Remove event handlers and style attributes
$clean = preg_replace('/\son\w+\s*=\s*("[^"]*"|\'[^\']*\'|[^\s>]+)/i', '', $clean);
$clean = preg_replace('/\sstyle\s*=\s*("[^"]*"|\'[^\']*\'|[^\s>]+)/i', '', $clean);
// Sanitize href/src attributes (no javascript: or data:)
$clean = preg_replace_callback('/\s(href|src)\s*=\s*("[^"]*"|\'[^\']*\'|[^\s>]+)/i', function ($matches) {
$attr = strtolower($matches[1]);
$value = trim($matches[2], "\"' ");
$lower = strtolower($value);
if (strpos($lower, 'javascript:') === 0 || strpos($lower, 'data:') === 0) {
return '';
}
return " $attr=\"" . htmlspecialchars($value, ENT_QUOTES) . "\"";
}, $clean);
return $clean;
}
function build_videocall_url($platform, $room, $custom_url, &$error)
{
$platform = strtolower(trim((string)$platform));
if ($platform === "jitsi") {
$room = trim((string)$room);
if ($room === "") {
$error = "El nombre de la sala es obligatorio para la videollamada.";
return ["", ""];
}
$safe_room = preg_replace("/[^a-zA-Z0-9_-]/", "-", $room);
return ["https://meet.jit.si/" . $safe_room, $safe_room];
}
if ($platform === "google_meet") {
$room = trim((string)$room);
if ($room === "") {
$error = "El código de Google Meet es obligatorio para la videollamada.";
return ["", ""];
}
$safe_room = preg_replace("/[^a-zA-Z0-9-]/", "", $room);
return ["https://meet.google.com/" . $safe_room, $safe_room];
}
$custom_url = trim((string)$custom_url);
if ($custom_url === "" || filter_var($custom_url, FILTER_VALIDATE_URL) === false) {
$error = "La URL de videollamada no es válida.";
return ["", ""];
}
return [$custom_url, ""];
}
function generate_id($name)
{
return strtolower(preg_replace("/[^a-zA-Z0-9]/", "_", $name)) . "_" . substr(md5(uniqid()), 0, 8);
}
function project_meta_path($project_dir)
{
return "$project_dir/_data_.eadat";
}
function find_project_path($projects_base, $project_id)
{
if (!is_dir($projects_base)) {
return null;
}
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($projects_base, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $fileinfo) {
if (!$fileinfo->isDir()) {
continue;
}
$meta_file = $fileinfo->getPathname() . "/_data_.eadat";
if (!file_exists($meta_file)) {
continue;
}
$data = json_decode(file_get_contents($meta_file), true);
if (($data["id"] ?? "") === $project_id) {
return $fileinfo->getPathname();
}
}
return null;
}
function delete_dir_recursive($dir)
{
if (!is_dir($dir)) {
return;
}
$items = array_diff(scandir($dir), [".", ".."]);
foreach ($items as $item) {
$path = "$dir/$item";
if (is_dir($path)) {
delete_dir_recursive($path);
} else {
unlink($path);
}
}
rmdir($dir);
}
function save_file_metadata($file_path, $data)
{
$meta_file = $file_path . ".eadat";
file_put_contents($meta_file, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
}
function parse_size_to_bytes($size)
{
$size = trim((string)$size);
if ($size === "") {
return 0;
}
$last = strtolower(substr($size, -1));
$num = (float)$size;
if (in_array($last, ["g", "m", "k"], true)) {
$num = (float)substr($size, 0, -1);
switch ($last) {
case "g":
$num *= 1024;
case "m":
$num *= 1024;
case "k":
$num *= 1024;
}
}
return (int)$num;
}
function format_bytes($bytes)
{
$bytes = (int)$bytes;
if ($bytes >= 1024 * 1024 * 1024) {
return round($bytes / (1024 * 1024 * 1024), 1) . "GB";
}
if ($bytes >= 1024 * 1024) {
return round($bytes / (1024 * 1024), 1) . "MB";
}
if ($bytes >= 1024) {
return round($bytes / 1024, 1) . "KB";
}
return $bytes . "B";
}
if (!defined('APP_MAX_UPLOAD_BYTES')) {
define('APP_MAX_UPLOAD_BYTES', 500 * 1024 * 1024);
}
$app_max_upload_bytes = APP_MAX_UPLOAD_BYTES;
$upload_limit = parse_size_to_bytes(ini_get("upload_max_filesize"));
$post_limit = parse_size_to_bytes(ini_get("post_max_size"));
$max_upload_bytes = $app_max_upload_bytes;
foreach ([$upload_limit, $post_limit] as $limit) {
if ($limit > 0 && $limit < $max_upload_bytes) {
$max_upload_bytes = $limit;
}
}
$max_upload_label = format_bytes($max_upload_bytes);
function load_project($proyectos_dir, $project_id)
{
$project_dir = find_project_path($proyectos_dir, $project_id);
if (!$project_dir) {
return null;
}
$project_file = project_meta_path($project_dir);
if (!file_exists($project_file)) {
return null;
}
return json_decode(file_get_contents($project_file), true);
}
function save_project($proyectos_dir, $project_id, $data)
{
$project_dir = find_project_path($proyectos_dir, $project_id);
if (!$project_dir && isset($data["_project_dir"])) {
$candidate_dir = (string)$data["_project_dir"];
$proyectos_base_real = realpath($proyectos_dir);
$candidate_real = realpath($candidate_dir);
if ($proyectos_base_real !== false && $candidate_real !== false && (strpos($candidate_real, $proyectos_base_real . DIRECTORY_SEPARATOR) === 0 || $candidate_real === $proyectos_base_real)) {
$project_dir = $candidate_dir;
}
}
if (!$project_dir) {
return false;
}
$project_file = project_meta_path($project_dir);
if (isset($data["_project_dir"])) {
unset($data["_project_dir"]);
}
$result = file_put_contents($project_file, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
if ($result === false) {
error_log("Failed to save project file: $project_file");
}
return $result;
}
function get_project_breadcrumb($proyectos_dir, $project_id)
{
$breadcrumb = [];
$current_id = $project_id;
while ($current_id) {
$project = load_project($proyectos_dir, $current_id);
if (!$project) break;
array_unshift($breadcrumb, $project);
$current_id = $project["parent_id"] ?? null;
}
return $breadcrumb;
}
function list_projects($proyectos_dir, $parent_id = null, $owner_aulario = null)
{
$projects = [];
if (!is_dir($proyectos_dir)) {
return $projects;
}
$base_dir = $proyectos_dir;
if ($parent_id !== null) {
$parent_dir = find_project_path($proyectos_dir, $parent_id);
if (!$parent_dir) {
return $projects;
}
$base_dir = $parent_dir;
}
$entries = array_diff(scandir($base_dir), [".", ".."]);
foreach ($entries as $entry) {
$entry_path = "$base_dir/$entry";
if (!is_dir($entry_path)) {
continue;
}
$meta_file = project_meta_path($entry_path);
if (!file_exists($meta_file)) {
continue;
}
$data = json_decode(file_get_contents($meta_file), true);
if ($data) {
if ($parent_id === null && $owner_aulario !== null) {
if (($data["owner_aulario"] ?? null) !== $owner_aulario) {
continue;
}
}
$projects[] = $data;
}
}
// Sort by creation date (newest first)
usort($projects, function ($a, $b) {
return ($b["created_at"] ?? 0) <=> ($a["created_at"] ?? 0);
});
return $projects;
}
/**
* Get linked projects from other aularios based on aulario configuration
*
* @param array $aulario The aulario configuration containing linked_projects array
* @param string $centro_id The centro ID for constructing file paths
* @return array Array of project data arrays with is_linked and source_aulario fields added
*/
function get_linked_projects($aulario, $centro_id)
{
$linked = [];
$linked_projects = $aulario["linked_projects"] ?? [];
foreach ($linked_projects as $link) {
$source_aulario = $link["source_aulario"] ?? "";
$project_id = $link["project_id"] ?? "";
if (empty($source_aulario) || empty($project_id)) {
continue;
}
$projects_base = "/DATA/entreaulas/Centros/$centro_id/Proyectos";
$project = load_project($projects_base, $project_id);
if ($project && ($project["parent_id"] ?? null) === null) {
// Mark as linked and add source info
$project["is_linked"] = true;
$project["source_aulario"] = $source_aulario;
$linked[] = $project;
}
}
return $linked;
}
// Handle actions
$message = "";
$error = "";
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$action = $_POST["action"] ?? "";
if ($action === "create_project") {
$name = trim($_POST["name"] ?? "");
$description = sanitize_html($_POST["description"] ?? "");
$parent_id = trim($_POST["parent_id"] ?? "");
if ($name !== "") {
// Determine level based on parent
$level = 1;
if ($parent_id !== "") {
$parent = load_project($proyectos_dir, $parent_id);
if ($parent) {
$level = ($parent["level"] ?? 1) + 1;
// Enforce max 6 levels
if ($level > 6) {
$error = "No se pueden crear más de 6 niveles de sub-proyectos.";
}
} else {
$error = "Proyecto padre no encontrado.";
}
}
if (empty($error)) {
$project_id = generate_id($name);
$parent_dir = $parent_id !== "" ? find_project_path($proyectos_dir, $parent_id) : null;
if ($parent_id !== "" && !$parent_dir) {
$error = "Proyecto padre no encontrado.";
}
}
if (empty($error)) {
$project_dir = $parent_id !== "" ? "$parent_dir/$project_id" : "$proyectos_dir/$project_id";
if (!is_dir($project_dir)) {
mkdir($project_dir, 0755, true);
}
$project_data = [
"id" => $project_id,
"name" => $name,
"description" => $description,
"created_at" => time(),
"updated_at" => time(),
"items" => [],
"subprojects" => [],
"parent_id" => $parent_id !== "" ? $parent_id : null,
"level" => $level,
"owner_aulario" => $aulario_id
];
$project_data["_project_dir"] = $project_dir;
save_project($proyectos_dir, $project_id, $project_data);
// Update parent's subprojects list
if ($parent_id !== "") {
$parent = load_project($proyectos_dir, $parent_id);
if ($parent) {
if (!isset($parent["subprojects"])) {
$parent["subprojects"] = [];
}
$parent["subprojects"][] = $project_id;
$parent["updated_at"] = time();
save_project($proyectos_dir, $parent_id, $parent);
}
}
header("Location: /entreaulas/proyectos.php?aulario=" . urlencode($aulario_id) . "&project=" . urlencode($project_id));
exit;
}
} else {
$error = "El nombre del proyecto es obligatorio.";
}
}
if ($action === "share_project") {
$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) {
$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 = db_get_aulario($centro_id, $target_aulario);
if ($target_config === null) {
$error = "Aulario de destino no encontrado.";
} else {
if (!isset($target_config["linked_projects"]) || !is_array($target_config["linked_projects"])) {
$target_config["linked_projects"] = [];
}
$existing_index = null;
foreach ($target_config["linked_projects"] as $idx => $link) {
if (($link["source_aulario"] ?? "") === $aulario_id && ($link["project_id"] ?? "") === $project_id) {
$existing_index = $idx;
break;
}
}
if ($existing_index !== null) {
array_splice($target_config["linked_projects"], $existing_index, 1);
$message = "Proyecto dejado de compartir.";
} else {
$target_config["linked_projects"][] = [
"source_aulario" => $aulario_id,
"project_id" => $project_id,
"permission" => "request_edit",
];
$message = "Proyecto compartido correctamente.";
}
// Save back: build extra JSON excluding standard fields
$extra_skip = ['name', 'icon'];
$extra = [];
foreach ($target_config as $k => $v) {
if (!in_array($k, $extra_skip, true)) {
$extra[$k] = $v;
}
}
db()->prepare(
"UPDATE aularios SET extra = ? WHERE centro_id = ? AND aulario_id = ?"
)->execute([json_encode($extra, JSON_UNESCAPED_UNICODE), $centro_id, $target_aulario]);
}
}
}
}
if ($action === "delete_project") {
if (in_array("entreaulas:proyectos:delete", $_SESSION["auth_data"]["permissions"] ?? []) === false) {
$error = "No tienes permisos para borrar proyectos.";
} else {
$project_id = safe_path_segment($_POST["project_id"] ?? "");
if ($project_id !== "") {
$project = load_project($proyectos_dir, $project_id);
$project_dir = find_project_path($proyectos_dir, $project_id);
// Remove from parent's subprojects list
if ($project && !empty($project["parent_id"])) {
$parent = load_project($proyectos_dir, $project["parent_id"]);
if ($parent && isset($parent["subprojects"])) {
$parent["subprojects"] = array_values(array_filter($parent["subprojects"], function ($id) use ($project_id) {
return $id !== $project_id;
}));
$parent["updated_at"] = time();
save_project($proyectos_dir, $project["parent_id"], $parent);
}
}
if ($project_dir) {
delete_dir_recursive($project_dir);
$message = "Proyecto eliminado correctamente.";
}
}
}
}
if ($action === "edit_project") {
$project_id = safe_path_segment($_POST["project_id"] ?? "");
$name = trim($_POST["name"] ?? "");
$description = sanitize_html($_POST["description"] ?? "");
if ($project_id !== "" && $name !== "") {
$project = load_project($proyectos_dir, $project_id);
if ($project) {
$project["name"] = $name;
$project["description"] = $description;
$project["updated_at"] = time();
save_project($proyectos_dir, $project_id, $project);
$message = "Proyecto actualizado correctamente.";
} else {
$error = "Proyecto no encontrado.";
}
} else {
$error = "El nombre del proyecto es obligatorio.";
}
}
if ($action === "add_item") {
$project_id = safe_path_segment($_POST["project_id"] ?? "");
$item_type = safe_path_segment($_POST["item_type"] ?? "link");
$item_name = trim($_POST["item_name"] ?? "");
$item_url = trim($_POST["item_url"] ?? "");
$item_content = sanitize_html($_POST["item_content"] ?? "");
$videocall_platform = safe_path_segment($_POST["videocall_platform"] ?? "jitsi");
$videocall_room = trim($_POST["videocall_room"] ?? "");
$videocall_url = trim($_POST["videocall_url"] ?? "");
$source_aulario_param = safe_path_segment($_POST["source_aulario"] ?? "");
// Determine which directory to use and permission level
$working_dir = $proyectos_dir;
$project_dir = null;
$needs_approval = false;
$source_aulario_id_for_save = "";
if (!empty($source_aulario_param)) {
// Validate the link
$linked_projects = $aulario["linked_projects"] ?? [];
foreach ($linked_projects as $link) {
if ((($link["source_aulario"] ?? "") === $source_aulario_param) &&
(($link["project_id"] ?? "") === $project_id)
) {
$permission = safe_path_segment($link["permission"] ?? "read_only");
if ($permission === "full_edit") {
$working_dir = $proyectos_dir;
} elseif ($permission === "request_edit") {
// Changes need approval - save as pending
$needs_approval = true;
$source_aulario_id_for_save = $source_aulario_param;
}
break;
}
}
}
if ($project_id !== "" && $item_name !== "") {
if ($needs_approval) {
$project_dir = find_project_path($proyectos_dir, $project_id);
if (!$project_dir) {
$error = "Proyecto no encontrado.";
}
}
if ($needs_approval && empty($error)) {
// Create pending change request
$pending_dir = "$project_dir/pending_changes";
if (!is_dir($pending_dir)) {
mkdir($pending_dir, 0755, true);
}
$change_id = uniqid("change_");
$change_data = [
"id" => $change_id,
"type" => "add_item",
"requested_by_aulario" => $aulario_id,
"requested_by_persona_name" => ($_SESSION["auth_data"]["display_name"] ?? "Desconocido"),
"requested_at" => time(),
"status" => "pending",
"item_type" => $item_type,
"item_name" => $item_name,
"item_url" => $item_url,
"item_content" => $item_content
];
if ($item_type === "videocall") {
$vc_error = "";
[$vc_url, $vc_room] = build_videocall_url($videocall_platform, $videocall_room, $videocall_url, $vc_error);
if ($vc_error !== "") {
$error = $vc_error;
} else {
$change_data["item_url"] = $vc_url;
$change_data["item_platform"] = $videocall_platform;
$change_data["item_room"] = $vc_room;
}
}
// Handle file upload for pending changes
if (($item_type === "file" || $item_type === "pdf_secure") && isset($_FILES["item_file"]) && $_FILES["item_file"]["error"] === UPLOAD_ERR_OK) {
if ($_FILES["item_file"]["size"] > $max_upload_bytes) {
$error = "El archivo es demasiado grande. Tamaño máximo: $max_upload_label.";
}
$ext = strtolower(pathinfo($_FILES["item_file"]["name"], PATHINFO_EXTENSION));
$allowed_extensions = $item_type === "pdf_secure" ? ["pdf"] : ["pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "jpg", "jpeg", "png", "gif", "webp", "txt", "zip", "mp4", "mp3"];
if (empty($error) && in_array($ext, $allowed_extensions, true)) {
$safe_name = safe_filename($_FILES["item_file"]["name"]);
$temp_file_path = "$pending_dir/{$change_id}_$safe_name";
if (move_uploaded_file($_FILES["item_file"]["tmp_name"], $temp_file_path)) {
$change_data["pending_filename"] = basename($temp_file_path);
$change_data["original_filename"] = $_FILES["item_file"]["name"];
}
}
}
$change_file = "$pending_dir/$change_id.json";
file_put_contents($change_file, json_encode($change_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
$message = "Solicitud de cambio enviada. El aulario origen debe aprobarla.";
$redirect_params = "aulario=" . urlencode($aulario_id) . "&project=" . urlencode($project_id);
if (!empty($source_aulario_param)) {
$redirect_params .= "&source=" . urlencode($source_aulario_param);
}
// Don't exit yet, let the view render with the message
} else {
// Direct edit (full_edit permission or local project)
$project_dir = find_project_path($working_dir, $project_id);
if (!$project_dir) {
$error = "Proyecto no encontrado.";
}
$project = load_project($working_dir, $project_id);
if ($project) {
$item_id = generate_id($item_name);
$item = [
"id" => $item_id,
"name" => $item_name,
"type" => $item_type,
"created_at" => time()
];
$can_add_item = true;
if ($item_type === "link" && $item_url !== "") {
$item["url"] = $item_url;
} elseif ($item_type === "notepad") {
$item["content"] = $item_content;
} elseif ($item_type === "videocall") {
$vc_error = "";
[$vc_url, $vc_room] = build_videocall_url($videocall_platform, $videocall_room, $videocall_url, $vc_error);
if ($vc_error !== "") {
$error = $vc_error;
$can_add_item = false;
} else {
$item["url"] = $vc_url;
$item["platform"] = $videocall_platform;
$item["room"] = $vc_room;
}
} elseif (($item_type === "file" || $item_type === "pdf_secure") && isset($_FILES["item_file"]) && $_FILES["item_file"]["error"] === UPLOAD_ERR_OK) {
// Handle file upload with validation
if (!is_dir($project_dir)) {
mkdir($project_dir, 0755, true);
}
// Validate file size (max 500MB as configured in PHP)
if ($_FILES["item_file"]["size"] > $max_upload_bytes) {
$error = "El archivo es demasiado grande. Tamaño máximo: $max_upload_label.";
$can_add_item = false;
}
// Validate file type
if ($can_add_item) {
$original_name = $_FILES["item_file"]["name"];
$ext = strtolower(pathinfo($original_name, PATHINFO_EXTENSION));
$allowed_extensions = $item_type === "pdf_secure" ? ["pdf"] : ["pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "jpg", "jpeg", "png", "gif", "webp", "txt", "zip", "mp4", "mp3"];
if (!in_array($ext, $allowed_extensions, true)) {
$error = $item_type === "pdf_secure" ? "El PDF seguro solo permite archivos PDF." : "Tipo de archivo no permitido. Extensiones permitidas: " . implode(", ", $allowed_extensions);
$can_add_item = false;
}
}
if ($can_add_item) {
$safe_name = safe_filename($original_name);
$target_path = "$project_dir/$safe_name";
// Make filename unique if exists
$counter = 1;
$basename = pathinfo($safe_name, PATHINFO_FILENAME);
while (file_exists($target_path)) {
$safe_name = safe_filename($basename . "_" . $counter . "." . $ext);
$target_path = "$project_dir/$safe_name";
$counter++;
}
if (move_uploaded_file($_FILES["item_file"]["tmp_name"], $target_path)) {
$item["filename"] = $safe_name;
$item["original_name"] = $original_name;
} else {
$error = "No se pudo subir el archivo.";
$can_add_item = false;
}
}
}
if ($can_add_item) {
if (!isset($project["items"])) {
$project["items"] = [];
}
$project["items"][] = $item;
$project["updated_at"] = time();
if (in_array($item_type, ["file", "pdf_secure"], true) && isset($item["filename"])) {
$file_meta = [
"id" => $item_id,
"name" => $item_name,
"type" => $item_type,
"original_name" => $item["original_name"] ?? $item["filename"],
"created_at" => $item["created_at"]
];
save_file_metadata($target_path, $file_meta);
}
save_project($working_dir, $project_id, $project);
$redirect_params = "aulario=" . urlencode($aulario_id) . "&project=" . urlencode($project_id);
if (!empty($source_aulario_param)) {
$redirect_params .= "&source=" . urlencode($source_aulario_param);
}
header("Location: /entreaulas/proyectos.php?" . $redirect_params);
exit;
}
}
}
}
}
if ($action === "approve_change" || $action === "reject_change") {
$change_id = safe_filename($_POST["change_id"] ?? "");
$project_id = safe_path_segment($_POST["project_id"] ?? "");
if (!empty($change_id) && !empty($project_id)) {
$project_dir = find_project_path($proyectos_dir, $project_id);
if (!$project_dir) {
$error = "Proyecto no encontrado.";
}
}
if (!empty($change_id) && !empty($project_id) && empty($error)) {
$pending_dir = "$project_dir/pending_changes";
$change_file = safe_join_file($pending_dir, $change_id . ".json");
if ($change_file && file_exists($change_file)) {
$change_data = json_decode(file_get_contents($change_file), true);
if ($action === "approve_change") {
$project = load_project($proyectos_dir, $project_id);
if ($project) {
if (($change_data["type"] ?? "") === "delete_item") {
$item_id = $change_data["item_id"] ?? "";
if ($item_id !== "" && isset($project["items"])) {
$new_items = [];
foreach ($project["items"] as $item) {
if ($item["id"] !== $item_id) {
$new_items[] = $item;
} else {
if (in_array($item["type"], ["file", "pdf_secure"], true) && isset($item["filename"])) {
$file_path = safe_join_file($project_dir, $item["filename"]);
if ($file_path && file_exists($file_path)) {
unlink($file_path);
if (file_exists($file_path . ".eadat")) {
unlink($file_path . ".eadat");
}
}
}
}
}
$project["items"] = $new_items;
}
$project["updated_at"] = time();
save_project($proyectos_dir, $project_id, $project);
$message = "Cambio aprobado y aplicado.";
} elseif (($change_data["type"] ?? "") === "edit_item") {
$item_id = $change_data["item_id"] ?? "";
if ($item_id !== "" && isset($project["items"])) {
foreach ($project["items"] as &$item) {
if ($item["id"] !== $item_id) {
continue;
}
$item["name"] = $change_data["item_name"] ?? $item["name"];
if (($item["type"] ?? "") === "link") {
$item["url"] = $change_data["item_url"] ?? $item["url"] ?? "";
} elseif (($item["type"] ?? "") === "notepad") {
$item["content"] = sanitize_html($change_data["item_content"] ?? "");
} elseif (($item["type"] ?? "") === "videocall") {
$item["url"] = $change_data["item_url"] ?? "";
$item["platform"] = $change_data["item_platform"] ?? "jitsi";
$item["room"] = $change_data["item_room"] ?? "";
}
if (in_array(($item["type"] ?? ""), ["file", "pdf_secure"], true) && !empty($change_data["pending_filename"])) {
$pending_filename = safe_filename($change_data["pending_filename"]);
$pending_file = safe_join_file($pending_dir, $pending_filename);
$target_file = safe_join_file($project_dir, $pending_filename);
if ($pending_file && $target_file && file_exists($pending_file)) {
if (!is_dir($project_dir)) {
mkdir($project_dir, 0755, true);
}
rename($pending_file, $target_file);
if (!empty($item["filename"])) {
$old_path = safe_join_file($project_dir, $item["filename"]);
if ($old_path && file_exists($old_path)) {
unlink($old_path);
if (file_exists($old_path . ".eadat")) {
unlink($old_path . ".eadat");
}
}
}
$item["filename"] = $pending_filename;
$item["original_name"] = safe_filename($change_data["original_filename"] ?? $pending_filename);
$file_meta = [
"id" => $item_id,
"name" => $item["name"],
"type" => $item["type"],
"original_name" => $item["original_name"],
"created_at" => $item["created_at"] ?? time()
];
save_file_metadata($target_file, $file_meta);
}
}
if (in_array(($item["type"] ?? ""), ["file", "pdf_secure"], true) && !empty($item["filename"])) {
$file_path = safe_join_file($project_dir, $item["filename"]);
if ($file_path && file_exists($file_path . ".eadat")) {
$file_meta = json_decode(file_get_contents($file_path . ".eadat"), true) ?: [];
$file_meta["name"] = $item["name"];
save_file_metadata($file_path, $file_meta);
}
}
break;
}
unset($item);
}
$project["updated_at"] = time();
save_project($proyectos_dir, $project_id, $project);
$message = "Cambio aprobado y aplicado.";
} else {
// Apply add_item change
$item_id = generate_id($change_data["item_name"]);
$item = [
"id" => $item_id,
"name" => $change_data["item_name"],
"type" => $change_data["item_type"],
"created_at" => time()
];
if ($change_data["item_type"] === "link") {
$item["url"] = $change_data["item_url"];
} elseif ($change_data["item_type"] === "notepad") {
$item["content"] = sanitize_html($change_data["item_content"] ?? "");
} elseif ($change_data["item_type"] === "videocall") {
$item["url"] = $change_data["item_url"] ?? "";
$item["platform"] = $change_data["item_platform"] ?? "jitsi";
$item["room"] = $change_data["item_room"] ?? "";
} elseif (in_array($change_data["item_type"], ["file", "pdf_secure"], true) && !empty($change_data["pending_filename"])) {
// Move file from pending to project directory
$pending_filename = safe_filename($change_data["pending_filename"]);
$pending_file = safe_join_file($pending_dir, $pending_filename);
$target_file = safe_join_file($project_dir, $pending_filename);
if ($pending_file && $target_file && file_exists($pending_file)) {
if (!is_dir($project_dir)) {
mkdir($project_dir, 0755, true);
}
rename($pending_file, $target_file);
$item["filename"] = $pending_filename;
$item["original_name"] = safe_filename($change_data["original_filename"] ?? $pending_filename);
$file_meta = [
"id" => $item_id,
"name" => $change_data["item_name"],
"type" => $change_data["item_type"],
"original_name" => $item["original_name"],
"created_at" => $item["created_at"]
];
save_file_metadata($target_file, $file_meta);
}
}
if (!isset($project["items"])) {
$project["items"] = [];
}
$project["items"][] = $item;
$project["updated_at"] = time();
save_project($proyectos_dir, $project_id, $project);
$message = "Cambio aprobado y aplicado.";
}
}
} else {
// Reject - just delete pending file if exists
if (!empty($change_data["pending_filename"])) {
$pending_file = safe_join_file($pending_dir, safe_filename($change_data["pending_filename"]));
if ($pending_file && file_exists($pending_file)) {
unlink($pending_file);
}
}
$message = "Cambio rechazado.";
}
// Delete the change request file
if ($change_file && file_exists($change_file)) {
unlink($change_file);
}
}
}
}
if ($action === "delete_item") {
$project_id = safe_path_segment($_POST["project_id"] ?? "");
$item_id = safe_path_segment($_POST["item_id"] ?? "");
$source_aulario_param = safe_path_segment($_POST["source_aulario"] ?? "");
// Determine which directory to use based on whether this is a linked project
$working_dir = $proyectos_dir;
$permission = "full_edit";
$needs_approval = false;
if (!empty($source_aulario_param)) {
// Validate the link
$linked_projects = $aulario["linked_projects"] ?? [];
foreach ($linked_projects as $link) {
if (($link["source_aulario"] ?? "") === $source_aulario_param &&
($link["project_id"] ?? "") === $project_id &&
(($link["permission"] ?? "read_only") === "full_edit" || ($link["permission"] ?? "read_only") === "request_edit")
) {
$working_dir = $proyectos_dir;
$permission = $link["permission"] ?? "read_only";
if ($permission === "request_edit") {
$needs_approval = true;
}
break;
}
}
}
if ($project_id !== "" && $item_id !== "") {
if (!empty($source_aulario_param) && $permission === "read_only") {
$error = "No tienes permisos para eliminar elementos en este proyecto.";
}
}
if ($project_id !== "" && $item_id !== "" && empty($error)) {
$project_dir = find_project_path($working_dir, $project_id);
$project = load_project($working_dir, $project_id);
if ($project && isset($project["items"])) {
if ($needs_approval) {
$pending_dir = $project_dir ? "$project_dir/pending_changes" : null;
if ($pending_dir && !is_dir($pending_dir)) {
mkdir($pending_dir, 0755, true);
}
$target_item = null;
foreach ($project["items"] as $item) {
if ($item["id"] === $item_id) {
$target_item = $item;
break;
}
}
if ($target_item && $pending_dir) {
$change_id = uniqid("change_");
$change_data = [
"id" => $change_id,
"type" => "delete_item",
"requested_by_aulario" => $aulario_id,
"requested_by_persona_name" => ($_SESSION["auth_data"]["display_name"] ?? "Desconocido"),
"requested_at" => time(),
"status" => "pending",
"item_id" => $item_id,
"item_name" => $target_item["name"] ?? "",
"item_type" => $target_item["type"] ?? ""
];
$change_file = "$pending_dir/$change_id.json";
file_put_contents($change_file, json_encode($change_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
$message = "Solicitud de eliminación enviada. El aulario origen debe aprobarla.";
$redirect_params = "aulario=" . urlencode($aulario_id) . "&project=" . urlencode($project_id);
if (!empty($source_aulario_param)) {
$redirect_params .= "&source=" . urlencode($source_aulario_param);
}
header("Location: /entreaulas/proyectos.php?" . $redirect_params);
exit;
}
}
$new_items = [];
foreach ($project["items"] as $item) {
if ($item["id"] !== $item_id) {
$new_items[] = $item;
} else {
// Delete file if it's a file type
if (in_array($item["type"], ["file", "pdf_secure"], true) && isset($item["filename"])) {
$file_path = $project_dir ? safe_join_file($project_dir, $item["filename"]) : null;
if ($file_path && file_exists($file_path)) {
unlink($file_path);
if (file_exists($file_path . ".eadat")) {
unlink($file_path . ".eadat");
}
}
}
}
}
$project["items"] = $new_items;
$project["updated_at"] = time();
save_project($working_dir, $project_id, $project);
$redirect_params = "aulario=" . urlencode($aulario_id) . "&project=" . urlencode($project_id);
if (!empty($source_aulario_param)) {
$redirect_params .= "&source=" . urlencode($source_aulario_param);
}
header("Location: /entreaulas/proyectos.php?" . $redirect_params);
exit;
}
}
}
if ($action === "edit_item") {
$project_id = safe_path_segment($_POST["project_id"] ?? "");
$item_id = safe_path_segment($_POST["item_id"] ?? "");
$item_name = trim($_POST["item_name"] ?? "");
$item_url = trim($_POST["item_url"] ?? "");
$item_content = sanitize_html($_POST["item_content"] ?? "");
$videocall_platform = safe_path_segment($_POST["edit_videocall_platform"] ?? "jitsi");
$videocall_room = trim($_POST["edit_videocall_room"] ?? "");
$videocall_url = trim($_POST["edit_videocall_url"] ?? "");
$source_aulario_param = safe_path_segment($_POST["source_aulario"] ?? "");
$working_dir = $proyectos_dir;
$permission = "full_edit";
$needs_approval = false;
if (!empty($source_aulario_param)) {
$linked_projects = $aulario["linked_projects"] ?? [];
foreach ($linked_projects as $link) {
if (($link["source_aulario"] ?? "") === $source_aulario_param &&
($link["project_id"] ?? "") === $project_id &&
(($link["permission"] ?? "read_only") === "full_edit" || ($link["permission"] ?? "read_only") === "request_edit")
) {
$working_dir = $proyectos_dir;
$permission = $link["permission"] ?? "read_only";
if ($permission === "request_edit") {
$needs_approval = true;
}
break;
}
}
}
if ($project_id !== "" && $item_id !== "" && $item_name !== "") {
$project_dir = find_project_path($working_dir, $project_id);
$project = load_project($working_dir, $project_id);
if ($project && isset($project["items"])) {
if ($needs_approval) {
$project_dir = find_project_path($proyectos_dir, $project_id);
if (!$project_dir) {
$error = "Proyecto no encontrado.";
}
}
if ($needs_approval && empty($error)) {
$pending_dir = "$project_dir/pending_changes";
if (!is_dir($pending_dir)) {
mkdir($pending_dir, 0755, true);
}
$target_item = null;
foreach ($project["items"] as $existing_item) {
if ($existing_item["id"] === $item_id) {
$target_item = $existing_item;
break;
}
}
if (!$target_item) {
$error = "Elemento no encontrado.";
} else {
$change_id = uniqid("change_");
$change_data = [
"id" => $change_id,
"type" => "edit_item",
"requested_by_aulario" => $aulario_id,
"requested_by_persona_name" => ($_SESSION["auth_data"]["display_name"] ?? "Desconocido"),
"requested_at" => time(),
"status" => "pending",
"item_id" => $item_id,
"item_name" => $item_name,
"item_type" => $target_item["type"] ?? "",
"item_url" => $item_url,
"item_content" => $item_content
];
if (($target_item["type"] ?? "") === "videocall") {
$vc_error = "";
[$vc_url, $vc_room] = build_videocall_url($videocall_platform, $videocall_room, $videocall_url, $vc_error);
if ($vc_error !== "") {
$error = $vc_error;
} else {
$change_data["item_url"] = $vc_url;
$change_data["item_platform"] = $videocall_platform;
$change_data["item_room"] = $vc_room;
}
}
if (in_array(($target_item["type"] ?? ""), ["file", "pdf_secure"], true) && isset($_FILES["edit_item_file"]) && $_FILES["edit_item_file"]["error"] === UPLOAD_ERR_OK) {
if ($_FILES["edit_item_file"]["size"] > $max_upload_bytes) {
$error = "El archivo es demasiado grande. Tamaño máximo: $max_upload_label.";
}
$ext = strtolower(pathinfo($_FILES["edit_item_file"]["name"], PATHINFO_EXTENSION));
$allowed_extensions = ($target_item["type"] ?? "") === "pdf_secure" ? ["pdf"] : ["pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "jpg", "jpeg", "png", "gif", "webp", "txt", "zip", "mp4", "mp3"];
if (empty($error) && in_array($ext, $allowed_extensions, true)) {
$safe_name = safe_filename($_FILES["edit_item_file"]["name"]);
$temp_file_path = "$pending_dir/{$change_id}_$safe_name";
if (move_uploaded_file($_FILES["edit_item_file"]["tmp_name"], $temp_file_path)) {
$change_data["pending_filename"] = basename($temp_file_path);
$change_data["original_filename"] = $_FILES["edit_item_file"]["name"];
}
}
}
if (empty($error)) {
$change_file = "$pending_dir/$change_id.json";
file_put_contents($change_file, json_encode($change_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
$message = "Solicitud de cambio enviada. El aulario origen debe aprobarla.";
$redirect_params = "aulario=" . urlencode($aulario_id) . "&project=" . urlencode($project_id);
if (!empty($source_aulario_param)) {
$redirect_params .= "&source=" . urlencode($source_aulario_param);
}
header("Location: /entreaulas/proyectos.php?" . $redirect_params);
exit;
}
}
}
$can_save = true;
foreach ($project["items"] as &$item) {
if ($item["id"] === $item_id) {
$item["name"] = $item_name;
if ($item["type"] === "link") {
$item["url"] = $item_url;
} elseif ($item["type"] === "notepad") {
$item["content"] = $item_content;
} elseif ($item["type"] === "videocall") {
$vc_error = "";
[$vc_url, $vc_room] = build_videocall_url($videocall_platform, $videocall_room, $videocall_url, $vc_error);
if ($vc_error !== "") {
$error = $vc_error;
$can_save = false;
break;
}
$item["url"] = $vc_url;
$item["platform"] = $videocall_platform;
$item["room"] = $vc_room;
}
if (in_array($item["type"], ["file", "pdf_secure"], true) && isset($_FILES["edit_item_file"]) && $_FILES["edit_item_file"]["error"] === UPLOAD_ERR_OK) {
if (!$project_dir) {
$error = "No se pudo acceder al directorio del proyecto.";
$can_save = false;
break;
}
if ($_FILES["edit_item_file"]["size"] > $max_upload_bytes) {
$error = "El archivo es demasiado grande. Tamaño máximo: $max_upload_label.";
$can_save = false;
break;
}
$original_name = $_FILES["edit_item_file"]["name"];
$ext = strtolower(pathinfo($original_name, PATHINFO_EXTENSION));
$allowed_extensions = $item["type"] === "pdf_secure" ? ["pdf"] : ["pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "jpg", "jpeg", "png", "gif", "webp", "txt", "zip", "mp4", "mp3"];
if (!in_array($ext, $allowed_extensions, true)) {
$error = $item["type"] === "pdf_secure" ? "El PDF seguro solo permite archivos PDF." : "Tipo de archivo no permitido. Extensiones permitidas: " . implode(", ", $allowed_extensions);
$can_save = false;
break;
}
if (!is_dir($project_dir)) {
mkdir($project_dir, 0755, true);
}
$safe_name = safe_filename($original_name);
$target_path = "$project_dir/$safe_name";
$counter = 1;
$basename = pathinfo($safe_name, PATHINFO_FILENAME);
while (file_exists($target_path)) {
$safe_name = safe_filename($basename . "_" . $counter . "." . $ext);
$target_path = "$project_dir/$safe_name";
$counter++;
}
if (!move_uploaded_file($_FILES["edit_item_file"]["tmp_name"], $target_path)) {
$error = "No se pudo subir el archivo.";
$can_save = false;
break;
}
if (isset($item["filename"])) {
$old_path = safe_join_file($project_dir, $item["filename"]);
if ($old_path && file_exists($old_path)) {
unlink($old_path);
if (file_exists($old_path . ".eadat")) {
unlink($old_path . ".eadat");
}
}
}
$item["filename"] = $safe_name;
$item["original_name"] = $original_name;
$file_meta = [
"id" => $item_id,
"name" => $item_name,
"type" => $item["type"],
"original_name" => $original_name,
"created_at" => $item["created_at"] ?? time()
];
save_file_metadata($target_path, $file_meta);
}
if (in_array($item["type"], ["file", "pdf_secure"], true) && $project_dir && isset($item["filename"])) {
$file_path = safe_join_file($project_dir, $item["filename"]);
if ($file_path && file_exists($file_path . ".eadat")) {
$file_meta = json_decode(file_get_contents($file_path . ".eadat"), true) ?: [];
$file_meta["name"] = $item_name;
save_file_metadata($file_path, $file_meta);
}
}
$project["updated_at"] = time();
break;
}
}
unset($item);
if ($can_save) {
save_project($working_dir, $project_id, $project);
$message = "Elemento actualizado correctamente.";
}
} else {
$error = "Proyecto no encontrado.";
}
} else {
$error = "El nombre del elemento es obligatorio.";
}
}
}
require_once "_incl/pre-body.php";
// Determine current view
$current_project = $_GET["project"] ?? null;
$view = $current_project ? "project" : "list";
?>
= htmlspecialchars($message) ?>
= htmlspecialchars($error) ?>
Proyectos
Los proyectos son carpetas que contienen enlaces y archivos para tu aulario.
($a["created_at"] ?? 0);
});
if (count($projects) > 0):
?>
= htmlspecialchars($project["name"]) ?>
= $project["description"] ?>
= count($project["items"] ?? []) ?> elementos
· = count($project["subprojects"]) ?> sub-proyectos
No hay proyectos creados aún. ¡Crea tu primer proyecto!
0):
?>
= htmlspecialchars($subproject["name"]) ?>
= $subproject["description"] ?>
= count($subproject["items"] ?? []) ?> elementos
· = count($subproject["subprojects"]) ?> carpetas
0): ?>
0):
?>
= htmlspecialchars($item["name"]) ?>
Enlace
PDF Seguro
Videollamada
Cuaderno
Archivo
(= htmlspecialchars($item["original_name"]) ?>)
" target="_blank" class="btn btn-primary">
Abrir
" target="_blank" class="btn btn-primary">
Abrir
" target="_blank" class="btn btn-primary">
Abrir
0):
?>
⏳ Cambios Pendientes de Aprobación (= count($pending_changes) ?>)
Los siguientes cambios fueron solicitados por otros aularios y requieren tu aprobación:
= htmlspecialchars($change["item_name"]) ?>
Pendiente
Solicitud: Eliminar elemento
Tipo: EnlaceVideollamadaCuadernoArchivo
Solicitud: Editar elemento
Tipo: EnlaceVideollamadaCuadernoArchivo
Tipo: EnlaceVideollamadaCuadernoArchivo
Solicitado por: = htmlspecialchars($req_persona_name) ?> · = htmlspecialchars($req_aul_name) ?>
Fecha: = date("d/m/Y H:i", $change["requested_at"]) ?> GMT
Solicitud:
Elemento:
Tipo:
Plataforma:
Sala / código:
Archivo: