N°8834 - Add compatibility with PHP 8.4 (#819)

* N°8834 - Add compatibility with PHP 8.4

* Rollback of scssphp/scssphp version upgrade due to compilation error
This commit is contained in:
Lenaick
2026-02-26 10:36:32 +01:00
committed by GitHub
parent d4821b7edc
commit fc967c06ce
961 changed files with 12298 additions and 7130 deletions

View File

@@ -1,9 +1,10 @@
# Emogrifier
[![Build Status](https://github.com/MyIntervals/emogrifier/workflows/CI/badge.svg?branch=main)](https://github.com/MyIntervals/emogrifier/actions/)
[![Build Status](https://github.com/MyIntervals/emogrifier/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/MyIntervals/emogrifier/actions/)
[![Latest Stable Version](https://poser.pugx.org/pelago/emogrifier/v/stable.svg)](https://packagist.org/packages/pelago/emogrifier)
[![Total Downloads](https://poser.pugx.org/pelago/emogrifier/downloads.svg)](https://packagist.org/packages/pelago/emogrifier)
[![License](https://poser.pugx.org/pelago/emogrifier/license.svg)](https://packagist.org/packages/pelago/emogrifier)
[![Coverage Status](https://coveralls.io/repos/github/MyIntervals/emogrifier/badge.svg?branch=main)](https://coveralls.io/github/MyIntervals/emogrifier?branch=main)
_n. e•mog•ri•fi•er [\ē-'mä-grƏ-,fī-Ər\] - a utility for changing completely the
nature or appearance of HTML email, esp. in a particularly fantastic or bizarre
@@ -31,6 +32,7 @@ into inline style attributes in your HTML code.
- [Usage](#usage)
- [Supported CSS selectors](#supported-css-selectors)
- [Caveats](#caveats)
- [Contributing](#contributing)
- [Steps to release a new version](#steps-to-release-a-new-version)
- [Maintainers](#maintainers)
@@ -157,6 +159,58 @@ $visualHtml = CssToAttributeConverter::fromDomDocument($domDocument)
->convertCssToVisualAttributes()->render();
```
### Evaluating CSS custom properties (variables)
The `CssVariableEvaluator` class can be used to apply the values of CSS
variables defined in inline style attributes to inline style properties which
use them.
For example, the following CSS defines and uses a custom property:
```css
:root {
--text-color: green;
}
p {
color: var(--text-color);
}
```
After `CssInliner` has inlined that CSS on the (contrived) HTML
`<html><body><p></p></body></html>`, it will look like this:
```html
<html style="--text-color: green;">
<body>
<p style="color: var(--text-color);">
<p>
</body>
</html>
```
The `CssVariableEvaluator` method `evaluateVariables` will apply the value of
`--text-color` so that the paragraph `style` attribute becomes `color: green;`.
It can be used like this:
```php
use Pelago\Emogrifier\HtmlProcessor\CssVariableEvaluator;
$evaluatedHtml = CssVariableEvaluator::fromHtml($html)
->evaluateVariables()->render();
```
You can also have the ` CssVariableEvaluator ` work on a `DOMDocument`:
```php
$evaluatedHtml = CssVariableEvaluator::fromDomDocument($domDocument)
->evaluateVariables()->render();
```
### Removing redundant content and attributes from the HTML
The `HtmlPruner` class can reduce the size of the HTML by removing elements with
@@ -174,11 +228,11 @@ $prunedHtml = HtmlPruner::fromHtml($html)->removeElementsWithDisplayNone()
->removeRedundantClasses($classesToKeep)->render();
```
The `removeRedundantClasses` method accepts a whitelist of names of classes that
should be retained. If this is a post-processing step after inlining CSS, you
can alternatively use `removeRedundantClassesAfterCssInlined`, passing it the
`CssInliner` instance that has inlined the CSS (and having the `HtmlPruner` work
on the `DOMDocument`). This will use information from the `CssInliner` to
The `removeRedundantClasses` method accepts an allowlist of names of classes
that should be retained. If this is a post-processing step after inlining CSS,
you can alternatively use `removeRedundantClassesAfterCssInlined`, passing it
the `CssInliner` instance that has inlined the CSS (and having the `HtmlPruner`
work on the `DOMDocument`). This will use information from the `CssInliner` to
determine which classes are still required (namely, those used in uninlinable
rules that have been copied to a `<style>` element):
@@ -189,16 +243,16 @@ $prunedHtml = HtmlPruner::fromDomDocument($cssInliner->getDomDocument())
```
The `removeElementsWithDisplayNone` method will not remove any elements which
have the class `-emogrifier-keep`. So if, for example, there are elements which
have the class `-emogrifier-keep`. So if, for example, there are elements which
by default have `display: none` but are revealed by an `@media` rule, or which
are intended as a preheader, you can add that class to those elements. The
are intended as a preheader, you can add that class to those elements. The
paragraph in this HTML snippet will not be removed even though it has
`display: none` (which has presumably been applied by `CssInliner::inlineCss()`
from a CSS rule `.preheader { display: none; }`):
```html
<p class="preheader -emogrifier-keep" style="display: none;">
Hello World!
Hello World!
</p>
```
@@ -229,15 +283,32 @@ calling the `inlineCss` method:
* `->removeAllowedMediaType(string $mediaName)` - You can use this
method to remove media types that Emogrifier keeps.
* `->addExcludedSelector(string $selector)` - Keeps elements from
being affected by CSS inlining. Note that only elements matching the supplied
being affected by CSS inlining. Note that only elements matching the supplied
selector(s) will be excluded from CSS inlining, not necessarily their
descendants. If you wish to exclude an entire subtree, you should provide
descendants. If you wish to exclude an entire subtree, you should provide
selector(s) which will match all elements in the subtree, for example by using
the universal selector:
```php
$cssInliner->addExcludedSelector('.message-preview');
$cssInliner->addExcludedSelector('.message-preview *');
```
* `->addExcludedCssSelector(string $selector)` - Contrary to
`addExcludedSelector`, which excludes HTML nodes, this method excludes CSS
selectors from being inlined. This is for example useful if you don't want
your CSS reset rules to be inlined on each HTML node (e.g.
`* { margin: 0; padding: 0; font-size: 100% }`).
Note that these selectors must precisely match the selectors you wish to
exclude.
Meaning that excluding `.example` will not exclude `p .example`.
```php
$cssInliner->addExcludedCssSelector('*');
$cssInliner->addExcludedCssSelector('form');
```
* `->removeExcludedCssSelector(string $selector)` - Removes previously added
excluded selectors, if any.
```php
$cssInliner->removeExcludedCssSelector('form');
```
### Migrating from the dropped `Emogrifier` class to the `CssInliner` class
@@ -286,11 +357,11 @@ $html = CssToAttributeConverter::fromDomDocument($domDocument)
Emogrifier currently supports the following
[CSS selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors):
* [type](https://developer.mozilla.org/en-US/docs/Web/CSS/Type_selectors)
* [class](https://developer.mozilla.org/en-US/docs/Web/CSS/Class_selectors)
* [ID](https://developer.mozilla.org/en-US/docs/Web/CSS/ID_selectors)
* [universal](https://developer.mozilla.org/en-US/docs/Web/CSS/Universal_selectors)
* [attribute](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors):
* [type](https://developer.mozilla.org/en-US/docs/Web/CSS/Type_selectors)
* [class](https://developer.mozilla.org/en-US/docs/Web/CSS/Class_selectors)
* [ID](https://developer.mozilla.org/en-US/docs/Web/CSS/ID_selectors)
* [universal](https://developer.mozilla.org/en-US/docs/Web/CSS/Universal_selectors)
* [attribute](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors):
* presence
* exact value match
* value with `~` (one word within a whitespace-separated list of words)
@@ -298,68 +369,71 @@ Emogrifier currently supports the following
* value with `^` (prefix match)
* value with `$` (suffix match)
* value with `*` (substring match)
* [adjacent](https://developer.mozilla.org/en-US/docs/Web/CSS/Adjacent_sibling_selectors)
* [general sibling](https://developer.mozilla.org/en-US/docs/Web/CSS/General_sibling_combinator)
* [child](https://developer.mozilla.org/en-US/docs/Web/CSS/Child_selectors)
* [descendant](https://developer.mozilla.org/en-US/docs/Web/CSS/Descendant_selectors)
* [pseudo-classes](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes):
* [empty](https://developer.mozilla.org/en-US/docs/Web/CSS/:empty)
* [first-child](https://developer.mozilla.org/en-US/docs/Web/CSS/:first-child)
* [first-of-type](https://developer.mozilla.org/en-US/docs/Web/CSS/:first-of-type)
(with a type, e.g. `p:first-of-type` but not `*:first-of-type`)
* [last-child](https://developer.mozilla.org/en-US/docs/Web/CSS/:last-child)
* [last-of-type](https://developer.mozilla.org/en-US/docs/Web/CSS/:last-of-type)
(with a type)
* [not()](https://developer.mozilla.org/en-US/docs/Web/CSS/:not)
* [nth-child()](https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-child)
* [nth-last-child()](https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-last-child)
* [nth-last-of-type()](https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-last-of-type)
(with a type)
* [nth-of-type()](https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-of-type)
(with a type)
* [only-child](https://developer.mozilla.org/en-US/docs/Web/CSS/:only-child)
* [only-of-type](https://developer.mozilla.org/en-US/docs/Web/CSS/:only-of-type)
(with a type)
* [adjacent](https://developer.mozilla.org/en-US/docs/Web/CSS/Adjacent_sibling_selectors)
* [general sibling](https://developer.mozilla.org/en-US/docs/Web/CSS/General_sibling_combinator)
* [child](https://developer.mozilla.org/en-US/docs/Web/CSS/Child_selectors)
* [descendant](https://developer.mozilla.org/en-US/docs/Web/CSS/Descendant_selectors)
* [pseudo-classes](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes):
* [empty](https://developer.mozilla.org/en-US/docs/Web/CSS/:empty)
* [first-child](https://developer.mozilla.org/en-US/docs/Web/CSS/:first-child)
* [first-of-type](https://developer.mozilla.org/en-US/docs/Web/CSS/:first-of-type)
(with a type, e.g. `p:first-of-type` but not `*:first-of-type`)
* [last-child](https://developer.mozilla.org/en-US/docs/Web/CSS/:last-child)
* [last-of-type](https://developer.mozilla.org/en-US/docs/Web/CSS/:last-of-type)
(with a type)
* [not()](https://developer.mozilla.org/en-US/docs/Web/CSS/:not)
* [nth-child()](https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-child)
* [nth-last-child()](https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-last-child)
* [nth-last-of-type()](https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-last-of-type)
(with a type)
* [nth-of-type()](https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-of-type)
(with a type)
* [only-child](https://developer.mozilla.org/en-US/docs/Web/CSS/:only-child)
* [only-of-type](https://developer.mozilla.org/en-US/docs/Web/CSS/:only-of-type)
(with a type)
* [root](https://developer.mozilla.org/en-US/docs/Web/CSS/:root)
The following selectors are not implemented yet:
* [case-insensitive attribute value](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors#case-insensitive)
* static [pseudo-classes](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes)
not listed above as supported rules involving them will nonetheless be
preserved and copied to a `<style>` element in the HTML including (but not
necessarily limited to) the following:
* [any-link](https://developer.mozilla.org/en-US/docs/Web/CSS/:any-link)
* [first-of-type](https://developer.mozilla.org/en-US/docs/Web/CSS/:first-of-type)
without a type
* [last-of-type](https://developer.mozilla.org/en-US/docs/Web/CSS/:last-of-type)
without a type
* [nth-last-of-type()](https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-last-of-type)
without a type
* [nth-of-type()](https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-of-type)
without a type
* [only-of-type()](https://developer.mozilla.org/en-US/docs/Web/CSS/:only-of-type)
without a type
* [optional](https://developer.mozilla.org/en-US/docs/Web/CSS/:optional)
* [required](https://developer.mozilla.org/en-US/docs/Web/CSS/:required)
* [case-insensitive attribute value](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors#case-insensitive)
* static
[pseudo-classes](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes)
not listed above as supported rules involving them will nonetheless be
preserved and copied to a `<style>` element in the HTML including (but not
necessarily limited to) the following:
* [any-link](https://developer.mozilla.org/en-US/docs/Web/CSS/:any-link)
* [first-of-type](https://developer.mozilla.org/en-US/docs/Web/CSS/:first-of-type)
without a type
* [last-of-type](https://developer.mozilla.org/en-US/docs/Web/CSS/:last-of-type)
without a type
* [nth-last-of-type()](https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-last-of-type)
without a type
* [nth-of-type()](https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-of-type)
without a type
* [only-of-type()](https://developer.mozilla.org/en-US/docs/Web/CSS/:only-of-type)
without a type
* [optional](https://developer.mozilla.org/en-US/docs/Web/CSS/:optional)
* [required](https://developer.mozilla.org/en-US/docs/Web/CSS/:required)
Rules involving the following selectors cannot be applied as inline styles.
They will, however, be preserved and copied to a `<style>` element in the HTML:
* dynamic [pseudo-classes](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes)
(such as `:hover`)
* [pseudo-elements](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements)
(such as `::after`)
* dynamic
[pseudo-classes](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes)
(such as `:hover`)
* [pseudo-elements](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements)
(such as `::after`)
## Caveats
* Emogrifier requires the HTML and the CSS to be UTF-8. Encodings like
ISO8859-1 or ISO8859-15 are not supported.
* Emogrifier preserves all valuable `@media` rules. Media queries can be very
useful in responsive email design. See
* Emogrifier preserves all applicable `@media` rules. Media queries can be very
useful in responsive email design. See
[media query support](https://litmus.com/help/email-clients/media-query-support/).
However, in order for them to be effective, you may need to add `!important`
to some of the declarations within them so that they will override CSS styles
that have been inlined. For example, with the following CSS, the `font-size`
that have been inlined. For example, with the following CSS, the `font-size`
declaration in the `@media` rule would not override the font size for `p`
elements from the preceding rule after that has been inlined as
`<p style="font-size: 16px;">` in the HTML, without the `!important` directive
@@ -374,11 +448,15 @@ They will, however, be preserved and copied to a `<style>` element in the HTML:
}
}
```
Any CSS custom properties (variables) defined in `@media` rules cannot be
applied to CSS property values that have been inlined and evaluated. However,
`@media` rules using custom properties (with `var()`) would still be able to
obtain their values (from the inlined definitions or `@media` rules) in email
clients that support custom properties.
* Emogrifier cannot inline CSS rules involving selectors with pseudo-elements
(such as `::after`) or dynamic pseudo-classes (such as `:hover`) it is
impossible. However, such rules will be preserved and copied to a `<style>`
element, as for `@media` rules. The same caveat about the possible need for
the `!important` directive also applies with pseudo-classes.
impossible. However, such rules will be preserved and copied to a `<style>`
element, as for `@media` rules, with the same caveats applying.
* Emogrifier will grab existing inline style attributes _and_ will
grab `<style>` blocks from your HTML, but it will not grab CSS files
referenced in `<link>` elements or `@import` rules (though it will leave them
@@ -402,20 +480,32 @@ They will, however, be preserved and copied to a `<style>` element in the HTML:
self-closing tags will lose their slash. To keep your HTML valid, it is
recommended to use HTML5 instead of one of the XHTML variants.
## API and deprecation policy
Please have a look at our
[API and deprecation policy](docs/API-and-deprecation-policy.md).
## Contributing
Contributions in the form of bug reports, feature requests, or pull requests are
more than welcome. :pray: Please have a look at our
[contribution guidelines](CONTRIBUTING.md) to learn more about how to
contribute to Emogrifier.
## Steps to release a new version
1. In the [composer.json](composer.json), update the `branch-alias` entry to
point to the release _after_ the upcoming release.
2. In the [CHANGELOG.md](CHANGELOG.md), create a new section with subheadings
1. In the [CHANGELOG.md](CHANGELOG.md), create a new section with subheadings
for changes _after_ the upcoming release, set the version number for the
upcoming release, and remove any empty sections.
3. Create a pull request "Prepare release of version x.y.z" with those
changes.
4. Have the pull request reviewed and merged.
5. Tag the new release.
6. In the [Releases tab](https://github.com/MyIntervals/emogrifier/releases),
1. Update the target milestone in the Dependabot configuration.
1. Create a pull request "Prepare release of version x.y.z" with those changes.
1. Have the pull request reviewed and merged.
1. Tag the new release.
1. In the [Releases tab](https://github.com/MyIntervals/emogrifier/releases),
create a new release and copy the change log entries to the new release.
7. Post about the new release on social media.
1. Post about the new release on social media.
## Maintainers