Problem/Motivation

In D7 the contrib module imagecache_actions defines the "Change file format" image effect (submodule imagecache_colorations). This effect is able to alter the output format (and quality setting when the ImageMagick toolkit is in use) of an image derivative. However, in D8 in its current state, this is no longer possible.

What is the difference between D7 and D8 causing this?
In D7 you could get and change the image info array and the save method of the toolkit would use this info array to determine the image format to save to. In D8 the info array has been split over individual protected properties that are publicly exposed by getters only.

Note: this issue is part of #2105863: [meta] Images, toolkits and operations.

Proposed resolution

Instead of publicly exposing the info array or adding setters for the individual items in the info array, I suggest to add a saveOptions property or getSaveOptions() method to ImageInterfae. This property/method provides an alterable key-value store that can contain options that influence how an image is saved. Think of
- format (output as jpg, png or gif).
- quality for jpg.
- compression level and filters to use for png.
- progressive or not
- ...other?

This key-value store can be used by implementations of ImageToolkitInterface::save() to determine how to save the image.

The idea is that we change the API first with this additional property/method and then let toolkits decide what they accept, though we could (and should) document some keys that are foreseen (quality, format, compression, filter)

Why do it this way?
- This allows to fully utilize the abilities of the underlying toolkit, we are not restricting what parameters can be passed.
- It will make it easier to write a patch for issue #1310452: Allow to override the image toolkit's global JPEG/PNG quality value (or it will allow us to do it in contrib, if we fail to get that one committed in a timely manner).
- Time: do this API change now, fill in new options later (not being API changes, the latter can be done after beta).

Remarks:
- The png filter is defined differently by different toolkits. The PNG_FILTER_XXX constants are defined by GD and not available when GD is disabled and thus cannot be used by our image system. So a set of own constants that the different toolkits have to map themselves should
be part of introducing that option.
- This patch will only introduce the format and quality options as they are necessary to remove the regression. and don't need UI changes.
- I propose to introduce compression and filter in #1310452: Allow to override the image toolkit's global JPEG/PNG quality value as well as a UI (in image toolkit settings form) to define them.
- Setting to major as this is a regression from D7.

Remaining tasks

- review patch.

User interface changes

For now: none in the core UI.

API changes

Yes, but additions only.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

fietserwin’s picture

Issue summary: View changes
fietserwin’s picture

Status: Active » Needs review
Issue tags: -D7

First shot. Some remarks:
- It is not an argument to the save method but a property/method on Image(interface). This way, everyone working with an image, including image effects, have (r/w) access to the set of options.
- I chose to store the options in a KeyValue store. this may look like a bit of overkill, but as it is an object, it allows for easier alter access. If it would be an array, the getSaveOptions() method would return a copy of it, thus not alterable, unless we would work with an ugly return by reference.
- I'm still thinking what we have to test here and how to do that.

fietserwin’s picture

FileSize
3.59 KB

and the patch ...

claudiu.cristea’s picture

Priority: Major » Normal

So we agree to attach to Image kind of internal storage? Well I thought exactly the same and hope that this will work also as a general storage for internal processing (like resource for GD and so on...).

Before reviewing this I'm asking myself if this couldn't be merged with #2103621: Move GD logic from ImageInterface to toolkit, tie toolkit instance to Image instance, toolkits should no longer be instantiated separately — mostly after reading this from summary: "This allows to fully utilize the abilities of the underlying toolkit, we are not restricting what parameters can be passed".

Switched back to 'normal' till having an agreement on the solution.

claudiu.cristea’s picture

fietserwin’s picture

Priority: Normal » Major
Issue tags: +Regression

Please do not merge. This is a bug (regression from D7), the other issue is a task and is by no measure major or critical.

Note: changing back priority: not yet having an agreement on a solution doesn't mean it is not major.

... hope that this will work also as a general storage for internal processing (like resource for GD ...)

No. The saveOptions should be accessible from "outside" (image effects, image field) and thus should be in the interface.

For the other issue, I'm fine with toolkit objects adding their own properties to Image objects and PHP allows to do so (*), But these properties (e.g. resource for GD) should not be part of ImageInterface as they should not be publicly exposed. The only reason that we fall back to this behavior is because toolkit and image are not 1 class (as they should be).

*: Kind of bizarre, but this is allowed in PHP (as if all classes eventually extend from stdClass):

