Skip to content

Conversation

@danloveg
Copy link
Contributor

@danloveg danloveg commented Oct 6, 2025

Closes #2163

  • Installs the imagick extension in the application image.
  • Adds a new sfImagickAdapter that can be used with the sfThumbnail class.
  • Adds deprecation warnings to methods using pdfinfo, convert, or identify with sfImageMagickAdapter. The old adapter still works, but the new adapter is used if imagick is installed.
  • Refactored exploding multi-page assets to use imagick if it's installed.

Things that are not complete:

  • I have not created a policy.xml file, I wanted some feedback on this PR before looking into that.
  • This PR does not update any of the deployment documentation or any Ansible scripts.
  • Unit tests. (Tests added)
  • Copyright block at the top of the new adapter? I added a new file to a plugin that was not developed at Artefactual, not sure what sort of copyright case that falls under. I'd guess the regular AtoM copyright block but I'm not sure whether the licenses are compatible. (Added the default AtoM copyright block based on CODE_OF_CONDUCT.md)

Also of note, I did not implement the fancy thumbnail method that sfImageMagickAdapter does since I couldn't see any place where those custom methods were being used.

@sarah-mason sarah-mason added Community Pull requests that have been contributed from community members outside Artefactual work-in-progress labels Oct 7, 2025
Copy link
Member

@sbreker sbreker left a comment

Choose a reason for hiding this comment

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

Hi Daniel - thank you so much for this code contribution! I have added some additional feedback regarding the memcache change, and the logic to fall back to using ImageMagick via exec(). Please let me know if there is anything I should be considering with respect to these requests. Interested to hear your thoughts.

