|false false if size is not original, otherwise array of preserved keys
+ */
public function get_preserve_options( $size_name ) {
if ( ! Tiny_Image::is_original( $size_name ) ) {
return false;
diff --git a/src/views/settings-original-image.php b/src/views/settings-original-image.php
index 261dd8d9..872055a3 100644
--- a/src/views/settings-original-image.php
+++ b/src/views/settings-original-image.php
@@ -68,6 +68,28 @@
+
+ get_backup_enabled();
+ ?>
+ />
+
+
+
render_preserve_input(
'creation',
diff --git a/test/helpers/wordpress.php b/test/helpers/wordpress.php
index 45e2406d..d48bb58e 100644
--- a/test/helpers/wordpress.php
+++ b/test/helpers/wordpress.php
@@ -282,6 +282,14 @@ public function createImage($file_size, $path, $name)
->at($dir);
}
+ /**
+ * Creates images on the virtual disk for testing
+ * @param null|array $sizes Array of size name (array key) => bytes to create; each file will be named "$name-.png"
+ * @param int $original_size Bytes of image
+ * @param string $path Path to image
+ * @param string $name Name of the image
+ * @return void
+ */
public function createImages($sizes = null, $original_size = 12345, $path = '14/01', $name = 'test')
{
vfsStream::newDirectory(self::UPLOAD_DIR . "/$path")->at($this->vfs);
@@ -309,6 +317,13 @@ public function createImagesFromJSON($virtual_images)
}
}
+ /**
+ * creates image meta data for testing
+ *
+ * @param string $path directory of the file in UPLOAD_DIR
+ * @param string $name name of the file without extension
+ * @return array metadata array
+ */
public function getTestMetadata($path = '14/01', $name = 'test')
{
$metadata = array(
@@ -338,6 +353,7 @@ public function getTestMetadata($path = '14/01', $name = 'test')
*/
public static function assertHook($hookname, $expected_args = null)
{
+
$found = self::findHook($hookname, $expected_args);
$message = is_null($expected_args)
@@ -366,7 +382,7 @@ public static function assertNotHook($hookname, $expected_args = null)
private static function findHook($hookname, $expected_args = null)
{
- $hooks = array('add_action', 'add_filter');
+ $hooks = array('add_action', 'add_filter', 'do_action', 'apply_filters');
$found = false;
foreach ($hooks as $method) {
@@ -425,7 +441,7 @@ public function current_time()
*/
public function wp_mkdir_p($dir)
{
- mkdir($dir, 0755, true);
+ return mkdir($dir, 0755, true) || is_dir($dir);
}
/**
@@ -486,9 +502,7 @@ public function wp_timezone_string()
*
* @return void
*/
- public function update_option()
- {
- }
+ public function update_option() {}
/**
* Mocked function for https://developer.wordpress.org/reference/functions/check_ajax_referer/
@@ -560,7 +574,7 @@ public function esc_html($text)
{
return $text;
}
-
+
/**
* Mocked function for https://developer.wordpress.org/reference/functions/insert_with_markers/
*
@@ -570,16 +584,16 @@ public function insert_with_markers($filename, $marker, $insertion)
{
$content = file_exists($filename) ? file_get_contents($filename) : '';
$insertion = is_array($insertion) ? implode("\n", $insertion) : $insertion;
-
+
$start = "# BEGIN {$marker}";
$end = "# END {$marker}";
-
+
$content = preg_replace('/' . preg_quote($start, '/') . '.*?' . preg_quote($end, '/') . '\s*/s', '', $content);
-
+
if ($insertion) {
$content = "{$start}\n{$insertion}\n{$end}\n" . ltrim($content);
}
-
+
return file_put_contents($filename, trim($content) . "\n") !== false;
}
}
diff --git a/test/unit/TinyImageTest.php b/test/unit/TinyImageTest.php
index f70f3b4a..03b8bd3e 100644
--- a/test/unit/TinyImageTest.php
+++ b/test/unit/TinyImageTest.php
@@ -306,4 +306,5 @@ public function test_conversion_same_mimetype()
// second call should be only with image/webp because first call was a image/webp
$this->assertEquals(array('image/webp'), $compress_calls[1]['convert_to']);
}
+
}
diff --git a/test/unit/TinyPluginTest.php b/test/unit/TinyPluginTest.php
index f99cd7e2..471be945 100644
--- a/test/unit/TinyPluginTest.php
+++ b/test/unit/TinyPluginTest.php
@@ -3,7 +3,9 @@
require_once dirname(__FILE__) . '/TinyTestCase.php';
use org\bovigo\vfs\vfsStream;
-use org\bovigo\vfs\content\LargeFileContent;
+
+use function PHPUnit\Framework\assertFalse;
+use function PHPUnit\Framework\assertTrue;
class Tiny_Plugin_Test extends Tiny_TestCase
{
@@ -495,4 +497,103 @@ public function test_conversion_enabled_and_not_filtered()
WordPressStubs::assertHook('template_redirect', array($tiny_picture, 'on_template_redirect'));
}
+
+ public function test_init_adds_backup_image_size_action() {
+ $tiny_plugin = new Tiny_Plugin();
+ $tiny_plugin->init();
+
+ // assert that backup is hooked into `tiny_image_size_before_compression`
+ WordPressStubs::assertHook('tiny_image_before_compression', array($tiny_plugin, 'backup_original_image'));
+ }
+
+ public function test_will_copy_original_file_on_backup() {
+ $this->wp->createImage( 37857, '2026/04', 'testfile.png' );
+ $expected_backup = $this->vfs->url() . '/wp-content/uploads/tinify_backup/2026/04/testfile.png';
+
+ $tiny_plugin = new Tiny_Plugin();
+
+ $ref = new \ReflectionClass($tiny_plugin);
+ $settings_prop = $ref->getProperty('settings');
+ $settings_prop->setAccessible(true);
+ $mock_settings = $this->createMock(Tiny_Settings::class);
+ $mock_settings->method('get_backup_enabled')->willReturn(true);
+ $settings_prop->setValue($tiny_plugin, $mock_settings);
+
+ $this->wp->stub('wp_get_attachment_metadata', function ($i) {
+ return array(
+ 'width' => 1256,
+ 'height' => 1256,
+ 'file' => '2026/04/testfile.png',
+ 'sizes' => array(),
+ );
+ });
+
+ $backup_made = $tiny_plugin->backup_original_image(1);
+
+ assertTrue($backup_made, 'expected backup to be made');
+ assertTrue(file_exists($expected_backup), 'expected backup to be created');
+ }
+
+ public function test_no_backup_when_backup_exists() {
+ $this->wp->createImage( 37857, '2026/04', 'testfile.png' );
+ $expected_backup = $this->vfs->url() . '/wp-content/uploads/tinify_backup/2026/04/testfile.png';
+
+ $tiny_plugin = new Tiny_Plugin();
+
+ $ref = new \ReflectionClass($tiny_plugin);
+ $settings_prop = $ref->getProperty('settings');
+ $settings_prop->setAccessible(true);
+ $mock_settings = $this->createMock(Tiny_Settings::class);
+ $mock_settings->method('get_backup_enabled')->willReturn(true);
+ $settings_prop->setValue($tiny_plugin, $mock_settings);
+
+ $this->wp->stub('wp_get_attachment_metadata', function ($i) {
+ return array(
+ 'width' => 1256,
+ 'height' => 1256,
+ 'file' => '2026/04/testfile.png',
+ 'sizes' => array(),
+ );
+ });
+
+ $this->wp->createImage( 37857, 'tinify_backup/2026/04', 'testfile.png' );
+
+ $backup_made = $tiny_plugin->backup_original_image(1);
+
+ assertFalse($backup_made, 'expected backup not to be made');
+ assertTrue(file_exists($expected_backup), 'expected backup to exist');
+ }
+
+ /**
+ * when the attachment file path contains the upload directory name as a path
+ * segment, the relative path must be extracted using only the leading basedir prefix
+ */
+ public function test_backup_preserves_upload_dir_name_in_relative_path() {
+ $this->wp->createImage( 37857, 'wp-content/uploads/2026/04', 'testfile.png' );
+
+ $tiny_plugin = new Tiny_Plugin();
+
+ $ref = new \ReflectionClass($tiny_plugin);
+ $settings_prop = $ref->getProperty('settings');
+ $settings_prop->setAccessible(true);
+ $mock_settings = $this->createMock(Tiny_Settings::class);
+ $mock_settings->method('get_backup_enabled')->willReturn(true);
+ $settings_prop->setValue($tiny_plugin, $mock_settings);
+
+ $this->wp->stub('wp_get_attachment_metadata', function ($i) {
+ return array(
+ 'width' => 1256,
+ 'height' => 1256,
+ 'file' => 'wp-content/uploads/2026/04/testfile.png',
+ 'sizes' => array(),
+ );
+ });
+
+ $backup_made = $tiny_plugin->backup_original_image(1);
+
+ assertTrue($backup_made, 'expected backup to be made');
+
+ $expected_backup = $this->vfs->url() . '/wp-content/uploads/tinify_backup/wp-content/uploads/2026/04/testfile.png';
+ assertTrue(file_exists($expected_backup), 'expected backup at path preserving the upload dir segment');
+ }
}
diff --git a/test/unit/TinySettingsAdminTest.php b/test/unit/TinySettingsAdminTest.php
index 3de0d81d..3feb1b1c 100644
--- a/test/unit/TinySettingsAdminTest.php
+++ b/test/unit/TinySettingsAdminTest.php
@@ -18,6 +18,7 @@ public function test_admin_init_should_register_keys() {
array( 'tinify', 'tinypng_compression_timing' ),
array( 'tinify', 'tinypng_sizes' ),
array( 'tinify', 'tinypng_resize_original' ),
+ array( 'tinify', 'tinypng_backup' ),
array( 'tinify', 'tinypng_preserve_data' ),
array( 'tinify', 'tinypng_convert_format' ),
array( 'tinify', 'tinypng_logging_enabled' ),
diff --git a/test/unit/TinyTestCase.php b/test/unit/TinyTestCase.php
index 4838a404..c64f22d6 100644
--- a/test/unit/TinyTestCase.php
+++ b/test/unit/TinyTestCase.php
@@ -40,6 +40,11 @@ public static function client_supported() {
}
abstract class Tiny_TestCase extends TestCase {
+ /**
+ * WordPress stubs
+ *
+ * @var \WordPressStubs
+ */
protected $wp;
protected $vfs;