Files
LMS-Estafeta/admin/recursos_scorm_upload.php
T

277 lines
9.1 KiB
PHP

<?php
require_once __DIR__.'/auth_guard.php';
// Subida de paquete SCORM (.zip): descomprime en data/scormpkg y devuelve la URL de index.html
require_once "../config.php";
header('Content-Type: application/json; charset=utf-8');
$return = new stdClass();
function responder($return) {
echo json_encode($return, JSON_UNESCAPED_UNICODE);
exit;
}
function slugScorm($text) {
$text = trim((string)$text);
$text = strtr($text, array(
'á'=>'a','à'=>'a','ä'=>'a','â'=>'a','Á'=>'A','À'=>'A','Ä'=>'A','Â'=>'A',
'é'=>'e','è'=>'e','ë'=>'e','ê'=>'e','É'=>'E','È'=>'E','Ë'=>'E','Ê'=>'E',
'í'=>'i','ì'=>'i','ï'=>'i','î'=>'i','Í'=>'I','Ì'=>'I','Ï'=>'I','Î'=>'I',
'ó'=>'o','ò'=>'o','ö'=>'o','ô'=>'o','Ó'=>'O','Ò'=>'O','Ö'=>'O','Ô'=>'O',
'ú'=>'u','ù'=>'u','ü'=>'u','û'=>'u','Ú'=>'U','Ù'=>'U','Ü'=>'U','Û'=>'U',
'ñ'=>'n','Ñ'=>'N'
));
$text = strtolower($text);
$text = preg_replace('~[^a-z0-9]+~', '-', $text);
$text = trim($text, '-');
if ($text === '') { $text = 'scorm'; }
return substr($text, 0, 60);
}
function limpiarRuta($p) {
$p = str_replace('\\', '/', $p);
$absoluta = (substr($p, 0, 1) === '/');
$parts = array();
foreach (explode('/', $p) as $seg) {
if ($seg === '' || $seg === '.') continue;
if ($seg === '..') { array_pop($parts); continue; }
$parts[] = $seg;
}
return ($absoluta ? '/' : '') . implode('/', $parts);
}
function buscarArchivoCI($dir, $nombre) {
$items = @scandir($dir);
if (!$items) return null;
foreach ($items as $it) {
if ($it === '.' || $it === '..') continue;
if (strcasecmp($it, $nombre) === 0 && is_file($dir . '/' . $it)) return $dir . '/' . $it;
}
return null;
}
function primerHtml($dir) {
$items = @scandir($dir);
if (!$items) return null;
foreach ($items as $it) {
if ($it === '.' || $it === '..') continue;
$p = $dir . '/' . $it;
if (is_file($p) && preg_match('~\.html?$~i', $it)) return $p;
}
return null;
}
function hrefDeManifest($manifest) {
$doc = new DOMDocument();
libxml_use_internal_errors(true);
$ok = @$doc->load($manifest);
libxml_clear_errors();
if (!$ok) return null;
$defaultOrg = '';
foreach ($doc->getElementsByTagName('organizations') as $orgs) {
$defaultOrg = $orgs->getAttribute('default');
break;
}
$ref = '';
foreach ($doc->getElementsByTagName('organization') as $org) {
if ($defaultOrg !== '' && $org->getAttribute('identifier') !== $defaultOrg) continue;
foreach ($org->getElementsByTagName('item') as $item) {
$ir = $item->getAttribute('identifierref');
if ($ir !== '') { $ref = $ir; break 2; }
}
if ($defaultOrg !== '') break;
}
$resources = $doc->getElementsByTagName('resource');
$href = '';
if ($ref !== '') {
foreach ($resources as $res) {
if ($res->getAttribute('identifier') === $ref) { $href = $res->getAttribute('href'); break; }
}
}
if ($href === '') {
foreach ($resources as $res) {
$st = $res->getAttribute('adlcp:scormtype');
if ($st === '') { $st = $res->getAttributeNS('http://www.adlnet.org/xsd/adlcp_rootv1p2', 'scormtype'); }
if (strtolower($st) === 'sco') { $href = $res->getAttribute('href'); break; }
}
}
if ($href === '') {
foreach ($resources as $res) {
$h = $res->getAttribute('href');
if ($h !== '') { $href = $h; break; }
}
}
return ($href !== '') ? $href : null;
}
function detectarEntrada($dest) {
// 1) imsmanifest.xml (raíz o un nivel dentro)
$manifests = array();
if (is_file($dest . '/imsmanifest.xml')) $manifests[] = $dest . '/imsmanifest.xml';
foreach (glob($dest . '/*', GLOB_ONLYDIR) as $sub) {
if (is_file($sub . '/imsmanifest.xml')) $manifests[] = $sub . '/imsmanifest.xml';
}
foreach ($manifests as $manifest) {
$href = hrefDeManifest($manifest);
if ($href) {
$cand = limpiarRuta(dirname($manifest) . '/' . urldecode($href));
if (is_file($cand)) return $cand;
}
}
// 2) nombres comunes (Storyline index_lms.html / story.html, Rise/HTML index.html, etc.)
$nombres = array('index.html', 'index_lms.html', 'story.html', 'storyline.html', 'launch.html',
'scorm.html', 'scormindex.html', 'default.html', 'start.html', 'index.htm');
$dirs = array($dest);
foreach (glob($dest . '/*', GLOB_ONLYDIR) as $sub) { $dirs[] = $sub; }
foreach ($nombres as $n) {
foreach ($dirs as $d) {
$p = buscarArchivoCI($d, $n);
if ($p) return $p;
}
}
// 3) primer .html/.htm
foreach ($dirs as $d) {
$p = primerHtml($d);
if ($p) return $p;
}
return null;
}
function rrmdir($dir) {
if (!is_dir($dir)) return;
$items = @scandir($dir);
if (!$items) return;
foreach ($items as $it) {
if ($it === '.' || $it === '..') continue;
$p = $dir . '/' . $it;
if (is_dir($p)) { rrmdir($p); } else { @unlink($p); }
}
@rmdir($dir);
}
if (!isset($_FILES['files']) || !is_array($_FILES['files']['tmp_name'])) {
$return->error = '1'; $return->txt = 'No se recibió ningún archivo.';
responder($return);
}
$tmp = $_FILES['files']['tmp_name'][0];
$errCode = isset($_FILES['files']['error'][0]) ? $_FILES['files']['error'][0] : 1;
$orig = '';
if (!empty($_FILES['files']['name'][0])) { $orig = $_FILES['files']['name'][0]; }
elseif (!empty($_POST['name'])) { $orig = $_POST['name']; }
if ($errCode !== 0) {
$return->error = '1'; $return->txt = 'Error al subir el archivo (código ' . $errCode . ').';
responder($return);
}
$ext = strtolower(pathinfo($orig, PATHINFO_EXTENSION));
if ($ext !== 'zip') {
$return->error = '1'; $return->txt = 'Solo se aceptan paquetes SCORM en formato .zip';
responder($return);
}
$fh = @fopen($tmp, 'rb');
$magic = $fh ? fread($fh, 4) : '';
if ($fh) { fclose($fh); }
if (substr($magic, 0, 2) !== 'PK') {
$return->error = '1'; $return->txt = 'El archivo no es un ZIP válido.';
responder($return);
}
$scormBase = dirname(__DIR__) . '/data/scormpkg';
if (!is_dir($scormBase)) { @mkdir($scormBase, 0775, true); }
if (!is_dir($scormBase) || !is_writable($scormBase)) {
$return->error = '1'; $return->txt = 'La carpeta data/scormpkg no existe o no tiene permisos de escritura.';
responder($return);
}
$catSlug = 'general';
if (!empty($_POST['catid'])) {
$cid = intval($_POST['catid']);
if ($cid > 0) {
dbConnect();
global $link;
$resCat = mysqli_query($link, "select nombre from categoria where id = $cid limit 1");
if ($resCat && ($rowCat = mysqli_fetch_row($resCat))) {
$catSlug = slugScorm($rowCat[0]);
}
if ($resCat) { mysqli_free_result($resCat); }
mysqli_close($link);
}
}
$destBase = $scormBase . '/' . $catSlug;
if (!is_dir($destBase)) { @mkdir($destBase, 0775, true); }
if (!is_dir($destBase) || !is_writable($destBase)) {
$return->error = '1'; $return->txt = 'No se pudo crear la carpeta de la categoría en data/scormpkg.';
responder($return);
}
$slug = slugScorm(pathinfo($orig, PATHINFO_FILENAME));
$folder = $slug . '_' . time();
$dest = $destBase . '/' . $folder;
$i = 1;
while (file_exists($dest)) { $folder = $slug . '_' . time() . '_' . $i; $dest = $destBase . '/' . $folder; $i++; }
$zip = new ZipArchive();
if ($zip->open($tmp) !== true) {
$return->error = '1'; $return->txt = 'No se pudo abrir el paquete .zip';
responder($return);
}
$totalSize = 0; $numEntries = 0;
for ($idx = 0; $idx < $zip->numFiles; $idx++) {
$stat = $zip->statIndex($idx);
$name = $stat['name'];
$numEntries++;
$totalSize += $stat['size'];
if (preg_match('~^(/|\\\\|[A-Za-z]:)~', $name) || strpos($name, '..') !== false) {
$zip->close();
$return->error = '1'; $return->txt = 'El .zip contiene rutas no permitidas.';
responder($return);
}
if ($numEntries > 20000 || $totalSize > 2147483648) {
$zip->close();
$return->error = '1'; $return->txt = 'El paquete es demasiado grande.';
responder($return);
}
}
if (!@mkdir($dest, 0775, true)) {
$zip->close();
$return->error = '1'; $return->txt = 'No se pudo crear la carpeta destino.';
responder($return);
}
if (!$zip->extractTo($dest)) {
$zip->close();
rrmdir($dest);
$return->error = '1'; $return->txt = 'No se pudo descomprimir el paquete.';
responder($return);
}
$zip->close();
$indexPath = detectarEntrada($dest);
if (!$indexPath) {
rrmdir($dest);
$return->error = '1'; $return->txt = 'No se encontró un archivo de inicio (index.html / imsmanifest.xml) en el paquete SCORM.';
responder($return);
}
$baseReal = realpath($scormBase);
$indexReal = realpath($indexPath);
$rel = ltrim(str_replace('\\', '/', substr($indexReal, strlen($baseReal))), '/');
$return->error = '0';
$return->category = $catSlug;
$return->folder = $catSlug . '/' . $folder;
$return->url = $CFG->wwwroot . 'data/scormpkg/' . $rel;
responder($return);
?>