N°3851 Update Emogrifier to a version supporting iTop PHP versions range

This commit is contained in:
Stephen Abello
2021-09-21 16:42:14 +02:00
parent c306c6e30d
commit 7c7386afc7
28 changed files with 2684 additions and 3254 deletions

View File

@@ -13,7 +13,7 @@
"combodo/tcpdf": "6.3.5",
"nikic/php-parser": "^4.12.0",
"pear/archive_tar": "1.4.13",
"pelago/emogrifier": "2.1.0",
"pelago/emogrifier": "3.1.0",
"scssphp/scssphp": "1.0.6",
"swiftmailer/swiftmailer": "5.4.12",
"symfony/console": "3.4.*",

56
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "75f17b71005971207815906ec7e9cf67",
"content-hash": "fb56686981ee4945791fe5b93735b022",
"packages": [
{
"name": "combodo/tcpdf",
@@ -411,34 +411,34 @@
},
{
"name": "pelago/emogrifier",
"version": "v2.1.0",
"version": "v3.1.0",
"source": {
"type": "git",
"url": "https://github.com/MyIntervals/emogrifier.git",
"reference": "40c3d4f475d44ffc7265a760d1dd0e81f579f96f"
"reference": "f6a5c7d44612d86c3901c93f1592f5440e6b2cd8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MyIntervals/emogrifier/zipball/40c3d4f475d44ffc7265a760d1dd0e81f579f96f",
"reference": "40c3d4f475d44ffc7265a760d1dd0e81f579f96f",
"url": "https://api.github.com/repos/MyIntervals/emogrifier/zipball/f6a5c7d44612d86c3901c93f1592f5440e6b2cd8",
"reference": "f6a5c7d44612d86c3901c93f1592f5440e6b2cd8",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"php": "^5.5.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0",
"symfony/css-selector": "^3.4.0 || ^4.0.0"
"php": "^5.6 || ~7.0 || ~7.1 || ~7.2 || ~7.3 || ~7.4",
"symfony/css-selector": "^2.8 || ^3.0 || ^4.0 || ^5.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.2.0",
"phpmd/phpmd": "^2.6.0",
"phpunit/phpunit": "^4.8.0",
"squizlabs/php_codesniffer": "^3.3.2"
"friendsofphp/php-cs-fixer": "^2.15.3",
"phpmd/phpmd": "^2.7.0",
"phpunit/phpunit": "^5.7.27",
"squizlabs/php_codesniffer": "^3.5.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.1.x-dev"
"dev-master": "4.0.x-dev"
}
},
"autoload": {
@@ -451,16 +451,6 @@
"MIT"
],
"authors": [
{
"name": "John Reeve",
"email": "jreeve@pelagodesign.com"
},
{
"name": "Cameron Brooks"
},
{
"name": "Jaime Prado"
},
{
"name": "Oliver Klee",
"email": "github@oliverklee.de"
@@ -469,9 +459,19 @@
"name": "Zoli Szabó",
"email": "zoli.szabo+github@gmail.com"
},
{
"name": "John Reeve",
"email": "jreeve@pelagodesign.com"
},
{
"name": "Jake Hotson",
"email": "jake@qzdesign.co.uk"
},
{
"name": "Cameron Brooks"
},
{
"name": "Jaime Prado"
}
],
"description": "Converts CSS styles into inline style attributes in your HTML code",
@@ -481,7 +481,11 @@
"email",
"pre-processing"
],
"time": "2018-12-08T13:55:46+00:00"
"support": {
"issues": "https://github.com/MyIntervals/emogrifier/issues",
"source": "https://github.com/MyIntervals/emogrifier"
},
"time": "2019-12-26T19:37:31+00:00"
},
{
"name": "psr/cache",
@@ -2609,7 +2613,7 @@
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=7.1.3",
"php": ">=7.1.3 <8.0.0",
"ext-ctype": "*",
"ext-dom": "*",
"ext-gd": "*",
@@ -2620,7 +2624,7 @@
},
"platform-dev": [],
"platform-overrides": {
"php": "7.2.0"
"php": "7.1.3"
},
"plugin-api-version": "2.0.0"
"plugin-api-version": "2.1.0"
}

View File

@@ -24,6 +24,10 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Pelago\Emogrifier\CssInliner;
use Pelago\Emogrifier\HtmlProcessor\CssToAttributeConverter;
use Pelago\Emogrifier\HtmlProcessor\HtmlPruner;
Swift_Preferences::getInstance()->setCharset('UTF-8');
@@ -335,8 +339,9 @@ class EMail
{
if (($sMimeType === 'text/html') && ($sCustomStyles !== null))
{
$emogrifier = new \Pelago\Emogrifier($sBody, $sCustomStyles);
$sBody = $emogrifier->emogrify(); // Adds html/body tags if not already present
$oDomDocument = CssInliner::fromHtml($sBody)->inlineCss($sCustomStyles)->getDomDocument();
HtmlPruner::fromDomDocument($oDomDocument)->removeElementsWithDisplayNone();
$sBody = CssToAttributeConverter::fromDomDocument($oDomDocument)->convertCssToVisualAttributes()->render(); // Adds html/body tags if not already present
}
$this->m_aData['body'] = array('body' => $sBody, 'mimeType' => $sMimeType);
$this->m_oMessage->setBody($sBody, $sMimeType);

View File

@@ -42,6 +42,8 @@ namespace Composer\Autoload;
*/
class ClassLoader
{
private $vendorDir;
// PSR-4
private $prefixLengthsPsr4 = array();
private $prefixDirsPsr4 = array();
@@ -57,6 +59,13 @@ class ClassLoader
private $missingClasses = array();
private $apcuPrefix;
private static $registeredLoaders = array();
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
}
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
@@ -300,6 +309,17 @@ class ClassLoader
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
}
/**
@@ -308,13 +328,17 @@ class ClassLoader
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
if (null !== $this->vendorDir) {
unset(self::$registeredLoaders[$this->vendorDir]);
}
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return bool|null True if loaded, null otherwise
* @return true|null True if loaded, null otherwise
*/
public function loadClass($class)
{
@@ -323,6 +347,8 @@ class ClassLoader
return true;
}
return null;
}
/**
@@ -367,6 +393,16 @@ class ClassLoader
return $file;
}
/**
* Returns the currently registered loaders indexed by their corresponding vendor directories.
*
* @return self[]
*/
public static function getRegisteredLoaders()
{
return self::$registeredLoaders;
}
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup

File diff suppressed because it is too large Load Diff

View File

