diff --git a/core/core.services.yml b/core/core.services.yml index 6104db9..c434462 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -559,13 +559,9 @@ services: image.toolkit.manager: class: Drupal\Core\ImageToolkit\ImageToolkitManager arguments: ['@container.namespaces', '@cache.cache', '@language_manager', '@config.factory'] - image.toolkit: - class: Drupal\Core\ImageToolkit\ImageToolkitInterface - factory_method: getDefaultToolkit - factory_service: image.toolkit.manager image.factory: class: Drupal\Core\Image\ImageFactory - arguments: ['@image.toolkit'] + arguments: ['@image.toolkit.manager'] breadcrumb: class: Drupal\Core\Breadcrumb\BreadcrumbManager arguments: ['@module_handler'] diff --git a/core/lib/Drupal/Core/Image/Image.php b/core/lib/Drupal/Core/Image/Image.php index 136c81e..6d4da44 100644 --- a/core/lib/Drupal/Core/Image/Image.php +++ b/core/lib/Drupal/Core/Image/Image.php @@ -34,13 +34,6 @@ class Image implements ImageInterface { protected $toolkit; /** - * An image file handle. - * - * @var resource - */ - protected $resource; - - /** * Height, in pixels. * * @var int @@ -112,6 +105,16 @@ public function isSupported() { /** * {@inheritdoc} */ + public function isExisting() { + if (!$this->processed) { + $this->processInfo(); + } + return $this->processed; + } + + /** + * {@inheritdoc} + */ public function getExtension() { $this->processInfo(); return $this->extension; @@ -176,32 +179,6 @@ public function getMimeType() { /** * {@inheritdoc} */ - public function setResource($resource) { - $this->resource = $resource; - return $this; - } - - /** - * {@inheritdoc} - */ - public function hasResource() { - return (bool) $this->resource; - } - - /** - * {@inheritdoc} - */ - public function getResource() { - if (!$this->hasResource()) { - $this->processInfo(); - $this->toolkit->load($this); - } - return $this->resource; - } - - /** - * {@inheritdoc} - */ public function setSource($source) { $this->source = $source; return $this; @@ -224,6 +201,14 @@ public function getToolkitId() { /** * {@inheritdoc} */ + public function getToolkit() { + $this->processInfo(); + return $this->toolkit; + } + + /** + * {@inheritdoc} + */ public function save($destination = NULL) { if (empty($destination)) { $destination = $this->getSource(); diff --git a/core/lib/Drupal/Core/Image/ImageFactory.php b/core/lib/Drupal/Core/Image/ImageFactory.php index 27773d8..9ab8851 100644 --- a/core/lib/Drupal/Core/Image/ImageFactory.php +++ b/core/lib/Drupal/Core/Image/ImageFactory.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Image; use Drupal\Core\ImageToolkit\ImageToolkitInterface; +use Drupal\Core\ImageToolkit\ImageToolkitManager; /** * Provides a factory for image objects. @@ -15,33 +16,40 @@ class ImageFactory { /** - * The image toolkit to use for this factory. + * The image toolkit plugin manager. * - * @var \Drupal\Core\ImageToolkit\ImageToolkitInterface + * @var \Drupal\Core\ImageToolkit\ImageToolkitManager */ - protected $toolkit; + protected $toolkitManager; + + /** + * The image toolkit plugin id to use for this factory. + * + * @var string + */ + protected $toolkitId; /** * Constructs a new ImageFactory object. * - * @param \Drupal\Core\ImageToolkit\ImageToolkitInterface $toolkit - * The image toolkit to use for this image factory. + * @param \Drupal\Core\ImageToolkit\ImageToolkitManager $toolkit_manager + * The image toolkit plugin manager. */ - public function __construct(ImageToolkitInterface $toolkit) { - $this->toolkit = $toolkit; + public function __construct(ImageToolkitManager $toolkit_manager) { + $this->toolkitManager = $toolkit_manager; } /** - * Sets a custom image toolkit. + * Sets an image toolkit plugin id for use by the factory. * - * @param \Drupal\Core\ImageToolkit\ImageToolkitInterface $toolkit - * The image toolkit to use for this image factory. + * @param string $toolkit_id + * The image toolkit plugin id to use for this image factory. * * @return self - * Returns this image. + * Returns this image factory. */ - public function setToolkit(ImageToolkitInterface $toolkit) { - $this->toolkit = $toolkit; + public function setToolkitId($toolkit_id) { + $this->toolkitId = $toolkit_id; return $this; } @@ -55,7 +63,11 @@ public function setToolkit(ImageToolkitInterface $toolkit) { * The new Image object. */ public function get($source) { - return new Image($source, $this->toolkit); + if (!$this->toolkitId) { + $this->toolkitId = $this->toolkitManager->getDefaultToolkitId(); + } + $toolkit = $this->toolkitManager->createInstance($this->toolkitId); + return new Image($source, $toolkit); } } diff --git a/core/lib/Drupal/Core/Image/ImageInterface.php b/core/lib/Drupal/Core/Image/ImageInterface.php index 411d39a..ab07113 100644 --- a/core/lib/Drupal/Core/Image/ImageInterface.php +++ b/core/lib/Drupal/Core/Image/ImageInterface.php @@ -21,6 +21,14 @@ public function isSupported(); /** + * Checks if the image is existing. + * + * @return bool + * TRUE if the image exists, FALSE if it is invalid. + */ + public function isExisting(); + + /** * Returns the extension of the image file. * * @return string @@ -90,33 +98,6 @@ public function getType(); public function getMimeType(); /** - * Sets the image file resource. - * - * @param resource $resource - * The image file handle. - * - * @return self - * Returns this image file. - */ - public function setResource($resource); - - /** - * Determines if this image file has a resource set. - * - * @return bool - * TRUE if this image file has a resource set, FALSE otherwise. - */ - public function hasResource(); - - /** - * Retrieves the image file resource. - * - * @return resource - * The image file handle. - */ - public function getResource(); - - /** * Sets the source path of the image file. * * @param string $source @@ -136,6 +117,14 @@ public function setSource($source); public function getSource(); /** + * Returns the image toolkit used for this image file. + * + * @return string + * The image toolkit. + */ + public function getToolkit(); + + /** * Returns the ID of the image toolkit used for this image file. * * @return string diff --git a/core/lib/Drupal/Core/ImageToolkit/ImageToolkitException.php b/core/lib/Drupal/Core/ImageToolkit/ImageToolkitException.php new file mode 100644 index 0000000..c40bc33 --- /dev/null +++ b/core/lib/Drupal/Core/ImageToolkit/ImageToolkitException.php @@ -0,0 +1,15 @@ +resource value will populated by this call. + * @param string $source + * String specifying the path of the image file. + * @param string $details + * An array of image details. * * @return bool * TRUE or FALSE, based on success. */ - public function load(ImageInterface $image); + public function load($source, array $details); /** * Writes an image resource to a destination file. @@ -217,12 +224,18 @@ public function scaleAndCrop(ImageInterface $image, $width, $height); public function getInfo(ImageInterface $image); /** - * Verifies Image Toolkit is set up correctly. + * Gets toolkit requirements in a format suitable for hook_requirements(). * - * @return bool - * True if the GD toolkit is available on this machine. + * @return array + * An associative requirements array as is returned by hook_requirements(). + * If the toolkit claims no requirements to the system, returns an empty + * array. The array can have arbitrary keys and they do not have to be + * prefixed by e.g. the module name or toolkit ID, as the system will make + * the keys globally unique. + * + * @see hook_requirements() */ - public static function isAvailable(); + public function requirements(); /** * Returns a list of image types supported by the toolkit. diff --git a/core/lib/Drupal/Core/ImageToolkit/ImageToolkitManager.php b/core/lib/Drupal/Core/ImageToolkit/ImageToolkitManager.php index 43686ff..57db08d 100644 --- a/core/lib/Drupal/Core/ImageToolkit/ImageToolkitManager.php +++ b/core/lib/Drupal/Core/ImageToolkit/ImageToolkitManager.php @@ -45,50 +45,23 @@ public function __construct(\Traversable $namespaces, CacheBackendInterface $cac } /** - * Gets the default image toolkit. + * Gets the default image toolkit plugin id. * - * @return \Drupal\Core\ImageToolkit\ImageToolkitInterface - * Object of the default toolkit, or FALSE on error. - */ - public function getDefaultToolkit() { - $toolkit_id = $this->configFactory->get('system.image')->get('toolkit'); - $toolkits = $this->getAvailableToolkits(); - - if (!isset($toolkits[$toolkit_id]) || !class_exists($toolkits[$toolkit_id]['class'])) { - // The selected toolkit isn't available so return the first one found. If - // none are available this will return FALSE. - reset($toolkits); - $toolkit_id = key($toolkits); - } - - if ($toolkit_id) { - $toolkit = $this->createInstance($toolkit_id); - } - else { - $toolkit = FALSE; - } - - return $toolkit; - } - - /** - * Gets a list of available toolkits. + * @throws \Drupal\Core\ImageToolkit\ImageToolkitException + * If the toolkit plugin is not valid. * - * @return array - * An array with the toolkit names as keys and the descriptions as values. + * @return string + * Id of the default toolkit. */ - public function getAvailableToolkits() { - // Use plugin system to get list of available toolkits. + public function getDefaultToolkitId() { + $toolkit_id = $this->configFactory->get('system.image')->get('toolkit'); $toolkits = $this->getDefinitions(); - $output = array(); - foreach ($toolkits as $id => $definition) { - // Only allow modules that aren't marked as unavailable. - if (call_user_func($definition['class'] . '::isAvailable')) { - $output[$id] = $definition; - } + if (empty($toolkit_id) || !isset($toolkits[$toolkit_id]) || !class_exists($toolkits[$toolkit_id]['class'])) { + throw new ImageToolkitException('Images can not be processed because the Image toolkit is missing or not configured properly. Visit the Image toolkit configuration page to correct this.'); } - return $output; + return $toolkit_id; } + } diff --git a/core/modules/file/file.module b/core/modules/file/file.module index 8ac1514..adc97c7 100644 --- a/core/modules/file/file.module +++ b/core/modules/file/file.module @@ -414,7 +414,8 @@ function file_validate_is_image(File $file) { $image = \Drupal::service('image.factory')->get($file->getFileUri()); if (!$image->isSupported()) { - $toolkit = \Drupal::service('image.toolkit'); + $toolkit_manager = \Drupal::service('image.toolkit.manager'); + $toolkit = $toolkit_manager->createInstance($toolkit_manager->getDefaultToolkitId()); $extensions = array(); foreach ($toolkit->supportedTypes() as $image_type) { $extensions[] = Unicode::strtoupper(image_type_to_extension($image_type)); @@ -461,7 +462,7 @@ function file_validate_image_resolution(File $file, $maximum_dimensions = 0, $mi if ($image->getWidth() > $width || $image->getHeight() > $height) { // Try to resize the image to fit the dimensions. $image = $image_factory->get($file->getFileUri()); - if ($image->getResource()) { + if ($image->isExisting()) { $image->scale($width, $height); $image->save(); $file->filesize = $image->getFileSize(); diff --git a/core/modules/file/lib/Drupal/file/Tests/ValidatorTest.php b/core/modules/file/lib/Drupal/file/Tests/ValidatorTest.php index cfde9a3..4258a56 100644 --- a/core/modules/file/lib/Drupal/file/Tests/ValidatorTest.php +++ b/core/modules/file/lib/Drupal/file/Tests/ValidatorTest.php @@ -79,7 +79,7 @@ function testFileValidateImageResolution() { $this->assertEqual(count($errors), 1, 'Small images report an error.', 'File'); // Maximum size. - if ($this->container->has('image.toolkit')) { + if ($toolkit_id = $this->container->get('image.toolkit.manager')->getDefaultToolkitId()) { // Copy the image so that the original doesn't get resized. copy('core/misc/druplicon.png', 'temporary://druplicon.png'); $this->image->setFileUri('temporary://druplicon.png'); @@ -87,7 +87,7 @@ function testFileValidateImageResolution() { $errors = file_validate_image_resolution($this->image, '10x5'); $this->assertEqual(count($errors), 0, 'No errors should be reported when an oversized image can be scaled down.', 'File'); - $image = $this->container->get('image.factory')->get($this->image->getFileUri()); + $image = $this->container->get('image.factory')->setToolkitId($toolkit_id)->get($this->image->getFileUri()); $this->assertTrue($image->getWidth() <= 10, 'Image scaled to correct width.', 'File'); $this->assertTrue($image->getHeight() <= 5, 'Image scaled to correct height.', 'File'); diff --git a/core/modules/image/image.install b/core/modules/image/image.install index d860e97..a1f3970 100644 --- a/core/modules/image/image.install +++ b/core/modules/image/image.install @@ -29,30 +29,35 @@ function image_uninstall() { * @param $phase */ function image_requirements($phase) { - $requirements = array(); + if ($phase != 'runtime') { + return array(); + } - if ($phase == 'runtime') { - // Check for the PHP GD library. - if (function_exists('imagegd2')) { - $info = gd_info(); - $requirements['image_gd'] = array( - 'value' => $info['GD Version'], - ); + // Get info about the default toolkit. + $requirements = array( + 'image.toolkit' => array( + 'title' => t('Image toolkit'), + ), + ); + try { + $toolkit_manager = \Drupal::service('image.toolkit.manager'); + $toolkit = $toolkit_manager->createInstance($toolkit_manager->getDefaultToolkitId()); + $plugin_definition = $toolkit->getPluginDefinition(); + $requirements['image.toolkit']['value'] = $toolkit->getPluginId(); + $requirements['image.toolkit']['description'] = $plugin_definition['title']; + $requirements['image.toolkit']['severity'] = REQUIREMENT_INFO; + } + catch (\Drupal\Core\ImageToolkit\ImageToolkitException $e) { + $requirements['image.toolkit']['value'] = t('None'); + $requirements['image.toolkit']['description'] = t("No image toolkit is configured on the site. Check PHP installed extensions, or add other contributed toolkits that do not require a PHP extension, and make sure that a valid image toolkit is enabled."); + $requirements['image.toolkit']['severity'] = REQUIREMENT_ERROR; + return $requirements; + } - // Check for filter and rotate support. - if (!function_exists('imagefilter') || !function_exists('imagerotate')) { - $requirements['image_gd']['severity'] = REQUIREMENT_WARNING; - $requirements['image_gd']['description'] = t('The GD Library for PHP is enabled, but was compiled without support for functions used by the rotate and desaturate effects. It was probably compiled using the official GD libraries from http://www.libgd.org instead of the GD library bundled with PHP. You should recompile PHP --with-gd using the bundled GD library. See the PHP manual.', array('@url' => 'http://www.php.net/manual/book.image.php')); - } - } - else { - $requirements['image_gd'] = array( - 'value' => t('Not installed'), - 'severity' => REQUIREMENT_ERROR, - 'description' => t('The GD library for PHP is missing or outdated. Check the PHP image documentation for information on how to correct this.', array('@url' => 'http://www.php.net/manual/book.image.php')), - ); - } - $requirements['image_gd']['title'] = t('GD library rotate and desaturate effects'); + // Get requirements provided by the toolkit. + foreach ($toolkit->requirements() as $key => $requirement) { + $namespaced_key = 'image.toolkit.' . $toolkit->getPluginId() . '.' . $key; + $requirements[$namespaced_key] = $requirement; } return $requirements; @@ -254,6 +259,10 @@ function image_update_8002() { * array by adding alt, title, width, and height options. */ function image_update_8003() { + $config = \Drupal::config('system.image'); + if (!$config->get('toolkit')) { + $config->set('toolkit', 'gd'); + } $image_factory = \Drupal::service('image.factory'); foreach (array('field', 'instance') as $type) { $prefix = "field.$type"; diff --git a/core/modules/image/lib/Drupal/image/Entity/ImageStyle.php b/core/modules/image/lib/Drupal/image/Entity/ImageStyle.php index d67bcad..b357f4e 100644 --- a/core/modules/image/lib/Drupal/image/Entity/ImageStyle.php +++ b/core/modules/image/lib/Drupal/image/Entity/ImageStyle.php @@ -291,7 +291,7 @@ public function createDerivative($original_uri, $derivative_uri) { } $image = \Drupal::service('image.factory')->get($original_uri); - if (!$image->getResource()) { + if (!$image->isExisting()) { return FALSE; } diff --git a/core/modules/system/lib/Drupal/system/Form/ImageToolkitForm.php b/core/modules/system/lib/Drupal/system/Form/ImageToolkitForm.php index 93d073b..2c0de92 100644 --- a/core/modules/system/lib/Drupal/system/Form/ImageToolkitForm.php +++ b/core/modules/system/lib/Drupal/system/Form/ImageToolkitForm.php @@ -7,6 +7,8 @@ namespace Drupal\system\Form; +use Drupal\Core\Ajax\AjaxResponse; +use Drupal\Core\Ajax\HtmlCommand; use Drupal\Core\Config\ConfigFactory; use Drupal\Core\Config\Context\ContextInterface; use Drupal\Core\Form\ConfigFormBase; @@ -38,7 +40,7 @@ class ImageToolkitForm extends ConfigFormBase { public function __construct(ConfigFactory $config_factory, ContextInterface $context, ImageToolkitManager $manager) { parent::__construct($config_factory, $context); - foreach ($manager->getAvailableToolkits() as $id => $definition) { + foreach ($manager->getDefinitions() as $id => $definition) { $this->availableToolkits[$id] = $manager->createInstance($id); } } @@ -65,34 +67,63 @@ public function getFormId() { * {@inheritdoc} */ public function buildForm(array $form, array &$form_state) { - $current_toolkit = $this->configFactory->get('system.image')->get('toolkit'); + $current_toolkit = isset($form_state['values']['image_toolkit']) ? $form_state['values']['image_toolkit'] : $this->configFactory->get('system.image')->get('toolkit'); $form['image_toolkit'] = array( '#type' => 'radios', '#title' => t('Select an image processing toolkit'), '#default_value' => $current_toolkit, '#options' => array(), + '#ajax' => array( + 'callback' => array($this, 'processToolkitDetails'), + ), ); - // If we have available toolkits, allow the user to select the image toolkit - // to use and load the settings forms. + // Build options and load the settings forms of the current toolkit. foreach ($this->availableToolkits as $id => $toolkit) { $definition = $toolkit->getPluginDefinition(); $form['image_toolkit']['#options'][$id] = $definition['title']; - $form['image_toolkit_settings'][$id] = array( - '#type' => 'fieldset', - '#title' => t('@toolkit settings', array('@toolkit' => $definition['title'])), - '#collapsible' => TRUE, - '#collapsed' => ($id == $current_toolkit) ? FALSE : TRUE, - '#tree' => TRUE, - ); - $form['image_toolkit_settings'][$id] += $toolkit->settingsForm(); + if ($id == $current_toolkit) { + $form['image_toolkit_settings'][$id] = array( + '#type' => 'fieldset', + '#title' => t('@toolkit settings', array('@toolkit' => $definition['title'])), + '#tree' => TRUE, + ); + $form['image_toolkit_settings'][$id] += $toolkit->settingsForm(); + } } return parent::buildForm($form, $form_state); } /** + * Refreshes the form after a change of the selected toolkit. + */ + public function processToolkitDetails($form, $form_state) { + drupal_set_message($this->t('Click the Save configuration button to confirm the toolkit selection.'), 'warning'); + $response = new AjaxResponse(); + $status_messages = array('#theme' => 'status_messages'); + $response->addCommand(new HtmlCommand('#console', drupal_render($status_messages))); + $response->addCommand(new HtmlCommand('#system-image-toolkit-settings', drupal_render($form))); + return $response; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, array &$form_state) { + if ($form_state['triggering_element']['#name'] != 'op') { + return; + } + + // Call the form validation handler for the selected toolkit. + $toolkit = $this->availableToolkits[$form_state['values']['image_toolkit']]; + $toolkit->settingsFormValidate($form, $form_state); + + parent::validateForm($form, $form_state); + } + + /** * {@inheritdoc} */ public function submitForm(array &$form, array &$form_state) { @@ -100,11 +131,8 @@ public function submitForm(array &$form, array &$form_state) { ->set('toolkit', $form_state['values']['image_toolkit']) ->save(); - // Call the form submit handler for each of the toolkits. - // Get the toolkit settings forms. - foreach ($this->availableToolkits as $id => $toolkit) { - $toolkit->settingsFormSubmit($form, $form_state); - } + // Call the form submit handler for the selected toolkit. + $this->availableToolkits[$form_state['values']['image_toolkit']]->settingsFormSubmit($form, $form_state); parent::submitForm($form, $form_state); } diff --git a/core/modules/system/lib/Drupal/system/Plugin/ImageToolkit/GDToolkit.php b/core/modules/system/lib/Drupal/system/Plugin/ImageToolkit/GDToolkit.php index b58939e..0b9e2ec 100644 --- a/core/modules/system/lib/Drupal/system/Plugin/ImageToolkit/GDToolkit.php +++ b/core/modules/system/lib/Drupal/system/Plugin/ImageToolkit/GDToolkit.php @@ -23,6 +23,37 @@ class GDToolkit extends PluginBase implements ImageToolkitInterface { /** + * An image file handle. + * + * @var resource + */ + protected $resource; + + /** + * Sets the image file resource. + * + * @param resource $resource + * The image file handle. + * + * @return self + * Returns this image file. + */ + public function setResource($resource) { + $this->resource = $resource; + return $this; + } + + /** + * Retrieves the image file resource. + * + * @return resource + * The image file handle. + */ + public function getResource() { + return $this->resource; + } + + /** * {@inheritdoc} */ public function settingsForm() { @@ -41,6 +72,43 @@ public function settingsForm() { /** * {@inheritdoc} */ + public function settingsFormValidate(array &$form, array &$form_state) { + require_once DRUPAL_ROOT . '/core/includes/install.inc'; + $requirements = $this->requirements(); + foreach ($requirements as $requirement) { + $requirement['severity'] = isset($requirement['severity']) ? $requirement['severity'] : REQUIREMENT_OK; + switch ($requirement['severity']) { + case REQUIREMENT_ERROR: + $severity = 'error'; + break; + + case REQUIREMENT_WARNING: + $severity = 'warning'; + break; + + default: + $severity = 'status'; + break; + + } + $message = $requirement['title']; + if (isset($requirement['value'])) { + $message .= ' - ' . $requirement['value']; + } + if (isset($requirement['description'])) { + $message .= ' - ' . $requirement['description']; + } + drupal_set_message($message, $severity); + if ($severity == 'error') { + $plugin_definition = $this->getPluginDefinition(); + \Drupal::formBuilder()->setErrorByName('image_toolkit', $this->t('Selected toolkit %toolkit_title is invalid.', array('%toolkit_title' => $plugin_definition['title']))); + } + } + } + + /** + * {@inheritdoc} + */ public function settingsFormSubmit($form, &$form_state) { \Drupal::config('system.image.gd') ->set('jpeg_quality', $form_state['values']['gd']['image_jpeg_quality']) @@ -56,16 +124,17 @@ public function resize(ImageInterface $image, $width, $height) { $width = (int) round($width); $height = (int) round($height); - $res = $this->createTmp($image, $width, $height); + $res = $this->createTmp($image->getType(), $width, $height); - if (!imagecopyresampled($res, $image->getResource(), 0, 0, 0, 0, $width, $height, $image->getWidth(), $image->getHeight())) { + if (!imagecopyresampled($res, $this->getResource(), 0, 0, 0, 0, $width, $height, $image->getWidth(), $image->getHeight())) { return FALSE; } - imagedestroy($image->getResource()); + imagedestroy($this->getResource()); // Update image object. + $this + ->setResource($res); $image - ->setResource($res) ->setWidth($width) ->setHeight($height); return TRUE; @@ -87,39 +156,39 @@ public function rotate(ImageInterface $image, $degrees, $background = NULL) { for ($i = 16; $i >= 0; $i -= 8) { $rgb[] = (($background >> $i) & 0xFF); } - $background = imagecolorallocatealpha($image->getResource(), $rgb[0], $rgb[1], $rgb[2], 0); + $background = imagecolorallocatealpha($this->getResource(), $rgb[0], $rgb[1], $rgb[2], 0); } // Set the background color as transparent if $background is NULL. else { // Get the current transparent color. - $background = imagecolortransparent($image->getResource()); + $background = imagecolortransparent($this->getResource()); // If no transparent colors, use white. if ($background == 0) { - $background = imagecolorallocatealpha($image->getResource(), 255, 255, 255, 0); + $background = imagecolorallocatealpha($this->getResource(), 255, 255, 255, 0); } } // Images are assigned a new color palette when rotating, removing any // transparency flags. For GIF images, keep a record of the transparent color. if ($image->getType() == IMAGETYPE_GIF) { - $transparent_index = imagecolortransparent($image->getResource()); + $transparent_index = imagecolortransparent($this->getResource()); if ($transparent_index != 0) { - $transparent_gif_color = imagecolorsforindex($image->getResource(), $transparent_index); + $transparent_gif_color = imagecolorsforindex($this->getResource(), $transparent_index); } } - $image->setResource(imagerotate($image->getResource(), 360 - $degrees, $background)); + $this->setResource(imagerotate($this->getResource(), 360 - $degrees, $background)); // GIFs need to reassign the transparent color after performing the rotate. if (isset($transparent_gif_color)) { - $background = imagecolorexactalpha($image->getResource(), $transparent_gif_color['red'], $transparent_gif_color['green'], $transparent_gif_color['blue'], $transparent_gif_color['alpha']); - imagecolortransparent($image->getResource(), $background); + $background = imagecolorexactalpha($this->getResource(), $transparent_gif_color['red'], $transparent_gif_color['green'], $transparent_gif_color['blue'], $transparent_gif_color['alpha']); + imagecolortransparent($this->getResource(), $background); } $image - ->setWidth(imagesx($image->getResource())) - ->setHeight(imagesy($image->getResource())); + ->setWidth(imagesx($this->getResource())) + ->setHeight(imagesy($this->getResource())); return TRUE; } @@ -135,16 +204,17 @@ public function crop(ImageInterface $image, $x, $y, $width, $height) { $width = (int) round($width); $height = (int) round($height); - $res = $this->createTmp($image, $width, $height); + $res = $this->createTmp($image->getType(), $width, $height); - if (!imagecopyresampled($res, $image->getResource(), 0, 0, $x, $y, $width, $height, $width, $height)) { + if (!imagecopyresampled($res, $this->getResource(), 0, 0, $x, $y, $width, $height, $width, $height)) { return FALSE; } // Destroy the original image and return the modified image. - imagedestroy($image->getResource()); + imagedestroy($this->getResource()); + $this + ->setResource($res); $image - ->setResource($res) ->setWidth($width) ->setHeight($height); return TRUE; @@ -160,7 +230,7 @@ public function desaturate(ImageInterface $image) { return FALSE; } - return imagefilter($image->getResource(), IMG_FILTER_GRAYSCALE); + return imagefilter($this->getResource(), IMG_FILTER_GRAYSCALE); } /** @@ -202,19 +272,19 @@ public function scaleAndCrop(ImageInterface $image, $width, $height) { /** * {@inheritdoc} */ - public function load(ImageInterface $image) { - $function = 'imagecreatefrom' . image_type_to_extension($image->getType(), FALSE); - if (function_exists($function) && $resource = $function($image->getSource())) { - $image->setResource($resource); + public function load($source, array $details) { + $function = 'imagecreatefrom' . image_type_to_extension($details['type'], FALSE); + if (function_exists($function) && $resource = $function($source)) { + $this->resource = $resource; if (!imageistruecolor($resource)) { // Convert indexed images to true color, so that filters work // correctly and don't result in unnecessary dither. - $new_image = $this->createTmp($image, $image->getWidth(), $image->getHeight()); - imagecopy($new_image, $resource, 0, 0, 0, 0, $image->getWidth(), $image->getHeight()); + $new_image = $this->createTmp($details['type'], $details['width'], $details['height']); + imagecopy($new_image, $resource, 0, 0, 0, 0, $details['width'], $details['height']); imagedestroy($resource); - $image->setResource($new_image); + $this->resource = $new_image; } - return (bool) $image->getResource(); + return (bool) $this->resource; } return FALSE; @@ -242,15 +312,15 @@ public function save(ImageInterface $image, $destination) { return FALSE; } if ($image->getType() == IMAGETYPE_JPEG) { - $success = $function($image->getResource(), $destination, \Drupal::config('system.image.gd')->get('jpeg_quality')); + $success = $function($this->getResource(), $destination, \Drupal::config('system.image.gd')->get('jpeg_quality')); } else { // Always save PNG images with full transparency. if ($image->getType() == IMAGETYPE_PNG) { - imagealphablending($image->getResource(), FALSE); - imagesavealpha($image->getResource(), TRUE); + imagealphablending($this->getResource(), FALSE); + imagesavealpha($this->getResource(), TRUE); } - $success = $function($image->getResource(), $destination); + $success = $function($this->getResource(), $destination); } // Move temporary local file to remote destination. if (isset($permanent_destination) && $success) { @@ -275,14 +345,18 @@ public function getInfo(ImageInterface $image) { ); } + if ($details) { + $this->load($image->getSource(), $details); + } return $details; } /** * Creates a truecolor image preserving transparency from a provided image. * - * @param \Drupal\Core\Image\ImageInterface $image - * An image object. + * @param string $type + * An image type represented by a PHP IMAGETYPE_* constant (e.g. + * IMAGETYPE_JPEG, IMAGETYPE_PNG, etc.). * @param int $width * The new width of the new image, in pixels. * @param int $height @@ -291,16 +365,16 @@ public function getInfo(ImageInterface $image) { * @return resource * A GD image handle. */ - public function createTmp(ImageInterface $image, $width, $height) { + public function createTmp($type, $width, $height) { $res = imagecreatetruecolor($width, $height); - if ($image->getType() == IMAGETYPE_GIF) { + if ($type == IMAGETYPE_GIF) { // Grab transparent color index from image resource. - $transparent = imagecolortransparent($image->getResource()); + $transparent = imagecolortransparent($this->getResource()); if ($transparent >= 0) { // The original must have a transparent color, allocate to the new image. - $transparent_color = imagecolorsforindex($image->getResource(), $transparent); + $transparent_color = imagecolorsforindex($this->getResource(), $transparent); $transparent = imagecolorallocate($res, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']); // Flood with our new transparent color. @@ -308,7 +382,7 @@ public function createTmp(ImageInterface $image, $width, $height) { imagecolortransparent($res, $transparent); } } - elseif ($image->getType() == IMAGETYPE_PNG) { + elseif ($type == IMAGETYPE_PNG) { imagealphablending($res, FALSE); $transparency = imagecolorallocatealpha($res, 0, 0, 0, 127); imagefill($res, 0, 0, $transparency); @@ -325,14 +399,33 @@ public function createTmp(ImageInterface $image, $width, $height) { /** * {@inheritdoc} */ - public static function isAvailable() { - if ($check = get_extension_funcs('gd')) { - if (in_array('imagegd2', $check)) { - // GD2 support is available. - return TRUE; - } + public function requirements() { + $requirements = array(); + + $check = get_extension_funcs('gd'); + if (!$check || !in_array('imagegd2', $check)) { + $requirements['image_gd'] = array( + 'title' => t('GD Library'), + 'value' => t('Not installed'), + 'description' => t('The GD library for PHP is missing or outdated. Check the PHP image documentation for information on how to correct this.', array('@url' => 'http://www.php.net/manual/book.image.php')), + 'severity' => REQUIREMENT_ERROR, + ); + return $requirements; } - return FALSE; + + $info = gd_info(); + $requirements['rotate_and_desaturate'] = array( + 'title' => t('GD library rotate and desaturate effects'), + 'value' => $info['GD Version'], + ); + + // Check for filter and rotate support. + if (!function_exists('imagefilter') || !function_exists('imagerotate')) { + $requirements['rotate_and_desaturate']['severity'] = REQUIREMENT_WARNING; + $requirements['rotate_and_desaturate']['description'] = t('The GD Library for PHP is enabled, but was compiled without support for functions used by the rotate and desaturate effects. It was probably compiled using the official GD libraries from http://www.libgd.org instead of the GD library bundled with PHP. You should recompile PHP --with-gd using the bundled GD library. See the PHP manual.', array('@url' => 'http://www.php.net/manual/book.image.php')); + } + + return $requirements; } /** diff --git a/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitGdTest.php b/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitGdTest.php index 89b9645..ca1a9d1 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitGdTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitGdTest.php @@ -81,14 +81,15 @@ function colorsAreEqual($color_a, $color_b) { * Function for finding a pixel's RGBa values. */ function getPixelColor(ImageInterface $image, $x, $y) { - $color_index = imagecolorat($image->getResource(), $x, $y); + $toolkit = $image->getToolkit(); + $color_index = imagecolorat($toolkit->getResource(), $x, $y); - $transparent_index = imagecolortransparent($image->getResource()); + $transparent_index = imagecolortransparent($toolkit->getResource()); if ($color_index == $transparent_index) { return array(0, 0, 0, 127); } - return array_values(imagecolorsforindex($image->getResource(), $color_index)); + return array_values(imagecolorsforindex($toolkit->getResource(), $color_index)); } /** @@ -216,19 +217,19 @@ function testManipulations() { ); } - $toolkit = $this->container->get('image.toolkit.manager')->createInstance('gd'); - $image_factory = $this->container->get('image.factory')->setToolkit($toolkit); + $image_factory = $this->container->get('image.factory')->setToolkitId('gd'); foreach ($files as $file) { foreach ($operations as $op => $values) { // Load up a fresh image. $image = $image_factory->get(drupal_get_path('module', 'simpletest') . '/files/' . $file); + $toolkit = $image->getToolkit(); if (!$image) { $this->fail(String::format('Could not load image %file.', array('%file' => $file))); continue 2; } // All images should be converted to truecolor when loaded. - $image_truecolor = imageistruecolor($image->getResource()); + $image_truecolor = imageistruecolor($toolkit->getResource()); $this->assertTrue($image_truecolor, String::format('Image %file after load is a truecolor image.', array('%file' => $file))); if ($image->getType() == IMAGETYPE_GIF) { @@ -247,7 +248,7 @@ function testManipulations() { $correct_dimensions_object = TRUE; // Check the real dimensions of the image first. - if (imagesy($image->getResource()) != $values['height'] || imagesx($image->getResource()) != $values['width']) { + if (imagesy($toolkit->getResource()) != $values['height'] || imagesx($toolkit->getResource()) != $values['width']) { $correct_dimensions_real = FALSE; } diff --git a/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitTest.php b/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitTest.php index b9e0d32..7ff023b 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitTest.php @@ -20,14 +20,17 @@ public static function getInfo() { } /** - * Check that ImageToolkitManager::getAvailableToolkits() only returns - * available toolkits. + * Checks toolkits and their requirements. */ - function testGetAvailableToolkits() { + function testGetToolkits() { $manager = $this->container->get('image.toolkit.manager'); - $toolkits = $manager->getAvailableToolkits(); - $this->assertTrue(isset($toolkits['test']), 'The working toolkit was returned.'); - $this->assertFalse(isset($toolkits['broken']), 'The toolkit marked unavailable was not returned'); + $toolkits = $manager->getDefinitions(); + $this->assertTrue(isset($toolkits['test']), 'The test toolkit was found.'); + $toolkit = $manager->createInstance('test'); + $this->assertTrue($this->checkToolkitRequirements($toolkit), 'The test toolkit is valid.'); + $this->assertTrue(isset($toolkits['broken']), 'The broken toolkit was found.'); + $toolkit = $manager->createInstance('broken'); + $this->assertFalse($this->checkToolkitRequirements($toolkit), 'The broken toolkit is invalid.'); $this->assertToolkitOperationsCalled(array()); } diff --git a/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitTestBase.php index ce328aa..3e8c2b2 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitTestBase.php +++ b/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitTestBase.php @@ -9,6 +9,7 @@ use Drupal\simpletest\WebTestBase; use Drupal\Component\Utility\String; +use Drupal\Core\ImageToolkit\ImageToolkitInterface; /** * Base class for image manipulation testing. @@ -69,13 +70,33 @@ function setUp() { */ protected function getImage() { $image = $this->container->get('image.factory') - ->setToolkit($this->toolkit) + ->setToolkitId('test') ->get($this->file); - $image->getResource(); + $image->isExisting(); return $image; } /** + * Checks toolkit requirements. + * + * @param \Drupal\Core\ImageToolkit\ImageToolkitInterface\ImageToolkitInterface $toolkit + * The toolkit object to be checked. + */ + protected function checkToolkitRequirements(ImageToolkitInterface $toolkit) { + $check = $toolkit->requirements(); + if (empty($check)) { + return TRUE; + } + foreach ($check as $requirement) { + $requirement['severity'] = isset($requirement['severity']) ? $requirement['severity'] : REQUIREMENT_OK; + if ($requirement['severity'] == REQUIREMENT_ERROR) { + return FALSE; + } + } + return TRUE; + } + + /** * Assert that all of the specified image toolkit operations were called * exactly once once, other values result in failure. * diff --git a/core/modules/system/tests/modules/image_test/lib/Drupal/image_test/Plugin/ImageToolkit/BrokenToolkit.php b/core/modules/system/tests/modules/image_test/lib/Drupal/image_test/Plugin/ImageToolkit/BrokenToolkit.php index e5ead27..d415227 100644 --- a/core/modules/system/tests/modules/image_test/lib/Drupal/image_test/Plugin/ImageToolkit/BrokenToolkit.php +++ b/core/modules/system/tests/modules/image_test/lib/Drupal/image_test/Plugin/ImageToolkit/BrokenToolkit.php @@ -8,7 +8,7 @@ namespace Drupal\image_test\Plugin\ImageToolkit; /** - * Defines a Test toolkit for image manipulation within Drupal. + * Defines a broken test toolkit for image manipulation within Drupal. * * @ImageToolkit( * id = "broken", @@ -20,7 +20,15 @@ class BrokenToolkit extends TestToolkit { /** * {@inheritdoc} */ - public static function isAvailable() { - return FALSE; + public function requirements() { + $requirements = array(); + $requirements['broken_toolkit'] = array( + 'title' => t('Broken image toolkit'), + 'value' => t('Broken'), + 'description' => t('This toolkit is broken on purpose.'), + 'severity' => REQUIREMENT_ERROR, + ); + return $requirements; } + } diff --git a/core/modules/system/tests/modules/image_test/lib/Drupal/image_test/Plugin/ImageToolkit/TestToolkit.php b/core/modules/system/tests/modules/image_test/lib/Drupal/image_test/Plugin/ImageToolkit/TestToolkit.php index 15040a8..5320d42 100644 --- a/core/modules/system/tests/modules/image_test/lib/Drupal/image_test/Plugin/ImageToolkit/TestToolkit.php +++ b/core/modules/system/tests/modules/image_test/lib/Drupal/image_test/Plugin/ImageToolkit/TestToolkit.php @@ -37,6 +37,11 @@ public function settingsFormSubmit($form, &$form_state) {} /** * {@inheritdoc} */ + public function settingsFormValidate(array &$form, array &$form_state) {} + + /** + * {@inheritdoc} + */ public function getInfo(ImageInterface $image) { $this->logCall('get_info', array($image)); @@ -52,15 +57,18 @@ public function getInfo(ImageInterface $image) { ); } + if ($details) { + $this->load($image->getSource(), $details); + } return $details; } /** * {@inheritdoc} */ - public function load(ImageInterface $image) { - $this->logCall('load', array($image)); - return $image; + public function load($source, array $details) { + $this->logCall('load', array($source, $details)); + return TRUE; } /** @@ -142,8 +150,8 @@ protected function logCall($op, $args) { /** * {@inheritdoc} */ - public static function isAvailable() { - return TRUE; + public function requirements() { + return array(); } /** diff --git a/core/tests/Drupal/Tests/Core/Image/ImageTest.php b/core/tests/Drupal/Tests/Core/Image/ImageTest.php index 728af5c..82603eb 100644 --- a/core/tests/Drupal/Tests/Core/Image/ImageTest.php +++ b/core/tests/Drupal/Tests/Core/Image/ImageTest.php @@ -138,26 +138,12 @@ public function testGetMimeType() { } /** - * Tests \Drupal\Core\Image\Image::setResource(). - */ - public function testSetResource() { - $resource = fopen($this->image->getSource(), 'r'); - $this->image->setResource($resource); - $this->assertEquals($this->image->getResource(), $resource); - - // Force \Drupal\Core\Image\Image::hasResource() to return FALSE. - $this->image->setResource(FALSE); - $this->assertNotNull($this->image->getResource()); - } - - /** - * Tests \Drupal\Core\Image\Image::hasResource(). + * Tests \Drupal\Core\Image\Image::isExisting(). */ public function testHasResource() { - $this->assertFalse($this->image->hasResource()); + $this->assertTrue($this->image->isExisting()); $resource = fopen($this->image->getSource(), 'r'); - $this->image->setResource($resource); - $this->assertTrue($this->image->hasResource()); + $this->assertTrue(!empty($resource)); } /**