1- using Microsoft . AspNetCore . Hosting ;
1+ using HtmlAgilityPack ;
2+ using Microsoft . AspNetCore . Hosting ;
23using Microsoft . AspNetCore . Razor . TagHelpers ;
4+ using Microsoft . Extensions . Options ;
5+ using Our . Umbraco . TagHelpers . Configuration ;
6+ using Our . Umbraco . TagHelpers . Utils ;
37using System ;
48using System . IO ;
59using System . Text . RegularExpressions ;
10+ using Umbraco . Cms . Core . Cache ;
611using Umbraco . Cms . Core . IO ;
712using Umbraco . Cms . Core . Models . PublishedContent ;
813using Umbraco . Cms . Core . Routing ;
@@ -20,12 +25,16 @@ public class InlineSvgTagHelper : TagHelper
2025 private MediaFileManager _mediaFileManager ;
2126 private IWebHostEnvironment _webHostEnvironment ;
2227 private IPublishedUrlProvider _urlProvider ;
28+ private OurUmbracoTagHelpersConfiguration _globalSettings ;
29+ private AppCaches _appCaches ;
2330
24- public InlineSvgTagHelper ( MediaFileManager mediaFileManager , IWebHostEnvironment webHostEnvironment , IPublishedUrlProvider urlProvider )
31+ public InlineSvgTagHelper ( MediaFileManager mediaFileManager , IWebHostEnvironment webHostEnvironment , IPublishedUrlProvider urlProvider , IOptions < OurUmbracoTagHelpersConfiguration > globalSettings , AppCaches appCaches )
2532 {
2633 _mediaFileManager = mediaFileManager ;
2734 _webHostEnvironment = webHostEnvironment ;
2835 _urlProvider = urlProvider ;
36+ _globalSettings = globalSettings . Value ;
37+ _appCaches = appCaches ;
2938 }
3039
3140 /// <summary>
@@ -42,6 +51,40 @@ public InlineSvgTagHelper(MediaFileManager mediaFileManager, IWebHostEnvironment
4251 [ HtmlAttributeName ( "media-item" ) ]
4352 public IPublishedContent ? MediaItem { get ; set ; }
4453
54+ /// <summary>
55+ /// A classic CSS class property to apply/append a CSS class or classes.
56+ /// </summary>
57+ [ HtmlAttributeName ( "class" ) ]
58+ public string ? CssClass { get ; set ; }
59+
60+ /// <summary>
61+ /// A boolean to ensure a viewbox is present within the SVG tag to ensure the vector is always responsive.
62+ /// NOTE: Use the appsettings configuration to apply this globally (e.g. "Our.Umbraco.TagHelpers": { "InlineSvgTagHelper": { "EnsureViewBox": true } } ).
63+ /// </summary>
64+ [ HtmlAttributeName ( "ensure-viewbox" ) ]
65+ public bool EnsureViewBox { get ; set ; }
66+
67+ /// <summary>
68+ /// A boolean to cache the SVG contents rather than performing the operation on each page load.
69+ /// NOTE: Use the appsettings configuration to apply this globally (e.g. "Our.Umbraco.TagHelpers": { "InlineSvgTagHelper": { "Cache": true } } ).
70+ /// </summary>
71+ [ HtmlAttributeName ( "cache" ) ]
72+ public bool Cache { get ; set ; }
73+
74+ /// <summary>
75+ /// An integer to set the cache minutes. Default: 180 minutes.
76+ /// NOTE: Use the appsettings configuration to apply this globally (e.g. "Our.Umbraco.TagHelpers": { "InlineSvgTagHelper": { "CacheMinutes": 180 } } ).
77+ /// </summary>
78+ [ HtmlAttributeName ( "cache-minutes" ) ]
79+ public int CacheMinutes { get ; set ; }
80+
81+ /// <summary>
82+ /// A boolean to ignore the appsettings.
83+ /// NOTE: Applies to 'ensure-viewbox' & 'cache' only
84+ /// </summary>
85+ [ HtmlAttributeName ( "ignore-appsettings" ) ]
86+ public bool IgnoreAppSettings { get ; set ; }
87+
4588 public override void Process ( TagHelperContext context , TagHelperOutput output )
4689 {
4790 // Can only use media-item OR src
@@ -55,40 +98,79 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
5598 return ;
5699 }
57100
101+ string ? cleanedFileContents = null ;
102+
103+ if ( Cache || ( _globalSettings . OurSVG . Cache && ! IgnoreAppSettings ) )
104+ {
105+ var cacheName = string . Empty ;
106+ var cacheMins = CacheMinutes > 0 ? CacheMinutes : _globalSettings . OurSVG . CacheMinutes ;
107+
108+ if ( MediaItem is not null )
109+ {
110+ cacheName = string . Concat ( "MediaItem-SvgContents (" , MediaItem . Key . ToString ( ) , ")" ) ;
111+ }
112+ else if ( string . IsNullOrWhiteSpace ( FileSource ) == false )
113+ {
114+ cacheName = string . Concat ( "File-SvgContents (" , FileSource , ")" ) ;
115+ }
116+
117+ cleanedFileContents = _appCaches . RuntimeCache . GetCacheItem ( cacheName , ( ) =>
118+ {
119+ return GetFileContents ( ) ;
120+ } , TimeSpan . FromMinutes ( cacheMins ) ) ;
121+ }
122+ else
123+ {
124+ cleanedFileContents = GetFileContents ( ) ;
125+ }
126+
127+ if ( string . IsNullOrEmpty ( cleanedFileContents ) )
128+ {
129+ output . SuppressOutput ( ) ;
130+ return ;
131+ }
132+
133+ // Remove the src attribute or media-item from the <svg>
134+ output . Attributes . RemoveAll ( "src" ) ;
135+ output . Attributes . RemoveAll ( "media-item" ) ;
136+
137+ output . TagName = null ; // Remove <our-svg>
138+ output . Content . SetHtmlContent ( cleanedFileContents ) ;
139+ }
140+
141+ private string ? GetFileContents ( )
142+ {
58143 // SVG fileContents to render to DOM
59144 var fileContents = string . Empty ;
60145
61- if ( MediaItem is not null )
146+ if ( MediaItem is not null )
62147 {
63148 // Check Umbraco Media Item that is picked/used
64149 // has a file that uses a .svg file extension
65150 var mediaItemPath = MediaItem . Url ( _urlProvider ) ;
66151 if ( mediaItemPath ? . EndsWith ( ".svg" , StringComparison . InvariantCultureIgnoreCase ) != true )
67152 {
68- output . SuppressOutput ( ) ;
69- return ;
153+ return null ;
70154 }
71155
72156 // Ensure the file actually exists on disk, Azure blob provider or ...
73157 // Anywhere else defined by IFileSystem to fetch & store files
74158 if ( _mediaFileManager . FileSystem . FileExists ( mediaItemPath ) == false )
75159 {
76- output . SuppressOutput ( ) ;
77- return ;
160+ return null ;
78161 }
79162
80163 // Read its contents (get its stream)
81164 var fileStream = _mediaFileManager . FileSystem . OpenFile ( mediaItemPath ) ;
82165 using var reader = new StreamReader ( fileStream ) ;
83166 fileContents = reader . ReadToEnd ( ) ;
84167 }
85- else if ( string . IsNullOrWhiteSpace ( FileSource ) == false )
168+ else if ( string . IsNullOrWhiteSpace ( FileSource ) == false )
86169 {
87170 // Check string src filepath ends with .svg
88171 if ( FileSource . EndsWith ( ".svg" , StringComparison . InvariantCultureIgnoreCase ) == false )
89172 {
90- output . SuppressOutput ( ) ;
91- return ;
173+ return null ;
92174 }
93175
94176 // Get file from wwwRoot using a path such as
@@ -98,10 +180,9 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
98180 var file = webRoot . GetFileInfo ( FileSource ) ;
99181
100182 // Ensure file exists in wwwroot path
101- if ( file . Exists == false )
183+ if ( file . Exists == false )
102184 {
103- output . SuppressOutput ( ) ;
104- return ;
185+ return null ;
105186 }
106187
107188 using var reader = new StreamReader ( file . CreateReadStream ( ) ) ;
@@ -120,12 +201,31 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
120201 @"syntax:error:" ,
121202 RegexOptions . IgnoreCase | RegexOptions . Singleline ) ;
122203
123- // Remove the src attribute or media-item from the <svg>
124- output . Attributes . RemoveAll ( "src" ) ;
125- output . Attributes . RemoveAll ( "media-item" ) ;
204+ if ( ( EnsureViewBox || ( _globalSettings . OurSVG . EnsureViewBox && ! IgnoreAppSettings ) ) || ! string . IsNullOrEmpty ( CssClass ) )
205+ {
206+ HtmlDocument doc = new HtmlDocument ( ) ;
207+ doc . LoadHtml ( cleanedFileContents ) ;
208+ var svgs = doc . DocumentNode . SelectNodes ( "//svg" ) ;
209+ foreach ( var svgNode in svgs )
210+ {
211+ if ( ! string . IsNullOrEmpty ( CssClass ) )
212+ {
213+ svgNode . AddClass ( CssClass ) ;
214+ }
215+ if ( ( EnsureViewBox || ( _globalSettings . OurSVG . EnsureViewBox && ! IgnoreAppSettings ) ) && svgNode . Attributes . Contains ( "width" ) && svgNode . Attributes . Contains ( "height" ) && ! svgNode . Attributes . Contains ( "viewbox" ) )
216+ {
217+ var width = StringUtils . GetDecimal ( svgNode . GetAttributeValue ( "width" , "0" ) ) ;
218+ var height = StringUtils . GetDecimal ( svgNode . GetAttributeValue ( "height" , "0" ) ) ;
219+ svgNode . SetAttributeValue ( "viewbox" , $ "0 0 { width } { height } ") ;
220+
221+ svgNode . Attributes . Remove ( "width" ) ;
222+ svgNode . Attributes . Remove ( "height" ) ;
223+ }
224+ }
225+ cleanedFileContents = doc . DocumentNode . OuterHtml ;
226+ }
126227
127- output . TagName = null ; // Remove <our-svg>
128- output . Content . SetHtmlContent ( cleanedFileContents ) ;
129- }
228+ return cleanedFileContents ;
229+ }
130230 }
131231}
0 commit comments