Last active
January 25, 2026 23:01
-
-
Save olafgleba/20b15d3254ab781cd305ee1a45716abf to your computer and use it in GitHub Desktop.
Clean up orphaned page folders in ProcessWire
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php namespace ProcessWire; | |
| /** | |
| * Cleanup orphaned page folders and their content | |
| * | |
| * - Only delete page folders WITH NO existing Page-ID. | |
| * - Implements a dry run mode | |
| * - Tested with ProcessWire 3.0.x, PHP 8.4.x | |
| * | |
| * Place the script file in the root of your PW installation (or | |
| * adapt the pw bootstrap path), than open the file within the browser. | |
| * | |
| * @author Olaf Gleba | |
| * @version 1.0.0 | |
| */ | |
| // Bootstrap ProcessWire | |
| require_once './index.php'; | |
| // Define mode (default `dry run`), set to `false` for actual deletion | |
| $dryRun = true; | |
| // Print to browser | |
| echo "<p>Orphaned page folder cleanup</p>"; | |
| echo "<p>Mode: " . ($dryRun ? "DRY RUN - NO DELETION" : "LIVE - ACTUAL DELETION") . "</p>"; | |
| /** | |
| * Load existing Page-Ids from database | |
| */ | |
| $existingPageIds = []; | |
| $sql = $database->query("SELECT id FROM pages"); | |
| while ($row = $sql->fetch(\PDO::FETCH_ASSOC)) { | |
| $existingPageIds[(int)$row['id']] = true; | |
| } | |
| // Print to browser | |
| echo "<p>Existing pages loaded: " . count($existingPageIds) . "</p>"; | |
| /** | |
| * Iterate over the `files` folder | |
| */ | |
| // Get files root | |
| $filesRoot = realpath($config->paths->files); | |
| $removedFolders = 0; | |
| $dirs = scandir($filesRoot); | |
| foreach ($dirs as $dir) { | |
| // Only nummeric folders (Page-IDs) | |
| if (!ctype_digit($dir)) { | |
| continue; | |
| } | |
| $pageId = (int)$dir; | |
| $pageDir = $filesRoot . DIRECTORY_SEPARATOR . $dir; | |
| if (!is_dir($pageDir)) { | |
| continue; | |
| } | |
| // Skip if page exists | |
| if (isset($existingPageIds[$pageId])) { | |
| continue; | |
| } | |
| // Print to browser | |
| echo "<p>Orphaned page folder: {$pageDir}</p>"; | |
| // List folder files | |
| $iterator = new \RecursiveIteratorIterator( | |
| new \RecursiveDirectoryIterator( | |
| $pageDir, | |
| \FilesystemIterator::SKIP_DOTS | |
| ), | |
| \RecursiveIteratorIterator::CHILD_FIRST | |
| ); | |
| foreach ($iterator as $item) { | |
| if ($item->isFile()) { | |
| echo " File: " . $item->getRealPath() . "<br />"; | |
| } | |
| } | |
| // Delete folder (if args `--delete` is present, s.above) | |
| if (!$dryRun) { | |
| deleteDirectory($pageDir); | |
| echo " FOLDER DELETED<br />"; | |
| } else { | |
| echo " (DRY RUN - NO DELETION)<br />"; | |
| } | |
| echo "<br/>"; | |
| $removedFolders++; | |
| } | |
| /** | |
| * Print summary to browser | |
| */ | |
| echo "<p>"; | |
| echo "---------------------------------<br />"; | |
| echo "Done<br />"; | |
| echo "Orphaned page folders: {$removedFolders}<br />"; | |
| echo "---------------------------------<br />"; | |
| echo "</p>"; | |
| /** | |
| * Recursive deletion | |
| */ | |
| function deleteDirectory(string $dir): void | |
| { | |
| if (!is_dir($dir)) { | |
| return; | |
| } | |
| $items = scandir($dir); | |
| foreach ($items as $item) { | |
| if ($item === '.' || $item === '..') { | |
| continue; | |
| } | |
| $path = $dir . DIRECTORY_SEPARATOR . $item; | |
| if (is_dir($path)) { | |
| deleteDirectory($path); | |
| } else { | |
| unlink($path); | |
| } | |
| } | |
| rmdir($dir); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment