'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); ?>