Skip to content

create a backup of the original image#98

Open
tijmenbruggeman wants to merge 18 commits intotinify:masterfrom
wcreateweb:feat/backup-original
Open

create a backup of the original image#98
tijmenbruggeman wants to merge 18 commits intotinify:masterfrom
wcreateweb:feat/backup-original

Conversation

@tijmenbruggeman
Copy link
Copy Markdown
Collaborator

@tijmenbruggeman tijmenbruggeman commented Mar 18, 2026

This will add the option to create a backup of the original uncompressed image.

image

Changes

  • adds tinypng_backup setting
    • considered to add it to preserve_opts but that targets the attributes to preserve on the image, not the image itself.
    • disabled by default
  • before compressing, will optionally create a backup
    • only for the original image
    • when option is enabled
    • uses copy instead of using the filedata, copy uses io instead of memory write

Further development

further development before we release it

  • add ability to restore the original image
  • on deleting the attachment, should also delete the backup

}

/**
* creates a backup of the image as <originalfile>.bak.<extension>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To not interfere with existing plugins and whatever watches the file upload folder. Maybe write the files to another folder?

e.g

TinifyBackups/{original_path}

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an opt-in setting to create a .bak backup of the original (uncompressed) uploaded image before the plugin compresses it, exposing the option in the settings UI and adding unit tests around the new behavior.

Changes:

  • Introduces a new tinypng_backup setting (registered in settings and surfaced in the “Original image” settings view).
  • Adds backup creation logic to Tiny_Image::compress() (backup named <filename>.bak.<ext>).
  • Adds unit tests and test helpers/docs to validate backup behavior.

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/class-tiny-image.php Creates a backup prior to compression and logs whether it was created.
src/class-tiny-settings.php Registers the new setting and adds get_backup_enabled() for feature gating.
src/views/settings-original-image.php Adds a checkbox to enable/disable backups in the settings UI.
test/unit/TinyImageTest.php Adds tests asserting when backups should/shouldn’t be created.
test/unit/TinySettingsAdminTest.php Updates expectations for settings registration to include the new key.
test/helpers/wordpress.php Updates helper docs used by unit tests (image creation/metadata).
test/unit/TinyTestCase.php Adds docblock for WP stubs property.
src/class-tiny-compress.php Improves compress_file parameter documentation.
bin/run-mocks Updates docker compose invocation to include --wait.
Comments suppressed due to low confidence (1)

src/class-tiny-compress.php:103

  • PHPDoc for compress_file is inaccurate: the method returns the $details array, not void. Also, "@param array{ string }" is not a valid shape for a list of mimetypes; consider documenting this as "array" (or "string[]") for $convert_to.
	 * @param string $file path to file
	 * @param array $resize_opts
	 * @param array $preserve_opts
	 * @param array{ string } conversion options
	 * @return void

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

$this->update_tiny_post_meta();
$resize = $this->settings->get_resize_options( $size_name );
$preserve = $this->settings->get_preserve_options( $size_name );
$backup_created = $this->create_backup( $size_name, $size->filename );
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Backup creation is triggered for every unprocessed size (including sizes that are only being processed for conversion). This can create/overwrite the .bak file from an already-compressed original, which defeats the goal of backing up the uncompressed upload. Consider only creating the backup when the original is actually uncompressed (e.g., gate on the Tiny_Image_Size::uncompressed() state) and avoid backing up during conversion-only runs.

Suggested change
$backup_created = $this->create_backup( $size_name, $size->filename );
$backup_created = false;
if ( method_exists( $size, 'uncompressed' ) && $size->uncompressed() ) {
$backup_created = $this->create_backup( $size_name, $size->filename );
}

Copilot uses AI. Check for mistakes.
$fileinfo['filename'],
$fileinfo['extension']
);

Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

copy() will overwrite an existing backup file. If compress() is re-run later (e.g., conversion enabled after initial compression), this can silently replace the backup with a newer (possibly already-compressed) file. Consider skipping backup creation when the backup already exists (or use a unique/immutable naming strategy) so the first backup remains the original upload.

Suggested change
if ( file_exists( $backup_file ) ) {
// Backup already exists; keep the first backup (original upload) intact.
return true;
}

Copilot uses AI. Check for mistakes.
Comment on lines +360 to +363
* Retrieves the preserve options for the original image
*
* @param string - size name
* @return false|array<string> false if size is not original, otherwise array of preserved keys
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docblock for get_preserve_options has an invalid @param tag (missing variable name) and the return type description is a bit inconsistent. Use a standard form like "@param string $size_name" and consider "array|false" for the return type to match PHPDoc conventions.

Suggested change
* Retrieves the preserve options for the original image
*
* @param string - size name
* @return false|array<string> false if size is not original, otherwise array of preserved keys
* Retrieves the preserve options for the original image.
*
* @param string $size_name Size name.
* @return array<string>|false Array of preserved keys if size is original, or false otherwise.

Copilot uses AI. Check for mistakes.
Comment on lines +325 to +329
$settings = new Tiny_Settings();
$mock_compressor = $this->createMock(Tiny_Compress::class);
$settings->set_compressor($mock_compressor);

$metadata = $this->wp->getTestMetadata($input_dir, $input_name);
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test sets a Tiny_Compress mock but doesn’t stub compress_file(). The mock will return null, which means the code under test doesn’t exercise/validate handling of a real compressor response and could become fragile if Tiny_Image starts assuming an array response. Consider stubbing compress_file() to return a minimal valid details array and optionally asserting it was called once for the original size.

Copilot uses AI. Check for mistakes.
Comment on lines +355 to +359
$settings = new Tiny_Settings();
$mock_compressor = $this->createMock(Tiny_Compress::class);
$settings->set_compressor($mock_compressor);

$metadata = $this->wp->getTestMetadata($input_dir, $input_name);
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above: compress_file() isn’t stubbed on the Tiny_Compress mock, so the test doesn’t validate behavior with a real compressor response and may become brittle. Consider stubbing compress_file() to return a minimal valid details array (and assert call count) to keep the test meaningful.

Copilot uses AI. Check for mistakes.
Comment on lines +387 to +392
$settings = new Tiny_Settings();
$mock_compressor = $this->createMock(Tiny_Compress::class);
$settings->set_compressor($mock_compressor);

$metadata = $this->wp->getTestMetadata($input_dir, $input_name);
$tinyimg = new Tiny_Image($settings, 999, $metadata);
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above: the Tiny_Compress mock isn’t configured to return a realistic value from compress_file(). Stubbing a minimal response would make this test less fragile and better aligned with actual runtime behavior.

Copilot uses AI. Check for mistakes.

/**
* Creates images on the virtual disk for testing
* @param null|array $sizes Array of size => bytes to create, file will be named $name-$size.png
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The createImages() docblock says generated files will be named "$name-$size.png", but the implementation uses the array key (size name) in the filename ("$name-$key.png"). Update the docblock wording so it matches what the helper actually writes.

Suggested change
* @param null|array $sizes Array of size => bytes to create, file will be named $name-$size.png
* @param null|array $sizes Array of size name (array key) => bytes to create; each file will be named "$name-<size name>.png"

Copilot uses AI. Check for mistakes.
*
* @param string $path directory of the file in UPLOAD_DIR
* @param string $name name of the file without extension
* @return array object containing metadata
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docblock says "@return array object containing metadata", but getTestMetadata() returns a plain array. Consider updating the @return description to avoid confusion for test authors.

Suggested change
* @return array object containing metadata
* @return array metadata array

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants