From 9994758b24b7bbfaa8d1603082ca429bbc0e533c Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tim=20D=C3=BCsterhus?= Date: Fri, 3 Jun 2016 00:08:14 +0200 Subject: [PATCH] Add pelago/emogrifier --- .../files/lib/system/api/composer.json | 3 +- .../files/lib/system/api/composer.lock | 66 +- .../lib/system/api/composer/autoload_psr4.php | 1 + .../system/api/composer/autoload_static.php | 16 + .../lib/system/api/composer/installed.json | 64 + .../system/api/pelago/emogrifier/.gitignore | 24 + .../system/api/pelago/emogrifier/.travis.yml | 31 + .../system/api/pelago/emogrifier/CHANGELOG.md | 92 + .../api/pelago/emogrifier/CONTRIBUTING.md | 78 + .../pelago/emogrifier/Classes/Emogrifier.php | 1271 +++++++++++ .../Standards/Emogrifier/ruleset.xml | 136 ++ .../lib/system/api/pelago/emogrifier/LICENSE | 21 + .../system/api/pelago/emogrifier/README.md | 198 ++ .../emogrifier/Tests/Unit/EmogrifierTest.php | 1948 +++++++++++++++++ .../api/pelago/emogrifier/composer.json | 46 + 15 files changed, 3992 insertions(+), 3 deletions(-) create mode 100644 wcfsetup/install/files/lib/system/api/pelago/emogrifier/.gitignore create mode 100644 wcfsetup/install/files/lib/system/api/pelago/emogrifier/.travis.yml create mode 100644 wcfsetup/install/files/lib/system/api/pelago/emogrifier/CHANGELOG.md create mode 100644 wcfsetup/install/files/lib/system/api/pelago/emogrifier/CONTRIBUTING.md create mode 100644 wcfsetup/install/files/lib/system/api/pelago/emogrifier/Classes/Emogrifier.php create mode 100644 wcfsetup/install/files/lib/system/api/pelago/emogrifier/Configuration/PhpCodeSniffer/Standards/Emogrifier/ruleset.xml create mode 100644 wcfsetup/install/files/lib/system/api/pelago/emogrifier/LICENSE create mode 100644 wcfsetup/install/files/lib/system/api/pelago/emogrifier/README.md create mode 100644 wcfsetup/install/files/lib/system/api/pelago/emogrifier/Tests/Unit/EmogrifierTest.php create mode 100644 wcfsetup/install/files/lib/system/api/pelago/emogrifier/composer.json diff --git a/wcfsetup/install/files/lib/system/api/composer.json b/wcfsetup/install/files/lib/system/api/composer.json index 59da5a24e3..08eb7faed9 100644 --- a/wcfsetup/install/files/lib/system/api/composer.json +++ b/wcfsetup/install/files/lib/system/api/composer.json @@ -4,6 +4,7 @@ }, "require": { "ezyang/htmlpurifier": "4.7.*", - "erusev/parsedown": "1.6.*" + "erusev/parsedown": "1.6.*", + "pelago/emogrifier": "1.0.*" } } diff --git a/wcfsetup/install/files/lib/system/api/composer.lock b/wcfsetup/install/files/lib/system/api/composer.lock index 78f3be2314..370165e5eb 100644 --- a/wcfsetup/install/files/lib/system/api/composer.lock +++ b/wcfsetup/install/files/lib/system/api/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "e898b55b2609b488f1b137f6b9dd8d01", - "content-hash": "7dabe058d3ef475f63962fe1d5774a34", + "hash": "7403d7c709a9942dc2f75396d5fe55fe", + "content-hash": "42237c86b167290edb2d1cd117a93105", "packages": [ { "name": "erusev/parsedown", @@ -89,6 +89,68 @@ "html" ], "time": "2015-08-05 01:03:42" + }, + { + "name": "pelago/emogrifier", + "version": "V1.0.0", + "source": { + "type": "git", + "url": "https://github.com/jjriv/emogrifier.git", + "reference": "1160bcbc523c7941d2d0dc2a9e59c51c66420b4b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jjriv/emogrifier/zipball/1160bcbc523c7941d2d0dc2a9e59c51c66420b4b", + "reference": "1160bcbc523c7941d2d0dc2a9e59c51c66420b4b", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "4.8.11", + "squizlabs/php_codesniffer": "2.3.4", + "typo3-ci/typo3sniffpool": "2.1.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Pelago\\": "Classes/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Reeve", + "email": "jreeve@pelagodesign.com" + }, + { + "name": "Cameron Brooks" + }, + { + "name": "Jaime Prado" + }, + { + "name": "Oliver Klee", + "email": "typo3-coding@oliverklee.de" + }, + { + "name": "Roman Ožana", + "email": "ozana@omdesign.cz" + } + ], + "description": "Converts CSS styles into inline style attributes in your HTML code", + "homepage": "http://www.pelagodesign.com/sidecar/emogrifier/", + "time": "2015-10-14 22:22:15" } ], "packages-dev": [], diff --git a/wcfsetup/install/files/lib/system/api/composer/autoload_psr4.php b/wcfsetup/install/files/lib/system/api/composer/autoload_psr4.php index 0c50d0a796..0835202b04 100644 --- a/wcfsetup/install/files/lib/system/api/composer/autoload_psr4.php +++ b/wcfsetup/install/files/lib/system/api/composer/autoload_psr4.php @@ -6,4 +6,5 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = $vendorDir; return array( + 'Pelago\\' => array($vendorDir . '/pelago/emogrifier/Classes'), ); diff --git a/wcfsetup/install/files/lib/system/api/composer/autoload_static.php b/wcfsetup/install/files/lib/system/api/composer/autoload_static.php index 79d01f7cce..22ce1f3aa8 100644 --- a/wcfsetup/install/files/lib/system/api/composer/autoload_static.php +++ b/wcfsetup/install/files/lib/system/api/composer/autoload_static.php @@ -10,6 +10,20 @@ class ComposerStaticInit4a4e0e985ef68770d710dc260edc44ab '2cffec82183ee1cea088009cef9a6fc3' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php', ); + public static $prefixLengthsPsr4 = array ( + 'P' => + array ( + 'Pelago\\' => 7, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Pelago\\' => + array ( + 0 => __DIR__ . '/..' . '/pelago/emogrifier/Classes', + ), + ); + public static $prefixesPsr0 = array ( 'P' => array ( @@ -30,6 +44,8 @@ class ComposerStaticInit4a4e0e985ef68770d710dc260edc44ab public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit4a4e0e985ef68770d710dc260edc44ab::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit4a4e0e985ef68770d710dc260edc44ab::$prefixDirsPsr4; $loader->prefixesPsr0 = ComposerStaticInit4a4e0e985ef68770d710dc260edc44ab::$prefixesPsr0; }, null, ClassLoader::class); diff --git a/wcfsetup/install/files/lib/system/api/composer/installed.json b/wcfsetup/install/files/lib/system/api/composer/installed.json index 9416badb24..2c9d6c7777 100644 --- a/wcfsetup/install/files/lib/system/api/composer/installed.json +++ b/wcfsetup/install/files/lib/system/api/composer/installed.json @@ -85,5 +85,69 @@ "markdown", "parser" ] + }, + { + "name": "pelago/emogrifier", + "version": "V1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/jjriv/emogrifier.git", + "reference": "1160bcbc523c7941d2d0dc2a9e59c51c66420b4b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jjriv/emogrifier/zipball/1160bcbc523c7941d2d0dc2a9e59c51c66420b4b", + "reference": "1160bcbc523c7941d2d0dc2a9e59c51c66420b4b", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "4.8.11", + "squizlabs/php_codesniffer": "2.3.4", + "typo3-ci/typo3sniffpool": "2.1.1" + }, + "time": "2015-10-14 22:22:15", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Pelago\\": "Classes/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Reeve", + "email": "jreeve@pelagodesign.com" + }, + { + "name": "Cameron Brooks" + }, + { + "name": "Jaime Prado" + }, + { + "name": "Oliver Klee", + "email": "typo3-coding@oliverklee.de" + }, + { + "name": "Roman Ožana", + "email": "ozana@omdesign.cz" + } + ], + "description": "Converts CSS styles into inline style attributes in your HTML code", + "homepage": "http://www.pelagodesign.com/sidecar/emogrifier/" } ] diff --git a/wcfsetup/install/files/lib/system/api/pelago/emogrifier/.gitignore b/wcfsetup/install/files/lib/system/api/pelago/emogrifier/.gitignore new file mode 100644 index 0000000000..6be926125b --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/pelago/emogrifier/.gitignore @@ -0,0 +1,24 @@ +######################### +# 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 +/vendor/ +composer.lock diff --git a/wcfsetup/install/files/lib/system/api/pelago/emogrifier/.travis.yml b/wcfsetup/install/files/lib/system/api/pelago/emogrifier/.travis.yml new file mode 100644 index 0000000000..961cb92de0 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/pelago/emogrifier/.travis.yml @@ -0,0 +1,31 @@ +sudo: false + +language: php + +cache: + directories: + - vendor + +env: + global: + secure: nOIIWvxRsDlkg+5H21dmVeqvFbweOAk3l3ZiyZO1m5XuGuuZR9yj10oOudee8m0hzJ7e9eoZ+dfB3t8lmK0fTRTB6w0G7RuGiQb89ief3Zhs1vOveYOgS5yfTMRym57iluxsLeCe7AxWmy7+0fWAvx1qL7bKp+THGK9yv/aj9eM= + +php: + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - hhvm + +before_script: + - composer install + - vendor/bin/phpcs --config-set encoding utf-8 + - if [ "$GITHUB_COMPOSER_AUTH" ]; then composer config -g github-oauth.github.com $GITHUB_COMPOSER_AUTH; fi + +script: + # Run PHP lint on all PHP files. + - find Classes/ Tests/ -name '*.php' -print0 | xargs -0 -n 1 -P 4 php -l + # Check the coding style. + - vendor/bin/phpcs --standard=Configuration/PhpCodeSniffer/Standards/Emogrifier/ Classes/ Tests/ + # Run the unit tests. + - vendor/bin/phpunit Tests/ diff --git a/wcfsetup/install/files/lib/system/api/pelago/emogrifier/CHANGELOG.md b/wcfsetup/install/files/lib/system/api/pelago/emogrifier/CHANGELOG.md new file mode 100644 index 0000000000..fcad58b979 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/pelago/emogrifier/CHANGELOG.md @@ -0,0 +1,92 @@ +# Emogrifier Change Log + +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + +Emogrifier is in a pre-1.0 state. This means that its APIs and behavior are +subject to breaking changes without deprecation notices. + + +## [1.0.0][] (2015-10-15) + +### Added +- Add branch alias ([#231](https://github.com/jjriv/emogrifier/pull/231)) +- Remove media queries which do not impact the document + ([#217](https://github.com/jjriv/emogrifier/pull/217)) +- Allow elements to be excluded from emogrification + ([#215](https://github.com/jjriv/emogrifier/pull/215)) +- Handle !important ([#214](https://github.com/jjriv/emogrifier/pull/214)) +- emogrifyBodyContent() method + ([#206](https://github.com/jjriv/emogrifier/pull/206)) +- Cache combinedStyles ([#211](https://github.com/jjriv/emogrifier/pull/211)) +- Allow user to define media types to keep + ([#200](https://github.com/jjriv/emogrifier/pull/200)) +- Ignore invalid CSS selectors + ([#194](https://github.com/jjriv/emogrifier/pull/194)) +- isRemoveDisplayNoneEnabled option + ([#162](https://github.com/jjriv/emogrifier/pull/162)) +- Allow disabling of "inline style" and "style block" parsing + ([#156](https://github.com/jjriv/emogrifier/pull/156)) +- Preserve @media if necessary + ([#62](https://github.com/jjriv/emogrifier/pull/62)) +- Add extraction of style blocks within the HTML +- Add several new pseudo-selectors (first-child, last-child, nth-child, + and nth-of-type) + + +### Changed +- Make HTML5 the default document type + ([#245](https://github.com/jjriv/emogrifier/pull/245)) +- Make copyCssWithMediaToStyleNode private + ([#218](https://github.com/jjriv/emogrifier/pull/218)) +- Stop encoding umlauts and dollar signs + ([#170](https://github.com/jjriv/emogrifier/pull/170)) +- Convert the classes to namespaces + ([#41](https://github.com/jjriv/emogrifier/pull/41)) + + +### Deprecated +- Support for PHP 5.4 will be removed in Emogrifier 2.0. + + +### Removed +- Drop support for PHP 5.3 + ([#114](https://github.com/jjriv/emogrifier/pull/114)) +- Support for character sets other than UTF-8 was removed. + + +### Fixed +- Fix failing tests on Windows due to line endings + ([#263](https://github.com/jjriv/emogrifier/pull/263)) +- Parsing CSS declaration blocks + ([#261](https://github.com/jjriv/emogrifier/pull/261)) +- Fix first-child and last-child selectors + ([#257](https://github.com/jjriv/emogrifier/pull/257)) +- Fix parsing of CSS for data URIs + ([#243](https://github.com/jjriv/emogrifier/pull/243)) +- Fix multi-line media queries + ([#241](https://github.com/jjriv/emogrifier/pull/241)) +- Keep CSS media queries even if followed by CSS comments + ([#201](https://github.com/jjriv/emogrifier/pull/201)) +- Fix CSS selectors with exact attribute only + ([#197](https://github.com/jjriv/emogrifier/pull/197)) +- Properly handle UTF-8 characters and entities + ([#189](https://github.com/jjriv/emogrifier/pull/189)) +- Add mbstring extension to composer.json + ([#93](https://github.com/jjriv/emogrifier/pull/93)) +- Prevent incorrectly capitalized CSS selectors from being stripped + ([#85](https://github.com/jjriv/emogrifier/pull/85)) +- Fix CSS selectors with exact attribute only + ([#197](https://github.com/jjriv/emogrifier/pull/197)) +- Wrong selector extraction from minified CSS + ([#69](https://github.com/jjriv/emogrifier/pull/69)) +- Restore libxml error handler state after clearing + ([#65](https://github.com/jjriv/emogrifier/pull/65)) +- Ignore all warnings produced by DOMDocument::loadHTML() + ([#63](https://github.com/jjriv/emogrifier/pull/63)) +- Style tags in HTML cause an Xpath invalid query error + ([#60](https://github.com/jjriv/emogrifier/pull/60)) +- Fix PHP warnings with PHP 5.5 + ([#26](https://github.com/jjriv/emogrifier/pull/26)) +- Make removal of invisible nodes operate in a case-insensitive manner +- Fix a bug that was overwriting existing inline styles from the original HTML diff --git a/wcfsetup/install/files/lib/system/api/pelago/emogrifier/CONTRIBUTING.md b/wcfsetup/install/files/lib/system/api/pelago/emogrifier/CONTRIBUTING.md new file mode 100644 index 0000000000..95294bfb40 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/pelago/emogrifier/CONTRIBUTING.md @@ -0,0 +1,78 @@ +# Contributing to Emogrifier + +Those that wish to contribute bug fixes, new features, refactorings and +clean-up to Emogrifier are more than welcome. + +When you contribute, please take the following things into account: + + +## General workflow + +After you have submitted a pull request, the Emogrifier team will review your +changes. This will probably result in quite a few comments on ways to improve +your pull request. The Emogrifier project receives contributions from +developers around the world, so we need the code to be the most consistent, +readable, and maintainable that it can be. + +Please do not feel frustrated by this - instead please view this both as our +contribution to your pull request as well as a way to learn more about +improving code quality. + +If you would like to know whether an idea would fit in the general strategy of +the Emogrifier project or would like to get feedback on the best architecture +for your ideas, we propose you open a ticket first and discuss your ideas there +first before investing a lot of time in writing code. + + +## Install the development dependencies + +To install the development dependencies (PHPUnit and PHP_CodeSniffer), please +run the following command: + + composer install + + +## Unit-test your changes + +Please cover all changes with unit tests and make sure that your code does not +break any existing tests. We will only merge pull request that include full +code coverage of the fixed bugs and the new features. + +To run the existing PHPUnit tests, run this command: + + vendor/bin/phpunit Tests/ + + +## Coding Style + +Please use the same coding style (PSR-2) as the rest of the code. Indentation +is four spaces. + +We will only merge pull requests that follow the project's coding style. + +Please check your code with the provided PHP_CodeSniffer standard: + + vendor/bin/phpcs --standard=Configuration/PhpCodeSniffer/Standards/Emogrifier/ Classes/ Tests/ + +Please make your code clean, well-readable and easy to understand. + +If you add new methods or fields, please add proper PHPDoc for the new +methods/fields. Please use grammatically correct, complete sentences in the +code documentation. + + +## Git commits + +Git commits should have a <= 50 character summary, optionally followed by a +blank line and a more in depth description of 79 characters per line. + +[Please squash related commits together](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html). + +If you already have a commit and work on it, you can also +[amend the first commit](https://nathanhoad.net/git-amend-your-last-commit). + +Please use grammatically correct, complete sentences in the commit messages. + +Also, please prefix the subject line of the commit message with either +[FEATURE], [TASK], [BUGFIX] OR [CLEANUP]. This makes it faster to see what +a commit is about. \ No newline at end of file diff --git a/wcfsetup/install/files/lib/system/api/pelago/emogrifier/Classes/Emogrifier.php b/wcfsetup/install/files/lib/system/api/pelago/emogrifier/Classes/Emogrifier.php new file mode 100644 index 0000000000..ca82172314 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/pelago/emogrifier/Classes/Emogrifier.php @@ -0,0 +1,1271 @@ + + * @author Roman Ožana + */ +class Emogrifier +{ + /** + * @var int + */ + const CACHE_KEY_CSS = 0; + + /** + * @var int + */ + const CACHE_KEY_SELECTOR = 1; + + /** + * @var int + */ + const CACHE_KEY_XPATH = 2; + + /** + * @var int + */ + const CACHE_KEY_CSS_DECLARATIONS_BLOCK = 3; + + /** + * @var int + */ + const CACHE_KEY_COMBINED_STYLES = 4; + + /** + * for calculating nth-of-type and nth-child selectors + * + * @var int + */ + const INDEX = 0; + + /** + * for calculating nth-of-type and nth-child selectors + * + * @var int + */ + const MULTIPLIER = 1; + + /** + * @var string + */ + const ID_ATTRIBUTE_MATCHER = '/(\\w+)?\\#([\\w\\-]+)/'; + + /** + * @var string + */ + const CLASS_ATTRIBUTE_MATCHER = '/(\\w+|[\\*\\]])?((\\.[\\w\\-]+)+)/'; + + /** + * @var string + */ + const CONTENT_TYPE_META_TAG = ''; + + /** + * @var string + */ + const DEFAULT_DOCUMENT_TYPE = ''; + + /** + * @var string + */ + private $html = ''; + + /** + * @var string + */ + private $css = ''; + + /** + * @var bool[] + */ + private $excludedSelectors = []; + + /** + * @var string[] + */ + private $unprocessableHtmlTags = ['wbr']; + + /** + * @var bool[] + */ + private $allowedMediaTypes = ['all' => true, 'screen' => true, 'print' => true]; + + /** + * @var array[] + */ + private $caches = [ + self::CACHE_KEY_CSS => [], + self::CACHE_KEY_SELECTOR => [], + self::CACHE_KEY_XPATH => [], + self::CACHE_KEY_CSS_DECLARATIONS_BLOCK => [], + self::CACHE_KEY_COMBINED_STYLES => [], + ]; + + /** + * the visited nodes with the XPath paths as array keys + * + * @var \DOMElement[] + */ + private $visitedNodes = []; + + /** + * the styles to apply to the nodes with the XPath paths as array keys for the outer array + * and the attribute names/values as key/value pairs for the inner array + * + * @var array[] + */ + private $styleAttributesForNodes = []; + + /** + * Determines whether the "style" attributes of tags in the the HTML passed to this class should be preserved. + * If set to false, the value of the style attributes will be discarded. + * + * @var bool + */ + private $isInlineStyleAttributesParsingEnabled = true; + + /** + * Determines whether the

