Skip to content

Commit

Permalink
Move svg function to HTML helper
Browse files Browse the repository at this point in the history
This allows php functions to call HTML::svg() directly
  • Loading branch information
leevigraham committed Sep 14, 2022
1 parent 60dd34b commit 392b0b0
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 78 deletions.
77 changes: 77 additions & 0 deletions src/helpers/Html.php
Expand Up @@ -8,6 +8,7 @@
namespace craft\helpers;

use Craft;
use craft\elements\Asset;
use craft\errors\InvalidHtmlTagException;
use craft\image\SvgAllowedAttributes;
use enshrined\svgSanitize\Sanitizer;
Expand Down Expand Up @@ -1000,4 +1001,80 @@ public static function encodeInvalidTags(string $html): string
$offset = $tag['end'];
}
}

/**
* Returns the contents of a given SVG file.
*
* @param string|Asset $svg An SVG asset, a file path, or raw SVG markup
* @param bool|null $sanitize Whether the SVG should be sanitized of potentially
* malicious scripts. By default the SVG will only be sanitized if an asset
* or markup is passed in. (File paths are assumed to be safe.)
* @param bool|null $namespace Whether class names and IDs within the SVG
* should be namespaced to avoid conflicts with other elements in the DOM.
* By default the SVG will only be namespaced if an asset or markup is passed in.
* @param string|null $class A CSS class name that should be added to the `<svg>` element.
* (This argument is deprecated. The `|attr` filter should be used instead.)
* @return string
*/
public static function svg(Asset|string $svg, ?bool $sanitize = null, ?bool $namespace = null, ?string $class = null): string
{
if ($svg instanceof Asset) {
try {
$svg = $svg->getContents();
} catch (Throwable $e) {
Craft::error("Could not get the contents of {$svg->getPath()}: {$e->getMessage()}", __METHOD__);
Craft::$app->getErrorHandler()->logException($e);
return '';
}
} elseif (stripos($svg, '<svg') === false) {
// No <svg> tag, so it's probably a file path
try {
$svg = Craft::getAlias($svg);
} catch (InvalidArgumentException $e) {
Craft::error("Could not get the contents of $svg: {$e->getMessage()}", __METHOD__);
Craft::$app->getErrorHandler()->logException($e);
return '';
}
if (!is_file($svg) || !FileHelper::isSvg($svg)) {
Craft::warning("Could not get the contents of $svg: The file doesn't exist", __METHOD__);
return '';
}
$svg = file_get_contents($svg);

// This came from a file path, so pretty good chance that the SVG can be trusted.
$sanitize = $sanitize ?? false;
$namespace = $namespace ?? false;
}

// Sanitize and namespace the SVG by default
$sanitize = $sanitize ?? true;
$namespace = $namespace ?? true;

// Sanitize?
if ($sanitize) {
$svg = Html::sanitizeSvg($svg);
}

// Remove the XML declaration
$svg = preg_replace('/<\?xml.*?\?>\s*/', '', $svg);

// Namespace class names and IDs
if ($namespace) {
$ns = StringHelper::randomString(10);
$svg = Html::namespaceAttributes($svg, $ns, true);
}

if ($class !== null) {
Craft::$app->getDeprecator()->log('svg()-class', 'The `class` argument of the `svg()` Twig function has been deprecated. The `|attr` filter should be used instead.');
try {
$svg = Html::modifyTagAttributes($svg, [
'class' => $class,
]);
} catch (InvalidArgumentException $e) {
Craft::warning('Unable to add a class to the SVG: ' . $e->getMessage(), __METHOD__);
}
}

return $svg;
}
}
79 changes: 1 addition & 78 deletions src/web/twig/Extension.php
Expand Up @@ -19,7 +19,6 @@
use craft\helpers\ArrayHelper;
use craft\helpers\DateTimeHelper;
use craft\helpers\Db;
use craft\helpers\FileHelper;
use craft\helpers\Gql;
use craft\helpers\Html;
use craft\helpers\HtmlPurifier;
Expand Down Expand Up @@ -1249,7 +1248,7 @@ public function getFunctions(): array
new TwigFunction('ol', [Html::class, 'ol'], ['is_safe' => ['html']]),
new TwigFunction('redirectInput', [Html::class, 'redirectInput'], ['is_safe' => ['html']]),
new TwigFunction('successMessageInput', [Html::class, 'successMessageInput'], ['is_safe' => ['html']]),
new TwigFunction('svg', [$this, 'svgFunction'], ['is_safe' => ['html']]),
new TwigFunction('svg', [Html::class, 'svg'], ['is_safe' => ['html']]),
new TwigFunction('tag', [$this, 'tagFunction'], ['is_safe' => ['html']]),
new TwigFunction('ul', [Html::class, 'ul'], ['is_safe' => ['html']]),