@@ -36,7 +36,6 @@ return array(
'AttributeBoolean' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeCaseLog' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeClass' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeClassAttCodeSet' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeClassState' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeCustomFields' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeDBField' => $baseDir . '/core/attributedef.class.inc.php',
@@ -51,7 +50,6 @@ return array(
'AttributeEmailAddress' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeEncryptedString' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeEnum' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeEnumSet' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeExternalField' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeExternalKey' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeFinalClass' => $baseDir . '/core/attributedef.class.inc.php',
@@ -74,14 +72,12 @@ return array(
'AttributePercentage' => $baseDir . '/core/attributedef.class.inc.php',
'AttributePhoneNumber' => $baseDir . '/core/attributedef.class.inc.php',
'AttributePropertySet' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeQueryAttCodeSet' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeRedundancySettings' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeSet' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeStopWatch' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeString' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeSubItem' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeTable' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeTagSet' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeTemplateHTML' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeTemplateString' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeTemplateText' => $baseDir . '/core/attributedef.class.inc.php',
@@ -388,17 +384,9 @@ return array(
'DashboardMenuNode' => $baseDir . '/application/menunode.class.inc.php',
'Dashlet' => $baseDir . '/application/dashlet.class.inc.php',
'DashletBadge' => $baseDir . '/application/dashlet.class.inc.php',
'DashletEmptyCell' => $baseDir . '/application/dashlet.class.inc.php',
'DashletGroupBy' => $baseDir . '/application/dashlet.class.inc.php',
'DashletGroupByBars' => $baseDir . '/application/dashlet.class.inc.php',
'DashletGroupByPie' => $baseDir . '/application/dashlet.class.inc.php',
'DashletGroupByTable' => $baseDir . '/application/dashlet.class.inc.php',
'DashletHeaderDynamic' => $baseDir . '/application/dashlet.class.inc.php',
'DashletHeaderStatic' => $baseDir . '/application/dashlet.class.inc.php',
'DashletObjectList' => $baseDir . '/application/dashlet.class.inc.php',
'DashletPlainText' => $baseDir . '/application/dashlet.class.inc.php',
'DashletProxy' => $baseDir . '/application/dashlet.class.inc.php',
'DashletUnknown' => $baseDir . '/application/dashlet.class.inc.php',
'DataTable' => $baseDir . '/application/datatable.class.inc.php',
'DataTableConfig' => $baseDir . '/sources/application/UI/Base/Component/DataTable/DataTableConfig/DataTableConfig.php',
'Datamatrix' => $vendorDir . '/combodo/tcpdf/include/barcodes/datamatrix.php',
@@ -410,21 +398,9 @@ return array(
'DeleteException' => $baseDir . '/application/exceptions/DeleteException.php',
'DeletionPlan' => $baseDir . '/core/deletionplan.class.inc.php',
'DeprecatedCallsLog' => $baseDir . '/core/log.class.inc.php',
'DesignerBooleanField' => $baseDir . '/application/forms.class.inc.php',
'DesignerComboField' => $baseDir . '/application/forms.class.inc.php',
'DesignerForm' => $baseDir . '/application/forms.class.inc.php',
'DesignerFormField' => $baseDir . '/application/forms.class.inc.php',
'DesignerFormSelectorField' => $baseDir . '/application/forms.class.inc.php',
'DesignerHiddenField' => $baseDir . '/application/forms.class.inc.php',
'DesignerIconSelectionField' => $baseDir . '/application/forms.class.inc.php',
'DesignerIntegerField' => $baseDir . '/application/forms.class.inc.php',
'DesignerLabelField' => $baseDir . '/application/forms.class.inc.php',
'DesignerLongTextField' => $baseDir . '/application/forms.class.inc.php',
'DesignerSortableField' => $baseDir . '/application/forms.class.inc.php',
'DesignerStaticTextField' => $baseDir . '/application/forms.class.inc.php',
'DesignerSubFormField' => $baseDir . '/application/forms.class.inc.php',
'DesignerTabularForm' => $baseDir . '/application/forms.class.inc.php',
'DesignerTextField' => $baseDir . '/application/forms.class.inc.php',
'Dict' => $baseDir . '/core/dict.class.inc.php',
'DictException' => $baseDir . '/application/exceptions/dict/DictException.php',
'DictExceptionMissingString' => $baseDir . '/application/exceptions/dict/DictExceptionMissingString.php',
@@ -451,6 +427,7 @@ return array(
'EventWebService' => $baseDir . '/core/event.class.inc.php',
'ExcelBulkExport' => $baseDir . '/core/excelbulkexport.class.inc.php',
'ExcelExporter' => $baseDir . '/application/excelexporter.class.inc.php',
'ExceptionLog' => $baseDir . '/core/log.class.inc.php',
'ExecAsyncTask' => $baseDir . '/core/asynctask.class.inc.php',
'ExecutionKPI' => $baseDir . '/core/kpi.class.inc.php',
'Expression' => $baseDir . '/core/oql/expression.class.inc.php',
@@ -569,11 +546,13 @@ return array(
'Page' => $baseDir . '/sources/application/WebPage/Page.php',
'ParseError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/ParseError.php',
'Pelago\\Emogrifier' => $vendorDir . '/pelago/emogrifier/src/Emogrifier.php',
'Pelago\\Emogrifier\\CssConcatenator' => $vendorDir . '/pelago/emogrifier/src/Emogrifier/CssConcatenator.php',
'Pelago\\Emogrifier\\CssInliner' => $vendorDir . '/pelago/emogrifier/src/Emogrifier/CssInliner.php',
'Pelago\\Emogrifier\\HtmlProcessor\\AbstractHtmlProcessor' => $vendorDir . '/pelago/emogrifier/src/Emogrifier/HtmlProcessor/AbstractHtmlProcessor.php',
'Pelago\\Emogrifier\\HtmlProcessor\\CssToAttributeConverter' => $vendorDir . '/pelago/emogrifier/src/Emogrifier/HtmlProcessor/CssToAttributeConverter.php',
'Pelago\\Emogrifier\\HtmlProcessor\\HtmlNormalizer' => $vendorDir . '/pelago/emogrifier/src/Emogrifier/HtmlProcessor/HtmlNormalizer.php',
'Pelago\\Emogrifier\\HtmlProcessor\\HtmlPruner' => $vendorDir . '/pelago/emogrifier/src/Emogrifier/HtmlProcessor/HtmlPruner.php',
'Pelago\\Emogrifier\\Utilities\\ArrayIntersector' => $vendorDir . '/pelago/emogrifier/src/Emogrifier/Utilities/ArrayIntersector.php',
'Pelago\\Emogrifier\\Utilities\\CssConcatenator' => $vendorDir . '/pelago/emogrifier/src/Emogrifier/Utilities/CssConcatenator.php',
'PhpParser\\Builder' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder.php',
'PhpParser\\BuilderFactory' => $vendorDir . '/nikic/php-parser/lib/PhpParser/BuilderFactory.php',
'PhpParser\\BuilderHelpers' => $vendorDir . '/nikic/php-parser/lib/PhpParser/BuilderHelpers.php',
@@ -864,8 +843,6 @@ return array(
'RowStatus_Modify' => $baseDir . '/core/bulkchange.class.inc.php',
'RowStatus_NewObj' => $baseDir . '/core/bulkchange.class.inc.php',
'RowStatus_NoChange' => $baseDir . '/core/bulkchange.class.inc.php',
'RunTimeIconSelectionField' => $baseDir . '/application/forms.class.inc.php',
'RuntimeDashboard' => $baseDir . '/application/dashboard.class.inc.php',
'SQLExpression' => $baseDir . '/core/oql/expression.class.inc.php',
'SQLObjectQuery' => $baseDir . '/core/sqlobjectquery.class.inc.php',
'SQLObjectQueryBuilder' => $baseDir . '/core/sqlobjectquerybuilder.class.inc.php',

View File

@@ -22,10 +22,8 @@ class ComposerAutoloaderInit0018331147de7601e7552f7da8e3bb8b
return self::$loader;
}
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInit0018331147de7601e7552f7da8e3bb8b', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
spl_autoload_unregister(array('ComposerAutoloaderInit0018331147de7601e7552f7da8e3bb8b', 'loadClassLoader'));
$includePaths = require __DIR__ . '/include_paths.php';

View File

@@ -266,7 +266,6 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
'AttributeBoolean' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeCaseLog' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeClass' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeClassAttCodeSet' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeClassState' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeCustomFields' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeDBField' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
@@ -281,7 +280,6 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
'AttributeEmailAddress' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeEncryptedString' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeEnum' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeEnumSet' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeExternalField' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeExternalKey' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeFinalClass' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
@@ -304,14 +302,12 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
'AttributePercentage' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributePhoneNumber' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributePropertySet' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeQueryAttCodeSet' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeRedundancySettings' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeSet' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeStopWatch' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeString' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeSubItem' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeTable' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeTagSet' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeTemplateHTML' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeTemplateString' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeTemplateText' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
@@ -618,17 +614,9 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
'DashboardMenuNode' => __DIR__ . '/../..' . '/application/menunode.class.inc.php',
'Dashlet' => __DIR__ . '/../..' . '/application/dashlet.class.inc.php',
'DashletBadge' => __DIR__ . '/../..' . '/application/dashlet.class.inc.php',
'DashletEmptyCell' => __DIR__ . '/../..' . '/application/dashlet.class.inc.php',
'DashletGroupBy' => __DIR__ . '/../..' . '/application/dashlet.class.inc.php',
'DashletGroupByBars' => __DIR__ . '/../..' . '/application/dashlet.class.inc.php',
'DashletGroupByPie' => __DIR__ . '/../..' . '/application/dashlet.class.inc.php',
'DashletGroupByTable' => __DIR__ . '/../..' . '/application/dashlet.class.inc.php',
'DashletHeaderDynamic' => __DIR__ . '/../..' . '/application/dashlet.class.inc.php',
'DashletHeaderStatic' => __DIR__ . '/../..' . '/application/dashlet.class.inc.php',
'DashletObjectList' => __DIR__ . '/../..' . '/application/dashlet.class.inc.php',
'DashletPlainText' => __DIR__ . '/../..' . '/application/dashlet.class.inc.php',
'DashletProxy' => __DIR__ . '/../..' . '/application/dashlet.class.inc.php',
'DashletUnknown' => __DIR__ . '/../..' . '/application/dashlet.class.inc.php',
'DataTable' => __DIR__ . '/../..' . '/application/datatable.class.inc.php',
'DataTableConfig' => __DIR__ . '/../..' . '/sources/application/UI/Base/Component/DataTable/DataTableConfig/DataTableConfig.php',
'Datamatrix' => __DIR__ . '/..' . '/combodo/tcpdf/include/barcodes/datamatrix.php',
@@ -640,21 +628,9 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
'DeleteException' => __DIR__ . '/../..' . '/application/exceptions/DeleteException.php',
'DeletionPlan' => __DIR__ . '/../..' . '/core/deletionplan.class.inc.php',
'DeprecatedCallsLog' => __DIR__ . '/../..' . '/core/log.class.inc.php',
'DesignerBooleanField' => __DIR__ . '/../..' . '/application/forms.class.inc.php',
'DesignerComboField' => __DIR__ . '/../..' . '/application/forms.class.inc.php',
'DesignerForm' => __DIR__ . '/../..' . '/application/forms.class.inc.php',
'DesignerFormField' => __DIR__ . '/../..' . '/application/forms.class.inc.php',
'DesignerFormSelectorField' => __DIR__ . '/../..' . '/application/forms.class.inc.php',
'DesignerHiddenField' => __DIR__ . '/../..' . '/application/forms.class.inc.php',
'DesignerIconSelectionField' => __DIR__ . '/../..' . '/application/forms.class.inc.php',
'DesignerIntegerField' => __DIR__ . '/../..' . '/application/forms.class.inc.php',
'DesignerLabelField' => __DIR__ . '/../..' . '/application/forms.class.inc.php',
'DesignerLongTextField' => __DIR__ . '/../..' . '/application/forms.class.inc.php',
'DesignerSortableField' => __DIR__ . '/../..' . '/application/forms.class.inc.php',
'DesignerStaticTextField' => __DIR__ . '/../..' . '/application/forms.class.inc.php',
'DesignerSubFormField' => __DIR__ . '/../..' . '/application/forms.class.inc.php',
'DesignerTabularForm' => __DIR__ . '/../..' . '/application/forms.class.inc.php',
'DesignerTextField' => __DIR__ . '/../..' . '/application/forms.class.inc.php',
'Dict' => __DIR__ . '/../..' . '/core/dict.class.inc.php',
'DictException' => __DIR__ . '/../..' . '/application/exceptions/dict/DictException.php',
'DictExceptionMissingString' => __DIR__ . '/../..' . '/application/exceptions/dict/DictExceptionMissingString.php',
@@ -681,6 +657,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
'EventWebService' => __DIR__ . '/../..' . '/core/event.class.inc.php',
'ExcelBulkExport' => __DIR__ . '/../..' . '/core/excelbulkexport.class.inc.php',
'ExcelExporter' => __DIR__ . '/../..' . '/application/excelexporter.class.inc.php',
'ExceptionLog' => __DIR__ . '/../..' . '/core/log.class.inc.php',
'ExecAsyncTask' => __DIR__ . '/../..' . '/core/asynctask.class.inc.php',
'ExecutionKPI' => __DIR__ . '/../..' . '/core/kpi.class.inc.php',
'Expression' => __DIR__ . '/../..' . '/core/oql/expression.class.inc.php',
@@ -799,11 +776,13 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
'Page' => __DIR__ . '/../..' . '/sources/application/WebPage/Page.php',
'ParseError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/ParseError.php',
'Pelago\\Emogrifier' => __DIR__ . '/..' . '/pelago/emogrifier/src/Emogrifier.php',
'Pelago\\Emogrifier\\CssConcatenator' => __DIR__ . '/..' . '/pelago/emogrifier/src/Emogrifier/CssConcatenator.php',
'Pelago\\Emogrifier\\CssInliner' => __DIR__ . '/..' . '/pelago/emogrifier/src/Emogrifier/CssInliner.php',
'Pelago\\Emogrifier\\HtmlProcessor\\AbstractHtmlProcessor' => __DIR__ . '/..' . '/pelago/emogrifier/src/Emogrifier/HtmlProcessor/AbstractHtmlProcessor.php',
'Pelago\\Emogrifier\\HtmlProcessor\\CssToAttributeConverter' => __DIR__ . '/..' . '/pelago/emogrifier/src/Emogrifier/HtmlProcessor/CssToAttributeConverter.php',
'Pelago\\Emogrifier\\HtmlProcessor\\HtmlNormalizer' => __DIR__ . '/..' . '/pelago/emogrifier/src/Emogrifier/HtmlProcessor/HtmlNormalizer.php',
'Pelago\\Emogrifier\\HtmlProcessor\\HtmlPruner' => __DIR__ . '/..' . '/pelago/emogrifier/src/Emogrifier/HtmlProcessor/HtmlPruner.php',
'Pelago\\Emogrifier\\Utilities\\ArrayIntersector' => __DIR__ . '/..' . '/pelago/emogrifier/src/Emogrifier/Utilities/ArrayIntersector.php',
'Pelago\\Emogrifier\\Utilities\\CssConcatenator' => __DIR__ . '/..' . '/pelago/emogrifier/src/Emogrifier/Utilities/CssConcatenator.php',
'PhpParser\\Builder' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder.php',
'PhpParser\\BuilderFactory' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/BuilderFactory.php',
'PhpParser\\BuilderHelpers' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/BuilderHelpers.php',
@@ -1094,8 +1073,6 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
'RowStatus_Modify' => __DIR__ . '/../..' . '/core/bulkchange.class.inc.php',
'RowStatus_NewObj' => __DIR__ . '/../..' . '/core/bulkchange.class.inc.php',
'RowStatus_NoChange' => __DIR__ . '/../..' . '/core/bulkchange.class.inc.php',
'RunTimeIconSelectionField' => __DIR__ . '/../..' . '/application/forms.class.inc.php',
'RuntimeDashboard' => __DIR__ . '/../..' . '/application/dashboard.class.inc.php',
'SQLExpression' => __DIR__ . '/../..' . '/core/oql/expression.class.inc.php',
'SQLObjectQuery' => __DIR__ . '/../..' . '/core/sqlobjectquery.class.inc.php',
'SQLObjectQueryBuilder' => __DIR__ . '/../..' . '/core/sqlobjectquerybuilder.class.inc.php',

View File

@@ -418,36 +418,36 @@
},
{
"name": "pelago/emogrifier",
"version": "v2.1.0",
"version_normalized": "2.1.0.0",
"version": "v3.1.0",
"version_normalized": "3.1.0.0",
"source": {
"type": "git",
"url": "https://github.com/MyIntervals/emogrifier.git",
"reference": "40c3d4f475d44ffc7265a760d1dd0e81f579f96f"
"reference": "f6a5c7d44612d86c3901c93f1592f5440e6b2cd8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MyIntervals/emogrifier/zipball/40c3d4f475d44ffc7265a760d1dd0e81f579f96f",
"reference": "40c3d4f475d44ffc7265a760d1dd0e81f579f96f",
"url": "https://api.github.com/repos/MyIntervals/emogrifier/zipball/f6a5c7d44612d86c3901c93f1592f5440e6b2cd8",
"reference": "f6a5c7d44612d86c3901c93f1592f5440e6b2cd8",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"php": "^5.5.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0",
"symfony/css-selector": "^3.4.0 || ^4.0.0"
"php": "^5.6 || ~7.0 || ~7.1 || ~7.2 || ~7.3 || ~7.4",
"symfony/css-selector": "^2.8 || ^3.0 || ^4.0 || ^5.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.2.0",
"phpmd/phpmd": "^2.6.0",
"phpunit/phpunit": "^4.8.0",
"squizlabs/php_codesniffer": "^3.3.2"
"friendsofphp/php-cs-fixer": "^2.15.3",
"phpmd/phpmd": "^2.7.0",
"phpunit/phpunit": "^5.7.27",
"squizlabs/php_codesniffer": "^3.5.0"
},
"time": "2018-12-08T13:55:46+00:00",
"time": "2019-12-26T19:37:31+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.1.x-dev"
"dev-master": "4.0.x-dev"
}
},
"installation-source": "dist",
@@ -461,16 +461,6 @@
"MIT"
],
"authors": [
{
"name": "John Reeve",
"email": "jreeve@pelagodesign.com"
},
{
"name": "Cameron Brooks"
},
{
"name": "Jaime Prado"
},
{
"name": "Oliver Klee",
"email": "github@oliverklee.de"
@@ -479,9 +469,19 @@
"name": "Zoli Szabó",
"email": "zoli.szabo+github@gmail.com"
},
{
"name": "John Reeve",
"email": "jreeve@pelagodesign.com"
},
{
"name": "Jake Hotson",
"email": "jake@qzdesign.co.uk"
},
{
"name": "Cameron Brooks"
},
{
"name": "Jaime Prado"
}
],
"description": "Converts CSS styles into inline style attributes in your HTML code",
@@ -491,6 +491,10 @@
"email",
"pre-processing"
],
"support": {
"issues": "https://github.com/MyIntervals/emogrifier/issues",
"source": "https://github.com/MyIntervals/emogrifier"
},
"install-path": "../pelago/emogrifier"
},
{

View File

@@ -1,444 +1,437 @@
<?php return array (
'root' =>
array (
'pretty_version' => 'dev-develop',
'version' => 'dev-develop',
'aliases' =>
array (
<?php return array(
'root' => array(
'pretty_version' => 'dev-develop',
'version' => 'dev-develop',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => 'd9ccac3aeaa3cb91f0e1b78b091f496b7c689166',
'name' => '__root__',
'dev' => true,
),
'reference' => 'c8dd8c3806d92e19b5a67c16be9d64ceaa2e0524',
'name' => '__root__',
),
'versions' =>
array (
'__root__' =>
array (
'pretty_version' => 'dev-develop',
'version' => 'dev-develop',
'aliases' =>
array (
),
'reference' => 'c8dd8c3806d92e19b5a67c16be9d64ceaa2e0524',
'versions' => array(
'__root__' => array(
'pretty_version' => 'dev-develop',
'version' => 'dev-develop',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => 'd9ccac3aeaa3cb91f0e1b78b091f496b7c689166',
'dev_requirement' => false,
),
'combodo/tcpdf' => array(
'pretty_version' => '6.3.5',
'version' => '6.3.5.0',
'type' => 'library',
'install_path' => __DIR__ . '/../combodo/tcpdf',
'aliases' => array(),
'reference' => 'aedd4b7b8cf7fcc24e617c405c9d3304150f4b94',
'dev_requirement' => false,
),
'nikic/php-parser' => array(
'pretty_version' => 'v4.12.0',
'version' => '4.12.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../nikic/php-parser',
'aliases' => array(),
'reference' => '6608f01670c3cc5079e18c1dab1104e002579143',
'dev_requirement' => false,
),
'paragonie/random_compat' => array(
'pretty_version' => 'v2.0.18',
'version' => '2.0.18.0',
'type' => 'library',
'install_path' => __DIR__ . '/../paragonie/random_compat',
'aliases' => array(),
'reference' => '0a58ef6e3146256cc3dc7cc393927bcc7d1b72db',
'dev_requirement' => false,
),
'pear/archive_tar' => array(
'pretty_version' => '1.4.13',
'version' => '1.4.13.0',
'type' => 'library',
'install_path' => __DIR__ . '/../pear/archive_tar',
'aliases' => array(),
'reference' => '19bb8e95490d3e3ad92fcac95500ca80bdcc7495',
'dev_requirement' => false,
),
'pear/console_getopt' => array(
'pretty_version' => 'v1.4.3',
'version' => '1.4.3.0',
'type' => 'library',
'install_path' => __DIR__ . '/../pear/console_getopt',
'aliases' => array(),
'reference' => 'a41f8d3e668987609178c7c4a9fe48fecac53fa0',
'dev_requirement' => false,
),
'pear/pear-core-minimal' => array(
'pretty_version' => 'v1.10.10',
'version' => '1.10.10.0',
'type' => 'library',
'install_path' => __DIR__ . '/../pear/pear-core-minimal',
'aliases' => array(),
'reference' => '625a3c429d9b2c1546438679074cac1b089116a7',
'dev_requirement' => false,
),
'pear/pear_exception' => array(
'pretty_version' => 'v1.0.2',
'version' => '1.0.2.0',
'type' => 'class',
'install_path' => __DIR__ . '/../pear/pear_exception',
'aliases' => array(),
'reference' => 'b14fbe2ddb0b9f94f5b24cf08783d599f776fff0',
'dev_requirement' => false,
),
'pelago/emogrifier' => array(
'pretty_version' => 'v3.1.0',
'version' => '3.1.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../pelago/emogrifier',
'aliases' => array(),
'reference' => 'f6a5c7d44612d86c3901c93f1592f5440e6b2cd8',
'dev_requirement' => false,
),
'psr/cache' => array(
'pretty_version' => '1.0.1',
'version' => '1.0.1.0',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/cache',
'aliases' => array(),
'reference' => 'd11b50ad223250cf17b86e38383413f5a6764bf8',
'dev_requirement' => false,
),
'psr/cache-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0',
),
),
'psr/container' => array(
'pretty_version' => '1.0.0',
'version' => '1.0.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/container',
'aliases' => array(),
'reference' => 'b7ce3b176482dbbc1245ebf52b181af44c2cf55f',
'dev_requirement' => false,
),
'psr/container-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0',
),
),
'psr/log' => array(
'pretty_version' => '1.1.2',
'version' => '1.1.2.0',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/log',
'aliases' => array(),
'reference' => '446d54b4cb6bf489fc9d75f55843658e6f25d801',
'dev_requirement' => false,
),
'psr/log-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0',
),
),
'psr/simple-cache' => array(
'pretty_version' => '1.0.1',
'version' => '1.0.1.0',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/simple-cache',
'aliases' => array(),
'reference' => '408d5eafb83c57f6365a3ca330ff23aa4a5fa39b',
'dev_requirement' => false,
),
'psr/simple-cache-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0',
),
),
'rsky/pear-core-min' => array(
'dev_requirement' => false,
'replaced' => array(
0 => 'v1.10.10',
),
),
'scssphp/scssphp' => array(
'pretty_version' => '1.0.6',
'version' => '1.0.6.0',
'type' => 'library',
'install_path' => __DIR__ . '/../scssphp/scssphp',
'aliases' => array(),
'reference' => '5b3c9d704950d8f9637f5110c36c281ec47dc13c',
'dev_requirement' => false,
),
'swiftmailer/swiftmailer' => array(
'pretty_version' => 'v5.4.12',
'version' => '5.4.12.0',
'type' => 'library',
'install_path' => __DIR__ . '/../swiftmailer/swiftmailer',
'aliases' => array(),
'reference' => '181b89f18a90f8925ef805f950d47a7190e9b950',
'dev_requirement' => false,
),
'symfony/cache' => array(
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/cache',
'aliases' => array(),
'reference' => '3d9f46a6960fd5cd7f030f86adc5b4b63bcfa4e3',
'dev_requirement' => false,
),
'symfony/class-loader' => array(
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/class-loader',
'aliases' => array(),
'reference' => 'e212b06996819a2bce026a63da03b7182d05a690',
'dev_requirement' => false,
),
'symfony/config' => array(
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/config',
'aliases' => array(),
'reference' => 'a599a867d0e4a07c342b5f1e656b3915a540ddbe',
'dev_requirement' => false,
),
'symfony/console' => array(
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/console',
'aliases' => array(),
'reference' => '1ee23b3b659b06c622f2bd2492a229e416eb4586',
'dev_requirement' => false,
),
'symfony/css-selector' => array(
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/css-selector',
'aliases' => array(),
'reference' => 'f819f71ae3ba6f396b4c015bd5895de7d2f1f85f',
'dev_requirement' => false,
),
'symfony/debug' => array(
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/debug',
'aliases' => array(),
'reference' => 'f72e33fdb1170b326e72c3157f0cd456351dd086',
'dev_requirement' => false,
),
'symfony/dependency-injection' => array(
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/dependency-injection',
'aliases' => array(),
'reference' => '0d201916bfb3af939fec3c0c8815ea16c60ac1a2',
'dev_requirement' => false,
),
'symfony/dotenv' => array(
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/dotenv',
'aliases' => array(),
'reference' => 'c7e8e471fea74e868ae797970b383dea89ae548a',
'dev_requirement' => false,
),
'symfony/event-dispatcher' => array(
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/event-dispatcher',
'aliases' => array(),
'reference' => 'f9031c22ec127d4a2450760f81a8677fe8a10177',
'dev_requirement' => false,
),
'symfony/filesystem' => array(
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/filesystem',
'aliases' => array(),
'reference' => '00cdad0936d06fab136944bc2342b762b1c3a4a2',
'dev_requirement' => false,
),
'symfony/finder' => array(
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/finder',
'aliases' => array(),
'reference' => '290ae21279b37bfd287cdcce640d51204e84afdf',
'dev_requirement' => false,
),
'symfony/framework-bundle' => array(
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'type' => 'symfony-bundle',
'install_path' => __DIR__ . '/../symfony/framework-bundle',
'aliases' => array(),
'reference' => '0d61117c7a770da0bd8bbe7ccfa34d8063f272ea',
'dev_requirement' => false,
),
'symfony/http-foundation' => array(
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/http-foundation',
'aliases' => array(),
'reference' => 'd2d0cfe8e319d9df44c4cca570710fcf221d4593',
'dev_requirement' => false,
),
'symfony/http-kernel' => array(
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/http-kernel',
'aliases' => array(),
'reference' => 'c42c8339acb28cfff0fb1786948db4d23d609ff7',
'dev_requirement' => false,
),
'symfony/polyfill-apcu' => array(
'pretty_version' => 'v1.13.1',
'version' => '1.13.1.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-apcu',
'aliases' => array(),
'reference' => 'a8e961c841b9ec52927a87914f8820a1ad8f8116',
'dev_requirement' => false,
),
'symfony/polyfill-ctype' => array(
'pretty_version' => 'v1.13.1',
'version' => '1.13.1.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-ctype',
'aliases' => array(),
'reference' => 'f8f0b461be3385e56d6de3dbb5a0df24c0c275e3',
'dev_requirement' => false,
),
'symfony/polyfill-mbstring' => array(
'pretty_version' => 'v1.13.1',
'version' => '1.13.1.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-mbstring',
'aliases' => array(),
'reference' => '7b4aab9743c30be783b73de055d24a39cf4b954f',
'dev_requirement' => false,
),
'symfony/polyfill-php56' => array(
'pretty_version' => 'v1.13.1',
'version' => '1.13.1.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-php56',
'aliases' => array(),
'reference' => '53dd1cdf3cb986893ccf2b96665b25b3abb384f4',
'dev_requirement' => false,
),
'symfony/polyfill-php70' => array(
'pretty_version' => 'v1.13.1',
'version' => '1.13.1.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-php70',
'aliases' => array(),
'reference' => 'af23c7bb26a73b850840823662dda371484926c4',
'dev_requirement' => false,
),
'symfony/polyfill-util' => array(
'pretty_version' => 'v1.13.1',
'version' => '1.13.1.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-util',
'aliases' => array(),
'reference' => '964a67f293b66b95883a5ed918a65354fcd2258f',
'dev_requirement' => false,
),
'symfony/routing' => array(
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/routing',
'aliases' => array(),
'reference' => 'b689ccd48e234ea404806d94b07eeb45f9f6f06a',
'dev_requirement' => false,
),
'symfony/stopwatch' => array(
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/stopwatch',
'aliases' => array(),
'reference' => 'efe0af281ad336bc3b10375c88b117499f1d8494',
'dev_requirement' => true,
),
'symfony/twig-bridge' => array(
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'type' => 'symfony-bridge',
'install_path' => __DIR__ . '/../symfony/twig-bridge',
'aliases' => array(),
'reference' => '49b824ddc7f2d250a1f172349cd9a111d63287c0',
'dev_requirement' => false,
),
'symfony/twig-bundle' => array(
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'type' => 'symfony-bundle',
'install_path' => __DIR__ . '/../symfony/twig-bundle',
'aliases' => array(),
'reference' => 'd39ed8f5df62aeeeb27a6f3bf7f58a6c02a58ea9',
'dev_requirement' => false,
),
'symfony/var-dumper' => array(
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/var-dumper',
'aliases' => array(),
'reference' => '569e261461600810845a8305ca3f64abd3e712c0',
'dev_requirement' => true,
),
'symfony/web-profiler-bundle' => array(
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'type' => 'symfony-bundle',
'install_path' => __DIR__ . '/../symfony/web-profiler-bundle',
'aliases' => array(),
'reference' => '3ae27cf1b2776cd68aa15fdb57089970f78bcf11',
'dev_requirement' => true,
),
'symfony/yaml' => array(
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/yaml',
'aliases' => array(),
'reference' => 'dab657db15207879217fc81df4f875947bf68804',
'dev_requirement' => false,
),
'tecnickcom/tcpdf' => array(
'dev_requirement' => false,
'replaced' => array(
0 => '6.3.5',
),
),
'twig/twig' => array(
'pretty_version' => 'v1.42.4',
'version' => '1.42.4.0',
'type' => 'library',
'install_path' => __DIR__ . '/../twig/twig',
'aliases' => array(),
'reference' => 'e587180584c3d2d6cb864a0454e777bb6dcb6152',
'dev_requirement' => false,
),
),
'combodo/tcpdf' =>
array (
'pretty_version' => '6.3.5',
'version' => '6.3.5.0',
'aliases' =>
array (
),
'reference' => 'aedd4b7b8cf7fcc24e617c405c9d3304150f4b94',
),
'nikic/php-parser' =>
array (
'pretty_version' => 'v4.12.0',
'version' => '4.12.0.0',
'aliases' =>
array (
),
'reference' => '6608f01670c3cc5079e18c1dab1104e002579143',
),
'paragonie/random_compat' =>
array (
'pretty_version' => 'v2.0.18',
'version' => '2.0.18.0',
'aliases' =>
array (
),
'reference' => '0a58ef6e3146256cc3dc7cc393927bcc7d1b72db',
),
'pear/archive_tar' =>
array (
'pretty_version' => '1.4.13',
'version' => '1.4.13.0',
'aliases' =>
array (
),
'reference' => '19bb8e95490d3e3ad92fcac95500ca80bdcc7495',
),
'pear/console_getopt' =>
array (
'pretty_version' => 'v1.4.3',
'version' => '1.4.3.0',
'aliases' =>
array (
),
'reference' => 'a41f8d3e668987609178c7c4a9fe48fecac53fa0',
),
'pear/pear-core-minimal' =>
array (
'pretty_version' => 'v1.10.10',
'version' => '1.10.10.0',
'aliases' =>
array (
),
'reference' => '625a3c429d9b2c1546438679074cac1b089116a7',
),
'pear/pear_exception' =>
array (
'pretty_version' => 'v1.0.2',
'version' => '1.0.2.0',
'aliases' =>
array (
),
'reference' => 'b14fbe2ddb0b9f94f5b24cf08783d599f776fff0',
),
'pelago/emogrifier' =>
array (
'pretty_version' => 'v2.1.0',
'version' => '2.1.0.0',
'aliases' =>
array (
),
'reference' => '40c3d4f475d44ffc7265a760d1dd0e81f579f96f',
),
'psr/cache' =>
array (
'pretty_version' => '1.0.1',
'version' => '1.0.1.0',
'aliases' =>
array (
),
'reference' => 'd11b50ad223250cf17b86e38383413f5a6764bf8',
),
'psr/cache-implementation' =>
array (
'provided' =>
array (
0 => '1.0',
),
),
'psr/container' =>
array (
'pretty_version' => '1.0.0',
'version' => '1.0.0.0',
'aliases' =>
array (
),
'reference' => 'b7ce3b176482dbbc1245ebf52b181af44c2cf55f',
),
'psr/container-implementation' =>
array (
'provided' =>
array (
0 => '1.0',
),
),
'psr/log' =>
array (
'pretty_version' => '1.1.2',
'version' => '1.1.2.0',
'aliases' =>
array (
),
'reference' => '446d54b4cb6bf489fc9d75f55843658e6f25d801',
),
'psr/log-implementation' =>
array (
'provided' =>
array (
0 => '1.0',
),
),
'psr/simple-cache' =>
array (
'pretty_version' => '1.0.1',
'version' => '1.0.1.0',
'aliases' =>
array (
),
'reference' => '408d5eafb83c57f6365a3ca330ff23aa4a5fa39b',
),
'psr/simple-cache-implementation' =>
array (
'provided' =>
array (
0 => '1.0',
),
),
'rsky/pear-core-min' =>
array (
'replaced' =>
array (
0 => 'v1.10.10',
),
),
'scssphp/scssphp' =>
array (
'pretty_version' => '1.0.6',
'version' => '1.0.6.0',
'aliases' =>
array (
),
'reference' => '5b3c9d704950d8f9637f5110c36c281ec47dc13c',
),
'swiftmailer/swiftmailer' =>
array (
'pretty_version' => 'v5.4.12',
'version' => '5.4.12.0',
'aliases' =>
array (
),
'reference' => '181b89f18a90f8925ef805f950d47a7190e9b950',
),
'symfony/cache' =>
array (
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'aliases' =>
array (
),
'reference' => '3d9f46a6960fd5cd7f030f86adc5b4b63bcfa4e3',
),
'symfony/class-loader' =>
array (
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'aliases' =>
array (
),
'reference' => 'e212b06996819a2bce026a63da03b7182d05a690',
),
'symfony/config' =>
array (
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'aliases' =>
array (
),
'reference' => 'a599a867d0e4a07c342b5f1e656b3915a540ddbe',
),
'symfony/console' =>
array (
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'aliases' =>
array (
),
'reference' => '1ee23b3b659b06c622f2bd2492a229e416eb4586',
),
'symfony/css-selector' =>
array (
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'aliases' =>
array (
),
'reference' => 'f819f71ae3ba6f396b4c015bd5895de7d2f1f85f',
),
'symfony/debug' =>
array (
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'aliases' =>
array (
),
'reference' => 'f72e33fdb1170b326e72c3157f0cd456351dd086',
),
'symfony/dependency-injection' =>
array (
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'aliases' =>
array (
),
'reference' => '0d201916bfb3af939fec3c0c8815ea16c60ac1a2',
),
'symfony/dotenv' =>
array (
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'aliases' =>
array (
),
'reference' => 'c7e8e471fea74e868ae797970b383dea89ae548a',
),
'symfony/event-dispatcher' =>
array (
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'aliases' =>
array (
),
'reference' => 'f9031c22ec127d4a2450760f81a8677fe8a10177',
),
'symfony/filesystem' =>
array (
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'aliases' =>
array (
),
'reference' => '00cdad0936d06fab136944bc2342b762b1c3a4a2',
),
'symfony/finder' =>
array (
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'aliases' =>
array (
),
'reference' => '290ae21279b37bfd287cdcce640d51204e84afdf',
),
'symfony/framework-bundle' =>
array (
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'aliases' =>
array (
),
'reference' => '0d61117c7a770da0bd8bbe7ccfa34d8063f272ea',
),
'symfony/http-foundation' =>
array (
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'aliases' =>
array (
),
'reference' => 'd2d0cfe8e319d9df44c4cca570710fcf221d4593',
),
'symfony/http-kernel' =>
array (
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'aliases' =>
array (
),
'reference' => 'c42c8339acb28cfff0fb1786948db4d23d609ff7',
),
'symfony/polyfill-apcu' =>
array (
'pretty_version' => 'v1.13.1',
'version' => '1.13.1.0',
'aliases' =>
array (
),
'reference' => 'a8e961c841b9ec52927a87914f8820a1ad8f8116',
),
'symfony/polyfill-ctype' =>
array (
'pretty_version' => 'v1.13.1',
'version' => '1.13.1.0',
'aliases' =>
array (
),
'reference' => 'f8f0b461be3385e56d6de3dbb5a0df24c0c275e3',
),
'symfony/polyfill-mbstring' =>
array (
'pretty_version' => 'v1.13.1',
'version' => '1.13.1.0',
'aliases' =>
array (
),
'reference' => '7b4aab9743c30be783b73de055d24a39cf4b954f',
),
'symfony/polyfill-php56' =>
array (
'pretty_version' => 'v1.13.1',
'version' => '1.13.1.0',
'aliases' =>
array (
),
'reference' => '53dd1cdf3cb986893ccf2b96665b25b3abb384f4',
),
'symfony/polyfill-php70' =>
array (
'pretty_version' => 'v1.13.1',
'version' => '1.13.1.0',
'aliases' =>
array (
),
'reference' => 'af23c7bb26a73b850840823662dda371484926c4',
),
'symfony/polyfill-util' =>
array (
'pretty_version' => 'v1.13.1',
'version' => '1.13.1.0',
'aliases' =>
array (
),
'reference' => '964a67f293b66b95883a5ed918a65354fcd2258f',
),
'symfony/routing' =>
array (
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'aliases' =>
array (
),
'reference' => 'b689ccd48e234ea404806d94b07eeb45f9f6f06a',
),
'symfony/stopwatch' =>
array (
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'aliases' =>
array (
),
'reference' => 'efe0af281ad336bc3b10375c88b117499f1d8494',
),
'symfony/twig-bridge' =>
array (
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'aliases' =>
array (
),
'reference' => '49b824ddc7f2d250a1f172349cd9a111d63287c0',
),
'symfony/twig-bundle' =>
array (
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'aliases' =>
array (
),
'reference' => 'd39ed8f5df62aeeeb27a6f3bf7f58a6c02a58ea9',
),
'symfony/var-dumper' =>
array (
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'aliases' =>
array (
),
'reference' => '569e261461600810845a8305ca3f64abd3e712c0',
),
'symfony/web-profiler-bundle' =>
array (
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'aliases' =>
array (
),
'reference' => '3ae27cf1b2776cd68aa15fdb57089970f78bcf11',
),
'symfony/yaml' =>
array (
'pretty_version' => 'v3.4.36',
'version' => '3.4.36.0',
'aliases' =>
array (
),
'reference' => 'dab657db15207879217fc81df4f875947bf68804',
),
'tecnickcom/tcpdf' =>
array (
'replaced' =>
array (
0 => '6.3.5',
),
),
'twig/twig' =>
array (
'pretty_version' => 'v1.42.4',
'version' => '1.42.4.0',
'aliases' =>
array (
),
'reference' => 'e587180584c3d2d6cb864a0454e777bb6dcb6152',
),
),
);

