diff --git a/src/Umbraco.Core/Configuration/ClientDependencyConfiguration.cs b/src/Umbraco.Core/Configuration/ClientDependencyConfiguration.cs index 6fbdec6901..62cd1ab59c 100644 --- a/src/Umbraco.Core/Configuration/ClientDependencyConfiguration.cs +++ b/src/Umbraco.Core/Configuration/ClientDependencyConfiguration.cs @@ -1,12 +1,19 @@ using System; -using System.Linq; +using System.Collections.Generic; +using System.IO; +using System.Web; using System.Xml.Linq; +using ClientDependency.Core.CompositeFiles.Providers; +using ClientDependency.Core.Config; using Umbraco.Core.IO; using Umbraco.Core.Logging; namespace Umbraco.Core.Configuration -{ - internal class ClientDependencyConfiguration +{ + /// + /// A utility class for working with CDF config and cache files - use sparingly! + /// + public class ClientDependencyConfiguration { private readonly ILogger _logger; private readonly string _fileName; @@ -21,7 +28,7 @@ namespace Umbraco.Core.Configuration /// /// Changes the version number in ClientDependency.config to a random value to avoid stale caches /// - internal bool IncreaseVersionNumber() + public bool IncreaseVersionNumber() { try { @@ -49,5 +56,55 @@ namespace Umbraco.Core.Configuration return false; } + + /// + /// Clears the temporary files stored for the ClientDependency folder + /// + /// + public bool ClearTempFiles(HttpContextBase currentHttpContext) + { + var cdfTempDirectories = new HashSet(); + foreach (BaseCompositeFileProcessingProvider provider in ClientDependencySettings.Instance + .CompositeFileProcessingProviderCollection) + { + var path = provider.CompositeFilePath.FullName; + cdfTempDirectories.Add(path); + } + + try + { + var fullPath = currentHttpContext.Server.MapPath(XmlFileMapper.FileMapVirtualFolder); + if (fullPath != null) + { + cdfTempDirectories.Add(fullPath); + } + } + catch (Exception ex) + { + //invalid path format or something... try/catch to be safe + LogHelper.Error("Could not get path from ClientDependency.config", ex); + } + + var success = true; + foreach (var directory in cdfTempDirectories) + { + var directoryInfo = new DirectoryInfo(directory); + if (directoryInfo.Exists == false) + continue; + + try + { + directoryInfo.Delete(true); + } + catch (Exception ex) + { + // Something could be locking the directory or the was another error, making sure we don't break the upgrade installer + LogHelper.Error("Could not clear temp files", ex); + success = false; + } + } + + return success; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index c6e7692cee..680086b55f 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -45,6 +45,9 @@ ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll True + + ..\packages\ClientDependency.1.9.2\lib\net45\ClientDependency.Core.dll + ..\packages\HtmlAgilityPack.1.4.9.5\lib\Net45\HtmlAgilityPack.dll True diff --git a/src/Umbraco.Core/packages.config b/src/Umbraco.Core/packages.config index c883ef1361..7c9fb9c19b 100644 --- a/src/Umbraco.Core/packages.config +++ b/src/Umbraco.Core/packages.config @@ -1,6 +1,7 @@  + diff --git a/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs b/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs index 672b9022e5..97f12cdaf8 100644 --- a/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs +++ b/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs @@ -21,6 +21,21 @@ namespace Umbraco.Tests.FrontEnd Assert.AreEqual("Hello world, this is some…", result); } + /// + /// If a truncated string ends with a space, we should trim the space before appending the ellipsis. + /// + [Test] + public void Truncate_Simple_With_Trimming() + { + var text = "Hello world, this is some text with a link"; + + var helper = new UmbracoHelper(); + + var result = helper.Truncate(text, 26).ToString(); + + Assert.AreEqual("Hello world, this is some…", result); + } + [Test] public void Truncate_Inside_Word() { @@ -78,5 +93,79 @@ namespace Umbraco.Tests.FrontEnd Assert.AreEqual(expectedResult, result); } + + [Test] + public void Truncate_By_Words() + { + var text = "Hello world, this is some text with a link"; + + var helper = new UmbracoHelper(); + + var result = helper.TruncateByWords(text, 4).ToString(); + + Assert.AreEqual("Hello world, this is…", result); + } + + [Test] + public void Truncate_By_Words_With_Tag() + { + var text = "Hello world, this is some text with a link"; + + var helper = new UmbracoHelper(); + + var result = helper.TruncateByWords(text, 4).ToString(); + + Assert.AreEqual("Hello world, this is…", result); + } + + [Test] + public void Truncate_By_Words_Mid_Tag() + { + var text = "Hello world, this is some text with a link"; + + var helper = new UmbracoHelper(); + + var result = helper.TruncateByWords(text, 7).ToString(); + + Assert.AreEqual("Hello world, this is some text with…", result); + } + + [Test] + public void Strip_All_Html() + { + var text = "Hello world, this is some text with a link"; + + var helper = new UmbracoHelper(); + + var result = helper.StripHtml(text, null).ToString(); + + Assert.AreEqual("Hello world, this is some text with a link", result); + } + + [Test] + public void Strip_Specific_Html() + { + var text = "Hello world, this is some text with a link"; + + string [] tags = {"b"}; + + var helper = new UmbracoHelper(); + + var result = helper.StripHtml(text, tags).ToString(); + + Assert.AreEqual("Hello world, this is some text with a link", result); + } + + [Test] + public void Strip_Invalid_Html() + { + var text = "Hello world, is some text with a link"; + + var helper = new UmbracoHelper(); + + var result = helper.StripHtml(text).ToString(); + + Assert.AreEqual("Hello world, is some text with a link", result); + } } } diff --git a/src/Umbraco.Web/HtmlStringUtilities.cs b/src/Umbraco.Web/HtmlStringUtilities.cs index 11ba7a4d83..c4ae1c4eac 100644 --- a/src/Umbraco.Web/HtmlStringUtilities.cs +++ b/src/Umbraco.Web/HtmlStringUtilities.cs @@ -3,8 +3,10 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Web; using HtmlAgilityPack; +using Umbraco.Web.WebApi.Filters; namespace Umbraco.Web { @@ -30,6 +32,7 @@ namespace Umbraco.Web { var doc = new HtmlDocument(); doc.LoadHtml("

" + html + "

"); + var targets = new List(); var nodes = doc.DocumentNode.FirstChild.SelectNodes(".//*"); @@ -55,7 +58,7 @@ namespace Umbraco.Web { return new HtmlString(html); } - return new HtmlString(doc.DocumentNode.FirstChild.InnerHtml); + return new HtmlString(doc.DocumentNode.FirstChild.InnerHtml.Replace(" ", " ")); } internal string Join(string seperator, params object[] args) @@ -85,6 +88,8 @@ namespace Umbraco.Web public IHtmlString Truncate(string html, int length, bool addElipsis, bool treatTagsAsContent) { + const string hellip = "…"; + using (var outputms = new MemoryStream()) { using (var outputtw = new StreamWriter(outputms)) @@ -106,7 +111,7 @@ namespace Umbraco.Web isTagClose = false; int ic = 0, - currentLength = 0, + //currentLength = 0, currentTextLength = 0; string currentTag = string.Empty, @@ -141,6 +146,10 @@ namespace Umbraco.Web { string thisTag = tagStack.Pop(); outputtw.Write(""); + if (treatTagsAsContent) + { + currentTextLength++; + } } if (!isTagClose && currentTag.Length > 0) { @@ -148,6 +157,10 @@ namespace Umbraco.Web { tagStack.Push(currentTag); outputtw.Write("<" + currentTag); + if (treatTagsAsContent) + { + currentTextLength++; + } if (!string.IsNullOrEmpty(tagContents)) { if (tagContents.EndsWith("/")) @@ -205,7 +218,7 @@ namespace Umbraco.Web { var charToWrite = (char)ic; outputtw.Write(charToWrite); - currentLength++; + //currentLength++; } } @@ -221,7 +234,7 @@ namespace Umbraco.Web // Reached truncate limit. if (addElipsis) { - outputtw.Write("…"); + outputtw.Write(hellip); } lengthReached = true; } @@ -235,10 +248,59 @@ namespace Umbraco.Web outputms.Position = 0; using (TextReader outputtr = new StreamReader(outputms)) { - return new HtmlString(outputtr.ReadToEnd().Replace(" ", " ").Trim()); + string result = string.Empty; + + string firstTrim = outputtr.ReadToEnd().Replace(" ", " ").Trim(); + + //Check to see if there is an empty char between the hellip and the output string + //if there is, remove it + if (string.IsNullOrWhiteSpace(firstTrim) == false) + { + result = firstTrim[firstTrim.Length - hellip.Length - 1] == ' ' ? firstTrim.Remove(firstTrim.Length - hellip.Length - 1, 1) : firstTrim; + } + return new HtmlString(result); } } } } + + /// + /// Returns the length of the words from a html block + /// + /// Html text + /// Amount of words you would like to measure + /// + /// + public int WordsToLength(string html, int words) + { + HtmlDocument doc = new HtmlDocument(); + doc.LoadHtml(html); + + int wordCount = 0, + length = 0, + maxWords = words; + + html = StripHtmlTags(html, null).ToString(); + + while (length < html.Length) + { + // Check to see if the current wordCount reached the maxWords allowed + if (wordCount.Equals(maxWords)) break; + // Check if current char is part of a word + while (length < html.Length && char.IsWhiteSpace(html[length]) == false) + { + length++; + } + + wordCount++; + + // Skip whitespace until the next word + while (length < html.Length && char.IsWhiteSpace(html[length]) && wordCount.Equals(maxWords) == false) + { + length++; + } + } + return length; + } } } diff --git a/src/Umbraco.Web/Install/Controllers/InstallController.cs b/src/Umbraco.Web/Install/Controllers/InstallController.cs index 360e35efda..7e3336e1e8 100644 --- a/src/Umbraco.Web/Install/Controllers/InstallController.cs +++ b/src/Umbraco.Web/Install/Controllers/InstallController.cs @@ -47,6 +47,8 @@ namespace Umbraco.Web.Install.Controllers // Update ClientDependency version var clientDependencyConfig = new ClientDependencyConfiguration(ApplicationContext.Current.ProfilingLogger.Logger); var clientDependencyUpdated = clientDependencyConfig.IncreaseVersionNumber(); + // Delete ClientDependency temp directories to make sure we get fresh caches + var clientDependencyTempFilesDeleted = clientDependencyConfig.ClearTempFiles(HttpContext); var result = _umbracoContext.Security.ValidateCurrentUser(false); diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 40126a175f..d94476b31c 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -1439,18 +1439,58 @@ namespace Umbraco.Web /// public IHtmlString Truncate(string html, int length, bool addElipsis, bool treatTagsAsContent) { - return _stringUtilities.Truncate(html, length, addElipsis, treatTagsAsContent); - } + return _stringUtilities.Truncate(html, length, addElipsis, treatTagsAsContent); + } + #region Truncate by Words + /// + /// Truncates a string to a given amount of words, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them + /// + public IHtmlString TruncateByWords(string html, int words) + { + int length = _stringUtilities.WordsToLength(html, words); - #endregion + return Truncate(html, length, true, false); + } - #region If + /// + /// Truncates a string to a given amount of words, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them + /// + public IHtmlString TruncateByWords(string html, int words, bool addElipsis) + { + int length = _stringUtilities.WordsToLength(html, words); - /// - /// If the test is true, the string valueIfTrue will be returned, otherwise the valueIfFalse will be returned. - /// - public HtmlString If(bool test, string valueIfTrue, string valueIfFalse) + return Truncate(html, length, addElipsis, false); + } + + /// + /// Truncates a string to a given amount of words, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them + /// + public IHtmlString TruncateByWords(IHtmlString html, int words) + { + int length = _stringUtilities.WordsToLength(html.ToHtmlString(), words); + + return Truncate(html, length, true, false); + } + + /// + /// Truncates a string to a given amount of words, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them + /// + public IHtmlString TruncateByWords(IHtmlString html, int words, bool addElipsis) + { + int length = _stringUtilities.WordsToLength(html.ToHtmlString(), words); + + return Truncate(html, length, addElipsis, false); + } + #endregion + #endregion + + #region If + + /// + /// If the test is true, the string valueIfTrue will be returned, otherwise the valueIfFalse will be returned. + /// + public HtmlString If(bool test, string valueIfTrue, string valueIfFalse) { return test ? new HtmlString(valueIfTrue) : new HtmlString(valueIfFalse); }