target

'; + $this->subject->setHtml($html); + + self::assertContains( + '

target

', + $this->subject->emogrify() + ); + } + + /** + * @test + */ + public function emogrifyRemovesStyleNodes() + { + $html = $this->html5DocumentType . ''; + $this->subject->setHtml($html); + + self::assertNotContains( + ''; + $this->subject->setHtml($html); + + $hasError = false; + set_error_handler(function ($errorNumber, $errorMessage) use (&$hasError) { + if ($errorMessage === 'DOMXPath::query(): Invalid expression') { + return true; + } + + $hasError = true; + return true; + }); + + $this->subject->emogrify(); + restore_error_handler(); + + self::assertFalse( + $hasError + ); + } + + /** + * Data provider for things that should be left out when applying the CSS. + * + * @return array[] + */ + public function unneededCssThingsDataProvider() + { + return [ + 'CSS comments with one asterisk' => ['p {color: #000;/* black */}', 'black'], + 'CSS comments with two asterisks' => ['p {color: #000;/** black */}', 'black'], + '@import directive' => ['@import "foo.css";', '@import'], + 'style in "aural" media type rule' => ['@media aural {p {color: #000;}}', '#000'], + 'style in "braille" media type rule' => ['@media braille {p {color: #000;}}', '#000'], + 'style in "embossed" media type rule' => ['@media embossed {p {color: #000;}}', '#000'], + 'style in "handheld" media type rule' => ['@media handheld {p {color: #000;}}', '#000'], + 'style in "projection" media type rule' => ['@media projection {p {color: #000;}}', '#000'], + 'style in "speech" media type rule' => ['@media speech {p {color: #000;}}', '#000'], + 'style in "tty" media type rule' => ['@media tty {p {color: #000;}}', '#000'], + 'style in "tv" media type rule' => ['@media tv {p {color: #000;}}', '#000'], + ]; + } + + /** + * @test + * + * @param string $css + * @param string $markerNotExpectedInHtml + * + * @dataProvider unneededCssThingsDataProvider + */ + public function emogrifyFiltersUnneededCssThings($css, $markerNotExpectedInHtml) + { + $html = $this->html5DocumentType . '

foo

'; + $this->subject->setHtml($html); + $this->subject->setCss($css); + + self::assertNotContains( + $markerNotExpectedInHtml, + $this->subject->emogrify() + ); + } + + /** + * Data provider for media rules. + * + * @return array[] + */ + public function mediaRulesDataProvider() + { + return [ + 'style in "only all" media type rule' => ['@media only all {p {color: #000;}}'], + 'style in "only screen" media type rule' => ['@media only screen {p {color: #000;}}'], + 'style in media type rule' => ['@media {p {color: #000;}}'], + 'style in "screen" media type rule' => ['@media screen {p {color: #000;}}'], + 'style in "print" media type rule' => ['@media print {p {color: #000;}}'], + 'style in "all" media type rule' => ['@media all {p {color: #000;}}'], + ]; + } + + /** + * @test + * + * @param string $css + * + * @dataProvider mediaRulesDataProvider + */ + public function emogrifyKeepsMediaRules($css) + { + $html = $this->html5DocumentType . '

foo

'; + $this->subject->setHtml($html); + $this->subject->setCss($css); + + self::assertContains( + $css, + $this->subject->emogrify() + ); + } + + /** + * @test + */ + public function removeAllowedMediaTypeRemovesStylesForTheGivenMediaType() + { + $css = '@media screen { html {} }'; + + $html = $this->html5DocumentType . ''; + $this->subject->setHtml($html); + $this->subject->setCss($css); + $this->subject->removeAllowedMediaType('screen'); + + self::assertNotContains( + $css, + $this->subject->emogrify() + ); + } + + /** + * @test + */ + public function addAllowedMediaTypeKeepsStylesForTheGivenMediaType() + { + $css = '@media braille { html { some-property: value; } }'; + + $html = $this->html5DocumentType . ''; + $this->subject->setHtml($html); + $this->subject->setCss($css); + $this->subject->addAllowedMediaType('braille'); + + self::assertContains( + $css, + $this->subject->emogrify() + ); + } + + /** + * @test + */ + public function emogrifyAddsMissingHeadElement() + { + $html = $this->html5DocumentType . ''; + $this->subject->setHtml($html); + $this->subject->setCss('@media all { html {} }'); + + self::assertContains( + '', + $this->subject->emogrify() + ); + } + + /** + * @test + */ + public function emogrifyKeepExistingHeadElementContent() + { + $html = $this->html5DocumentType . ''; + $this->subject->setHtml($html); + $this->subject->setCss('@media all { html {} }'); + + self::assertContains( + '', + $this->subject->emogrify() + ); + } + + /** + * @test + */ + public function emogrifyKeepExistingHeadElementAddStyleElement() + { + $html = $this->html5DocumentType . ''; + $this->subject->setHtml($html); + $this->subject->setCss('@media all { html {} }'); + + self::assertContains( + '

'; + $this->subject->setHtml($html); + + self::assertContains( + $css, + $this->subject->emogrify() + ); + } + + /** + * @test + * + * @param string $css + * + * @dataProvider validMediaPreserveDataProvider + */ + public function emogrifyWithValidMediaQueryNotContainsInlineCss($css) + { + $html = $this->html5DocumentType . PHP_EOL . '

'; + $this->subject->setHtml($html); + $this->subject->setCss($css); + + self::assertNotContains( + 'style="color:red"', + $this->subject->emogrify() + ); + } + + /** + * Invalid media query which need to be strip + * + * @return array[] + */ + public function invalidMediaPreserveDataProvider() + { + return [ + 'style in "braille" type rule' => ['@media braille { h1 { color:red; } }'], + 'style in "embossed" type rule' => ['@media embossed { h1 { color:red; } }'], + 'style in "handheld" type rule' => ['@media handheld { h1 { color:red; } }'], + 'style in "projection" type rule' => ['@media projection { h1 { color:red; } }'], + 'style in "speech" type rule' => ['@media speech { h1 { color:red; } }'], + 'style in "tty" type rule' => ['@media tty { h1 { color:red; } }'], + 'style in "tv" type rule' => ['@media tv { h1 { color:red; } }'], + ]; + } + + /** + * @test + * + * @param string $css + * + * @dataProvider invalidMediaPreserveDataProvider + */ + public function emogrifyWithInvalidMediaQueryaNotContainsInnerCss($css) + { + $html = $this->html5DocumentType . PHP_EOL . '

'; + $this->subject->setHtml($html); + $this->subject->setCss($css); + + self::assertNotContains( + $css, + $this->subject->emogrify() + ); + } + + /** + * @test + * + * @param string $css + * + * @dataProvider invalidMediaPreserveDataProvider + */ + public function emogrifyWithInValidMediaQueryNotContainsInlineCss($css) + { + $html = $this->html5DocumentType . PHP_EOL . '

'; + $this->subject->setHtml($html); + $this->subject->setCss($css); + + self::assertNotContains( + 'style="color: red"', + $this->subject->emogrify() + ); + } + + /** + * @test + * + * @param string $css + * + * @dataProvider invalidMediaPreserveDataProvider + */ + public function emogrifyFromHtmlWithInValidMediaQueryNotContainsInnerCss($css) + { + $html = $this->html5DocumentType . PHP_EOL . '

'; + $this->subject->setHtml($html); + + self::assertNotContains( + $css, + $this->subject->emogrify() + ); + } + + /** + * @test + * + * @param string $css + * + * @dataProvider invalidMediaPreserveDataProvider + */ + public function emogrifyFromHtmlWithInValidMediaQueryNotContainsInlineCss($css) + { + $html = $this->html5DocumentType . PHP_EOL . '

'; + $this->subject->setHtml($html); + + self::assertNotContains( + 'style="color: red"', + $this->subject->emogrify() + ); + } + + /** + * @test + */ + public function emogrifyAppliesCssFromStyleNodes() + { + $styleAttributeValue = 'color: #ccc;'; + $html = $this->html5DocumentType . + ''; + $this->subject->setHtml($html); + + self::assertContains( + '', + $this->subject->emogrify() + ); + } + + /** + * @test + */ + public function emogrifyWhenDisabledNotAppliesCssFromStyleBlocks() + { + $styleAttributeValue = 'color: #ccc;'; + $html = $this->html5DocumentType . + ''; + $this->subject->setHtml($html); + $this->subject->disableStyleBlocksParsing(); + + self::assertNotContains( + '', + $this->subject->emogrify() + ); + } + + /** + * @test + */ + public function emogrifyWhenStyleBlocksParsingDisabledKeepInlineStyles() + { + $styleAttributeValue = 'text-align: center;'; + $html = $this->html5DocumentType . '' . + '

paragraph

'; + $expected = '

'; + $this->subject->setHtml($html); + $this->subject->disableStyleBlocksParsing(); + + self::assertContains( + $expected, + $this->subject->emogrify() + ); + } + + /** + * @test + */ + public function emogrifyWhenDisabledNotAppliesCssFromInlineStyles() + { + $styleAttributeValue = 'color: #ccc;'; + $html = $this->html5DocumentType . ''; + $this->subject->setHtml($html); + $this->subject->disableInlineStyleAttributesParsing(); + + self::assertNotContains( + 'subject->emogrify() + ); + } + + /** + * @test + */ + public function emogrifyWhenInlineStyleAttributesParsingDisabledKeepStyleBlockStyles() + { + $styleAttributeValue = 'color: #ccc;'; + $html = $this->html5DocumentType . + '' . + '

paragraph

'; + $expected = '

'; + $this->subject->setHtml($html); + $this->subject->disableInlineStyleAttributesParsing(); + + self::assertContains( + $expected, + $this->subject->emogrify() + ); + } + + /** + * @test + */ + public function emogrifyAppliesCssWithUpperCaseSelector() + { + $html = $this->html5DocumentType . + '

paragraph

'; + $expected = '

'; + $this->subject->setHtml($html); + + self::assertContains( + $expected, + $this->subject->emogrify() + ); + } + + /** + * Emogrify was handling case differently for passed in CSS vs CSS parsed from style blocks. + * @test + */ + public function emogrifyAppliesCssWithMixedCaseAttributesInStyleBlock() + { + $html = $this->html5DocumentType . + '' . + '

some content

'; + $expected = '

'; + $this->subject->setHtml($html); + + self::assertContains( + $expected, + $this->subject->emogrify() + ); + } + + /** + * Passed in CSS sets the order, but style block CSS overrides values. + * @test + */ + public function emogrifyMergesCssWithMixedCaseAttribute() + { + $css = 'p { margin: 0; padding-TOP: 0; PADDING-bottom: 1PX;}'; + $html = $this->html5DocumentType . + '' . + '

some content

'; + $expected = '

'; + $this->subject->setHtml($html); + $this->subject->setCss($css); + + self::assertContains( + $expected, + $this->subject->emogrify() + ); + } + + /** + * @test + */ + public function emogrifyMergesCssWithMixedUnits() + { + $css = 'p { margin: 1px; padding-bottom:0;}'; + $html = $this->html5DocumentType . + '' . + '

some content

'; + $expected = '

'; + $this->subject->setHtml($html); + $this->subject->setCss($css); + + self::assertContains( + $expected, + $this->subject->emogrify() + ); + } + + /** + * @test + */ + public function emogrifyByDefaultRemovesElementsWithDisplayNoneFromExternalCss() + { + $css = 'div.foo { display: none; }'; + $html = $this->html5DocumentType . '

'; + + $expected = '
'; + + $this->subject->setHtml($html); + $this->subject->setCss($css); + + self::assertContains( + $expected, + $this->subject->emogrify() + ); + } + + /** + * @test + */ + public function emogrifyByDefaultRemovesElementsWithDisplayNoneInStyleAttribute() + { + $html = $this->html5DocumentType . + '
' . + ''; + + $expected = '
'; + + $this->subject->setHtml($html); + + self::assertContains( + $expected, + $this->subject->emogrify() + ); + } + + /** + * @test + */ + public function emogrifyAfterDisableInvisibleNodeRemovalPreservesInvisibleElements() + { + $css = 'div.foo { display: none; }'; + $html = $this->html5DocumentType . '
'; + + $expected = '