diff --git a/system/Images/Handlers/BaseHandler.php b/system/Images/Handlers/BaseHandler.php index de3e15974a1b..5996be0b52ba 100644 --- a/system/Images/Handlers/BaseHandler.php +++ b/system/Images/Handlers/BaseHandler.php @@ -769,4 +769,17 @@ public function getHeight() { return ($this->resource !== null) ? $this->_getHeight() : $this->height; } + + /** + * Clears image metadata. + * + * This method has no use in the GDHandler, + * since all the data are cleared automatically. + * + * GDHandler can't preserve the image metadata. + */ + public function clearMetadata(): static + { + return $this; + } } diff --git a/system/Images/Handlers/ImageMagickHandler.php b/system/Images/Handlers/ImageMagickHandler.php index 08886937a4b1..2e8b56acd858 100644 --- a/system/Images/Handlers/ImageMagickHandler.php +++ b/system/Images/Handlers/ImageMagickHandler.php @@ -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; + } } diff --git a/tests/system/Images/GDHandlerTest.php b/tests/system/Images/GDHandlerTest.php index 121ce040904a..d85d1a142ffd 100644 --- a/tests/system/Images/GDHandlerTest.php +++ b/tests/system/Images/GDHandlerTest.php @@ -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); + } } diff --git a/tests/system/Images/ImageMagickHandlerTest.php b/tests/system/Images/ImageMagickHandlerTest.php index 924095253e48..be6bee2cf798 100644 --- a/tests/system/Images/ImageMagickHandlerTest.php +++ b/tests/system/Images/ImageMagickHandlerTest.php @@ -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)); + } } diff --git a/user_guide_src/source/changelogs/v4.7.0.rst b/user_guide_src/source/changelogs/v4.7.0.rst index eb12d62068cb..87f0b6c94f80 100644 --- a/user_guide_src/source/changelogs/v4.7.0.rst +++ b/user_guide_src/source/changelogs/v4.7.0.rst @@ -59,6 +59,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 ===================== diff --git a/user_guide_src/source/libraries/images.rst b/user_guide_src/source/libraries/images.rst index f263eb173d06..b2b6b0238942 100644 --- a/user_guide_src/source/libraries/images.rst +++ b/user_guide_src/source/libraries/images.rst @@ -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 diff --git a/user_guide_src/source/libraries/images/015.php b/user_guide_src/source/libraries/images/015.php new file mode 100644 index 000000000000..446deab2d35f --- /dev/null +++ b/user_guide_src/source/libraries/images/015.php @@ -0,0 +1,6 @@ +withFile('/path/to/image/mypic.jpg') + ->clearMetadata() + ->save('/path/to/new/image.jpg'); diff --git a/utils/phpstan-baseline/loader.neon b/utils/phpstan-baseline/loader.neon index 1f7ef749cff2..805dc4dfa964 100644 --- a/utils/phpstan-baseline/loader.neon +++ b/utils/phpstan-baseline/loader.neon @@ -1,4 +1,4 @@ -# total 3314 errors +# total 3316 errors includes: - argument.type.neon - assign.propertyType.neon diff --git a/utils/phpstan-baseline/varTag.type.neon b/utils/phpstan-baseline/varTag.type.neon index 4be1089ace00..099fce1db55c 100644 --- a/utils/phpstan-baseline/varTag.type.neon +++ b/utils/phpstan-baseline/varTag.type.neon @@ -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\\|null\.$#' count: 1