class A {
public $a;
}

$a = new A();
$a->a = 'a';
$a->b = 'b';
echo $a->a;
echo $a->b;
fietserwin’s picture

FileSize
8.22 KB

New patch, now including:
- documentation on supported options
- tests

I think this patch is feature, documentation and test complete. This should be ready to go after testing and reviewing.

claudiu.cristea’s picture

Please add an interdiff to each patch making easier to review the patch. Also, because I don't have a 100% understanding of the scope, can't you point us to a use case (even from D7)? That imagecache actions effect that would eventually need this.

fietserwin’s picture

Issue summary: View changes
dman’s picture

Status: Needs review » Reviewed & tested by the community

Code looks good. It's concise and looks like it applies to the right places.
On visual review, I can't see any problems this would be introducing, and architecturally, moving towards a set of arbitrary keyed default options that can be over-ridden is solid.

Some of the use cases - that ARE currently supported in D7 and are in use in the real world (hence regression) can be seen in the features list of imagecache_actions
* have different recompression parameters for different derivatives
* Change file type is the big one
* - as putting transparent rounded corners or masks on a JPG requires saving as PNG
* - as PNGs can be really large filesizes when generating a thumbnail
* - once upon a time I had SVG support (really! Only unsupported because it required too much server-side install instructions) and that can come back
* We allow PHP-code image convolution, so users can add their own algorithms for arbitrary effects. . If there is some need for parameters to be passed during the process, attaching them to the object is better than using globals..
* there is now provision for imagemagick to insert unpredicted wierdness during pre-processing, and then pull it out again at save time.

eg, if you need to set an environment variable that points to a system library or font folder or ??? then do things with that at the other end when about to save. This is convoluted, but I HAVE had to do that before, and leaving a storage array open for things we don't know is handy for future overloading. Previously, as described in the summary, I just threw anything into the $info array,

I've not thought through whether this patch or a similar one helps, but I have had requests to use different storage methods depending on the derivative. (eg full-size is 'private' or 'cdn') - this may not be directly the right place for that, and may even already be solved elsewhere (?) but it's a powerful use-case to consider.

I would like to actually RUN the code with this patch and see that an example of what it's doing does what I hope. I also would like to supply an eg of it in action - but that will require upgrading a harness module all the way up, so I dunno.
In the meantime
- this is a request for hook-ability in a reasonable place to give outside modules the control they need, and used to have in D7
Upgrading the modules that will use the optional feature will have to wait until it's practical to use it.

I'm going to thumbs-up this to move it along.

fietserwin’s picture

Thanks for reviewing.

Changing scheme or the extension part of the filename wont be possible with this patch and will require some more thoughts as it needs to go into 2 directions:
- given an image (name and location) and a style: what will be the URI of the derivative (currently in a theme function used by many modules - but also still computed by some other contrib modules and themes - and we have seen how many modules failed when the secure derivative token was introduced in 7.22)
- and on requesting an image derivative on a certain URI, what was the original (and under what scheme to find it) and what style needs to be applied?

dman’s picture

Yeah, lets not talk about that here. I was just blue-skying.

tim.plunkett’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/lib/Drupal/Core/Image/Image.php
    @@ -8,6 +8,7 @@
    +use Drupal\Core\KeyValueStore\MemoryStorage;
    
    @@ -100,6 +108,7 @@ class Image implements ImageInterface {
    +    $this->saveOptions = new MemoryStorage('image save options');
    

    A couple things about this:
    1) I think you should be using KeyValueMemoryFactory, not MemoryStorage directly.
    2) One step further, why not just use the 'keyvalue' service directly, and not worry about which implementation it is?
    3) 'image save options', without underscores, seems very strange.

  2. +++ b/core/modules/system/lib/Drupal/system/Plugin/ImageToolkit/GDToolkit.php
    @@ -223,6 +223,16 @@ public function load(ImageInterface $image) {
    +        'quality' => \Drupal::config('system.image.gd')->get('jpeg_quality'),
    

    Plugins can use dependency injection, see ContainerFactoryPluginInterface

fietserwin’s picture

Status: Needs work » Needs review

#13: Thanks for reviewing.
1) I was not aware of this factory or service, nor of any naming guidelines. thanks for mentioning. It did have more impact on the test setup than on the class itself.
2) This is existing code (only moved). However, I did change the toolkit to make use of dependency injection. I hope I did it in the right/logical way. I copied the config() method (in the base toolkit class) more or less from the ControllerBase. Not sure if the way I'm passing the config factory into the toolkit is THE way to do it. I hope you can confirm so or tell me how to do it differently.

