1
- using Microsoft . AspNetCore . Hosting ;
1
+ using HtmlAgilityPack ;
2
+ using Microsoft . AspNetCore . Hosting ;
2
3
using Microsoft . AspNetCore . Razor . TagHelpers ;
4
+ using Microsoft . Extensions . Options ;
5
+ using Our . Umbraco . TagHelpers . Configuration ;
6
+ using Our . Umbraco . TagHelpers . Utils ;
3
7
using System ;
4
8
using System . IO ;
5
9
using System . Text . RegularExpressions ;
10
+ using Umbraco . Cms . Core . Cache ;
6
11
using Umbraco . Cms . Core . IO ;
7
12
using Umbraco . Cms . Core . Models . PublishedContent ;
8
13
using Umbraco . Cms . Core . Routing ;
@@ -20,12 +25,16 @@ public class InlineSvgTagHelper : TagHelper
20
25
private MediaFileManager _mediaFileManager ;
21
26
private IWebHostEnvironment _webHostEnvironment ;
22
27
private IPublishedUrlProvider _urlProvider ;
28
+ private OurUmbracoTagHelpersConfiguration _globalSettings ;
29
+ private AppCaches _appCaches ;
23
30
24
- public InlineSvgTagHelper ( MediaFileManager mediaFileManager , IWebHostEnvironment webHostEnvironment , IPublishedUrlProvider urlProvider )
31
+ public InlineSvgTagHelper ( MediaFileManager mediaFileManager , IWebHostEnvironment webHostEnvironment , IPublishedUrlProvider urlProvider , IOptions < OurUmbracoTagHelpersConfiguration > globalSettings , AppCaches appCaches )
25
32
{
26
33
_mediaFileManager = mediaFileManager ;
27
34
_webHostEnvironment = webHostEnvironment ;
28
35
_urlProvider = urlProvider ;
36
+ _globalSettings = globalSettings . Value ;
37
+ _appCaches = appCaches ;
29
38
}
30
39
31
40
/// <summary>
@@ -42,6 +51,40 @@ public InlineSvgTagHelper(MediaFileManager mediaFileManager, IWebHostEnvironment
42
51
[ HtmlAttributeName ( "media-item" ) ]
43
52
public IPublishedContent ? MediaItem { get ; set ; }
44
53
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
+
45
88
public override void Process ( TagHelperContext context , TagHelperOutput output )
46
89
{
47
90
// Can only use media-item OR src
@@ -55,40 +98,79 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
55
98
return ;
56
99
}
57
100
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
+ {
58
143
// SVG fileContents to render to DOM
59
144
var fileContents = string . Empty ;
60
145
61
- if ( MediaItem is not null )
146
+ if ( MediaItem is not null )
62
147
{
63
148
// Check Umbraco Media Item that is picked/used
64
149
// has a file that uses a .svg file extension
65
150
var mediaItemPath = MediaItem . Url ( _urlProvider ) ;
66
151
if ( mediaItemPath ? . EndsWith ( ".svg" , StringComparison . InvariantCultureIgnoreCase ) != true )
67
152
{
68
- output . SuppressOutput ( ) ;
69
- return ;
153
+ return null ;
70
154
}
71
155
72
156
// Ensure the file actually exists on disk, Azure blob provider or ...
73
157
// Anywhere else defined by IFileSystem to fetch & store files
74
158
if ( _mediaFileManager . FileSystem . FileExists ( mediaItemPath ) == false )
75
159
{
76
- output . SuppressOutput ( ) ;
77
- return ;
160
+ return null ;
78
161
}
79
162
80
163
// Read its contents (get its stream)
81
164
var fileStream = _mediaFileManager . FileSystem . OpenFile ( mediaItemPath ) ;
82
165
using var reader = new StreamReader ( fileStream ) ;
83
166
fileContents = reader . ReadToEnd ( ) ;
84
167
}
85
- else if ( string . IsNullOrWhiteSpace ( FileSource ) == false )
168
+ else if ( string . IsNullOrWhiteSpace ( FileSource ) == false )
86
169
{
87
170
// Check string src filepath ends with .svg
88
171
if ( FileSource . EndsWith ( ".svg" , StringComparison . InvariantCultureIgnoreCase ) == false )
89
172
{
90
- output . SuppressOutput ( ) ;
91
- return ;
173
+ return null ;
92
174
}
93
175
94
176
// Get file from wwwRoot using a path such as
@@ -98,10 +180,9 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
98
180
var file = webRoot . GetFileInfo ( FileSource ) ;
99
181
100
182
// Ensure file exists in wwwroot path
101
- if ( file . Exists == false )
183
+ if ( file . Exists == false )
102
184
{
103
- output . SuppressOutput ( ) ;
104
- return ;
185
+ return null ;
105
186
}
106
187
107
188
using var reader = new StreamReader ( file . CreateReadStream ( ) ) ;
@@ -120,12 +201,31 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
120
201
@"syntax:error:" ,
121
202
RegexOptions . IgnoreCase | RegexOptions . Singleline ) ;
122
203
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
+ }
126
227
127
- output . TagName = null ; // Remove <our-svg>
128
- output . Content . SetHtmlContent ( cleanedFileContents ) ;
129
- }
228
+ return cleanedFileContents ;
229
+ }
130
230
}
131
231
}
0 commit comments