Restore backup - #119
Conversation
Replace <form method="dialog"> with div + command/commandfor attributes, aligning with the Invoker Commands API. Cancel no longer auto-closes via form submission; both buttons now use `command="close"` explicitly. JS listener simplified: drop named handler + self-removal, check for `returnValue === 'submit'` instead of `'confirm'`.
Clearer dialog title/body split, added spinner, descriptive confirmation text, and fixed HTML nesting.
|
@coderabbitai review |
|
Tip For best results, initiate chat on the files or code changes.
|
✅ Action performedReview finished.
|
📝 WalkthroughWalkthroughAdds original-image backup lifecycle methods, authenticated admin restoration, backup controls in compression details, filesystem failure handling, cleanup on attachment deletion, and unit and integration coverage. ChangesImage backup restoration
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant AdminUI
participant admin.js
participant restore_backup_image
participant Tiny_Image
participant WordPressFilesystem
AdminUI->>admin.js: Confirm backup restoration
admin.js->>restore_backup_image: POST tiny_restore_backup with attachment ID
restore_backup_image->>Tiny_Image: Call restore_backup()
Tiny_Image->>WordPressFilesystem: Copy backup over original image
restore_backup_image-->>admin.js: Return compression details
admin.js-->>AdminUI: Replace media container
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/class-tiny-image.php`:
- Around line 720-772: Handle exceptions from Tiny_Helpers::get_wp_filesystem()
in all backup methods: create_backup(), get_backup(), restore_backup(), and
delete_backup(). Wrap each filesystem initialization in try/catch and return
false when initialization fails, preserving normal behavior when it succeeds so
media rendering and compression degrade gracefully.
- Around line 774-825: Update restore_backup() to reuse get_original_image() for
the restored original image instead of duplicating the
ORIGINAL_UNSCALED/ORIGINAL lookup. After wp_generate_attachment_metadata()
succeeds, rebuild $this->sizes from the new attachment metadata so
get_image_sizes() and get_image_size() immediately reflect regenerated, added,
removed, or renamed thumbnails; retain the existing metadata update and success
behavior.
In `@src/js/admin.js`:
- Around line 22-64: Move the restore workflow from the dialog’s cancel listener
to the confirm button’s click handler, so Escape only dismisses the dialog and
never calls restoreBackup. In the relevant admin.js handler, explicitly remove
any previously registered confirm handler before adding or assigning the new
one, rather than relying on { once: true }, so cancelling and reopening cannot
accumulate listeners. Update the confirm button markup to use type="button" and
remove command="request-close" and commandfor.
In `@src/views/compress-details-backup.php`:
- Around line 18-49: Fix the conditional markup around the backup view in the
template: remove the paragraph wrapper that contains the flow-content dialog,
ensure the no-backup branch emits no orphaned closing tag, and keep the dialog
as valid sibling markup. Also update the “original upload” translation call to
use esc_html_e() without echo, add the tiny-compress-images text domain to “View
uncompressed file,” and use esc_url() for the $backup href.
In `@src/views/compress-details.php`:
- Around line 116-121: Update the modal_title construction and Thickbox name
attribute around $modal_title to escape the translated title exactly once for
its HTML attribute context. Keep the filename unescaped while formatting the
translated string, then rely on esc_attr() at output without changing the
displayed title or surrounding link behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 6a9aa22b-2fe7-40ef-b27c-4d843c3c1e32
📒 Files selected for processing (9)
src/class-tiny-image.phpsrc/class-tiny-plugin.phpsrc/css/admin.csssrc/js/admin.jssrc/views/compress-details-backup.phpsrc/views/compress-details.phptest/helpers/wordpress.phptest/unit/TinyPluginBackupTest.phptest/wp-includes-for-tests/file.php
get_wp_filesystem() now returns WP_Filesystem_Base|false and logs via error_log() on failure instead of throwing. Removes the separate get_filesystem() wrapper. All callers guard against false.
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/js/admin.js (1)
1-12: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winPropagate restore failures to the AJAX error path.
src/class-tiny-plugin.php, Lines 979-1001, emits validation and restore failures withecho ...; exit()but does not return a non-2xx status. The AJAX helper can therefore treat a failed restore as successful. Lines 40-50 then close the dialog and replace the container with plain error text, so thecatchblock is bypassed.Return a structured error or a non-2xx response from
restore_backup_image(), or makerestoreBackupreject the failure response before resolving.Also applies to: 40-50
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/js/admin.js` around lines 1 - 12, Update the restoreBackup flow and its success/error handling so failures from restore_backup_image are rejected rather than resolved as successful AJAX responses. Ensure the PHP endpoint returns a structured error or non-2xx status, or validate the response in restoreBackup and reject failure payloads, allowing the existing catch path to handle errors instead of closing the dialog and replacing the container with plain text.
🧹 Nitpick comments (4)
test/unit/TinyPluginBackupTest.php (3)
174-174: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRemove unused stub parameters.
The two
wp_create_image_subsizescallbacks do not read$idor$file. Declare the callbacks without these parameters to remove the PHPMDUnusedFormalParameterwarnings.Also applies to: 200-200
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/unit/TinyPluginBackupTest.php` at line 174, Remove the unused $id and $file parameters from both wp_create_image_subsizes callback declarations in TinyPluginBackupTest, including the callbacks at both referenced locations, while preserving their existing stub behavior.Source: Linters/SAST tools
195-242: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winAssert that persisted Tiny metadata is cleared.
The test only checks the regenerated thumbnail file size. It can pass if
restore_backup()skips clearing or persisting compression metadata. Seed the stored Tiny metadata, then assert that theTiny_Image::ORIGINALandthumbnailmetadata are cleared after restoration. Also verify that regenerated attachment metadata is persisted.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/unit/TinyPluginBackupTest.php` around lines 195 - 242, Update test_restore_backup_clears_all_sizes_metadata to seed the stored Tiny metadata for both Tiny_Image::ORIGINAL and thumbnail, then assert those entries are cleared after Tiny_Image::restore_backup(). Also assert that the regenerated attachment metadata is persisted, while retaining the existing regenerated thumbnail filesize assertion.
167-193: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winCover replacement of an existing original file.
This test creates only the upload directory. It does not prove that
restore_backup()overwrites a compressed original. Create a small, writabletestfile.pngbefore restoration. The existing size assertion will then detect a failed replacement.Suggested setup change
- $this->wp->createImage( 1, '2026/04', '_placeholder.png' ); + $this->wp->createImage( 1, '2026/04', 'testfile.png' );🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/unit/TinyPluginBackupTest.php` around lines 167 - 193, Update test_restore_backup_restores_file_from_backup to create a small, writable original testfile.png in the 2026/04 uploads directory before invoking restore_backup(), while retaining the existing backup file and final size assertion so the test verifies replacement of existing content rather than creation in an empty directory.src/class-tiny-logger.php (1)
182-184: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winAvoid duplicate filesystem initialization during logging.
log()callsrotate_logs()before callingTiny_Helpers::get_wp_filesystem()again.rotate_logs()performs the same lookup. When initialization fails, one log attempt emits the same PHP error-log message twice.Initialize the filesystem once and pass the result to
rotate_logs(), or suppress duplicate failure logging.Also applies to: 207-209
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/class-tiny-logger.php` around lines 182 - 184, Update the logging flow in log() and rotate_logs() to perform Tiny_Helpers::get_wp_filesystem() only once per log attempt, passing the initialized filesystem result into rotate_logs() for reuse. Preserve the existing early return when initialization fails and ensure both affected call paths no longer emit duplicate failure messages.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/class-tiny-diagnostics.php`:
- Around line 209-214: Update the caller of create_diagnostic_zip() before
download_zip() to guard with is_wp_error( $zippath ). When it returns a
WP_Error, report its error message and stop before passing the value to
download_zip(); preserve the existing download flow for valid string paths.
In `@src/class-tiny-helpers.php`:
- Around line 136-138: Update the filesystem initialization flow around
WP_Filesystem() to capture its return value and require a true result before
returning the global $wp_filesystem object. Preserve the existing error logging
and false return path when initialization fails, even if $wp_filesystem contains
an object.
In `@src/css/admin.css`:
- Line 524: Update the fill declaration containing currentColor to use the
lowercase CSS keyword currentcolor, satisfying the value-keyword-case Stylelint
rule.
In `@src/js/admin.js`:
- Around line 34-39: Update the confirmButton.onclick handler to add an
in-flight guard and disable confirmButton before invoking restoreBackup,
preventing rapid duplicate requests. In the handler’s finally block, re-enable
the button only when the existing retry flow allows another attempt, while
preserving the current spinner and restore behavior.
---
Outside diff comments:
In `@src/js/admin.js`:
- Around line 1-12: Update the restoreBackup flow and its success/error handling
so failures from restore_backup_image are rejected rather than resolved as
successful AJAX responses. Ensure the PHP endpoint returns a structured error or
non-2xx status, or validate the response in restoreBackup and reject failure
payloads, allowing the existing catch path to handle errors instead of closing
the dialog and replacing the container with plain text.
---
Nitpick comments:
In `@src/class-tiny-logger.php`:
- Around line 182-184: Update the logging flow in log() and rotate_logs() to
perform Tiny_Helpers::get_wp_filesystem() only once per log attempt, passing the
initialized filesystem result into rotate_logs() for reuse. Preserve the
existing early return when initialization fails and ensure both affected call
paths no longer emit duplicate failure messages.
In `@test/unit/TinyPluginBackupTest.php`:
- Line 174: Remove the unused $id and $file parameters from both
wp_create_image_subsizes callback declarations in TinyPluginBackupTest,
including the callbacks at both referenced locations, while preserving their
existing stub behavior.
- Around line 195-242: Update test_restore_backup_clears_all_sizes_metadata to
seed the stored Tiny metadata for both Tiny_Image::ORIGINAL and thumbnail, then
assert those entries are cleared after Tiny_Image::restore_backup(). Also assert
that the regenerated attachment metadata is persisted, while retaining the
existing regenerated thumbnail filesize assertion.
- Around line 167-193: Update test_restore_backup_restores_file_from_backup to
create a small, writable original testfile.png in the 2026/04 uploads directory
before invoking restore_backup(), while retaining the existing backup file and
final size assertion so the test verifies replacement of existing content rather
than creation in an empty directory.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 26e35339-fd2b-4153-8c4f-e7269e7c66b5
📒 Files selected for processing (14)
docs/hooks/tiny_image_before_compression.mdsrc/class-tiny-diagnostics.phpsrc/class-tiny-helpers.phpsrc/class-tiny-image.phpsrc/class-tiny-logger.phpsrc/class-tiny-plugin.phpsrc/css/admin.csssrc/js/admin.jssrc/views/compress-details-backup.phpsrc/views/compress-details.phptest/helpers/wordpress.phptest/integration/backup.spec.tstest/integration/utils.tstest/unit/TinyPluginBackupTest.php
🚧 Files skipped from review as they are similar to previous changes (4)
- src/views/compress-details-backup.php
- src/views/compress-details.php
- src/class-tiny-image.php
- src/class-tiny-plugin.php
| if ( false === $wp_filesystem ) { | ||
| return new WP_Error( | ||
| 'filesystem_unavailable', | ||
| __( 'WordPress filesystem could not be initialized.', 'tiny-compress-images' ) | ||
| ); | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
echo "== candidate files =="
git ls-files | rg '(^|/)(src/class-tiny-diagnostics\.php|.*diagnostic.*|.*tiny.*diagnostic.*)$' || true
echo "== relevant symbols and call sites =="
rg -n -C 8 'create_diagnostic_zip|download_diagnostics|download_zip|filesystem_unavailable|is_wp_error' src/class-tiny-diagnostics.php
echo "== file outline =="
ast-grep outline src/class-tiny-diagnostics.php --lang phpRepository: tinify/wordpress-plugin
Length of output: 3042
🏁 Script executed:
#!/bin/bash
set -eu
echo "== download and archive methods =="
sed -n '176,295p' src/class-tiny-diagnostics.php
echo "== all create_diagnostic_zip callers =="
rg -n -C 6 'create_diagnostic_zip\s*\(' .
echo "== diagnostics tests and related error handling =="
git ls-files | rg '(^|/)(test|tests|spec|phpunit|.*diagnostic.*)' || true
rg -n -C 5 'wp_die\(|wp_send_json_error\(|is_wp_error\(' src | head -n 240Repository: tinify/wordpress-plugin
Length of output: 10307
🏁 Script executed:
#!/bin/bash
set -eu
echo "== filesystem helper implementation =="
rg -n -C 12 'function\s+get_wp_filesystem|get_wp_filesystem\s*\(' src test | head -n 220
echo "== WP_Error definition and filesystem exists implementations in repository =="
rg -n -C 8 'class\s+WP_Error|function\s+exists\s*\(' . | head -n 260
echo "== diagnostics unit test context =="
sed -n '1,145p' test/unit/Tiny_Diagnostics_Test.php
echo "== exact diagnostic error-return paths =="
python3 - <<'PY'
from pathlib import Path
p = Path("src/class-tiny-diagnostics.php")
text = p.read_text()
start = text.index("public function create_diagnostic_zip")
end = text.index("\n\t/**", start)
for i, line in enumerate(text[start:end].splitlines(), start=text[:start].count("\n")+1):
if "return new WP_Error" in line or "return $temp_path" in line:
print(f"{i}: {line.strip()}")
PYRepository: tinify/wordpress-plugin
Length of output: 18033
🏁 Script executed:
#!/bin/bash
set -eu
echo "== helper return contract =="
sed -n '114,145p' src/class-tiny-helpers.php
echo "== test filesystem path contract =="
sed -n '1,35p' test/wp-includes-for-tests/file.php
echo "== standalone PHP probe: WP_Error passed to file_exists =="
if command -v php >/dev/null 2>&1; then
php -d display_errors=1 -r '
class WP_Error {}
$path = new WP_Error();
var_dump(file_exists($path));
'
else
echo "php unavailable"
fi
echo "== read-only structural verifier for create_diagnostic_zip =="
python3 - <<'PY'
from pathlib import Path
import re
text = Path("src/class-tiny-diagnostics.php").read_text()
start = text.index("public function create_diagnostic_zip")
end = text.index("\n\t/**", start)
body = text[start:end]
errors = re.findall(r"return\s+new\s+WP_Error\s*\(\s*'([^']+)'", body)
print("WP_Error return codes:", errors)
print("success return present:", "return $temp_path;" in body)
print("caller guards result before download_zip:",
bool(re.search(r"\$zippath\s*=\s*\$this->create_diagnostic_zip\(\);\s*if\s*\(\s*is_wp_error\s*\(\s*\$zippath\s*\)", text)))
PYRepository: tinify/wordpress-plugin
Length of output: 2592
Handle every WP_Error from create_diagnostic_zip() before calling download_zip().
The error paths zip_not_available, filesystem_unavailable, and zip_create_failed pass a WP_Error object to a method that requires a string path. Add an is_wp_error( $zippath ) guard and report the error message.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/class-tiny-diagnostics.php` around lines 209 - 214, Update the caller of
create_diagnostic_zip() before download_zip() to guard with is_wp_error(
$zippath ). When it returns a WP_Error, report its error message and stop before
passing the value to download_zip(); preserve the existing download flow for
valid string paths.
| // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log | ||
| error_log( 'Tiny Compress: Unable to initialize WordPress filesystem.' ); | ||
| return false; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- target file context ---'
sed -n '100,155p' src/class-tiny-helpers.php
printf '%s\n' '--- related filesystem calls and declarations ---'
rg -n -C 3 'WP_Filesystem|wp_filesystem|WP_Filesystem_Base' src tests .github 2>/dev/null || true
printf '%s\n' '--- repository metadata and dependency references ---'
rg -n -C 2 'wordpress|WP_Filesystem' composer.json composer.lock package.json phpunit.xml* README.md 2>/dev/null || trueRepository: tinify/wordpress-plugin
Length of output: 16525
🌐 Web query:
WordPress core WP_Filesystem function source assigns global wp_filesystem before connect returns false
💡 Result:
In the WordPress core function WP_Filesystem, the global variable $wp_filesystem is indeed assigned an instance of the chosen filesystem class before the $wp_filesystem->connect method is called [1][2][3]. The implementation follows this sequence: 1. The filesystem method is determined via get_filesystem_method [1][4]. 2. The corresponding filesystem class is instantiated and assigned to the global variable: $wp_filesystem = new $method($args); [1][5]. 3. The function then proceeds to perform checks (such as verifying potential errors) and defines connection timeouts [1][5][3]. 4. Finally, it attempts to connect using $wp_filesystem->connect [1][5][3]. If $wp_filesystem->connect returns false, the function returns false [1][5][3]. Because the assignment to the global $wp_filesystem has already occurred, the global variable remains populated with the instantiated object even if the connection attempt fails [1][2]. Consequently, developers should verify both that the function returned true and that the filesystem object successfully connected or is ready for use, rather than relying solely on the existence of the global variable [6][7][8].
Citations:
- 1: https://developer.wordpress.org/reference/functions/wp_filesystem/
- 2: https://github.com/WordPress/WordPress/blob/dcdfd7f412cb83f43116f6e37d7bfba4b4673cde/wp-admin/includes/file.php
- 3: https://github.com/WordPress/WordPress/blob/4.2.2/wp-admin/includes/file.php
- 4: https://developer.wordpress.org/reference/functions/get_filesystem_method/
- 5: https://github.com/WordPress/WordPress/blob/eeefec932f3d4f3b50369f6523c2cd8fad3d467f/wp-admin/includes/file.php
- 6: https://github.com/WordPress/WordPress/blob/master/wp-admin/includes/file.php
- 7: https://codex.wordpress.org/Filesystem_API
- 8: https://github.com/WordPress/wordpress-develop/blob/master/src/wp-admin/includes/file.php
🏁 Script executed:
#!/bin/bash
set -eu
python3 - <<'PY'
import urllib.request
url = "https://raw.githubusercontent.com/WordPress/wordpress-develop/trunk/src/wp-admin/includes/file.php"
text = urllib.request.urlopen(url, timeout=10).read().decode()
start = text.index("function WP_Filesystem(")
end = text.find("\n}", start) + 2
print(text[start:end])
PYRepository: tinify/wordpress-plugin
Length of output: 2447
🏁 Script executed:
#!/bin/bash
set -eu
python3 - <<'PY'
import ssl
import urllib.request
url = "https://raw.githubusercontent.com/WordPress/wordpress-develop/trunk/src/wp-admin/includes/file.php"
context = ssl._create_unverified_context()
text = urllib.request.urlopen(url, context=context, timeout=10).read().decode()
start = text.index("function WP_Filesystem(")
# Print only the function body region, bounded by the next documented top-level function.
next_function = text.find("\nfunction ", start + 1)
print(text[start:next_function if next_function != -1 else start + 5000])
PYRepository: tinify/wordpress-plugin
Length of output: 3341
Check the return value from WP_Filesystem().
A failed connection can leave a WP_Filesystem_Base object in $wp_filesystem while WP_Filesystem() returns false. Capture the return value and require true before returning the global object.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/class-tiny-helpers.php` around lines 136 - 138, Update the filesystem
initialization flow around WP_Filesystem() to capture its return value and
require a true result before returning the global $wp_filesystem object.
Preserve the existing error logging and false return path when initialization
fails, even if $wp_filesystem contains an object.
|
|
||
| .tiny-icon-backup { | ||
| flex-shrink: 0; | ||
| fill: currentColor; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Use the CSS keyword casing required by Stylelint.
Stylelint reports currentColor at Line 524 for value-keyword-case. Change it to currentcolor to prevent a lint failure.
Suggested fix
- fill: currentColor;
+ fill: currentcolor;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| fill: currentColor; | |
| fill: currentcolor; |
🧰 Tools
🪛 Stylelint (17.14.1)
[error] 524-524: Expected "currentColor" to be "currentcolor" (value-keyword-case)
(value-keyword-case)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/css/admin.css` at line 524, Update the fill declaration containing
currentColor to use the lowercase CSS keyword currentcolor, satisfying the
value-keyword-case Stylelint rule.
Source: Linters/SAST tools
| confirmButton.onclick = async () => { | ||
| const spinner = dialog.querySelector('.spinner'); | ||
| try { | ||
| if (spinner) { | ||
| spinner.style.visibility = 'visible'; | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Prevent concurrent restore requests.
The confirm handler remains active while restoreBackup is pending. A rapid double-click can start two restore requests. Tiny_Image::restore_backup() rewrites the original file and regenerates sizes for each request, which can cause duplicate work and concurrent metadata updates.
Add an in-flight guard and disable confirmButton before the request starts. Re-enable it in finally when retry is allowed.
Suggested fix
+ let restoreInFlight = false;
+
confirmButton.onclick = async () => {
+ if (restoreInFlight) {
+ return;
+ }
+ restoreInFlight = true;
+ confirmButton.disabled = true;
+
const spinner = dialog.querySelector('.spinner');
try {
...
} finally {
+ restoreInFlight = false;
+ confirmButton.disabled = false;
if (spinner) {
spinner.style.visibility = 'hidden';
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| confirmButton.onclick = async () => { | |
| const spinner = dialog.querySelector('.spinner'); | |
| try { | |
| if (spinner) { | |
| spinner.style.visibility = 'visible'; | |
| } | |
| let restoreInFlight = false; | |
| confirmButton.onclick = async () => { | |
| if (restoreInFlight) { | |
| return; | |
| } | |
| restoreInFlight = true; | |
| confirmButton.disabled = true; | |
| const spinner = dialog.querySelector('.spinner'); | |
| try { | |
| if (spinner) { | |
| spinner.style.visibility = 'visible'; | |
| } | |
| // existing restore workflow remains unchanged | |
| } finally { | |
| restoreInFlight = false; | |
| confirmButton.disabled = false; | |
| if (spinner) { | |
| spinner.style.visibility = 'hidden'; | |
| } | |
| } | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/js/admin.js` around lines 34 - 39, Update the confirmButton.onclick
handler to add an in-flight guard and disable confirmButton before invoking
restoreBackup, preventing rapid duplicate requests. In the handler’s finally
block, re-enable the button only when the existing retry flow allows another
attempt, while preserving the current spinner and restore behavior.
#98 added backups of the original image. This will add the restore functionality.
Screenshots

When a backup is created, we will check in the media library if the image has a backup available. If so, the customer can restore the image in the details modal. This will make a API call to the back-end (ajax action). This will attempt to restore the uncompressed uploaded image.
Additionally in this pull request:
Summary by CodeRabbit
New Features
Bug Fixes
Style