View File

@@ -1,26 +0,0 @@
<?php
// platform_check.php @generated by Composer
$issues = array();
if (!(PHP_VERSION_ID >= 70103)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 7.1.3". You are running ' . PHP_VERSION . '.';
}
if ($issues) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
} elseif (!headers_sent()) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
trigger_error(
'Composer detected issues in your platform: ' . implode(' ', $issues),
E_USER_ERROR
);
}

View File

@@ -1,25 +0,0 @@
#########################
# global ignore file
########################
# ignoring temporary files (left by e.g. vim)
# ignoring by common IDE's used directories/files
# dont ignore .rej and .orig as we want to see/clean files after conflict resolution
#
# for local exclude patterns please edit .git/info/exclude
#
*~
*.bak
*.idea
*.project
*.swp
.buildpath
.cache
.project
.session
.settings
.TemporaryItems
.webprj
nbproject
/.php_cs.cache
/vendor/
composer.lock

View File

@@ -1,65 +0,0 @@
sudo: false
language: php
php:
- 5.5
- 5.6
- 7.0
- 7.1
- 7.2
- 7.3
cache:
directories:
- vendor
- $HOME/.composer/cache
env:
matrix:
- DEPENDENCIES_PREFERENCE="--prefer-lowest"
- DEPENDENCIES_PREFERENCE=""
before_install:
- phpenv config-rm xdebug.ini || echo "xdebug not available"
install:
- >
export IGNORE_PLATFORM_REQS="$(composer php:version |grep -q '^7.3' && printf -- --ignore-platform-reqs)";
echo;
echo "Updating the dependencies";
composer update $IGNORE_PLATFORM_REQS --with-dependencies $DEPENDENCIES_PREFERENCE;
composer show;
script:
- >
echo;
echo "Validating the composer.json";
composer validate --no-check-all --no-check-lock --strict;
- >
echo;
echo "Linting all PHP files";
composer ci:php:lint;
- >
echo;
echo "Running the unit tests";
composer ci:tests:unit;
- >
echo;
echo "Running PHPMD";
composer ci:php:md;
- >
echo;
function version_gte() { test "$(printf '%s\n' "$@" | sort -n -t. -r | head -n 1)" = "$1"; };
if version_gte $(composer php:version) 7; then
echo "Installing slevomat/coding-standard only for PHP 7.x";
composer require $IGNORE_PLATFORM_REQS --dev slevomat/coding-standard:^4.0 $DEPENDENCIES_PREFERENCE;
echo "Running PHP_CodeSniffer";
composer ci:php:sniff;
else
echo "Skipped PHP_CodeSniffer due to insufficient PHP version: $(composer php:version)";
fi;

