Skip to content

feat: add clearMetadata() method to provide privacy options when using imagick handler #9538

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: 4.7
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions system/Images/Handlers/BaseHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -769,4 +769,14 @@ public function getHeight()
{
return ($this->resource !== null) ? $this->_getHeight() : $this->height;
}

/**
* Placeholder method for implementing metadata clearing logic.
*
* This method should be implemented to remove or reset metadata as needed.
*/
public function clearMetadata(): static
{
return $this;
}
}
16 changes: 16 additions & 0 deletions system/Images/Handlers/ImageMagickHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -537,4 +537,20 @@ public function reorient(bool $silent = false)
default => $this,
};
}

/**
* Clears metadata from the image.
*
* @return $this
*
* @throws ImagickException
*/
public function clearMetadata(): static
{
$this->ensureResource();

$this->resource->stripImage();

return $this;
}
}
7 changes: 7 additions & 0 deletions system/Images/ImageHandlerInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,11 @@ public function text(string $text, array $options = []);
* @return bool
*/
public function save(?string $target = null, int $quality = 90);

/**
* Clear metadata before saving image as a new file.
*
* @return $this
*/
public function clearMetadata(): static;
}
9 changes: 9 additions & 0 deletions tests/system/Images/GDHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -454,4 +454,13 @@ public function testImageReorientPortrait(): void
$this->assertSame(['red' => 62, 'green' => 62, 'blue' => 62, 'alpha' => 0], $rgb);
}
}

public function testClearMetadataReturnsSelf(): void
{
$this->handler->withFile($this->path);

$result = $this->handler->clearMetadata();

$this->assertSame($this->handler, $result);
}
}
36 changes: 36 additions & 0 deletions tests/system/Images/ImageMagickHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -443,4 +443,40 @@ public function testImageReorientPortrait(): void
$this->assertSame(['red' => 62, 'green' => 62, 'blue' => 62, 'alpha' => 0], $rgb);
}
}

public function testClearMetadataEnsuresResource(): void
{
$this->expectException(ImageException::class);
$this->handler->clearMetadata();
}

public function testClearMetadataReturnsSelf(): void
{
$this->handler->withFile($this->path);

$result = $this->handler->clearMetadata();

$this->assertSame($this->handler, $result);
}

public function testClearMetadata(): void
{
$this->handler->withFile($this->origin . 'Steveston_dusk.JPG');
/** @var Imagick $imagick */
$imagick = $this->handler->getResource();
$before = $imagick->getImageProperties();

$this->assertGreaterThan(40, count($before));

$this->handler
->clearMetadata()
->save($this->root . 'exif-info-no-metadata.jpg');

$this->handler->withFile($this->root . 'exif-info-no-metadata.jpg');
/** @var Imagick $imagick */
$imagick = $this->handler->getResource();
$after = $imagick->getImageProperties();

$this->assertLessThanOrEqual(5, count($after));
}
}
3 changes: 3 additions & 0 deletions user_guide_src/source/changelogs/v4.7.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ Behavior Changes
Interface Changes
=================

- **Images:** The ``ImageHandlerInterface`` now includes a new method: ``clearMetadata()``. If you've implemented your own handler from scratch, you will need to provide an implementation for this method to ensure compatibility.

Method Signature Changes
========================

Expand Down Expand Up @@ -64,6 +66,7 @@ Libraries

**Email:** Added support for choosing the SMTP authorization method. You can change it via ``Config\Email::$SMTPAuthMethod`` option.
**Image:** The ``ImageMagickHandler`` has been rewritten to rely solely on the PHP ``imagick`` extension.
**Image:** Added ``ImageMagickHandler::clearMetadata()`` method to remove image metadata for privacy protection.

Helpers and Functions
=====================
Expand Down
15 changes: 15 additions & 0 deletions user_guide_src/source/libraries/images.rst
Original file line number Diff line number Diff line change
Expand Up @@ -260,3 +260,18 @@ The possible options that are recognized are as follows:
- ``vOffset`` Additional offset on the y axis, in pixels
- ``fontPath`` The full server path to the TTF font you wish to use. System font will be used if none is given.
- ``fontSize`` The font size to use. When using the GD handler with the system font, valid values are between ``1`` to ``5``.

Clearing Image Metadata
=======================

This method removes metadata (EXIF, XMP, ICC, IPTC, comments, etc.) from an image.

.. important:: The GD image library automatically strips all metadata during processing,
so this method has no additional effect when using the GD handler.
This behavior is built into GD itself and cannot be modified.

Some essential technical metadata (dimensions, color depth) will be regenerated during save operations
as they're required for image display. However, all privacy-sensitive information such as GPS location,
camera details, and timestamps will be completely removed.

.. literalinclude:: images/015.php
6 changes: 6 additions & 0 deletions user_guide_src/source/libraries/images/015.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php

service('image', 'imagick')
->withFile('/path/to/image/mypic.jpg')
->clearMetadata()
->save('/path/to/new/image.jpg');
2 changes: 1 addition & 1 deletion utils/phpstan-baseline/loader.neon
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# total 3253 errors
# total 3255 errors
includes:
- argument.type.neon
- assign.propertyType.neon
Expand Down
7 changes: 6 additions & 1 deletion utils/phpstan-baseline/varTag.type.neon
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
# total 2 errors
# total 4 errors

parameters:
ignoreErrors:
-
message: '#^PHPDoc tag @var with type Imagick is not subtype of type resource\.$#'
count: 2
path: ../../tests/system/Images/ImageMagickHandlerTest.php

-
message: '#^PHPDoc tag @var with type Tests\\Support\\Entity\\UserWithCasts is not subtype of type list\<array\{\}\>\|null\.$#'
count: 1
Expand Down
Loading