Expand Down Expand Up @@ -1412,82 +1411,6 @@ public function shuffleFunction(iterable $arr): array
return $arr;
}

/**
* Returns the contents of a given SVG file.
*
* @param string|Asset $svg An SVG asset, a file path, or raw SVG markup
* @param bool|null $sanitize Whether the SVG should be sanitized of potentially
* malicious scripts. By default the SVG will only be sanitized if an asset
* or markup is passed in. (File paths are assumed to be safe.)
* @param bool|null $namespace Whether class names and IDs within the SVG
* should be namespaced to avoid conflicts with other elements in the DOM.
* By default the SVG will only be namespaced if an asset or markup is passed in.
* @param string|null $class A CSS class name that should be added to the `<svg>` element.
* (This argument is deprecated. The `|attr` filter should be used instead.)
* @return string
*/
public function svgFunction(Asset|string $svg, ?bool $sanitize = null, ?bool $namespace = null, ?string $class = null): string
{
if ($svg instanceof Asset) {
try {
$svg = $svg->getContents();
} catch (Throwable $e) {
Craft::error("Could not get the contents of {$svg->getPath()}: {$e->getMessage()}", __METHOD__);
Craft::$app->getErrorHandler()->logException($e);
return '';
}
} elseif (stripos($svg, '<svg') === false) {
// No <svg> tag, so it's probably a file path
try {
$svg = Craft::getAlias($svg);
} catch (InvalidArgumentException $e) {
Craft::error("Could not get the contents of $svg: {$e->getMessage()}", __METHOD__);
Craft::$app->getErrorHandler()->logException($e);
return '';
}
if (!is_file($svg) || !FileHelper::isSvg($svg)) {
Craft::warning("Could not get the contents of $svg: The file doesn't exist", __METHOD__);
return '';
}
$svg = file_get_contents($svg);

// This came from a file path, so pretty good chance that the SVG can be trusted.
$sanitize = $sanitize ?? false;
$namespace = $namespace ?? false;
}

// Sanitize and namespace the SVG by default
$sanitize = $sanitize ?? true;
$namespace = $namespace ?? true;

// Sanitize?
if ($sanitize) {
$svg = Html::sanitizeSvg($svg);
}

// Remove the XML declaration
$svg = preg_replace('/<\?xml.*?\?>\s*/', '', $svg);

// Namespace class names and IDs
if ($namespace) {
$ns = StringHelper::randomString(10);
$svg = Html::namespaceAttributes($svg, $ns, true);
}

if ($class !== null) {
Craft::$app->getDeprecator()->log('svg()-class', 'The `class` argument of the `svg()` Twig function has been deprecated. The `|attr` filter should be used instead.');
try {
$svg = Html::modifyTagAttributes($svg, [
'class' => $class,
]);
} catch (InvalidArgumentException $e) {
Craft::warning('Unable to add a class to the SVG: ' . $e->getMessage(), __METHOD__);
}
}

return $svg;
}

/**
* Generates a complete HTML tag.
*
Expand Down

0 comments on commit 392b0b0

Please sign in to comment.