root_metadata.json * "" -> str_replace(['/', '\\', ' '], '-', '') . '_metadata.json' */ require_once __DIR__ . '/../config/config.php'; // ---------- helpers that mirror model behavior ---------- /** Compute the metadata JSON path for a folder key (e.g., "root", "invoices/2025"). */ function folder_metadata_path(string $folderKey): string { if (strtolower(trim($folderKey)) === 'root' || trim($folderKey) === '') { return rtrim(META_DIR, '/\\') . '/root_metadata.json'; } $safe = str_replace(['/', '\\', ' '], '-', trim($folderKey)); return rtrim(META_DIR, '/\\') . '/' . $safe . '_metadata.json'; } /** Turn an absolute path under UPLOAD_DIR into a folder key (“root” or relative with slashes). */ function to_folder_key(string $absPath): string { $base = rtrim(UPLOAD_DIR, '/\\') . DIRECTORY_SEPARATOR; if (realpath($absPath) === realpath(rtrim(UPLOAD_DIR, '/\\'))) { return 'root'; } $rel = ltrim(str_replace('\\', '/', substr($absPath, strlen($base))), '/'); return $rel; } /** List immediate files in a directory (no subdirs). */ function list_files(string $dir): array { $out = []; $entries = @scandir($dir); if ($entries === false) return $out; foreach ($entries as $name) { if ($name === '.' || $name === '..') continue; $p = $dir . DIRECTORY_SEPARATOR . $name; if (is_file($p)) $out[] = $name; } sort($out, SORT_NATURAL | SORT_FLAG_CASE); return $out; } /** Recursively list subfolders (relative folder keys), skipping trash/. */ function list_all_folders(string $root): array { $root = rtrim($root, '/\\'); $folders = ['root']; $it = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($root, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST ); foreach ($it as $path => $info) { if ($info->isDir()) { // relative key like "foo/bar" $rel = ltrim(str_replace(['\\'], '/', substr($path, strlen($root) + 1)), '/'); if ($rel === '') continue; // skip trash and profile_pics subtrees if ($rel === 'trash' || strpos($rel, 'trash/') === 0) continue; if ($rel === 'profile_pics' || strpos($rel, 'profile_pics/') === 0) continue; // obey the app’s folder-name regex to stay consistent if (preg_match(REGEX_FOLDER_NAME, basename($rel))) { $folders[] = $rel; } } } // de-dup and sort $folders = array_values(array_unique($folders)); sort($folders, SORT_NATURAL | SORT_FLAG_CASE); return $folders; } // ---------- main ---------- $uploads = rtrim(UPLOAD_DIR, '/\\'); $metaDir = rtrim(META_DIR, '/\\'); // Ensure metadata dir exists if (!is_dir($metaDir)) { @mkdir($metaDir, 0775, true); } $now = date(DATE_TIME_FORMAT); $folders = list_all_folders($uploads); $totalCreated = 0; $totalPruned = 0; foreach ($folders as $folderKey) { $absFolder = ($folderKey === 'root') ? $uploads : $uploads . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $folderKey); if (!is_dir($absFolder)) continue; $files = list_files($absFolder); $metaPath = folder_metadata_path($folderKey); $metadata = []; if (is_file($metaPath)) { $decoded = json_decode(@file_get_contents($metaPath), true); if (is_array($decoded)) $metadata = $decoded; } // Build a quick lookup of existing entries $existing = array_keys($metadata); // ADD missing files foreach ($files as $name) { // Keep same filename validation used in FileModel if (!preg_match(REGEX_FILE_NAME, $name)) continue; if (!isset($metadata[$name])) { $metadata[$name] = [ 'uploaded' => $now, 'modified' => $now, 'uploader' => 'Imported' ]; $totalCreated++; echo "Indexed: " . ($folderKey === 'root' ? '' : $folderKey . '/') . $name . PHP_EOL; } } // PRUNE stale metadata entries for files that no longer exist foreach ($existing as $name) { if (!in_array($name, $files, true)) { unset($metadata[$name]); $totalPruned++; } } // Ensure parent dir exists and write metadata @mkdir(dirname($metaPath), 0775, true); if (@file_put_contents($metaPath, json_encode($metadata, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)) === false) { fwrite(STDERR, "Failed to write metadata for folder: {$folderKey}\n"); } } echo "Done. Created {$totalCreated} entr" . ($totalCreated === 1 ? "y" : "ies") . ", pruned {$totalPruned}.\n";