refactor(photos): extract shared zero-padded PhotoRenumberer helper

Factor cache-on-save's renumberPhotos into a shared PhotoRenumberer class
(Grav\Plugin\Shared), the single owner of the photo-NN naming invariant used
by both the create/edit reconcile and the upcoming live reorder route, so their
numbering can't diverge.

Changes vs the old private method:
- Zero-pads to photo-01..NN (pad width grows with the set) so lexicographic
  media order equals numeric order past 9 photos — cover = images|first stays
  correct for 10+ photos. Normalises pre-existing un-padded photo-N on first pass.
- Image-extension guard moved into the helper: only real image files on disk are
  renamed, so a crafted manifest naming the entry .md, a .gpx or a .meta.yaml is
  skipped by every caller, not just cache-on-save.

Create-mode entries now also emit photo-01..NN — an intentional, accepted side
effect of sharing one helper.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-05 18:56:59 +02:00
co-authored by Claude Opus 4.8
parent 05db592836
commit fcf52a0e44
2 changed files with 88 additions and 34 deletions
+4 -34
View File
@@ -6,15 +6,17 @@ use Grav\Common\Plugin;
use RocketTheme\Toolbox\Event\Event;
require_once __DIR__ . '/classes/EntryScopeGuard.php';
require_once __DIR__ . '/classes/PhotoRenumberer.php';
use Grav\Plugin\Shared\EntryScopeGuard;
use Grav\Plugin\Shared\PhotoRenumberer;
class CacheOnSavePlugin extends Plugin
{
/**
* onFormProcessed fires once per `process:` action (add_page, upload, message,
* reset — 4x for the post form). Photo reconciliation must run exactly once:
* the first pass renames the kept photos to photo-1..N, so a second pass with
* the first pass renames the kept photos to photo-01..NN, so a second pass with
* the same manifest would see those renamed files as "unlisted" and delete
* them. This latches after the first run (the plugin instance persists for the
* request); the first fire is the `add_page` action, after add-page-by-form
@@ -244,7 +246,7 @@ class CacheOnSavePlugin extends Plugin
}
}
$this->renumberPhotos($dir, $names);
PhotoRenumberer::renumber($dir, $names);
}
/**
@@ -276,38 +278,6 @@ class CacheOnSavePlugin extends Plugin
}
}
/**
* Rename the files named in $names to photo-1..N in that order, in $dir.
* Two-phase via temp names so a target (photo-2.jpg) can't clobber a
* not-yet-moved source of the same name.
*/
private function renumberPhotos(string $dir, array $names): void
{
$planned = [];
$i = 1;
foreach ($names as $name) {
$src = $dir . DIRECTORY_SEPARATOR . $name;
if (!is_file($src)) {
continue; // skip anything not actually on disk
}
$ext = strtolower(pathinfo($name, PATHINFO_EXTENSION)) ?: 'jpg';
$tmp = $dir . DIRECTORY_SEPARATOR . '.reorder-tmp-' . $i . '.' . $ext;
$final = $dir . DIRECTORY_SEPARATOR . 'photo-' . $i . '.' . $ext;
if ($src === $final) {
$i++;
continue; // already correctly named
}
@rename($src, $tmp);
$planned[] = [$tmp, $final];
$i++;
}
foreach ($planned as [$tmp, $final]) {
if (is_file($tmp)) {
@rename($tmp, $final);
}
}
}
/**
* Locate the freshly-created entry folder: the child of the active trip's
* dailies directory that contains all of the uploaded files. Matching by the