$pages = sfImageMagickAdapter::getPdfinfoPageCount($filename);
} else {
$command = 'identify '.$filename;
} elseif (sfImageMagickAdapter::isImageMagickAvailable()) {
Copy link
Member

Choose a reason for hiding this comment

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

I've been thinking about the logic to fall back to using ImageMagick in the event that the Imagick PHP extension is not present. I think that while this may provide some convenience with respect to AtoM upgrade requirements, I think it is more important to completely remove the original way of calling ImageMagick with exec() calls and rely solely on your new work to introduce Imagick. Removing the calls to exec() entirely will avoid triggering alerts in future security scans and remove the risk associated with these calls. In this case, if we remove all dependency on sfImageMagickAdapter.class.php, then we might be able to delete sfImageMagickAdapter.class.php entirely (I have not checked if there are other locations where it is referenced - but removing it would have implications for the PDF page count methods in there).

Copy link
Contributor Author

@danloveg danloveg Nov 4, 2025

Choose a reason for hiding this comment

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

That would certainly simplify things in the code. One issue I can see with that change is that we'll want to ensure the upgrade instructions are up to date for whichever version of AtoM this ends up in since php8.3-imagick will be a dependency requirement , and imagemagick will no longer be required.

The imagick adapter handles PDF page counts without the use of Poppler (i.e., pdfinfo), having spent some time with this part of the code, I think completely removing sfImageMagickAdapter shouldn't be an issue at all.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I made some commits for what it would look like without sfImageMagickAdapter, let me know what you think.

If needed, I can revert this commit range to bring it back:

d29c313..5438fc5

$pages = null;

if ($this->canThumbnail()) {
if (extension_loaded('imagick')) {
Copy link
Member

Choose a reason for hiding this comment

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

This gets checked a number of times to ensure if imagick is available. Perhaps this should be removed to be a method in sfImagickAdapter like sfImageMagickAdapter does. I think Imagick also requires ImageMagick to be installed, so perhaps a a method like sfImagickAdapter::isImagickAvailable() could encapsulate the logic to check that both Imagick and ImageMagick are present. Let me know what you think.

Copy link
Contributor Author

@danloveg danloveg Nov 4, 2025

Choose a reason for hiding this comment

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

The two places I have this are in those methods that fall back to using sfImageMagickAdapter when the extension isn't loaded - if we completely remove the old adapter, then there'll be no need to call extension_loaded('imagick').

And Imagick doesn't require ImageMagick to be installed, the ImageMagick C code is directly compiled into the imagick extension when it's built. See here, the MagickWand C API is included and that's how the C gets compiled into the resulting PHP extension. The only thing that's needed is the imagemagick-dev package for building the extension. In Ubuntu, that won't be needed since there is already a pre-built package available: php8.3-imagick.

EDIT: You do still need imagemagick installed, I misunderstood how it works! The extension fails to be loaded if imagemagick is not installed.

try
{
return call_user_func_array('QubitObject::__get', $args);
return parent::__get(...$args);
Copy link
Member

Choose a reason for hiding this comment

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

I think this file is generated by Symfony. Was this change the result of running a php symfony commands?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can't remember why I made that change initially, but things are still working with that change reverted.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I take that back - I remember why I made that change now. It's because if you change it back to how it was, this is how the unit test output looks:

deprecated-warnings

$command .= $path;
$command .= ' '.$filenameMinusExtension.'_%02d.'.self::THUMB_EXTENSION;
exec($command, $output, $status);
$imagick = new Imagick();
Copy link
Member

Choose a reason for hiding this comment

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

We should check that extension_loaded('imagick') is true before and issue some kind of error if it is not.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This function will only attempt to instantiate an Imagick instance now if the imagick extension is loaded: 4a92bf2

exec($command, $output, $status);
$pages = count($output);
}
$im = new Imagick();
Copy link
Member

Choose a reason for hiding this comment

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

We should check that extension_loaded('imagick') is true before and issue some kind of error if it is not.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This function will only attempt to instantiate an Imagick instance now if the imagick extension is loaded: 4a92bf2

if (!$adapterClass) {
if (extension_loaded('gd')) {
$adapterClass = 'sfGDAdapter';
} elseif (extension_loaded('imagick')) {
Copy link
Member

Choose a reason for hiding this comment

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

Above in QubitDigitalObject.php we preferred Imagick over 'gd' but here we prefer 'gd' which could result in strange behaviour. It might be better to try loading first using Imagick here .

See:
#2194 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in d6de81e

$path = $this->getAbsolutePath();
}

$filenameMinusExtension = preg_replace('/\.[a-zA-Z]{2,3}$/', '', $path);
Copy link
Member

Choose a reason for hiding this comment

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

Unrelated to your change but this parsing of extensions seems brittle. We should consider changing to use pathinfo($path, PATHINFO_DIRNAME|PATHINFO_FILENAME|etc).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I changed this line to use pathinfo: c721411

public function freeSource()
{
if (null !== $this->source) {
$this->source->clear();
Copy link
Member

Choose a reason for hiding this comment

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

We should perhaps also $this->source = null after calling clear() to ensure the PHP clears the memory.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in d6de81e

This is based on the sfImageMagickAdapter. I opted not to use pdfinfo so
that there is no exec() going on in this new adapter.
This has to be set *before* reading an image.
I needed to remove it from three places:

1. From the digital object derivatives setting action. imagick handles
   PDF page extraction, not pdfinfo any more. So I changed the error
   message to reflect that.
2. From the QubitDigitalObject class. I was using the
   sfImageMagickAdapter as a fallback for sfImagickAdapter, so I removed
   those fallback cases.
3. From the lime unit tests in the sfThumbnailPlugin.

The unit tests do run successfully in the sfThumbnailPlugin class, but
it's tedious to run them.

In case you want to run them, you need to run these commands in the
Docker container to install gd.

    apk add libpng-dev libjpeg-turbo-dev
    docker-php-ext-configure gd --with-jpeg
    docker-php-ext-install gd

This is because the tests *require* you to have gd installed, and gd is
not installed in the base AtoM image.

And then to run the tests, you invoke the test file directly:

    php plugins/sfThumbnailPlugin/test/unit/sfThumbnailTest.php

You'll see this when the tests run:

    # Looks like everything went fine.
    # Looks like everything went fine.
It's no longer needed!

Also, added a note to the README of the plugin since the way the plugin
works has changed a lot.
@danloveg danloveg force-pushed the dev/2163-switch-to-imagick-library branch from f413650 to 49302a5 Compare December 22, 2025 20:19
@danloveg danloveg marked this pull request as ready for review December 22, 2025 22:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Community Pull requests that have been contributed from community members outside Artefactual work-in-progress

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enhancement: Use Imagick PHP extension instead of calling ImageMagick directly

3 participants