Browse Source

Merge pull request #15448 from klimov-paul/optional-html-purifier

"ezyang/htmlpurifier" package has been made optional
tags/3.0.0-alpha1
Dmitry Naumenko 7 years ago committed by GitHub
parent
commit
866c70d62e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      composer.json
  2. 1
      framework/CHANGELOG.md
  3. 3
      framework/UPGRADE.md
  4. 4
      framework/composer.json
  5. 123
      framework/helpers/BaseHtmlPurifier.php
  6. 5
      framework/helpers/BaseStringHelper.php
  7. 54
      tests/framework/helpers/HtmlPurifierTest.php

3
composer.json

@ -76,7 +76,6 @@
"yiisoft/yii2-composer": "~2.0.4",
"psr/simple-cache": "~1.0.0",
"psr/http-message": "~1.0.0",
"ezyang/htmlpurifier": "~4.6",
"cebe/markdown": "~1.0.0 | ~1.1.0",
"bower-asset/jquery": "3.2.*@stable | 3.1.*@stable | 2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable",
"bower-asset/inputmask": "~3.2.2 | ~3.3.5",
@ -84,6 +83,7 @@
"bower-asset/yii2-pjax": "~2.0.1"
},
"require-dev": {
"ezyang/htmlpurifier": "~4.6",
"phpunit/phpunit": "~6.2.3",
"cebe/indent": "~1.0.2",
"friendsofphp/php-cs-fixer": "~2.2.3"
@ -95,6 +95,7 @@
}
],
"suggest": {
"ezyang/htmlpurifier": "required at `yii\\helpers\\HtmlPurifier` for 'html' data format support (e.g. `yii\\i18n\\Formatter:asHtml()`)",
"yiisoft/yii2-coding-standards": "you can use this package to check for code style issues when contributing to yii"
},
"autoload": {

1
framework/CHANGELOG.md

@ -39,6 +39,7 @@ Yii Framework 2 Change Log
- Chg #14178: Removed HHVM-specific code (samdark)
- Enh #14671: use `random_int()` instead of `mt_rand()` to generate cryptographically secure pseudo-random integers (yyxx9988)
- Chg #14761: Removed Yii autoloader in favor of Composer's PSR-4 implementation (samdark)
- Chg #15448: Package "ezyang/htmlpurifier" has been made optional and is not installed by default (klimov-paul)
2.0.14 under development
------------------------

3
framework/UPGRADE.md

@ -154,6 +154,9 @@ Upgrade from Yii 2.0.x
`yii\validators\FileValidator::buildMimeTypeRegexp()` have been made `public`. Make sure you use correct
access level specification in case you override these methods.
* Default script position for the `yii\web\View::registerJs()` changed to `View::POS_END`.
* Package "ezyang/htmlpurifier" has been made optional and is not installed by default. If you need to use
`yii\helpers\HtmlPurifier` or `yii\i18n\Formatter::asHtml()` (e.g. 'html' data format), you'll have to install
this package manually for your project.
Upgrade from Yii 2.0.13

4
framework/composer.json

@ -71,12 +71,14 @@
"yiisoft/yii2-composer": "~2.0.4",
"psr/simple-cache": "~1.0.0",
"psr/http-message": "~1.0.0",
"ezyang/htmlpurifier": "~4.6",
"cebe/markdown": "~1.0.0 | ~1.1.0",
"bower-asset/jquery": "3.2.*@stable | 3.1.*@stable | 2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable",
"bower-asset/punycode": "1.3.*",
"bower-asset/yii2-pjax": "~2.0.1"
},
"suggest": {
"ezyang/htmlpurifier": "version '~4.6' required at 'yii\\helpers\\HtmlPurifier' for 'html' data format support (e.g. 'yii\\i18n\\Formatter:asHtml()' and 'yii\\helpers\\StringHelper::truncateHtml()')"
},
"autoload": {
"psr-4": {"yii\\": ""},
"classmap": [

123
framework/helpers/BaseHtmlPurifier.php

@ -7,11 +7,22 @@
namespace yii\helpers;
use Yii;
use yii\base\InvalidConfigException;
/**
* BaseHtmlPurifier provides concrete implementation for [[HtmlPurifier]].
*
* Do not use BaseHtmlPurifier. Use [[HtmlPurifier]] instead.
*
* This helper requires `ezyang/htmlpurifier` library to be installed. This can be done via composer:
*
* ```
* composer require --prefer-dist "ezyang/htmlpurifier:~4.6"
* ```
*
* @see http://htmlpurifier.org/
*
* @author Alexander Makarov <sam@rmcreative.ru>
* @since 2.0
*/
@ -39,28 +50,128 @@ class BaseHtmlPurifier
* ->addAttribute('img', 'data-type', 'Text');
* });
* ```
*
* @return string the purified HTML content.
*/
public static function process($content, $config = null)
{
$configInstance = \HTMLPurifier_Config::create($config instanceof \Closure ? null : $config);
$configInstance = static::createConfig($config);
$configInstance->autoFinalize = false;
$purifier = \HTMLPurifier::instance($configInstance);
$purifier->config->set('Cache.SerializerPath', \Yii::$app->getRuntimePath());
$purifier->config->set('Cache.SerializerPermissions', 0775);
return $purifier->purify($content);
}
/**
* Truncate a HTML string.
*
* @param string $html The HTML string to be truncated.
* @param int $count
* @param string $suffix String to append to the end of the truncated string.
* @param string|bool $encoding
* @return string
* @since 2.1.0
*/
public static function truncate($html, $count, $suffix, $encoding = false)
{
$config = static::createConfig();
$lexer = \HTMLPurifier_Lexer::create($config);
$tokens = $lexer->tokenizeHTML($html, $config, new \HTMLPurifier_Context());
$openTokens = [];
$totalCount = 0;
$depth = 0;
$truncated = [];
foreach ($tokens as $token) {
if ($token instanceof \HTMLPurifier_Token_Start) { //Tag begins
$openTokens[$depth] = $token->name;
$truncated[] = $token;
++$depth;
} elseif ($token instanceof \HTMLPurifier_Token_Text && $totalCount <= $count) { //Text
if ($encoding === false) {
preg_match('/^(\s*)/um', $token->data, $prefixSpace) ?: $prefixSpace = ['', ''];
$token->data = $prefixSpace[1] . StringHelper::truncateWords(ltrim($token->data), $count - $totalCount, '');
$currentCount = StringHelper::countWords($token->data);
} else {
$token->data = StringHelper::truncate($token->data, $count - $totalCount, '', $encoding);
$currentCount = mb_strlen($token->data, $encoding);
}
$totalCount += $currentCount;
$truncated[] = $token;
} elseif ($token instanceof \HTMLPurifier_Token_End) { //Tag ends
if ($token->name === $openTokens[$depth - 1]) {
--$depth;
unset($openTokens[$depth]);
$truncated[] = $token;
}
} elseif ($token instanceof \HTMLPurifier_Token_Empty) { //Self contained tags, i.e. <img/> etc.
$truncated[] = $token;
}
if ($totalCount >= $count) {
if (0 < count($openTokens)) {
krsort($openTokens);
foreach ($openTokens as $name) {
$truncated[] = new \HTMLPurifier_Token_End($name);
}
}
break;
}
}
$context = new \HTMLPurifier_Context();
$generator = new \HTMLPurifier_Generator($config, $context);
return $generator->generateFromTokens($truncated) . ($totalCount >= $count ? $suffix : '');
}
/**
* Creates a HtmlPurifier configuration instance.
* @see \HTMLPurifier_Config::create()
* @param array|\Closure|null $config The config to use for HtmlPurifier.
* If not specified or `null` the default config will be used.
* You can use an array or an anonymous function to provide configuration options:
*
* - An array will be passed to the `HTMLPurifier_Config::create()` method.
* - An anonymous function will be called after the config was created.
* The signature should be: `function($config)` where `$config` will be an
* instance of `HTMLPurifier_Config`.
*
* Here is a usage example of such a function:
*
* ```php
* // Allow the HTML5 data attribute `data-type` on `img` elements.
* $content = HtmlPurifier::process($content, function ($config) {
* $config->getHTMLDefinition(true)
* ->addAttribute('img', 'data-type', 'Text');
* });
* ```
*
* @return \HTMLPurifier_Config HTMLPurifier config instance.
* @throws InvalidConfigException in case "ezyang/htmlpurifier" package is not available.
* @since 2.1.0
*/
public static function createConfig($config = null)
{
if (!class_exists(\HTMLPurifier_Config::class)) {
throw new InvalidConfigException('Unable to load "' . \HTMLPurifier_Config::class . '" class. Make sure you have installed "ezyang/htmlpurifier:~4.6" composer package.');
}
$configInstance = \HTMLPurifier_Config::create($config instanceof \Closure ? null : $config);
if (Yii::$app !== null) {
$configInstance->set('Cache.SerializerPath', Yii::$app->getRuntimePath());
$configInstance->set('Cache.SerializerPermissions', 0775);
}
static::configure($configInstance);
if ($config instanceof \Closure) {
call_user_func($config, $configInstance);
}
return $purifier->purify($content);
return $configInstance;
}
/**
* Allow the extended HtmlPurifier class to set some default config options.
* @param \HTMLPurifier_Config $config
* @param \HTMLPurifier_Config $config HTMLPurifier config instance.
* @since 2.0.3
*/
protected static function configure($config)

5
framework/helpers/BaseStringHelper.php

@ -154,10 +154,7 @@ class BaseStringHelper
*/
protected static function truncateHtml($string, $count, $suffix, $encoding = false)
{
$config = \HTMLPurifier_Config::create(null);
if (Yii::$app !== null) {
$config->set('Cache.SerializerPath', Yii::$app->getRuntimePath());
}
$config = HtmlPurifier::createConfig();
$lexer = \HTMLPurifier_Lexer::create($config);
$tokens = $lexer->tokenizeHTML($string, $config, new \HTMLPurifier_Context());
$openTokens = [];

54
tests/framework/helpers/HtmlPurifierTest.php

@ -0,0 +1,54 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yiiunit\framework\helpers;
use yii\helpers\HtmlPurifier;
use yiiunit\TestCase;
/**
* @group html-purifier
*/
class HtmlPurifierTest extends TestCase
{
/**
* {@inheritdoc}
*/
protected function setUp()
{
if (!class_exists(\HTMLPurifier_Config::class)) {
$this->markTestSkipped('"ezyang/htmlpurifier" package required');
return;
}
parent::setUp();
$this->mockApplication();
}
/**
* Data provider for [[testProcess()]]
* @return array test data.
*/
public function dataProviderProcess()
{
return [
['Some <b>html</b>', 'Some <b>html</b>'],
['Some script<script>alert("!")</script>', 'Some script'],
];
}
/**
* @dataProvider dataProviderProcess
*
* @param string $content
* @param string $expectedResult
*/
public function testProcess($content, $expectedResult)
{
$this->assertSame($expectedResult, HtmlPurifier::process($content));
}
}
Loading…
Cancel
Save