$id, 'original_name' => $orig, 'stored_name' => $cat_path . '/' . $orig, 'title' => $title, 'description' => $desc, 'version' => $version, 'author' => $author, 'homepage' => $homepage, 'category' => $cat_id, 'os2_version' => $os2_ver, 'tags' => $tags, 'license' => $license, 'requirements' => $requires, 'size' => filesize($dest_file), 'uploader' => $user['username'], 'uploaded' => time(), 'approved' => $approve, 'approved_by' => $approve ? $user['username'] : null, 'approved_at' => $approve ? time() : null, 'downloads' => 0, 'source' => 'ftp', ]; file_meta_save($meta); if ($approve) search_index_file($meta); flash('success', 'Pool file imported' . ($approve ? ' and approved' : ' (pending approval)') . '.'); redirect('/pool'); } } if (!empty($errs)) { foreach ($errs as $e) flash('error', $e); redirect('/pool'); } } // ── Handle FTP pool folder batch import ───────────────────────────────────── if ($_SERVER['REQUEST_METHOD'] === 'POST' && $action === 'import_folder') { csrf_check(); set_time_limit(600); $folder_name = basename($_POST['pool_folder'] ?? ''); $folder_path = POOL_DIR . '/' . $folder_name; // Security: ensure path resolves inside POOL_DIR $real_pool = realpath(POOL_DIR); $real_folder = $folder_name ? realpath($folder_path) : false; if (!$real_folder || !str_starts_with($real_folder, $real_pool . DIRECTORY_SEPARATOR) || !is_dir($folder_path)) { flash('error', 'Folder not found in pool.'); redirect('/pool'); } $approve = !empty($_POST['approve_now']); $user = current_user(); // Load categories fresh, bypassing static cache $cats_data = storage_read(CATS_FILE); $cats = $cats_data['categories'] ?? []; // Root folder → top-level category $root_cat_name = ucwords(str_replace(['-', '_'], ' ', $folder_name)); $root_cat_id = category_find_or_create($cats, $root_cat_name, null); // Pre-scan folder structure to create all categories upfront, then persist // before touching any files. This ensures category IDs are always saved to // disk even if the import is interrupted mid-way. $all_files = pool_folder_files($folder_path); foreach ($all_files as $finfo) { $rel_dir = dirname($finfo['rel']); if ($rel_dir === '.') continue; $parts = explode('/', str_replace('\\', '/', $rel_dir)); $par_id = $root_cat_id; foreach ($parts as $part) { $cat_name = ucwords(str_replace(['-', '_'], ' ', $part)); $par_id = category_find_or_create($cats, $cat_name, $par_id); } } categories_save($cats); // persist categories before any files are moved $imported = 0; $skipped = 0; $errors = []; foreach ($all_files as $finfo) { $filename = $finfo['name']; $rel = $finfo['rel']; // e.g. "subdir/file.zip" or "file.zip" $abs = $finfo['abs']; if (!is_allowed_file($filename) || in_array(get_extension($filename), POOL_BULK_EXCLUDE, true)) { $skipped++; continue; } // Determine target category from subdirectory path $rel_dir = dirname($rel); $cat_id = $root_cat_id; if ($rel_dir !== '.') { $parts = explode('/', str_replace('\\', '/', $rel_dir)); $par_id = $root_cat_id; foreach ($parts as $part) { $cat_name = ucwords(str_replace(['-', '_'], ' ', $part)); $par_id = category_find_or_create($cats, $cat_name, $par_id); } $cat_id = $par_id; } $orig = safe_filename($filename); $cat_path = category_upload_path_from_cats($cats, $cat_id); $dest_dir = UPLOADS_DIR . '/' . $cat_path; if (!is_dir($dest_dir)) mkdir($dest_dir, 0755, true); $dest_file = $dest_dir . '/' . $orig; if (file_exists($dest_file)) { $errors[] = 'Skipped (duplicate): ' . $rel; $skipped++; continue; } if (!rename($abs, $dest_file)) { $errors[] = 'Failed to move: ' . $rel; $skipped++; continue; } // Derive a readable title from filename; fall back to "Unknown" $name_part = pathinfo($filename, PATHINFO_FILENAME); $derived_title = ucwords(str_replace(['-', '_', '.'], ' ', $name_part)); $id = hobbes_id(); $meta = [ 'id' => $id, 'original_name' => $orig, 'stored_name' => $cat_path . '/' . $orig, 'title' => $derived_title ?: 'Unknown', 'description' => 'Unknown', 'version' => 'Unknown', 'author' => 'Unknown', 'homepage' => '', 'category' => $cat_id, 'os2_version' => '', 'tags' => '', 'license' => '', 'requirements' => '', 'size' => filesize($dest_file), 'uploader' => $user['username'], 'uploaded' => time(), 'approved' => $approve, 'approved_by' => $approve ? $user['username'] : null, 'approved_at' => $approve ? time() : null, 'downloads' => 0, 'source' => 'ftp', ]; file_meta_save($meta); if ($approve) search_index_file($meta); $imported++; } // Persist the updated (possibly expanded) category tree categories_save($cats); // Remove the now-empty pool folder rmdir_recursive($folder_path); $msg = "Folder \"{$folder_name}\" imported: {$imported} file(s) added"; if ($skipped) $msg .= ", {$skipped} skipped"; flash('success', $msg . '.'); foreach (array_slice($errors, 0, 10) as $e) flash('error', $e); redirect('/pool'); } // ── Handle FTP pool folder rejection ──────────────────────────────────────── if ($_SERVER['REQUEST_METHOD'] === 'POST' && $action === 'reject_folder') { csrf_check(); $folder_name = basename($_POST['pool_folder'] ?? ''); $folder_path = POOL_DIR . '/' . $folder_name; $real_pool = realpath(POOL_DIR); $real_folder = $folder_name ? realpath($folder_path) : false; if ($real_folder && str_starts_with($real_folder, $real_pool . DIRECTORY_SEPARATOR) && is_dir($folder_path)) { rmdir_recursive($folder_path); flash('success', 'Folder "' . $folder_name . '" deleted from pool.'); } else { flash('error', 'Folder not found in pool.'); } redirect('/pool'); } // ── Approve all pending web uploads ───────────────────────────────────────── if ($_SERVER['REQUEST_METHOD'] === 'POST' && $action === 'approve_all') { csrf_check(); $user = current_user(); $now = time(); $count = 0; // Approve every record in data/files/ that is still pending. // This includes both web uploads (source=web) and FTP files that // were imported without immediate approval (source=ftp). // Unimported FTP pool files and folders have no files/ record yet // and are therefore unaffected. foreach (file_meta_list(false) as $meta) { if (!empty($meta['approved'])) continue; if (in_array(get_extension($meta['original_name'] ?? ''), POOL_BULK_EXCLUDE, true)) continue; $meta['approved'] = true; $meta['approved_by'] = $user['username']; $meta['approved_at'] = $now; file_meta_save($meta); search_index_file($meta); $count++; } flash('success', $count ? "{$count} file(s) approved." : 'Nothing pending approval.'); redirect('/pool'); } $pool_items = pool_list(); $page_title = 'Approval Pool'; $_page = 'pool'; include ROOT_DIR . '/templates/header.php'; ?>
Files uploaded via the web form or FTP appear here for review. Editors and Admins can approve, reject, or edit metadata before publishing to the archive. FTP folders are batch-imported: sub-folders become sub-categories and required fields default to Unknown when no metadata is available.
!empty($i['id']) && !in_array(get_extension($i['name'] ?? ''), POOL_BULK_EXCLUDE, true) )); ?> 0): ?>No files pending approval.
Folder from FTP pool. Sub-folders will be created as sub-categories mirroring the directory structure. Required fields with no available metadata will be set to Unknown. Titles are derived from filenames.
Submitted by to
File from FTP pool. Please fill in the required information below before importing.