open($tmp) !== true) { $error = 'That file is not a valid zip archive.'; } else { if (!is_dir($dir)) { @mkdir($dir, 0775, true); } // Collect entries, dropping macOS/Windows junk. $entries = []; for ($i = 0; $i < $zip->numFiles; $i++) { $stat = $zip->statIndex($i); if ($stat === false) { continue; } $name = $stat['name']; $base = basename(rtrim($name, '/')); if (strpos($name, '__MACOSX/') === 0 || $base === '.DS_Store' || $base === 'Thumbs.db') { continue; } $entries[] = $stat; } // Detect a single wrapping top-level folder to strip (e.g. "myrepo-main/"). $stripPrefix = null; foreach ($entries as $stat) { $first = explode('/', $stat['name'], 2)[0]; if ($first === '') { continue; } if ($stripPrefix === null) { $stripPrefix = $first; } elseif ($stripPrefix !== $first) { $stripPrefix = null; // more than one root: keep structure as-is break; } } $saved = 0; $skipped = []; $totalOut = 0; $aborted = false; foreach ($entries as $stat) { $name = $stat['name']; $isDir = substr($name, -1) === '/'; // Strip the common wrapping folder if there is one. $name = ($stripPrefix !== null) ? (string) substr($name, strlen($stripPrefix) + 1) : $name; $rel = safe_relpath($name); if ($rel === '') { continue; // e.g. the stripped root folder itself, or junk } if ($target !== '') { $rel = $target . '/' . $rel; } if ($isDir) { @mkdir($dir . '/' . $rel, 0775, true); create_folder((int) $repo['id'], $rel); continue; } $size = (int) $stat['size']; if ($size > MAX_FILE_SIZE) { $skipped[] = $name . ' (too large)'; continue; } $totalOut += $size; if ($totalOut > MAX_ZIP_TOTAL) { $aborted = true; break; } $dest = $dir . '/' . $rel; $destDir = dirname($dest); if (!is_dir($destDir)) { @mkdir($destDir, 0775, true); } // Copy via the zip stream wrapper (never extractTo(), which // would honor the archive's raw, unsanitised path). $stream = $zip->getStream($stat['name']); if ($stream === false) { $skipped[] = $name . ' (unreadable)'; continue; } $out = @fopen($dest, 'wb'); if ($out === false) { fclose($stream); $skipped[] = $name . ' (could not save)'; continue; } // Manual chunked copy — some shared hosts disable // stream_copy_to_file() via disable_functions. while (!feof($stream)) { $chunk = fread($stream, 1 << 16); if ($chunk === false) { break; } fwrite($out, $chunk); } fclose($out); fclose($stream); create_file((int) $repo['id'], $rel, $repo['slug'] . '/' . $rel, (int) filesize($dest)); $dirRel = dirname($rel); if ($dirRel !== '.' && $dirRel !== '') { create_folder((int) $repo['id'], $dirRel); } $saved++; } $zip->close(); if ($aborted) { $error = 'Archive too large: extraction stopped after ' . human_size(MAX_ZIP_TOTAL) . '. ' . $saved . ' file(s) were imported.'; } elseif ($saved > 0 && empty($skipped)) { header('Location: ' . url('/admin/upload.php?repo=' . urlencode($repo['slug']) . '&uploaded=' . $saved)); exit; } else { $notice = $saved . ' file(s) imported from archive.'; if ($skipped) { $error = 'Skipped: ' . implode(', ', $skipped); } } } } } elseif (empty($_FILES['files']) || !is_array($_FILES['files']['name'])) { $error = 'No files were selected.'; } else { $dir = UPLOAD_DIR . '/' . $repo['slug']; $target = safe_relpath($_POST['target'] ?? ''); // optional destination folder if (!is_dir($dir)) { @mkdir($dir, 0775, true); } $saved = 0; $skipped = []; $count = count($_FILES['files']['name']); for ($i = 0; $i < $count; $i++) { $err = $_FILES['files']['error'][$i]; if ($err === UPLOAD_ERR_NO_FILE) { continue; } $origName = $_FILES['files']['name'][$i]; $tmp = $_FILES['files']['tmp_name'][$i]; $size = (int) $_FILES['files']['size'][$i]; if ($err !== UPLOAD_ERR_OK) { $skipped[] = $origName . ' (upload error)'; continue; } if ($size > MAX_FILE_SIZE) { $skipped[] = $origName . ' (too large)'; continue; } if (!is_uploaded_file($tmp)) { $skipped[] = $origName . ' (invalid)'; continue; } // Preserve folder structure: use the relative path the browser sent // (folder upload) when present, else fall back to the basename. $rel = isset($_POST['relpaths'][$i]) ? safe_relpath((string) $_POST['relpaths'][$i]) : ''; if ($rel === '') { $rel = safe_filename($origName); } // Nest under the chosen target folder, if any. if ($target !== '') { $rel = $target . '/' . $rel; } $dest = $dir . '/' . $rel; $destDir = dirname($dest); if (!is_dir($destDir)) { @mkdir($destDir, 0775, true); } if (move_uploaded_file($tmp, $dest)) { create_file((int) $repo['id'], $rel, $repo['slug'] . '/' . $rel, $size); // Register the file's folder (and ancestors) so the tree stays complete. $dirRel = dirname($rel); if ($dirRel !== '.' && $dirRel !== '') { create_folder((int) $repo['id'], $dirRel); } $saved++; } else { $skipped[] = $origName . ' (could not save)'; } } if ($saved > 0 && empty($skipped)) { header('Location: ' . url('/admin/upload.php?repo=' . urlencode($repo['slug']) . '&uploaded=' . $saved)); exit; } $notice = $saved . ' file(s) uploaded.'; if ($skipped) { $error = 'Skipped: ' . implode(', ', $skipped); } } } if (isset($_GET['uploaded'])) { $notice = ((int) $_GET['uploaded']) . ' file(s) uploaded.'; } if (isset($_GET['foldercreated'])) { $notice = 'Folder created.'; } if (isset($_GET['deleted'])) { $notice = 'Deleted.'; } $allRepos = get_repositories(); $files = $repo ? get_files_by_repo((int) $repo['id']) : []; $folders = $repo ? get_folders_by_repo((int) $repo['id']) : []; $page_title = $repo ? ('Upload files to ' . $repo['name']) : 'Upload files'; require __DIR__ . '/../includes/header.php'; ?>
= e($notice) ?>
= e($error) ?>
Empty. Upload files or create a folder above.
| Name | Size | |
|---|---|---|
| 📁 = e($fo['path']) ?>/ | folder | |
| = e($f['filename']) ?> | = e(human_size((int) $f['filesize'])) ?> |