View File

@@ -15,6 +15,200 @@ This project adheres to [Semantic Versioning](https://semver.org/).
### Fixed
## 3.1.0
### Added
- Add support for PHP 7.4
([#821](https://github.com/MyIntervals/emogrifier/pull/821),
[#829](https://github.com/MyIntervals/emogrifier/pull/829))
### Changed
- Upgrade to Symfony 5.0
([#822](https://github.com/MyIntervals/emogrifier/pull/820)
## 3.0.0
### Added
- Test and document excluding entire subtree with `addExcludedSelector()`
([#347](https://github.com/MyIntervals/emogrifier/issues/347),
[#768](https://github.com/MyIntervals/emogrifier/pull/768))
- Test that rules with `:optional` or `:required` are copied to the `<style>`
element ([#748](https://github.com/MyIntervals/emogrifier/issues/748),
[#765](https://github.com/MyIntervals/emogrifier/pull/765))
- Test that rules with `:only-of-type` are copied to the `<style>` element
([#748](https://github.com/MyIntervals/emogrifier/issues/748),
[#760](https://github.com/MyIntervals/emogrifier/pull/760))
- Support `:last-of-type`
([#748](https://github.com/MyIntervals/emogrifier/issues/748),
[#758](https://github.com/MyIntervals/emogrifier/pull/758))
- Support `:first-of-type`
([#748](https://github.com/MyIntervals/emogrifier/issues/748),
[#757](https://github.com/MyIntervals/emogrifier/pull/757))
- Support `:empty`
([#748](https://github.com/MyIntervals/emogrifier/issues/748),
[#756](https://github.com/MyIntervals/emogrifier/pull/756))
- Test that rules with `:any-link` are copied to the `<style>` element
([#748](https://github.com/MyIntervals/emogrifier/issues/748),
[#755](https://github.com/MyIntervals/emogrifier/pull/755))
- Support and test `:only-child`
([#747](https://github.com/MyIntervals/emogrifier/issues/747),
[#754](https://github.com/MyIntervals/emogrifier/pull/754))
- Support and test `:nth-last-of-type`
([#747](https://github.com/MyIntervals/emogrifier/issues/747),
[#751](https://github.com/MyIntervals/emogrifier/pull/751))
- Support and test `:nth-last-child`
([#747](https://github.com/MyIntervals/emogrifier/issues/747),
[#750](https://github.com/MyIntervals/emogrifier/pull/750))
- Support and test general sibling combinator
([#723](https://github.com/MyIntervals/emogrifier/issues/723),
[#745](https://github.com/MyIntervals/emogrifier/pull/745))
- Test universal selector with combinators
([#723](https://github.com/MyIntervals/emogrifier/issues/723),
[#743](https://github.com/MyIntervals/emogrifier/pull/743))
- Preserve `display: none` elements with `-emogrifier-keep` class
([#252](https://github.com/MyIntervals/emogrifier/issues/252),
[#737](https://github.com/MyIntervals/emogrifier/pull/737))
- Preserve valid `@import` rules
([#338](https://github.com/MyIntervals/emogrifier/issues/338),
[#334](https://github.com/MyIntervals/emogrifier/pull/334),
[#732](https://github.com/MyIntervals/emogrifier/pull/732),
[#735](https://github.com/MyIntervals/emogrifier/pull/735))
- Add `HtmlPruner::removeRedundantClassesAfterCssInlined`
([#380](https://github.com/MyIntervals/emogrifier/issues/380),
[#724](https://github.com/MyIntervals/emogrifier/pull/724))
- Check on Travis that PHP-CS-Fixer will not change anything
[#727](https://github.com/MyIntervals/emogrifier/pull/727))
- Support `:not(…)` as an entire selector
([#469](https://github.com/MyIntervals/emogrifier/issues/469),
[#725](https://github.com/MyIntervals/emogrifier/pull/725))
- Add `HtmlPruner::removeRedundantClasses`
([#380](https://github.com/MyIntervals/emogrifier/issues/380),
[#708](https://github.com/MyIntervals/emogrifier/pull/708))
- Support multiple attributes selectors
([#385](https://github.com/MyIntervals/emogrifier/issues/385),
[#721](https://github.com/MyIntervals/emogrifier/pull/721))
- Support `> :first-child` and `> :last-child` in selectors
([#384](https://github.com/MyIntervals/emogrifier/issues/384),
[#720](https://github.com/MyIntervals/emogrifier/pull/720))
- Add an `ArrayIntersector` class
([#708](https://github.com/MyIntervals/emogrifier/pull/708),
[#710](https://github.com/MyIntervals/emogrifier/pull/710))
- Add `CssInliner::getMatchingUninlinableSelectors`
([#380](https://github.com/MyIntervals/emogrifier/issues/380),
[#707](https://github.com/MyIntervals/emogrifier/pull/707))
- Add tests for `:nth-child` and `:nth-of-type`
([#71](https://github.com/MyIntervals/emogrifier/issues/71),
[#698](https://github.com/MyIntervals/emogrifier/pull/698))
### Changed
- Relax the dependency on `symfony/css-selector`
([#762](https://github.com/MyIntervals/emogrifier/pull/762))
- Rename `HtmlPruner::removeInvisibleNodes` to
`HtmlPruner::removeElementsWithDisplayNone`
([#717](https://github.com/MyIntervals/emogrifier/issues/717),
[#718](https://github.com/MyIntervals/emogrifier/pull/718))
- Mark the utility classes as internal
([#715](https://github.com/MyIntervals/emogrifier/pull/715))
- Move utility classes to the `Pelago\Emogrifier\Utilities` namespace
([#712](https://github.com/MyIntervals/emogrifier/pull/712))
- Make the `$css` parameter of the `inlineCss` method optional
([#700](https://github.com/MyIntervals/emogrifier/pull/700))
- Update the development dependencies
([#691](https://github.com/MyIntervals/emogrifier/pull/691))
### Deprecated
- Support for PHP 5.6 will be removed in Emogrifier 4.0.
- Deprecate the `Emogrifier` class
([#701](https://github.com/MyIntervals/emogrifier/pull/701))
### Removed
- Drop `enableCssToHtmlMapping` and `disableInvisibleNodeRemoval`
([#692](https://github.com/MyIntervals/emogrifier/pull/692))
- Drop support for PHP 5.5
([#690](https://github.com/MyIntervals/emogrifier/pull/690))
### Fixed
- Fix PhpStorm code inspection warnings
([#729](https://github.com/MyIntervals/emogrifier/issues/729),
[#770](https://github.com/MyIntervals/emogrifier/pull/770))
- Uppercase type combined with class or ID in selector
([#590](https://github.com/MyIntervals/emogrifier/issues/590),
[#769](https://github.com/MyIntervals/emogrifier/pull/769))
- Dynamic pseudo-class combined with static one (rules copied to `<style>`
element, [#746](https://github.com/MyIntervals/emogrifier/pull/746))
- Descendant attribute selectors (such as `html input[disabled]`)
([#375](https://github.com/MyIntervals/emogrifier/pull/375),
[#709](https://github.com/MyIntervals/emogrifier/pull/709))
- Attribute selectors with hyphen in attribute name
([#284](https://github.com/MyIntervals/emogrifier/issues/284),
[#540](https://github.com/MyIntervals/emogrifier/pull/540),
[#704](https://github.com/MyIntervals/emogrifier/pull/702))
- Attribute selectors with space, hyphen, colon, semicolon or (most) other
non-alphanumeric characters in attribute value
([#284](https://github.com/MyIntervals/emogrifier/issues/284),
[#333](https://github.com/MyIntervals/emogrifier/issues/333),
[#550](https://github.com/MyIntervals/emogrifier/issues/550),
[#540](https://github.com/MyIntervals/emogrifier/pull/540),
[#704](https://github.com/MyIntervals/emogrifier/pull/702))
- Dont create empty `style` attributes for unparsable declarations
([#259](https://github.com/MyIntervals/emogrifier/issues/259),
[#702](https://github.com/MyIntervals/emogrifier/pull/702))
- Allow `:not(:behavioural-pseudo-class)` in selectors
([#697](https://github.com/MyIntervals/emogrifier/pull/697),
[#703](https://github.com/MyIntervals/emogrifier/pull/703))
## 2.2.0
### Added
- Add a `HtmlPruner` class
([#679](https://github.com/MyIntervals/emogrifier/pull/679))
- Add `AbstractHtmlProcessor::fromDomDocument`
([#676](https://github.com/MyIntervals/emogrifier/pull/676))
- Add `AbstractHtmlProcessor::fromHtml`
([#675](https://github.com/MyIntervals/emogrifier/pull/675))
### Changed
- Make the closures static
([#674](https://github.com/MyIntervals/emogrifier/pull/674))
- Keep `<wbr>` elements by default with `CssInliner`
([#665](https://github.com/MyIntervals/emogrifier/pull/665))
- Make the `CssInliner` inherit `AbstractHtmlProcessor`
([#660](https://github.com/MyIntervals/emogrifier/pull/660))
- Separate `CssInliner::inlineCss` and the rendering
([#654](https://github.com/MyIntervals/emogrifier/pull/654))
### Removed
- Drop the removal of unprocessable tags from `CssInliner`
([#685](https://github.com/MyIntervals/emogrifier/pull/685))
- Drop the removal of invisible nodes from `CssInliner`
([#684](https://github.com/MyIntervals/emogrifier/pull/684))
### Fixed
- Remove opening `<body>` tag from `body` content when element has attribute(s)
([#677](https://github.com/MyIntervals/emogrifier/issues/677),
[#683](https://github.com/MyIntervals/emogrifier/pull/683))
- Keep development files out of the Composer packages
([#678](https://github.com/MyIntervals/emogrifier/pull/678))
- Call all static methods statically in `CssConcatenator`
([#670](https://github.com/MyIntervals/emogrifier/pull/670))
- Support all HTML5 self-closing tags, including `<embed>`, `<source>`,
`<track>` and `<wbr>`
([#653](https://github.com/MyIntervals/emogrifier/pull/653))
- Remove all "unprocessable" (e.g. `<wbr>`) tags
([#650](https://github.com/MyIntervals/emogrifier/pull/650))
- Correct translated xpath of `:nth-child` selector
([#648](https://github.com/MyIntervals/emogrifier/pull/648))
## 2.1.1
### Changed
- Add a test that a missing document type gets added
([#641](https://github.com/MyIntervals/emogrifier/pull/641))
### Fixed
- Keep the `style` element the `head`
([#642](https://github.com/MyIntervals/emogrifier/pull/642))
## 2.1.0
### Added
@@ -26,7 +220,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
([#633](https://github.com/MyIntervals/emogrifier/pull/633))
- Add a `getDomDocument()` method
([#630](https://github.com/MyIntervals/emogrifier/pull/630))
- Add a Composer script for PHP CS Fixer
- Add a Composer script for PHP CS Fixer
([#607](https://github.com/MyIntervals/emogrifier/pull/607))
- Copy matching rules with dynamic pseudo-classes or pseudo-elements in
selectors to the style element
@@ -34,18 +228,18 @@ This project adheres to [Semantic Versioning](https://semver.org/).
[#562](https://github.com/MyIntervals/emogrifier/pull/562),
[#567](https://github.com/MyIntervals/emogrifier/pull/567))
- Add a CssToAttributeConverter
([#546](https://github.com/jjriv/emogrifier/pull/546))
([#546](https://github.com/MyIntervals/emogrifier/pull/546))
- Expose the DOMDocument in AbstractHtmlProcessor
([#520](https://github.com/jjriv/emogrifier/pull/520))
([#520](https://github.com/MyIntervals/emogrifier/pull/520))
- Add an HtmlNormalizer class
([#513](https://github.com/jjriv/emogrifier/pull/513),
[#516](https://github.com/jjriv/emogrifier/pull/516))
([#513](https://github.com/MyIntervals/emogrifier/pull/513),
[#516](https://github.com/MyIntervals/emogrifier/pull/516))
- Add a CssInliner class
([#514](https://github.com/jjriv/emogrifier/pull/514),
[#522](https://github.com/jjriv/emogrifier/pull/522))
([#514](https://github.com/MyIntervals/emogrifier/pull/514),
[#522](https://github.com/MyIntervals/emogrifier/pull/522))
- Composer scripts for the various CI build steps
- Validate the composer.json on Travis
([#476](https://github.com/jjriv/emogrifier/pull/476))
([#476](https://github.com/MyIntervals/emogrifier/pull/476))
### Changed
- Mark the work-in-progress classes as `@internal`
@@ -62,20 +256,20 @@ This project adheres to [Semantic Versioning](https://semver.org/).
- Add type hint checking to the code sniffs
([#566](https://github.com/MyIntervals/emogrifier/pull/566))
- Check the code with PHPMD
([#561](https://github.com/jjriv/emogrifier/pull/561))
([#561](https://github.com/MyIntervals/emogrifier/pull/561))
- Add the cyclomatic complexity to the checked code sniffs
([#558](https://github.com/jjriv/emogrifier/pull/558))
([#558](https://github.com/MyIntervals/emogrifier/pull/558))
- Use the Symfony CSS selector component
([#540](https://github.com/jjriv/emogrifier/pull/540))
([#540](https://github.com/MyIntervals/emogrifier/pull/540))
### Deprecated
- Support for PHP 5.5 will be removed in Emogrifier 3.0.
- Support for PHP 5.6 will be removed in Emogrifier 4.0.
- The removal of invisible nodes will be removed in Emogrifier 3.0.
([#473](https://github.com/jjriv/emogrifier/pull/473))
([#473](https://github.com/MyIntervals/emogrifier/pull/473))
- Converting CSS styles to (non-CSS) HTML attributes will be removed
in Emogrifier 3.0. Please use the new CssToAttributeConverter instead.
([#474](https://github.com/jjriv/emogrifier/pull/474))
([#474](https://github.com/MyIntervals/emogrifier/pull/474))
- Emogrifier 3.x.y will be the last release that supports usage without
Composer (i.e., you can still require the class file).
Starting with version 4.0, Emogrifier will only work with Composer.
@@ -114,7 +308,8 @@ This project adheres to [Semantic Versioning](https://semver.org/).
ignored, [#507](https://github.com/MyIntervals/emogrifier/pull/507))
- Allow attribute selectors in descendants
([#506](https://github.com/MyIntervals/emogrifier/pull/506),
[#381](https://github.com/MyIntervals/emogrifier/issues/381))
[#381](https://github.com/MyIntervals/emogrifier/issues/381),
[#443](https://github.com/MyIntervals/emogrifier/issues/443))
- Allow adjacent sibling CSS selector combinator in minified CSS
([#505](https://github.com/MyIntervals/emogrifier/pull/505))
- Allow CSS property values containing newlines
@@ -124,7 +319,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
### Added
- Support for CSS :not() selector
([#431](https://github.com/jjriv/emogrifier/pull/431))
([#431](https://github.com/MyIntervals/emogrifier/pull/431))
- Automatically remove !important annotations from final inline style declarations
([#420](https://github.com/MyIntervals/emogrifier/pull/420))
- Automatically move `<style>` block from `<head>` to `<body>`

View File

@@ -1,77 +0,0 @@
# Contributor Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age,
body size, disability, ethnicity, gender identity and expression, level of
experience, nationality, personal appearance, race, religion, or sexual
identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an
appointed representative at an online or offline event. Representation of a
project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at (emogrifier at myintervals dot com).
All complaints will be reviewed and investigated and will result in a response
that is deemed necessary and appropriate to the circumstances. The project team
is obligated to maintain confidentiality with regard to the reporter of an
incident. Further details of specific enforcement policies may be posted
separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 1.4, available at
[http://contributor-covenant.org/version/1/4/][version].
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

View File

@@ -30,11 +30,9 @@ into inline style attributes in your HTML code.
- [How it works](#how-it-works)
- [Installation](#installation)
- [Usage](#usage)
- [Options](#options)
- [Installing with Composer](#installing-with-composer)
- [Supported CSS selectors](#supported-css-selectors)
- [Caveats](#caveats)
- [Processing HTML](#processing-html)
- [Steps to release a new version](#steps-to-release-a-new-version)
- [Maintainers](#maintainers)
## How it Works
@@ -45,15 +43,212 @@ selectors.
## Installation
For installing emogrifier, either add pelago/emogrifier to your
project's composer.json, or you can use composer as below:
For installing emogrifier, either add `pelago/emogrifier` to the `require`
section in your project's `composer.json`, or you can use composer as below:
```bash
composer require pelago/emogrifier
```
See https://getcomposer.org/ for more information and documentation.
## Usage
### Inlining Css
The most basic way to use the `CssInliner` class is to create an instance with
the original HTML, inline the external CSS, and then get back the resulting
HTML:
```php
use Pelago\Emogrifier\CssInliner;
$visualHtml = CssInliner::fromHtml($html)->inlineCss($css)->render();
```
If there is no external CSS file and all CSS is located within `<style>`
elements in the HTML, you can omit the `$css` parameter:
```php
$visualHtml = CssInliner::fromHtml($html)->inlineCss()->render();
```
If you would like to get back only the content of the `<body>` element instead of
the complete HTML document, you can use the `renderBodyContent` method instead:
```php
$bodyContent = $visualHtml = CssInliner::fromHtml($html)->inlineCss()
->renderBodyContent();
```
If you would like to modify the inlining process with any of the available
[options](#options), you will need to call the corresponding methods
before inlining the CSS. The code then would look like this:
```php
$visualHtml = CssInliner::fromHtml($html)->disableStyleBlocksParsing()
->inlineCss($css)->render();
```
There are also some other HTML-processing classes available
(all of which are subclasses of `AbstractHtmlProcessor`) which you can use
to further change the HTML after inlining the CSS.
(For more details on the classes, please have a look at the sections below.)
`CssInliner` and all HTML-processing classes can share the same `DOMDocument`
instance to work on:
```php
use Pelago\Emogrifier\CssInliner;
use Pelago\Emogrifier\HtmlProcessor\CssToAttributeConverter;
use Pelago\Emogrifier\HtmlProcessor\HtmlPruner;
$cssInliner = CssInliner::fromHtml($html)->inlineCss($css);
$domDocument = $cssInliner->getDomDocument();
HtmlPruner::fromDomDocument($domDocument)->removeElementsWithDisplayNone()
->removeRedundantClassesAfterCssInlined($cssInliner);
$finalHtml = CssToAttributeConverter::fromDomDocument($domDocument)
->convertCssToVisualAttributes()->render();
```
### Normalizing and cleaning up HTML
The `HtmlNormalizer` class normalizes the given HTML in the following ways:
- add a document type (HTML5) if missing
- disentangle incorrectly nested tags
- add HEAD and BODY elements (if they are missing)
- reformat the HTML
The class can be used like this:
```php
use Pelago\Emogrifier\HtmlProcessor\HtmlNormalizer;
$cleanHtml = HtmlNormalizer::fromHtml($rawHtml)->render();
```
### Converting CSS styles to visual HTML attributes
The `CssToAttributeConverter` converts a few style attributes values to visual
HTML attributes. This allows to get at least a bit of visual styling for email
clients that do not support CSS well. For example, `style="width: 100px"`
will be converted to `width="100"`.
The class can be used like this:
```php
use Pelago\Emogrifier\HtmlProcessor\CssToAttributeConverter;
$visualHtml = CssToAttributeConverter::fromHtml($rawHtml)
->convertCssToVisualAttributes()->render();
```
You can also have the `CssToAttributeConverter` work on a `DOMDocument`:
```php
$visualHtml = CssToAttributeConverter::fromDomDocument($domDocument)
->convertCssToVisualAttributes()->render();
```
### Removing redundant content and attributes from the HTML
The `HtmlPruner` class can reduce the size of the HTML by removing elements with
a `display: none` style declaration, and/or removing classes from `class`
attributes that are not required.
It can be used like this:
```php
use Pelago\Emogrifier\HtmlProcessor\HtmlPruner;
$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
determine which classes are still required (namely, those used in uninlinable
rules that have been copied to a `<style>` element):
```php
$prunedHtml = HtmlPruner::fromDomDocument($cssInliner->getDomDocument())
->removeElementsWithDisplayNone()
->removeRedundantClassesAfterCssInlined($cssInliner)->render();
```
The `removeElementsWithDisplayNone` method will not remove any 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
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!
</p>
```
The `removeRedundantClassesAfterCssInlined` (or `removeRedundantClasses`)
method, if invoked after `removeElementsWithDisplayNone`, will remove the
`-emogrifier-keep` class.
### Options
There are several options that you can set on the `CssInliner` instance before
calling the `inlineCss` method (or on the `Emogrifier` instance before calling
the `emogrify` method):
* `->disableStyleBlocksParsing()` - By default, `CssInliner` will grab
all `<style>` blocks in the HTML and will apply the CSS styles as inline
"style" attributes to the HTML. The `<style>` blocks will then be removed
from the HTML. If you want to disable this functionality so that `CssInliner`
leaves these `<style>` blocks in the HTML and does not parse them, you should
use this option. If you use this option, the contents of the `<style>` blocks
will _not_ be applied as inline styles and any CSS you want `CssInliner` to
use must be passed in as described in the [Usage section](#usage) above.
* `->disableInlineStylesParsing()` - By default, `CssInliner`
preserves all of the "style" attributes on tags in the HTML you pass to it.
However if you want to discard all existing inline styles in the HTML before
the CSS is applied, you should use this option.
* `->addAllowedMediaType(string $mediaName)` - By default, `CssInliner`
will keep only media types `all`, `screen` and `print`. If you want to keep
some others, you can use this method to define them.
* `->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
selector(s) will be excluded from CSS inlining, not necessarily their
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 *');
```
### Using the legacy Emogrifier class
In version 3.0.0, the `Emogrifier` class has been deprecated, and it will be
removed for version 4.0.0. Please update your code to use the new
`CssInliner` class instead.
If you are still using the deprecated class, here is how to use it:
First, you provide Emogrifier with the HTML and CSS you would like to merge.
This can happen directly during instantiation:
@@ -92,74 +287,48 @@ the complete HTML document, you can use the `emogrifyBodyContent` instead:
$bodyContent = $emogrifier->emogrifyBodyContent();
```
## Options
### Migrating from `Emogrifier` to `CssInliner`
There are several options that you can set on the Emogrifier object before
calling the `emogrify` method:
#### Minimal example
* `$emogrifier->disableStyleBlocksParsing()` - By default, Emogrifier will grab
all `<style>` blocks in the HTML and will apply the CSS styles as inline
"style" attributes to the HTML. The `<style>` blocks will then be removed
from the HTML. If you want to disable this functionality so that Emogrifier
leaves these `<style>` blocks in the HTML and does not parse them, you should
use this option. If you use this option, the contents of the `<style>` blocks
will _not_ be applied as inline styles and any CSS you want Emogrifier to
use must be passed in as described in the [Usage section](#usage) above.
* `$emogrifier->disableInlineStylesParsing()` - By default, Emogrifier
preserves all of the "style" attributes on tags in the HTML you pass to it.
However if you want to discard all existing inline styles in the HTML before
the CSS is applied, you should use this option.
* `$emogrifier->disableInvisibleNodeRemoval()` - By default, Emogrifier removes
elements from the DOM that have the style attribute `display: none;`. If
you would like to keep invisible elements in the DOM, use this option.
Note: This option will be removed in Emogrifier 3.0. HTML tags with
`display: none;` then will always be retained.
* `$emogrifier->addAllowedMediaType(string $mediaName)` - By default, Emogrifier
will keep only media types `all`, `screen` and `print`. If you want to keep
some others, you can use this method to define them.
* `$emogrifier->removeAllowedMediaType(string $mediaName)` - You can use this
method to remove media types that Emogrifier keeps.
* `$emogrifier->addExcludedSelector(string $selector)` - Keeps elements from
being affected by emogrification.
* `$emogrifier->enableCssToHtmlMapping()` - Some email clients don't support CSS
well, even if inline and prefer HTML attributes. This function allows you to
put properties such as height, width, background color and font color in your
CSS while the transformed content will have all the available HTML
attributes set. This option will be removed in Emogrifier 3.0. Please use the
`CssToAttributeConverter` class instead.
Old code using `Emogrifier`:
## Installing with Composer
Download the [`composer.phar`](https://getcomposer.org/composer.phar) locally
or install [Composer](https://getcomposer.org/) globally:
```bash
curl -s https://getcomposer.org/installer | php
```php
$emogrifier = new Emogrifier($html);
$html = $emogrifier->emogrify();
```
Run the following command for a local installation:
New code using `CssInliner`:
```bash
php composer.phar require pelago/emogrifier:^2.1.0
```php
$html = CssInliner::fromHtml($html)->inlineCss()->render();
```
Or for a global installation, run the following command:
NB: In this example, the old code removes elements with `display: none;`
while the new code does not, as the default behaviors of the old and
the new class differ in this regard.
```bash
composer require pelago/emogrifier:^2.1.0
#### More complex example
Old code using `Emogrifier`:
```php
$emogrifier = new Emogrifier($html, $css);
$emogrifier->enableCssToHtmlMapping();
$html = $emogrifier->emogrify();
```
You can also add follow lines to your `composer.json` and run the
`composer update` command:
New code using `CssInliner` and family:
```json
"require": {
"pelago/emogrifier": "^2.1.0"
}
```php
$domDocument = CssInliner::fromHtml($html)->inlineCss($css)->getDomDocument();
HtmlPruner::fromDomDocument($domDocument)->removeElementsWithDisplayNone(),
$html = CssToAttributeConverter::fromDomDocument($domDocument)
->convertCssToVisualAttributes()->render();
```
See https://getcomposer.org/ for more information and documentation.
## Supported CSS selectors
Emogrifier currently supports the following
@@ -168,7 +337,8 @@ Emogrifier currently supports the following
* [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):
* [universal](https://developer.mozilla.org/en-US/docs/Web/CSS/Universal_selectors)
(partial support)
* [attribute](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors):
* presence
* exact value match
@@ -178,42 +348,103 @@ Emogrifier currently supports the following
* 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` which will
behave as `*:first-child`)
* [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)
The following selectors are not implemented yet:
* [universal](https://developer.mozilla.org/en-US/docs/Web/CSS/Universal_selectors)
* [universal](https://developer.mozilla.org/en-US/docs/Web/CSS/Universal_selectors):
* with
[child combinator](https://developer.mozilla.org/en-US/docs/Web/CSS/Child_combinator)
* as ancestor with
[descendant combinator](https://developer.mozilla.org/en-US/docs/Web/CSS/Descendant_combinator)
(e.g. `p *` is supported but `* p` is not)
* [case-insensitive attribute value](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors#case-insensitive)
* [general sibling](https://developer.mozilla.org/en-US/docs/Web/CSS/General_sibling_selectors)
* [pseudo-classes](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes)
(some of them will never be supported)
* static [pseudo-classes](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes):
* [first-of-type](https://developer.mozilla.org/en-US/docs/Web/CSS/:first-of-type)
without a type (will behave as `:first-child`)
* [last-of-type](https://developer.mozilla.org/en-US/docs/Web/CSS/:last-of-type)
without a type (will behave as `:last-child`)
* [nth-last-of-type()](https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-last-of-type)
without a type (will behave as `:nth-last-child()`)
* [nth-of-type()](https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-of-type)
without a type (will behave as `:nth-child()`)
* any 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)
* [only-of-type](https://developer.mozilla.org/en-US/docs/Web/CSS/:only-of-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`)
## Caveats
* Emogrifier requires the HTML and the CSS to be UTF-8. Encodings like
ISO8859-1 or ISO8859-15 are not supported.
* Emogrifier now preserves all valuable @media queries. Media queries
can be very useful in responsive email design. See
* Emogrifier preserves all valuable `@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`
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
(even though `!important` would not be necessary if the CSS were not inlined):
```css
p {
font-size: 16px;
}
@media (max-width: 640px) {
p {
font-size: 14px !important;
}
}
```
* 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.
* 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. (The problem email clients are going to ignore
these tags anyway, so why leave them in your HTML?)
referenced in `<link>` elements or `@import` rules (though it will leave them
intact for email clients that support them).
* Even with styles inline, certain CSS properties are ignored by certain email
clients. For more information, refer to these resources:
* [http://www.email-standards.org/](http://www.email-standards.org/)
* [https://www.campaignmonitor.com/css/](https://www.campaignmonitor.com/css/)
* [http://templates.mailchimp.com/resources/email-client-css-support/](http://templates.mailchimp.com/resources/email-client-css-support/)
* All CSS attributes that apply to a node will be applied, even if they are
* All CSS attributes that apply to an element will be applied, even if they are
redundant. For example, if you define a font attribute _and_ a font-size
attribute, both attributes will be applied to that node (in other words, the
more specific attribute will not be combined into the more general
attribute, both attributes will be applied to that element (in other words,
the more specific attribute will not be combined into the more general
attribute).
* There's a good chance you might encounter problems if your HTML is not
well-formed and valid (DOMDocument might complain). If you get problems like
@@ -223,53 +454,6 @@ The following selectors are not implemented yet:
* Emogrifier automatically converts the provided (X)HTML into HTML5, i.e.,
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.
* Emogrifier only supports CSS1 level selectors and a few CSS2 level selectors
(but not all of them). It does not support pseudo selectors. (Emogrifier
works by converting CSS selectors to XPath selectors, and pseudo selectors
cannot be converted accurately).
## Processing HTML
The Emogrifier package also provides classes for (post-)processing the HTML
generated by `emogrify` (and it also works on any other HTML).
### Normalizing and cleaning up HTML
The `HtmlNormalizer` class normalizes the given HTML in the following ways:
- add a document type (HTML5) if missing
- disentangle incorrectly nested tags
- add HEAD and BODY elements (if they are missing)
- reformat the HTML
The class can be used like this:
```php
$normalizer = new \Pelago\Emogrifier\HtmlProcessor\HtmlNormalizer($rawHtml);
$cleanHtml = $normalizer->render();
```
### Converting CSS styles to visual HTML attributes
The `CssToAttributeConverter` converts a few style attributes values to visual
HTML attributes. This allows to get at least a bit of visual styling for email
clients that do not support CSS well. For example, `style="width: 100px"`
will be converted to `width="100"`.
The class can be used like this:
```php
$converter = new \Pelago\Emogrifier\HtmlProcessor\CssToAttributeConverter($rawHtml);
$visualHtml = $converter->convertCssToVisualAttributes()->render();
```
### Technology preview of new classes
Currently, a refactoring effort is underway, aiming towards replacing the
grown-over-time `Emogrifier` class with the new `CssInliner` class and moving
additional HTML processing into separate `CssProcessor` classes (which will
inherit from `AbstractHtmlProcessor`). You can try the new classes, but be
aware that the APIs of the new classes still are subject to change.
## Steps to release a new version
@@ -277,11 +461,11 @@ aware that the APIs of the new classes still are subject to change.
changes.
1. In the [composer.json](composer.json), update the `branch-alias` entry to
point to the release _after_ the upcoming release.
1. In the [README.md](README.md), update the version numbers in the section
[Installing with Composer](#installing-with-composer).
1. In the [CHANGELOG.md](CHANGELOG.md), set the version number and remove any
empty sections.
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.
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.
1. Post about the new release on social media.

View File

@@ -37,16 +37,16 @@
"source": "https://github.com/MyIntervals/emogrifier"
},
"require": {
"php": "^5.5.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0",
"php": "^5.6 || ~7.0 || ~7.1 || ~7.2 || ~7.3 || ~7.4",
"ext-dom": "*",
"ext-libxml": "*",
"symfony/css-selector": "^3.4.0 || ^4.0.0"
"symfony/css-selector": "^2.8 || ^3.0 || ^4.0 || ^5.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.2.0",
"squizlabs/php_codesniffer": "^3.3.2",
"phpmd/phpmd": "^2.6.0",
"phpunit/phpunit": "^4.8.0"
"friendsofphp/php-cs-fixer": "^2.15.3",
"squizlabs/php_codesniffer": "^3.5.0",
"phpmd/phpmd": "^2.7.0",
"phpunit/phpunit": "^5.7.27"
},
"autoload": {
"psr-4": {
@@ -69,6 +69,7 @@
"php:fix": "php-cs-fixer --config=config/php-cs-fixer.php fix config/ src/ tests/",
"ci:php:lint": "find config src tests -name '*.php' -print0 | xargs -0 -n 1 -P 4 php -l",
"ci:php:sniff": "phpcs config src tests",
"ci:php:fixer": "php-cs-fixer --config=config/php-cs-fixer.php fix --dry-run -v --show-progress=dots --diff-format=udiff config/ src/ tests/",
"ci:php:md": "phpmd src text config/phpmd.xml",
"ci:tests:unit": "phpunit tests/",
"ci:tests": [
@@ -80,6 +81,7 @@
"ci:static": [
"@ci:php:lint",
"@ci:php:sniff",
"@ci:php:fixer",
"@ci:php:md"
],
"ci": [
@@ -89,7 +91,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "2.1.x-dev"
"dev-master": "4.0.x-dev"
}
}
}

View File

@@ -1,93 +0,0 @@
<?php
if (PHP_SAPI !== 'cli') {
die('This script supports command line usage only. Please check your command.');
}
return \PhpCsFixer\Config::create()
->setRiskyAllowed(true)
->setRules(
[
// copied from the TYPO3 Core
'@PSR2' => true,
'@DoctrineAnnotation' => true,
'no_leading_import_slash' => true,
'no_trailing_comma_in_singleline_array' => true,
'no_singleline_whitespace_before_semicolons' => true,
'no_unused_imports' => true,
'concat_space' => ['spacing' => 'one'],
'no_whitespace_in_blank_line' => true,
'ordered_imports' => true,
'single_quote' => true,
'no_empty_statement' => true,
'no_extra_consecutive_blank_lines' => true,
'phpdoc_no_package' => true,
'phpdoc_scalar' => true,
'no_blank_lines_after_phpdoc' => true,
'array_syntax' => ['syntax' => 'short'],
'whitespace_after_comma_in_array' => true,
'function_typehint_space' => true,
'hash_to_slash_comment' => true,
'no_alias_functions' => true,
'lowercase_cast' => true,
'no_leading_namespace_whitespace' => true,
'native_function_casing' => true,
'no_short_bool_cast' => true,
'no_unneeded_control_parentheses' => true,
'phpdoc_trim' => true,
'no_superfluous_elseif' => true,
'no_useless_else' => true,
'phpdoc_types' => true,
'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'],
'return_type_declaration' => ['space_before' => 'none'],
'cast_spaces' => ['space' => 'none'],
'declare_equal_normalize' => ['space' => 'single'],
'dir_constant' => true,
// additional rules
'combine_consecutive_issets' => true,
'combine_consecutive_unsets' => true,
'compact_nullable_typehint' => true,
// PHP >= 7.0
// 'declare_strict_types' => true,
'elseif' => true,
'encoding' => true,
'escape_implicit_backslashes' => ['single_quoted' => true],
'is_null' => true,
'linebreak_after_opening_tag' => true,
'magic_constant_casing' => true,
'method_separation' => true,
'modernize_types_casting' => true,
// not yet, but maybe later to improve performance
// 'native_function_invocation' => true,
'new_with_braces' => true,
'no_blank_lines_after_class_opening' => true,
'no_empty_comment' => true,
'no_empty_phpdoc' => true,
'no_extra_blank_lines' => true,
'no_multiline_whitespace_before_semicolons' => true,
'no_php4_constructor' => true,
'no_short_echo_tag' => true,
'no_spaces_after_function_name' => true,
'no_spaces_inside_parenthesis' => true,
'no_unneeded_curly_braces' => true,
'no_useless_return' => true,
'no_whitespace_before_comma_in_array' => true,
'php_unit_construct' => true,
'php_unit_fqcn_annotation' => true,
'php_unit_set_up_tear_down_visibility' => true,
'phpdoc_add_missing_param_annotation' => true,
'phpdoc_indent' => true,
'phpdoc_separation' => true,
'semicolon_after_instruction' => true,
'short_scalar_cast' => true,
'space_after_semicolon' => true,
'standardize_not_equals' => true,
'psr4' => true,
'ternary_operator_spaces' => true,
// PHP >= 7.0
// 'ternary_to_null_coalescing' => true,
'trailing_comma_in_multiline_array' => true,
'unary_operator_spaces' => true,
]
);

View File

@@ -1,49 +0,0 @@
<?xml version="1.0"?>
<ruleset name="phpList">
<description>
PHPMD rules for Emogrifier
</description>
<!-- The commented-out rules will be enabled once the code does not generate any warnings anymore. -->
<rule ref="rulesets/cleancode.xml/BooleanArgumentFlag"/>
<rule ref="rulesets/cleancode.xml/StaticAccess"/>
<rule ref="rulesets/codesize.xml/CyclomaticComplexity"/>
<rule ref="rulesets/codesize.xml/NPathComplexity"/>
<rule ref="rulesets/codesize.xml/ExcessiveMethodLength"/>
<!--<rule ref="rulesets/codesize.xml/ExcessiveClassLength"/>-->
<!--<rule ref="rulesets/codesize.xml/ExcessiveParameterList"/>-->
<rule ref="rulesets/codesize.xml/ExcessivePublicCount"/>
<!--<rule ref="rulesets/codesize.xml/TooManyFields"/>-->
<!--<rule ref="rulesets/codesize.xml/TooManyMethods"/>-->
<!--<rule ref="rulesets/codesize.xml/TooManyPublicMethods"/>-->
<!--<rule ref="rulesets/codesize.xml/ExcessiveClassComplexity"/>-->
<rule ref="rulesets/controversial.xml/Superglobals"/>
<rule ref="rulesets/controversial.xml/CamelCaseClassName"/>
<rule ref="rulesets/controversial.xml/CamelCasePropertyName"/>
<rule ref="rulesets/controversial.xml/CamelCaseMethodName"/>
<rule ref="rulesets/controversial.xml/CamelCaseParameterName"/>
<rule ref="rulesets/controversial.xml/CamelCaseVariableName"/>
<rule ref="rulesets/design.xml/ExitExpression"/>
<rule ref="rulesets/design.xml/EvalExpression"/>
<rule ref="rulesets/design.xml/GotoStatement"/>
<rule ref="rulesets/design.xml/NumberOfChildren"/>
<rule ref="rulesets/design.xml/DepthOfInheritance"/>
<rule ref="rulesets/design.xml/CouplingBetweenObjects"/>
<rule ref="rulesets/design.xml/DevelopmentCodeFragment"/>
<!--<rule ref="rulesets/naming.xml/ShortVariable"/>-->
<!--<rule ref="rulesets/naming.xml/LongVariable"/>-->
<rule ref="rulesets/naming.xml/ShortMethodName"/>
<rule ref="rulesets/naming.xml/ConstructorWithNameAsEnclosingClass"/>
<rule ref="rulesets/naming.xml/ConstantNamingConventions"/>
<rule ref="rulesets/naming.xml/BooleanGetMethodName"/>
<rule ref="rulesets/unusedcode.xml/UnusedPrivateField"/>
<rule ref="rulesets/unusedcode.xml/UnusedLocalVariable"/>
<!--<rule ref="rulesets/unusedcode.xml/UnusedPrivateMethod"/>-->
<!--<rule ref="rulesets/unusedcode.xml/UnusedFormalParameter"/>-->
</ruleset>

View File

@@ -1,140 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="Coding Standard">
<description>
This standard requires PHP_CodeSniffer >= 3.2.0.
</description>
<config name="installed_paths" value="../../slevomat/coding-standard"/>
<!--The complete PSR-2 ruleset-->
<rule ref="PSR2"/>
<!-- Arrays -->
<rule ref="Generic.Arrays.DisallowLongArraySyntax"/>
<rule ref="Squiz.Arrays.ArrayBracketSpacing"/>
<rule ref="Squiz.Arrays.ArrayDeclaration.NoCommaAfterLast"/>
<!-- Classes -->
<rule ref="Generic.Classes.DuplicateClassName"/>
<rule ref="Squiz.Classes.ClassFileName"/>
<rule ref="Squiz.Classes.DuplicateProperty"/>
<rule ref="Squiz.Classes.LowercaseClassKeywords"/>
<rule ref="Squiz.Classes.SelfMemberReference"/>
<!-- Code analysis -->
<rule ref="Generic.CodeAnalysis.EmptyStatement"/>
<rule ref="Generic.CodeAnalysis.AssignmentInCondition"/>
<rule ref="Generic.CodeAnalysis.ForLoopShouldBeWhileLoop"/>
<rule ref="Generic.CodeAnalysis.ForLoopWithTestFunctionCall"/>
<rule ref="Generic.CodeAnalysis.JumbledIncrementer"/>
<rule ref="Generic.CodeAnalysis.UnconditionalIfStatement"/>
<rule ref="Generic.CodeAnalysis.UnnecessaryFinalModifier"/>
<rule ref="Generic.CodeAnalysis.UnusedFunctionParameter"/>
<rule ref="Generic.CodeAnalysis.UselessOverridingMethod"/>
<!-- Commenting -->
<rule ref="Generic.Commenting.Fixme"/>
<rule ref="Generic.Commenting.Todo"/>
<rule ref="PEAR.Commenting.InlineComment"/>
<rule ref="Squiz.Commenting.DocCommentAlignment"/>
<rule ref="Squiz.Commenting.EmptyCatchComment"/>
<rule ref="Squiz.Commenting.FunctionComment">
<!-- Allow PHP-5-compatible type hinting. -->
<exclude name="Squiz.Commenting.FunctionComment.ScalarTypeHintMissing"/>
<!-- Allow no comment for self-describing parameter and exception class names. -->
<exclude name="Squiz.Commenting.FunctionComment.MissingParamComment"/>
<exclude name="Squiz.Commenting.FunctionComment.EmptyThrows"/>
<!-- Allow "int" rather than "integer", etc., in PHPDoc. -->
<exclude name="Squiz.Commenting.FunctionComment.IncorrectParamVarName"/>
<exclude name="Squiz.Commenting.FunctionComment.InvalidReturn"/>
<!-- Allow "@return" to be omitted (for methods which do not return a value). -->
<exclude name="Squiz.Commenting.FunctionComment.MissingReturn"/>
<!-- Allow parameter type, name and comment not all vertically aligned. -->
<exclude name="Squiz.Commenting.FunctionComment.SpacingAfterParamType"/>
<exclude name="Squiz.Commenting.FunctionComment.SpacingAfterParamName"/>
<!-- Allow parameter and exception descriptions which are not full sentences. -->
<exclude name="Squiz.Commenting.FunctionComment.ParamCommentNotCapital"/>
<exclude name="Squiz.Commenting.FunctionComment.ParamCommentFullStop"/>
<exclude name="Squiz.Commenting.FunctionComment.ThrowsNotCapital"/>
<exclude name="Squiz.Commenting.FunctionComment.ThrowsNoFullStop"/>
</rule>
<rule ref="Squiz.Commenting.FunctionCommentThrowTag"/>
<rule ref="Squiz.Commenting.PostStatementComment"/>
<!-- Control structures -->
<rule ref="PEAR.ControlStructures.ControlSignature"/>
<!-- Debug -->
<rule ref="Generic.Debug.ClosureLinter"/>
<!-- Files -->
<rule ref="Generic.Files.OneClassPerFile"/>
<rule ref="Generic.Files.OneInterfacePerFile"/>
<rule ref="Generic.Files.OneObjectStructurePerFile"/>
<rule ref="Zend.Files.ClosingTag"/>
<!-- Formatting -->
<rule ref="Generic.Formatting.NoSpaceAfterCast"/>
<rule ref="PEAR.Formatting.MultiLineAssignment"/>
<!-- Functions -->
<rule ref="Generic.Functions.CallTimePassByReference"/>
<rule ref="SlevomatCodingStandard.Namespaces.FullyQualifiedGlobalFunctions"/>
<rule ref="Squiz.Functions.FunctionDuplicateArgument"/>
<rule ref="Squiz.Functions.GlobalFunction"/>
<!-- Metrics -->
<rule ref="Generic.Metrics.CyclomaticComplexity"/>
<rule ref="Generic.Metrics.NestingLevel"/>
<!-- Naming conventions -->
<rule ref="Generic.NamingConventions.ConstructorName"/>
<rule ref="PEAR.NamingConventions.ValidClassName"/>
<!-- Objects -->
<rule ref="Squiz.Objects.ObjectMemberComma"/>
<!-- Operators -->
<rule ref="Squiz.Operators.IncrementDecrementUsage"/>
<rule ref="Squiz.Operators.ValidLogicalOperators"/>
<!-- PHP -->
<rule ref="Generic.PHP.BacktickOperator"/>
<rule ref="Generic.PHP.CharacterBeforePHPOpeningTag"/>
<rule ref="Generic.PHP.DeprecatedFunctions"/>
<rule ref="Generic.PHP.DisallowAlternativePHPTags"/>
<rule ref="Generic.PHP.DisallowShortOpenTag"/>
<rule ref="Generic.PHP.DiscourageGoto"/>
<rule ref="Generic.PHP.ForbiddenFunctions"/>
<rule ref="Generic.PHP.NoSilencedErrors"/>
<rule ref="Squiz.PHP.CommentedOutCode">
<properties>
<property name="maxPercentage" value="70"/>
</properties>
</rule>
<rule ref="Squiz.PHP.DisallowMultipleAssignments"/>
<rule ref="Squiz.PHP.DisallowSizeFunctionsInLoops"/>
<rule ref="Squiz.PHP.DiscouragedFunctions"/>
<rule ref="Squiz.PHP.Eval"/>
<rule ref="Squiz.PHP.GlobalKeyword"/>
<rule ref="Squiz.PHP.Heredoc"/>
<rule ref="Squiz.PHP.InnerFunctions"/>
<rule ref="Squiz.PHP.LowercasePHPFunctions"/>
<rule ref="Squiz.PHP.NonExecutableCode"/>
<!-- Scope -->
<rule ref="Squiz.Scope.MemberVarScope"/>
<rule ref="Squiz.Scope.StaticThisUsage"/>
<!--Strings-->
<rule ref="Squiz.Strings.DoubleQuoteUsage"/>
<!-- Whitespace -->
<rule ref="PEAR.WhiteSpace.ObjectOperatorIndent"/>
<rule ref="PEAR.WhiteSpace.ScopeClosingBrace"/>
<rule ref="Squiz.WhiteSpace.CastSpacing"/>
<rule ref="Squiz.WhiteSpace.LogicalOperatorSpacing"/>
<rule ref="Squiz.WhiteSpace.OperatorSpacing"/>
<rule ref="Squiz.WhiteSpace.PropertyLabelSpacing"/>
<rule ref="Squiz.WhiteSpace.SemicolonSpacing"/>
</ruleset>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -7,8 +7,6 @@ namespace Pelago\Emogrifier\HtmlProcessor;
*
* The "vanilla" subclass is the HtmlNormalizer.
*
* @internal This class currently is a new technology preview, and its API is still in flux. Don't use it in production.
*
* @author Oliver Klee <github@oliverklee.de>
*/
abstract class AbstractHtmlProcessor
@@ -23,17 +21,44 @@ abstract class AbstractHtmlProcessor
*/
const CONTENT_TYPE_META_TAG = '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">';
/**
* @var string Regular expression part to match tag names that PHP's DOMDocument implementation is not aware are
* self-closing. These are mostly HTML5 elements, but for completeness <command> (obsolete) and <keygen>
* (deprecated) are also included.
*
* @see https://bugs.php.net/bug.php?id=73175
*/
const PHP_UNRECOGNIZED_VOID_TAGNAME_MATCHER = '(?:command|embed|keygen|source|track|wbr)';
/**
* @var \DOMDocument
*/
protected $domDocument = null;
/**
* @var \DOMXPath
*/
protected $xPath = null;
/**
* The constructor.
*
* Please use ::fromHtml instead.
*/
private function __construct()
{
}
/**
* Builds a new instance from the given HTML.
*
* @param string $unprocessedHtml raw HTML, must be UTF-encoded, must not be empty
*
* @return static
*
* @throws \InvalidArgumentException if $unprocessedHtml is anything other than a non-empty string
*/
public function __construct($unprocessedHtml)
public static function fromHtml($unprocessedHtml)
{
if (!\is_string($unprocessedHtml)) {
throw new \InvalidArgumentException('The provided HTML must be a string.', 1515459744);
@@ -42,7 +67,25 @@ abstract class AbstractHtmlProcessor
throw new \InvalidArgumentException('The provided HTML must not be empty.', 1515763647);
}
$this->setHtml($unprocessedHtml);
$instance = new static();
$instance->setHtml($unprocessedHtml);
return $instance;
}
/**
* Builds a new instance from the given DOM document.
*
* @param \DOMDocument $document a DOM document returned by getDomDocument() of another instance
*
* @return static
*/
public static function fromDomDocument(\DOMDocument $document)
{
$instance = new static();
$instance->setDomDocument($document);
return $instance;
}
/**
@@ -67,6 +110,17 @@ abstract class AbstractHtmlProcessor
return $this->domDocument;
}
/**
* @param \DOMDocument $domDocument
*
* @return void
*/
private function setDomDocument(\DOMDocument $domDocument)
{
$this->domDocument = $domDocument;
$this->xPath = new \DOMXPath($this->domDocument);
}
/**
* Renders the normalized and processed HTML.
*
@@ -74,7 +128,9 @@ abstract class AbstractHtmlProcessor
*/
public function render()
{
return $this->domDocument->saveHTML();
$htmlWithPossibleErroneousClosingTags = $this->domDocument->saveHTML();
return $this->removeSelfClosingTagsClosingTags($htmlWithPossibleErroneousClosingTags);
}
/**
@@ -84,9 +140,22 @@ abstract class AbstractHtmlProcessor
*/
public function renderBodyContent()
{
$bodyNodeHtml = $this->domDocument->saveHTML($this->getBodyElement());
$htmlWithPossibleErroneousClosingTags = $this->domDocument->saveHTML($this->getBodyElement());
$bodyNodeHtml = $this->removeSelfClosingTagsClosingTags($htmlWithPossibleErroneousClosingTags);
return \str_replace(['<body>', '</body>'], '', $bodyNodeHtml);
return \preg_replace('%</?+body(?:\\s[^>]*+)?+>%', '', $bodyNodeHtml);
}
/**
* Eliminates any invalid closing tags for void elements from the given HTML.
*
* @param string $html
*
* @return string
*/
private function removeSelfClosingTagsClosingTags($html)
{
return \preg_replace('%</' . static::PHP_UNRECOGNIZED_VOID_TAGNAME_MATCHER . '>%', '', $html);
}
/**
@@ -133,11 +202,11 @@ abstract class AbstractHtmlProcessor
\libxml_clear_errors();
\libxml_use_internal_errors($libXmlState);
$this->domDocument = $domDocument;
$this->setDomDocument($domDocument);
}
/**
* Returns the HTML with added document type and Content-Type meta tag if needed,
* Returns the HTML with added document type, Content-Type meta tag, and self-closing slashes, if needed,
* ensuring that the HTML will be good for creating a DOM document from it.
*
* @param string $html
@@ -146,7 +215,8 @@ abstract class AbstractHtmlProcessor
*/
private function prepareHtmlForDomConversion($html)
{
$htmlWithDocumentType = $this->ensureDocumentType($html);
$htmlWithSelfClosingSlashes = $this->ensurePhpUnrecognizedSelfClosingTagsAreXml($html);
$htmlWithDocumentType = $this->ensureDocumentType($htmlWithSelfClosingSlashes);
return $this->addContentTypeMetaTag($htmlWithDocumentType);
}
@@ -171,6 +241,8 @@ abstract class AbstractHtmlProcessor
/**
* Adds a Content-Type meta tag for the charset.
*
* This method also ensures that there is a HEAD element.
*
* @param string $html
*
* @return string the HTML with the meta tag added
@@ -202,10 +274,29 @@ abstract class AbstractHtmlProcessor
return $reworkedHtml;
}
/**
* Makes sure that any self-closing tags not recognized as such by PHP's DOMDocument implementation have a
* self-closing slash.
*
* @param string $html
*
* @return string HTML with problematic tags converted.
*/
private function ensurePhpUnrecognizedSelfClosingTagsAreXml($html)
{
return \preg_replace(
'%<' . static::PHP_UNRECOGNIZED_VOID_TAGNAME_MATCHER . '\\b[^>]*+(?<!/)(?=>)%',
'$0/',
$html
);
}
/**
* Checks that $this->domDocument has a BODY element and adds it if it is missing.
*
* @return void
*
* @throws \UnexpectedValueException
*/
private function ensureExistenceOfBodyElement()
{
@@ -214,6 +305,9 @@ abstract class AbstractHtmlProcessor
}
$htmlElement = $this->domDocument->getElementsByTagName('html')->item(0);
if ($htmlElement === null) {
throw new \UnexpectedValueException('There is no HTML element although there should be one.', 1569930853);
}
$htmlElement->appendChild($this->domDocument->createElement('body'));
}
}

View File

@@ -10,8 +10,6 @@ namespace Pelago\Emogrifier\HtmlProcessor;
*
* To trigger the conversion, call the convertCssToVisualAttributes method.
*
* @internal This class currently is a new technology preview, and its API is still in flux. Don't use it in production.
*
* @author Oliver Klee <github@oliverklee.de>
*/
class CssToAttributeConverter extends AbstractHtmlProcessor
@@ -52,7 +50,7 @@ class CssToAttributeConverter extends AbstractHtmlProcessor
/**
* Maps the CSS from the style nodes to visual HTML attributes.
*
* @return CssToAttributeConverter fluent interface
* @return self fluent interface
*/
public function convertCssToVisualAttributes()
{
@@ -72,9 +70,7 @@ class CssToAttributeConverter extends AbstractHtmlProcessor
*/
private function getAllNodesWithStyleAttribute()
{
$xPath = new \DOMXPath($this->domDocument);
return $xPath->query('//*[@style]');
return $this->xPath->query('//*[@style]');
}
/**
@@ -103,9 +99,7 @@ class CssToAttributeConverter extends AbstractHtmlProcessor
}
$properties = [];
$declarations = \preg_split('/;(?!base64|charset)/', $cssDeclarationsBlock);
foreach ($declarations as $declaration) {
foreach (\preg_split('/;(?!base64|charset)/', $cssDeclarationsBlock) as $declaration) {
$matches = [];
if (!\preg_match('/^([A-Za-z\\-]+)\\s*:\\s*(.+)$/s', \trim($declaration), $matches)) {
continue;
@@ -176,13 +170,12 @@ class CssToAttributeConverter extends AbstractHtmlProcessor
$mapping = $this->cssToHtmlMap[$property];
$nodesMatch = !isset($mapping['nodes']) || \in_array($node->nodeName, $mapping['nodes'], true);
$valuesMatch = !isset($mapping['values']) || \in_array($value, $mapping['values'], true);
if (!$nodesMatch || !$valuesMatch) {
return false;
$canBeMapped = $nodesMatch && $valuesMatch;
if ($canBeMapped) {
$node->setAttribute($mapping['attribute'], $value);
}
$node->setAttribute($mapping['attribute'], $value);
return true;
return $canBeMapped;
}
/**
@@ -224,12 +217,14 @@ class CssToAttributeConverter extends AbstractHtmlProcessor
private function mapBackgroundProperty(\DOMElement $node, $value)
{
// parse out the color, if any
$styles = \explode(' ', $value);
$styles = \explode(' ', $value, 2);
$first = $styles[0];
if (!\is_numeric($first[0]) && \strpos($first, 'url') !== 0) {
// as this is not a position or image, assume it's a color
$node->setAttribute('bgcolor', $first);
if (\is_numeric($first[0]) || \strncmp($first, 'url', 3) === 0) {
return;
}
// as this is not a position or image, assume it's a color
$node->setAttribute('bgcolor', $first);
}
/**
@@ -307,6 +302,7 @@ class CssToAttributeConverter extends AbstractHtmlProcessor
*/
private function parseCssShorthandValue($value)
{
/** @var string[] $values */
$values = \preg_split('/\\s+/', $value);
$css = [];

View File

@@ -9,8 +9,6 @@ namespace Pelago\Emogrifier\HtmlProcessor;
* - add HEAD and BODY elements (if they are missing)
* - reformat the HTML
*
* @internal This class currently is a new technology preview, and its API is still in flux. Don't use it in production.
*
* @author Oliver Klee <github@oliverklee.de>
*/
class HtmlNormalizer extends AbstractHtmlProcessor

View File

@@ -0,0 +1,143 @@
<?php
namespace Pelago\Emogrifier\HtmlProcessor;
use Pelago\Emogrifier\CssInliner;
use Pelago\Emogrifier\Utilities\ArrayIntersector;
/**
* This class can remove things from HTML.
*
* @author Oliver Klee <github@oliverklee.de>
* @author Jake Hotson <jake.github@qzdesign.co.uk>
*/
class HtmlPruner extends AbstractHtmlProcessor
{
/**
* We need to look for display:none, but we need to do a case-insensitive search. Since DOMDocument only
* supports XPath 1.0, lower-case() isn't available to us. We've thus far only set attributes to lowercase,
* not attribute values. Consequently, we need to translate() the letters that would be in 'NONE' ("NOE")
* to lowercase.
*
* @var string
*/
const DISPLAY_NONE_MATCHER
= '//*[@style and contains(translate(translate(@style," ",""),"NOE","noe"),"display:none")'
. ' and not(@class and contains(concat(" ", normalize-space(@class), " "), " -emogrifier-keep "))]';
/**
* Removes elements that have a "display: none;" style.
*
* @return self fluent interface
*/
public function removeElementsWithDisplayNone()
{
$elementsWithStyleDisplayNone = $this->xPath->query(self::DISPLAY_NONE_MATCHER);
if ($elementsWithStyleDisplayNone->length === 0) {
return $this;
}
/** @var \DOMNode $element */
foreach ($elementsWithStyleDisplayNone as $element) {
$parentNode = $element->parentNode;
if ($parentNode !== null) {
$parentNode->removeChild($element);
}
}
return $this;
}
/**
* Removes classes that are no longer required (e.g. because there are no longer any CSS rules that reference them)
* from `class` attributes.
*
* Note that this does not inspect the CSS, but expects to be provided with a list of classes that are still in use.
*
* This method also has the (presumably beneficial) side-effect of minifying (removing superfluous whitespace from)
* `class` attributes.
*
* @param string[] $classesToKeep names of classes that should not be removed
*
* @return self fluent interface
*/
public function removeRedundantClasses(array $classesToKeep = [])
{
$elementsWithClassAttribute = $this->xPath->query('//*[@class]');
if ($classesToKeep !== []) {
$this->removeClassesFromElements($elementsWithClassAttribute, $classesToKeep);
} else {
// Avoid unnecessary processing if there are no classes to keep.
$this->removeClassAttributeFromElements($elementsWithClassAttribute);
}
return $this;
}
/**
* Removes classes from the `class` attribute of each element in `$elements`, except any in `$classesToKeep`,
* removing the `class` attribute itself if the resultant list is empty.
*
* @param \DOMNodeList $elements
* @param string[] $classesToKeep
*
* @return void
*/
private function removeClassesFromElements(\DOMNodeList $elements, array $classesToKeep)
{
$classesToKeepIntersector = new ArrayIntersector($classesToKeep);
/** @var \DOMNode $element */
foreach ($elements as $element) {
$elementClasses = \preg_split('/\\s++/', \trim($element->getAttribute('class')));
$elementClassesToKeep = $classesToKeepIntersector->intersectWith($elementClasses);
if ($elementClassesToKeep !== []) {
$element->setAttribute('class', \implode(' ', $elementClassesToKeep));
} else {
$element->removeAttribute('class');
}
}
}
/**
* Removes the `class` attribute from each element in `$elements`.
*
* @param \DOMNodeList $elements
*
* @return void
*/
private function removeClassAttributeFromElements(\DOMNodeList $elements)
{
/** @var \DOMNode $element */
foreach ($elements as $element) {
$element->removeAttribute('class');
}
}
/**
* After CSS has been inlined, there will likely be some classes in `class` attributes that are no longer referenced
* by any remaining (uninlinable) CSS. This method removes such classes.
*
* Note that it does not inspect the remaining CSS, but uses information readily available from the `CssInliner`
* instance about the CSS rules that could not be inlined.
*
* @param CssInliner $cssInliner object instance that performed the CSS inlining
*
* @return self fluent interface
*
* @throws \BadMethodCallException if `inlineCss` has not first been called on `$cssInliner`
*/
public function removeRedundantClassesAfterCssInlined(CssInliner $cssInliner)
{
$classesToKeepAsKeys = [];
foreach ($cssInliner->getMatchingUninlinableSelectors() as $selector) {
\preg_match_all('/\\.(-?+[_a-zA-Z][\\w\\-]*+)/', $selector, $matches);
$classesToKeepAsKeys += \array_fill_keys($matches[1], true);
}
$this->removeRedundantClasses(\array_keys($classesToKeepAsKeys));
return $this;
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace Pelago\Emogrifier\Utilities;
/**
* When computing many array intersections using the same array, it is more efficient to use `array_flip()` first and
* then `array_intersect_key()`, than `array_intersect()`. See the discussion at
* {@link https://stackoverflow.com/questions/6329211/php-array-intersect-efficiency Stack Overflow} for more
* information.
*
* Of course, this is only possible if the arrays contain integer or string values, and either don't contain duplicates,
* or that fact that duplicates will be removed does not matter.
*
* This class takes care of the detail.
*
* @internal
*
* @author Jake Hotson <jake.github@qzdesign.co.uk>
*/
class ArrayIntersector
{
/**
* the array with which the object was constructed, with all its keys exchanged with their associated values
*
* @var (int|string)[]
*/
private $invertedArray;
/**
* Constructs the object with the array that will be reused for many intersection computations.
*
* @param (int|string)[] $array
*/
public function __construct(array $array)
{
$this->invertedArray = \array_flip($array);
}
/**
* Computes the intersection of `$array` and the array with which this object was constructed.
*
* @param (int|string)[] $array
*
* @return (int|string)[] Returns an array containing all of the values in `$array` whose values exist in the array
* with which this object was constructed. Note that keys are preserved, order is maintained, but
* duplicates are removed.
*/
public function intersectWith(array $array)
{
$invertedArray = \array_flip($array);
$invertedIntersection = \array_intersect_key($invertedArray, $this->invertedArray);
return \array_flip($invertedIntersection);
}
}

View File

@@ -1,6 +1,6 @@
<?php
namespace Pelago\Emogrifier;
namespace Pelago\Emogrifier\Utilities;
/**
* Facilitates building a CSS string by appending rule blocks one at a time, checking whether the media query,
@@ -33,6 +33,8 @@ namespace Pelago\Emogrifier;
* ` }
* ` }
*
* @internal
*
* @author Jake Hotson <jake.github@qzdesign.co.uk>
*/
class CssConcatenator
@@ -72,7 +74,7 @@ class CssConcatenator
$lastRuleBlock->selectorsAsKeys += $selectorsAsKeys;
} else {
$hasSameSelectorsAsLastRule = $lastRuleBlock !== false
&& static::hasEquivalentSelectors($selectorsAsKeys, $lastRuleBlock->selectorsAsKeys);
&& self::hasEquivalentSelectors($selectorsAsKeys, $lastRuleBlock->selectorsAsKeys);
if ($hasSameSelectorsAsLastRule) {
$lastDeclarationsBlockWithoutSemicolon = \rtrim(\rtrim($lastRuleBlock->declarationsBlock), ';');
$lastRuleBlock->declarationsBlock = $lastDeclarationsBlockWithoutSemicolon . ';' . $declarationsBlock;
@@ -87,7 +89,7 @@ class CssConcatenator
*/
public function getCss()
{
return \implode('', \array_map([$this, 'getMediaRuleCss'], $this->mediaRules));
return \implode('', \array_map([self::class, 'getMediaRuleCss'], $this->mediaRules));
}
/**
@@ -133,7 +135,7 @@ class CssConcatenator
*/
private static function getMediaRuleCss(\stdClass $mediaRule)
{
$css = \implode('', \array_map([static::class, 'getRuleBlockCss'], $mediaRule->ruleBlocks));
$css = \implode('', \array_map([self::class, 'getRuleBlockCss'], $mediaRule->ruleBlocks));
if ($mediaRule->media !== '') {
$css = $mediaRule->media . '{' . $css . '}';
}