fietserwin’s picture

FileSize
11.46 KB
5.26 KB

Shall I attach a patch as well...

Status: Needs review » Needs work

The last submitted patch, 15: 2168511-14.patch, failed testing.

fietserwin’s picture

FileSize
11.54 KB
662 bytes

Tests can reveal that you missed some instantiations of toolkits, often in the tests themselves, but also 1 in the image toolkit form.

fietserwin’s picture

Status: Needs work » Needs review
tim.plunkett’s picture

  1. +++ b/core/lib/Drupal/Core/Image/Image.php
    @@ -8,6 +8,7 @@
    +use Drupal\Core\KeyValueStore\MemoryStorage;
    

    Not needed now

  2. +++ b/core/lib/Drupal/Core/Image/Image.php
    @@ -100,6 +108,7 @@ class Image implements ImageInterface {
    +    $this->saveOptions = \Drupal::service('keyvalue')->get('image_save_options');
    

    This should be in ImageFactory, which is a service.

  3. +++ b/core/lib/Drupal/Core/ImageToolkit/ImageToolkitBase.php
    @@ -12,6 +12,27 @@
    +    $configFactory = $this->configuration['config_factory'];
    
    +++ b/core/lib/Drupal/Core/ImageToolkit/ImageToolkitManager.php
    @@ -72,6 +72,14 @@ public function getDefaultToolkit() {
    +    return parent::createInstance($plugin_id, $configuration + array('config_factory' => $this->configFactory));
    

    That's not exactly what I meant. You should use ContainerFactoryPluginInterface to do the injection. If you'd like me to jump in and fix it up, I can, I just didn't want to step on any toes.

Status: Needs review » Needs work

The last submitted patch, 17: 2168511-17.patch, failed testing.

fietserwin’s picture

No, you won't step on my toes, I'm eager to learn it, but right now I'm thinking into circles as I probably don't fully grasp the concepts factory, manager,, service, plugin, etc and their interactions yet. I see this ContainerFactoryPluginInterface, ContainerFactory, etc, but don't manage to assemble it in a neat way.

If you would do your own points 2 and 3, I will solve point 1 and the failing tests after that (if they are still failing by then). Thanks.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
11.9 KB
8.32 KB

I got about halfway done fixing the injection, when I thought "why oh why is the Image class now explicitly dependent on KeyValue?"
My last few comments were just on the implementation, but didn't question the architectural decisions.

Reading through the issue, it doesn't seem like there is any strong reason to couple this to KV. We use getter and setters in dozens of other places, and it keeps the architecture cleaner IMO.

I added an @todo in testGetSaveOptions, since it was previously just testing the KV system, and now is testing how arrays work...

tim.plunkett’s picture

FileSize
11.51 KB
5.05 KB

Whoops, forgot the other half: config factory. This is what I meant earlier.

The last submitted patch, 22: image-2168511-22.patch, failed testing.

tim.plunkett’s picture

The only thing that worries me is that toolkits are not forced to use getSaveOptions(), so even if someone set an option, a toolkit might skip it completely.

mondrake’s picture

Few comments

  1. +++ b/core/lib/Drupal/Core/Image/Image.php
    @@ -90,6 +90,13 @@ class Image implements ImageInterface {
    +   ....
    +  protected $saveOptions = array();
    +  .....
    +  public function getSaveOptions() {
    +  ....
    +  public function getSaveOption($key) {
    +  ....
    +  public function setSaveOptions(array $save_options) {
    +  ...
    +  public function setSaveOption($key, $save_option) {
    +  ...
    

    Why not making one abstraction step further and just name these $options, getOption/s(), setOption/s()? It has been discussed elsewhere that it would help to have in Image a place where contrib can store additional information about the image itself (example: define a background color to use across a sequence of image effects, so set it on in the first effect and carry it over - I have that use case in Textimage). More semantics though, the setup here would allow that anyway, just in that case the naming would be misleading.

  2. +++ b/core/modules/system/lib/Drupal/system/Plugin/ImageToolkit/GDToolkit.php
    @@ -31,7 +31,7 @@ public function settingsForm() {
    +      '#default_value' => $this->configFactory->get('system.image.gd')->get('jpeg_quality'),
    

    We could add a $config property to the toolkit implementation and set it up in its constructor to $this->config = $this->configFactory->get('system.image.gd'), then in the methods just call $this->config->get('jpeg_quality') to avoid repeating the ->get('system.image.gd') piece in all calls.

  3. +++ b/core/modules/system/lib/Drupal/system/Plugin/ImageToolkit/GDToolkit.php
    @@ -236,12 +246,12 @@ public function save(ImageInterface $image, $destination) {
    -    $function = 'image' . image_type_to_extension($image->getType(), FALSE);
    +    $function = 'image' . image_type_to_extension($options['format'], FALSE);
         if (!function_exists($function)) {
           return FALSE;
         }
         if ($image->getType() == IMAGETYPE_JPEG) {
    -      $success = $function($image->getResource(), $destination, \Drupal::config('system.image.gd')->get('jpeg_quality'));
    +      $success = $function($image->getResource(), $destination, $options['quality']);
         }
    

    In case we are changing the image format, shouldn't we be aligning for consistency also the relevant properties on the Image class ($source, $extension, $type, $mimeType)?

    E.g. we load 'public://image.jpg'. This will lead to

    $source = 'public://image.jpg'
    $extension = 'jpg'
    $type = IMAGETYPE_JPEG
    $mimeType = 'image/jpeg'

    then we set option 'format' = IMAGETYPE_PNG; I should expect that the image properties after save result in

    $source = 'public://image.png'
    $extension = 'png'
    $type = IMAGETYPE_PNG
    $mimeType = 'image/png'

    Isn't it?

  4. In general, wouldn't it make sense to have a convertFormat() method in the toolkit to cover this, instead of binding it to the save operation? That would cover concern in #25, plus allow some advanced stuff like e.g. load a jpeg, convert it to png to do some in-memory manipulation that involve transparency, then convert back to jpeg before saving to minimize file size.
fietserwin’s picture

FileSize
13.45 KB
5.75 KB

@tim.plunkett: thanks for fixing this.

re #22: My idea about using the KV stuff was, why redo something that is already available? Instead of adding 4 methods to the interface and the base class, we can do it with 1 property of a readily available utility class. But if you think that the KV stuff is too heavy or is not really meant as a general utility class, it may be better to not use it. (Though I think it would be a good idea to have such a (light weight) utility handy. How many code I have seen that implement get, getAll, set, setMultiple, unset, remove or whatever, etc, etc. In that sense, our 4 methods are not even 'complete').

re #22, what are we testing: It was previously testing that the property was returned by reference and thus was alterable. If the implementation would have been changed to an array, it would not have worked anymore, therefore that we now have added 2 alter methods. In its current state the tests do indeed not add much value. End to end tests that check that the options are set, passed and used cover that now: Remove these tests? i did int his patch, see interdiff.txt.

re #25: I guess that is always the case, you can ignore properties and, but to a lesser extend, parameters at will. That said, I think that dding an $options parameter to the save method, would make it more explicit. See interdiff.txt.

re #26.1: Sounds reasonable, but all those options will be passed to the save method, especially as we do not have an (explicit) unset on the options currently. Moreover, your use case would better be handled by changing the effect parameters directly to not make all effects depend on each other via an options property on the image. But for the rest, I have no strong opinion for one or the other

re #26.2: Another toolkit might need access to system.image.im (if a contrib module is allowed to use the system namespace at all) and I think that, eventually, we should move toolkit independent options (like jpeg quality) to system.image and keep system.image.gd for GD specific settings only (none so far, but IM in its current D7 way will have some). Conclusion: I think we should pass the whole config hierarchy and not only a part of it.

re #26.3: Good catch. Normal behavior is that the image object will go out of scope soon after being saved, but it should still be changed. In fact: this is a bug in setSource() as well and as setSource() is called after the call to save(), I solved it over there.

re #26.4: That covers 1 option, not the others. BTW: internally an image is normally full color, full transparency, etc. In GD you might have to explicitly set this, but this is already done on load() in the GdToolkit. In IM the image that is being operated on is always fully featured and only on save the number of colors is reduced or the transparency is removed (or flattened).

This patch:
- removes the test.
- solves the bug discovered by #26.3.
- adds an explicit $options parameter to save.

This patch does not (for now):
- rename to a more generic options (instead of saveOptions).

Questions:
- Is there a light weight KV store that we can use, because I would prefer such a utility over adding 4 methods for just 1 property?
- Can someone confirm that removing the test (introduced by an earlier patch) is OK?
- Can someone choose between the 2 ways (explicit $options parameter or options are on image that is already passed in)?

tim.plunkett’s picture

+++ b/core/modules/system/lib/Drupal/system/Plugin/ImageToolkit/GDToolkit.php
@@ -222,9 +222,9 @@ public function load(ImageInterface $image) {
-  public function save(ImageInterface $image, $destination) {
-    // Get the save options and complete them with some defaults.
-    $options = $image->getSaveOptions() + array(
+  public function save(ImageInterface $image, $destination, array $options) {
+    // Complete the options with defaults from the image and the config.
+    $options += array(

Oh this is sooo much better, and resolves my concern in #25.

fietserwin’s picture

That point is decided on then. Any opinion on the other questions/open points from #27?

fietserwin’s picture

Note for the next patch: as per #2211227: Refactor image and imagetoolkit: isExisting, isSupported, supportedTypes, getMimeType, comment 16: make getType() protected.

Eyal Shalev’s picture

Is there a reason why we don't pass the compression parameter to the imagepng function?

i.e.

    else if ($this->getType() == IMAGETYPE_PNG) {
      // Always save PNG images with full transparency.
      imagealphablending($image->getResource(), FALSE);
      imagesavealpha($this->getResource(), TRUE);
      $success = $function($image->getResource(), $destination, $options['compression']);
    } 
    else {
      $success = $function($image->getResource(), $destination);
    }
fietserwin’s picture

Because we currently only have a jpeg quality option, not a way to define compression and filter for PNG images. After this gets in, we can start adding save options for png, gif or whatever we need, and yes compression and filter are foreseen.

Eyal Shalev’s picture

Not having a visual interface to edit a png compression (/filters) is not an acceptable answer.
What if the compression variable won't enter core (from some reason or the other)?

The GD library should allow a 3rd party developer to use the png compression regardless.

I'm happy you say that it is foreseen.
Is there an active issue that deals with it?

fietserwin’s picture

See referenced by.

jhedstrom’s picture

Status: Needs review » Needs work
Issue tags: +Needs reroll

Patch no longer applies.

mondrake’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll +Needs issue summary update
FileSize
3.42 KB

A long way has gone since patch #27. #2330899: Allow image effects to change the MIME type + extension, add a "convert" image effect introduced image format conversion, and #2096703: Image toolkits should use PluginFormInterface and ContainerFactoryPluginInterface service injection in the toolkit.

IMHO, the only open point here is about allowing contrib to alter the JPEG image save quality at effect/operation level. Currently it is always fetched from the config.

My proposal would be to simply add to GDToolkit a protected property with getter/setter that contrib can use to change the JPEG quality. I do not see need to introduce the getter/setter on ImageToolkitInterface as contrib toolkits may address this differently.

fietserwin’s picture

I still would like contrib to be able to add more options:
- png: quality and filter (#1310452: Allow to override the image toolkit's global JPEG/PNG quality value is postponed on this issue).
- gif: transparency (treshold when converting from truecolor)
- webp: may have parameters and I think that during the lifetime of D8(.0) webp support will arrive in contrib
- other formats that contrib might be going to support may yet have other parameters.
- the convert format effect you mention may be simplified by this patch.

So for me, this patch is not what I was aiming for.

Moreover, doing it only on GD, and leaving iyt up to other contrib toolkits to do it their way, requires the contrib effect that is going to use this to implement toolkit specific operations for this, while it can easily be done without.

What do you think?

mgifford queued 36: 2168511-36.patch for re-testing.

Status: Needs review » Needs work

The last submitted patch, 36: 2168511-36.patch, failed testing.

catch’s picture

Category: Bug report » Task

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.8.x-dev

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.2.x-dev

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.15 was released on June 1st, 2022 and is the final full bugfix release for the Drupal 9.3.x series. Drupal 9.3.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.4.x-dev branch from now on, and new development or disruptive changes should be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.9 was released on December 7, 2022 and is the final full bugfix release for the Drupal 9.4.x series. Drupal 9.4.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.5.x-dev branch from now on, and new development or disruptive changes should be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.