From ad2764e3acf7a1ea0e4393abfd766fb93bee48ef Mon Sep 17 00:00:00 2001 From: Jeremy Pyne Date: Mon, 24 Jun 2013 14:51:52 -0400 Subject: [PATCH 0001/1046] Added code to select the parent node when doing Move/Copy operations. This will save lots of time for users. This code can also be added to other places that use the TreeControl to pre-select the currently selected item(media pickers for example) so that changing content will be quicker. --- .../umbraco/controls/Tree/TreeControl.ascx | 6 ++++++ .../umbraco/controls/Tree/TreeControl.ascx.cs | 10 ++++++++++ .../umbraco/dialogs/moveOrCopy.aspx.cs | 4 ++++ 3 files changed, 20 insertions(+) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/TreeControl.ascx b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/TreeControl.ascx index c7fa6aa5d5..3d379f68d8 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/TreeControl.ascx +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/TreeControl.ascx @@ -47,6 +47,12 @@ jQuery(document).ready(function() { dataUrl: "<%#umbraco.IO.IOHelper.ResolveUrl(umbraco.IO.SystemDirectories.Umbraco)%>/webservices/TreeDataService.ashx", serviceUrl: "<%#umbraco.IO.IOHelper.ResolveUrl(umbraco.IO.SystemDirectories.Umbraco)%>/webservices/TreeClientService.asmx/GetInitAppTreeData"}); + <%if(!String.IsNullOrEmpty(this.SelectedNodePath)) {%> + setTimeout(function() { + jQuery("#<%=ClientID%>").UmbracoTreeAPI().syncTree('<%=this.SelectedNodePath%>', true, true); + }, 500); + <% } %> + //add event handler for ajax errors, this will refresh the whole application var mainTree = UmbClientMgr.mainTree(); if (mainTree != null) { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/TreeControl.ascx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/TreeControl.ascx.cs index fd1b180005..c9d4cc8f74 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/TreeControl.ascx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/TreeControl.ascx.cs @@ -62,6 +62,7 @@ namespace umbraco.controls.Tree private TreeMode m_TreeType = TreeMode.Standard; private bool m_IsInit = false; private TreeService m_TreeService = new TreeService(); + private string m_SelectedNodePath; #region Public Properties @@ -100,6 +101,15 @@ namespace umbraco.controls.Tree } } + public string SelectedNodePath + { + get { return m_SelectedNodePath; } + set + { + m_SelectedNodePath = value; + } + } + public string TreeType { get { return m_TreeService.TreeType; } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs index ebad6f2151..d80500870c 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs @@ -80,6 +80,10 @@ namespace umbraco.dialogs var cmsNode = new CMSNode(int.Parse(Request.GetItemAsString("id"))); + // Preselect the parent of the seslected item. + if(cmsNode.ParentId > 0) + JTree.SelectedNodePath = cmsNode.Parent.Path; + var validAction = true; if (CurrentApp == Constants.Applications.Content && cmsNode.HasChildren) validAction = ValidAction(Request.GetItemAsString("mode") == "cut" ? 'M' : 'O'); From fe23502e67c431d3a31b73246c038db7a8a29aaf Mon Sep 17 00:00:00 2001 From: Jeremy Pyne Date: Tue, 25 Jun 2013 15:48:16 -0400 Subject: [PATCH 0002/1046] Added code to actually pre-select the parent node instead of just locating it in the tree. This seams keeps the Ui in a sane state. Updated control code in Umbraco.Web.Ui from Umbraco.Web. --- .../umbraco/controls/Tree/TreeControl.ascx | 15 +++++++++++++++ .../umbraco/controls/Tree/TreeControl.ascx | 11 ++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/umbraco/controls/Tree/TreeControl.ascx b/src/Umbraco.Web.UI/umbraco/controls/Tree/TreeControl.ascx index c7fa6aa5d5..fb4fac1e2d 100644 --- a/src/Umbraco.Web.UI/umbraco/controls/Tree/TreeControl.ascx +++ b/src/Umbraco.Web.UI/umbraco/controls/Tree/TreeControl.ascx @@ -47,6 +47,21 @@ jQuery(document).ready(function() { dataUrl: "<%#umbraco.IO.IOHelper.ResolveUrl(umbraco.IO.SystemDirectories.Umbraco)%>/webservices/TreeDataService.ashx", serviceUrl: "<%#umbraco.IO.IOHelper.ResolveUrl(umbraco.IO.SystemDirectories.Umbraco)%>/webservices/TreeClientService.asmx/GetInitAppTreeData"}); + <%if(!String.IsNullOrEmpty(this.SelectedNodePath)) {%> + setTimeout(function() { + treeApi = jQuery("#<%=ClientID%>").UmbracoTreeAPI(); + treeApi.syncTree('<%=this.SelectedNodePath%>', true, true); + + // select the parent node once found. + var iv = setInterval(function() { + var node = treeApi.findNode('<%=this.SelectedNodePath%>'.split(',').pop(), true); + if(node != false) { + treeApi.selectNode(node, false, true); + clearInterval(iv); + } + }, 500); + <% } %> + //add event handler for ajax errors, this will refresh the whole application var mainTree = UmbClientMgr.mainTree(); if (mainTree != null) { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/TreeControl.ascx b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/TreeControl.ascx index 3d379f68d8..fb4fac1e2d 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/TreeControl.ascx +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/TreeControl.ascx @@ -49,7 +49,16 @@ jQuery(document).ready(function() { <%if(!String.IsNullOrEmpty(this.SelectedNodePath)) {%> setTimeout(function() { - jQuery("#<%=ClientID%>").UmbracoTreeAPI().syncTree('<%=this.SelectedNodePath%>', true, true); + treeApi = jQuery("#<%=ClientID%>").UmbracoTreeAPI(); + treeApi.syncTree('<%=this.SelectedNodePath%>', true, true); + + // select the parent node once found. + var iv = setInterval(function() { + var node = treeApi.findNode('<%=this.SelectedNodePath%>'.split(',').pop(), true); + if(node != false) { + treeApi.selectNode(node, false, true); + clearInterval(iv); + } }, 500); <% } %> From 7e645f9a6a8e0cba89cd2058643f7f419a79eaa2 Mon Sep 17 00:00:00 2001 From: DougMac Date: Sat, 17 Aug 2013 10:50:26 +0100 Subject: [PATCH 0003/1046] Fixes: U4-2060 Spell Checker Broke After Google Update The Google spell check at https://www.google.com/tbproxy/spell no longer works. This updates to a newer Google Api (again undocumented) --- .../plugins/tinymce3/GoogleSpellChecker.cs | 65 ++++++++++--------- .../plugins/tinymce3/SpellCheckerResult.cs | 30 +++++++++ 2 files changed, 66 insertions(+), 29 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/plugins/tinymce3/GoogleSpellChecker.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/plugins/tinymce3/GoogleSpellChecker.cs index 42bf22e5fe..edaa5950c4 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/plugins/tinymce3/GoogleSpellChecker.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/plugins/tinymce3/GoogleSpellChecker.cs @@ -5,18 +5,27 @@ using System.Web.Script.Serialization; using System.Net; using System.Text; using System.Xml; +using System.Collections.Generic; // NB: This class was moved out of the client tinymce folder to aid with upgrades // but we'll keep the old namespace to make things easier for now (MB) namespace umbraco.presentation.umbraco_client.tinymce3.plugins.spellchecker { public class GoogleSpellChecker : SpellChecker, IHttpHandler - { + { private static string SendRequest(string lang, string data) { string googleResponse; - string requestUriString = string.Format("https://www.google.com:443/tbproxy/spell?lang={0}&hl={0}", lang); - string s = string.Format("{0}", HttpContext.Current.Server.UrlEncode(data)); + string requestUriString = "https://www.googleapis.com:443/rpc"; + var requestData = new Dictionary(); + var requestParams = new Dictionary(); + requestParams.Add("language", lang); + requestParams.Add("text", data); + requestParams.Add("key", "AIzaSyCLlKc60a3z7lo8deV-hAyDU7rHYgL4HZg"); + requestData.Add("method", "spelling.check"); + requestData.Add("apiVersion", "v2"); + requestData.Add("params", requestParams); + string jsonString = new JavaScriptSerializer().Serialize(requestData); StreamReader reader = null; HttpWebResponse response = null; Stream requestStream = null; @@ -25,15 +34,10 @@ namespace umbraco.presentation.umbraco_client.tinymce3.plugins.spellchecker HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUriString); request.KeepAlive = false; request.Method = "POST"; - request.ContentType = "application/PTI26"; - request.ContentLength = s.Length; - WebHeaderCollection headers = request.Headers; - headers.Add("MIME-Version: 1.0"); - headers.Add("Request-number: 1"); - headers.Add("Document-type: Request"); - headers.Add("Interface-Version: Test 1.4"); + request.ContentType = "application/json"; + request.ContentLength = jsonString.Length; requestStream = request.GetRequestStream(); - byte[] bytes = new ASCIIEncoding().GetBytes(s); + byte[] bytes = new ASCIIEncoding().GetBytes(jsonString); requestStream.Write(bytes, 0, bytes.Length); response = (HttpWebResponse)request.GetResponse(); reader = new StreamReader(response.GetResponseStream()); @@ -64,18 +68,20 @@ namespace umbraco.presentation.umbraco_client.tinymce3.plugins.spellchecker /// The words to be checked. /// public override SpellCheckerResult CheckWords(string language, string[] words) - { - XmlDocument document = new XmlDocument(); + { string data = string.Join(" ", words); //turn them into a space-separated string as that's what google takes - string xml = SendRequest(language, data); - document.LoadXml(xml); + string json = SendRequest(language, data); + var jsonRes = new JavaScriptSerializer().Deserialize(json); - var res = new SpellCheckerResult(); - foreach (XmlNode node in document.SelectNodes("//c")) //go through each of the incorrectly spelt words + var res = new SpellCheckerResult(); + // Get list of misspelled words + if (jsonRes.result != null && jsonRes.result.spellingCheckResponse != null) { - XmlElement element = (XmlElement)node; - res.result.Add(data.Substring(Convert.ToInt32(element.GetAttribute("o")), Convert.ToInt32(element.GetAttribute("l")))); - } + foreach (var misspelling in jsonRes.result.spellingCheckResponse.misspellings) + { + res.result.Add(data.Substring(misspelling.charStart, misspelling.charLength)); + } + } return res; } @@ -87,22 +93,23 @@ namespace umbraco.presentation.umbraco_client.tinymce3.plugins.spellchecker /// The word that is misspelt. /// public override SpellCheckerResult GetSuggestions(string language, string word) - { - XmlDocument document = new XmlDocument(); - string xml = SendRequest(language, word); - document.LoadXml(xml); + { + string json = SendRequest(language, word); + var jsonRes = new JavaScriptSerializer().Deserialize(json); + var res = new SpellCheckerResult(); - foreach (XmlNode node in document.SelectNodes("//c")) //select each incorrectly spelt work + // Get list of suggestions + if (jsonRes.result != null && jsonRes.result.spellingCheckResponse != null) { - XmlElement element = (XmlElement)node; - foreach (string s in element.InnerText.Split(new char[] { '\t' })) //they are tab-separated for suggestions + foreach (var misspelling in jsonRes.result.spellingCheckResponse.misspellings) { - if (!string.IsNullOrEmpty(s)) + foreach (var suggestion in misspelling.suggestions) { - res.result.Add(s); + res.result.Add(suggestion.suggestion); } } } + return res; } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/plugins/tinymce3/SpellCheckerResult.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/plugins/tinymce3/SpellCheckerResult.cs index 1372629cf8..c6d16e903c 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/plugins/tinymce3/SpellCheckerResult.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/plugins/tinymce3/SpellCheckerResult.cs @@ -32,4 +32,34 @@ namespace umbraco.presentation.umbraco_client.tinymce3.plugins.spellchecker /// The error. public string error { get; set; } } + + /// + /// Object used to deserialise the Google Json response + /// + public class JsonSpellCheckerResult + { + public GoogleResponseResult result { get; set; } + + public class GoogleResponseResult + { + public GoogleResponseSpellingCheckResponse spellingCheckResponse { get; set; } + + } + public class GoogleResponseSpellingCheckResponse + { + public List misspellings { get; set; } + + } + public class GoogleResponseMisspelling + { + public int charStart { get; set; } + public int charLength { get; set; } + public List suggestions { get; set; } + + } + public class GoogleResponseSuggestion + { + public string suggestion { get; set; } + } + } } From 0218e39664dbd0adce3bf18898a5e7ce0d16138f Mon Sep 17 00:00:00 2001 From: DougMac Date: Mon, 26 Aug 2013 22:50:53 +0100 Subject: [PATCH 0004/1046] Fixes: U4-2060 Spell Checker Broke After Google Update --- .../plugins/tinymce3/GoogleSpellChecker.cs | 20 +++++++++---------- .../plugins/tinymce3/SpellCheckerResult.cs | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/plugins/tinymce3/GoogleSpellChecker.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/plugins/tinymce3/GoogleSpellChecker.cs index edaa5950c4..ef3a6376b0 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/plugins/tinymce3/GoogleSpellChecker.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/plugins/tinymce3/GoogleSpellChecker.cs @@ -12,7 +12,7 @@ using System.Collections.Generic; namespace umbraco.presentation.umbraco_client.tinymce3.plugins.spellchecker { public class GoogleSpellChecker : SpellChecker, IHttpHandler - { + { private static string SendRequest(string lang, string data) { string googleResponse; @@ -25,7 +25,7 @@ namespace umbraco.presentation.umbraco_client.tinymce3.plugins.spellchecker requestData.Add("method", "spelling.check"); requestData.Add("apiVersion", "v2"); requestData.Add("params", requestParams); - string jsonString = new JavaScriptSerializer().Serialize(requestData); + string jsonString = new JavaScriptSerializer().Serialize(requestData); StreamReader reader = null; HttpWebResponse response = null; Stream requestStream = null; @@ -35,7 +35,7 @@ namespace umbraco.presentation.umbraco_client.tinymce3.plugins.spellchecker request.KeepAlive = false; request.Method = "POST"; request.ContentType = "application/json"; - request.ContentLength = jsonString.Length; + request.ContentLength = jsonString.Length; requestStream = request.GetRequestStream(); byte[] bytes = new ASCIIEncoding().GetBytes(jsonString); requestStream.Write(bytes, 0, bytes.Length); @@ -68,20 +68,20 @@ namespace umbraco.presentation.umbraco_client.tinymce3.plugins.spellchecker /// The words to be checked. /// public override SpellCheckerResult CheckWords(string language, string[] words) - { + { string data = string.Join(" ", words); //turn them into a space-separated string as that's what google takes string json = SendRequest(language, data); var jsonRes = new JavaScriptSerializer().Deserialize(json); - var res = new SpellCheckerResult(); + var res = new SpellCheckerResult(); // Get list of misspelled words if (jsonRes.result != null && jsonRes.result.spellingCheckResponse != null) { foreach (var misspelling in jsonRes.result.spellingCheckResponse.misspellings) - { + { res.result.Add(data.Substring(misspelling.charStart, misspelling.charLength)); } - } + } return res; } @@ -93,12 +93,12 @@ namespace umbraco.presentation.umbraco_client.tinymce3.plugins.spellchecker /// The word that is misspelt. /// public override SpellCheckerResult GetSuggestions(string language, string word) - { + { string json = SendRequest(language, word); var jsonRes = new JavaScriptSerializer().Deserialize(json); var res = new SpellCheckerResult(); - // Get list of suggestions + // Get list of suggestions if (jsonRes.result != null && jsonRes.result.spellingCheckResponse != null) { foreach (var misspelling in jsonRes.result.spellingCheckResponse.misspellings) @@ -138,7 +138,7 @@ namespace umbraco.presentation.umbraco_client.tinymce3.plugins.spellchecker suggestions = new SpellCheckerResult(); break; } - + suggestions.id = input.Id; JavaScriptSerializer ser = new JavaScriptSerializer(); var res = ser.Serialize(suggestions); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/plugins/tinymce3/SpellCheckerResult.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/plugins/tinymce3/SpellCheckerResult.cs index c6d16e903c..fc5f7a0bfd 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/plugins/tinymce3/SpellCheckerResult.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/plugins/tinymce3/SpellCheckerResult.cs @@ -60,6 +60,6 @@ namespace umbraco.presentation.umbraco_client.tinymce3.plugins.spellchecker public class GoogleResponseSuggestion { public string suggestion { get; set; } - } + } } } From 7a2a4445793d680222c72cec196f31dd02e3f28e Mon Sep 17 00:00:00 2001 From: Jeremy Pyne Date: Wed, 7 Aug 2013 15:28:41 -0400 Subject: [PATCH 0005/1046] Fix for select parent o move or copy in 6.1.3. --- src/Umbraco.Web/Umbraco.Web.csproj | 11 +++++++---- .../umbraco/dialogs/moveOrCopy.aspx.cs | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 67d9b6335d..2986b64059 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -291,7 +291,7 @@ - + @@ -1871,7 +1871,8 @@ - + + @@ -1935,7 +1936,9 @@ - + + ASPXCodeBehind + @@ -2058,4 +2061,4 @@ - \ No newline at end of file + diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs index 3069d9485d..a66dd8634c 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs @@ -90,8 +90,8 @@ namespace umbraco.dialogs } // Preselect the parent of the seslected item. - if(cmsNode.ParentId > 0) - JTree.SelectedNodePath = cmsNode.Parent.Path; + if (currContent.ParentId > 0) + JTree.SelectedNodePath = currContent.Path.Substring(0, currContent.Path.LastIndexOf(',')); var validAction = true; if (CurrentApp == Constants.Applications.Content && Umbraco.Core.Models.ContentExtensions.HasChildren(currContent, Services)) From e65a71a05a265bda0cccfda129377cbe73602299 Mon Sep 17 00:00:00 2001 From: Jeremy Pyne Date: Thu, 29 Aug 2013 11:00:15 -0400 Subject: [PATCH 0006/1046] Added code to pre-select the current node in content tree pickers based on the location of the document in the tree. This is a HUGE time saver with an even moderately complex site hierarchy. --- src/Umbraco.Web.UI/umbraco/controls/Tree/TreeControl.ascx | 8 -------- .../umbraco/controls/Tree/TreeControl.ascx | 8 -------- .../umbraco/dialogs/treePicker.aspx.cs | 6 ++++++ src/umbraco.controls/TreePicker/SimpleContentPicker.cs | 4 ++++ src/umbraco.editorControls/pagepicker/pagePicker.cs | 4 ++++ 5 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/controls/Tree/TreeControl.ascx b/src/Umbraco.Web.UI/umbraco/controls/Tree/TreeControl.ascx index fb4fac1e2d..2d143e6153 100644 --- a/src/Umbraco.Web.UI/umbraco/controls/Tree/TreeControl.ascx +++ b/src/Umbraco.Web.UI/umbraco/controls/Tree/TreeControl.ascx @@ -51,14 +51,6 @@ jQuery(document).ready(function() { setTimeout(function() { treeApi = jQuery("#<%=ClientID%>").UmbracoTreeAPI(); treeApi.syncTree('<%=this.SelectedNodePath%>', true, true); - - // select the parent node once found. - var iv = setInterval(function() { - var node = treeApi.findNode('<%=this.SelectedNodePath%>'.split(',').pop(), true); - if(node != false) { - treeApi.selectNode(node, false, true); - clearInterval(iv); - } }, 500); <% } %> diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/TreeControl.ascx b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/TreeControl.ascx index fb4fac1e2d..2d143e6153 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/TreeControl.ascx +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/TreeControl.ascx @@ -51,14 +51,6 @@ jQuery(document).ready(function() { setTimeout(function() { treeApi = jQuery("#<%=ClientID%>").UmbracoTreeAPI(); treeApi.syncTree('<%=this.SelectedNodePath%>', true, true); - - // select the parent node once found. - var iv = setInterval(function() { - var node = treeApi.findNode('<%=this.SelectedNodePath%>'.split(',').pop(), true); - if(node != false) { - treeApi.selectNode(node, false, true); - clearInterval(iv); - } }, 500); <% } %> diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/treePicker.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/treePicker.aspx.cs index 4a60b0605b..38518804b4 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/treePicker.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/treePicker.aspx.cs @@ -20,6 +20,12 @@ namespace umbraco.dialogs base.OnLoad(e); TreeParams = TreeRequestParams.FromQueryStrings().CreateTreeService(); DataBind(); + + if(Request.QueryString["selected"] != null && TreeParams.TreeType == "content") { + var currContent = Services.ContentService.GetById(int.Parse(Request.QueryString["selected"])); + if (currContent.ParentId > 0) + DialogTree.SelectedNodePath = currContent.Path; + } } protected TreeService TreeParams { get; private set; } diff --git a/src/umbraco.controls/TreePicker/SimpleContentPicker.cs b/src/umbraco.controls/TreePicker/SimpleContentPicker.cs index de93637134..3d70cbbef4 100644 --- a/src/umbraco.controls/TreePicker/SimpleContentPicker.cs +++ b/src/umbraco.controls/TreePicker/SimpleContentPicker.cs @@ -1,4 +1,5 @@ using System; +using System.Web; using System.Collections.Generic; using System.Linq; using System.Text; @@ -12,6 +13,9 @@ namespace umbraco.uicontrols.TreePicker { get { + if (HttpContext.Current.Request.QueryString["id"] != null) + return TreeUrlGenerator.GetPickerUrl(Constants.Applications.Content, "content") + "&selected=" + HttpContext.Current.Request.QueryString["id"]; + return TreeUrlGenerator.GetPickerUrl(Constants.Applications.Content, "content"); } } diff --git a/src/umbraco.editorControls/pagepicker/pagePicker.cs b/src/umbraco.editorControls/pagepicker/pagePicker.cs index 16693194dd..791bb20352 100644 --- a/src/umbraco.editorControls/pagepicker/pagePicker.cs +++ b/src/umbraco.editorControls/pagepicker/pagePicker.cs @@ -1,4 +1,5 @@ using System; +using System.Web; using System.Web.UI; using umbraco.cms.presentation.Trees; @@ -26,6 +27,9 @@ namespace umbraco.editorControls { get { + if(HttpContext.Current.Request.QueryString["id"] != null) + return TreeService.GetPickerUrl(Umbraco.Core.Constants.Applications.Content, "content") + "&selected=" + HttpContext.Current.Request.QueryString["id"]; + return TreeService.GetPickerUrl(Umbraco.Core.Constants.Applications.Content, "content"); } } From 69830f28c2324d2c6b5b561b645c87392cc2fc8c Mon Sep 17 00:00:00 2001 From: enkelmedia Date: Sun, 1 Sep 2013 14:05:54 +0200 Subject: [PATCH 0007/1046] Added SVG as static content so SVG-icons in the backoffice will be served by IIS --- src/Umbraco.Web.UI/web.Template.config | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index bc0fd45487..b1515c74db 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -211,6 +211,7 @@ + From fdfb6187e9f89387587c4c9a606364c4918aaf32 Mon Sep 17 00:00:00 2001 From: enkelmedia Date: Sun, 1 Sep 2013 16:51:00 +0200 Subject: [PATCH 0008/1046] Removing SVG from staticContent before addings it. --- src/Umbraco.Web.UI/web.Template.config | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index b1515c74db..a6a8826b44 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -211,6 +211,7 @@ + From baa22301e9a95d213e6aafb41a5abe6985d23d4c Mon Sep 17 00:00:00 2001 From: Stefan Kip Date: Thu, 5 Sep 2013 11:31:06 +0200 Subject: [PATCH 0009/1046] Added Exception which tells you which datatype is unable to load --- src/umbraco.cms/businesslogic/Property/Property.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/umbraco.cms/businesslogic/Property/Property.cs b/src/umbraco.cms/businesslogic/Property/Property.cs index 8404081677..faf263607e 100644 --- a/src/umbraco.cms/businesslogic/Property/Property.cs +++ b/src/umbraco.cms/businesslogic/Property/Property.cs @@ -37,6 +37,8 @@ namespace umbraco.cms.businesslogic.property _pt = pt; _id = Id; + if (_pt.DataTypeDefinition.DataType == null) + throw new Exception(string.Format("Could not load datatype '{0}'", _pt.DataTypeDefinition.Text)); _data = _pt.DataTypeDefinition.DataType.Data; _data.PropertyId = Id; } @@ -47,6 +49,8 @@ namespace umbraco.cms.businesslogic.property _pt = PropertyType.GetPropertyType( SqlHelper.ExecuteScalar("select propertytypeid from cmsPropertyData where id = @id", SqlHelper.CreateParameter("@id", Id))); + if (_pt.DataTypeDefinition.DataType == null) + throw new Exception(string.Format("Could not load datatype '{0}'", _pt.DataTypeDefinition.Text)); _data = _pt.DataTypeDefinition.DataType.Data; _data.PropertyId = Id; } @@ -59,6 +63,8 @@ namespace umbraco.cms.businesslogic.property //Just to ensure that there is a PropertyType available _pt = PropertyType.GetPropertyType(property.PropertyTypeId); + if (_pt.DataTypeDefinition.DataType == null) + throw new Exception(string.Format("Could not load datatype '{0}'", _pt.DataTypeDefinition.Text)); _data = _pt.DataTypeDefinition.DataType.Data; _data.PropertyId = Id; } @@ -142,4 +148,4 @@ namespace umbraco.cms.businesslogic.property } } -} \ No newline at end of file +} From 0d49a59cfe9079e13002d7a15f9cb2e97866a94e Mon Sep 17 00:00:00 2001 From: dprothero Date: Thu, 5 Sep 2013 15:15:40 -0700 Subject: [PATCH 0010/1046] Fix #U4-2796 Possible to have duplicate Login Name (Member.LoginName) for Umbraco Members --- .../umbraco/members/EditMember.aspx.cs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/members/EditMember.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/members/EditMember.aspx.cs index cf54379d96..83f0577120 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/members/EditMember.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/members/EditMember.aspx.cs @@ -26,6 +26,7 @@ namespace umbraco.cms.presentation.members protected TextBox MemberLoginNameTxt = new TextBox(); protected RequiredFieldValidator MemberLoginNameVal = new RequiredFieldValidator(); + protected CustomValidator MemberLoginNameExistCheck = new CustomValidator(); protected PlaceHolder MemberPasswordTxt = new PlaceHolder(); protected TextBox MemberEmail = new TextBox(); @@ -67,12 +68,19 @@ namespace umbraco.cms.presentation.members MemberLoginNameVal.EnableClientScript = false; MemberLoginNameVal.Display = ValidatorDisplay.Dynamic; + MemberLoginNameExistCheck.ErrorMessage = ui.Text("errorHandling", "errorExistsWithoutTab", "Login Name", BasePages.UmbracoEnsuredPage.CurrentUser); + MemberLoginNameExistCheck.EnableClientScript = false; + MemberLoginNameExistCheck.ValidateEmptyText = false; + MemberLoginNameExistCheck.ControlToValidate = MemberEmail.ID; + MemberLoginNameExistCheck.ServerValidate += MemberLoginNameExistCheck_ServerValidate; + MemberEmailExistCheck.ErrorMessage = ui.Text("errorHandling", "errorExistsWithoutTab", "E-mail", BasePages.UmbracoEnsuredPage.CurrentUser); MemberEmailExistCheck.EnableClientScript = false; MemberEmailExistCheck.ValidateEmptyText = false; MemberEmailExistCheck.ControlToValidate = MemberEmail.ID; MemberEmailExistCheck.ServerValidate += MemberEmailExistCheck_ServerValidate; + _contentControl.PropertiesPane.addProperty("", MemberLoginNameExistCheck); _contentControl.PropertiesPane.addProperty(ui.Text("login"), ph); _contentControl.PropertiesPane.addProperty(ui.Text("password"), MemberPasswordTxt); _contentControl.PropertiesPane.addProperty("", MemberEmailExistCheck); @@ -141,7 +149,18 @@ namespace umbraco.cms.presentation.members } - void MemberEmailExistCheck_ServerValidate(object source, ServerValidateEventArgs args) + void MemberLoginNameExistCheck_ServerValidate(object source, ServerValidateEventArgs args) + { + var oldLoginName = _document.LoginName.Replace(" ", "").ToLower(); + var newLoginName = MemberLoginNameTxt.Text.Replace(" ", "").ToLower(); + + if (oldLoginName != newLoginName && newLoginName != "" && Member.GetMemberFromLoginName(newLoginName) != null) + args.IsValid = false; + else + args.IsValid = true; + } + + void MemberEmailExistCheck_ServerValidate(object source, ServerValidateEventArgs args) { var oldEmail = _document.Email.ToLower(); var newEmail = MemberEmail.Text.ToLower(); From 6675bac1dc09a3f03e22181624d4ede00d87c722 Mon Sep 17 00:00:00 2001 From: dprothero Date: Thu, 5 Sep 2013 15:23:25 -0700 Subject: [PATCH 0011/1046] Fix #U4-2796 - fixed copy/paste error (does not affect functionality) --- .../umbraco.presentation/umbraco/members/EditMember.aspx.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/members/EditMember.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/members/EditMember.aspx.cs index 83f0577120..d83d52f8e3 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/members/EditMember.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/members/EditMember.aspx.cs @@ -71,7 +71,7 @@ namespace umbraco.cms.presentation.members MemberLoginNameExistCheck.ErrorMessage = ui.Text("errorHandling", "errorExistsWithoutTab", "Login Name", BasePages.UmbracoEnsuredPage.CurrentUser); MemberLoginNameExistCheck.EnableClientScript = false; MemberLoginNameExistCheck.ValidateEmptyText = false; - MemberLoginNameExistCheck.ControlToValidate = MemberEmail.ID; + MemberLoginNameExistCheck.ControlToValidate = MemberLoginNameTxt.ID; MemberLoginNameExistCheck.ServerValidate += MemberLoginNameExistCheck_ServerValidate; MemberEmailExistCheck.ErrorMessage = ui.Text("errorHandling", "errorExistsWithoutTab", "E-mail", BasePages.UmbracoEnsuredPage.CurrentUser); From 0af73b0290432e63a461bf8555dc1c205cc37252 Mon Sep 17 00:00:00 2001 From: Mads Krohn Date: Thu, 12 Sep 2013 00:14:44 +0200 Subject: [PATCH 0012/1046] Fixed U4-2842 Umb 6.1.5 - moving a media item from recycle bin / trash --- src/Umbraco.Core/Services/MediaService.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 68b4253f77..fbf6d0f67b 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -479,16 +479,22 @@ namespace Umbraco.Core.Services if (Moving.IsRaisedEventCancelled(new MoveEventArgs(media, parentId), this)) return; - media.ParentId = parentId; + //If we aren't moving to recycle bin, we must be moving from recycle bin -> #U4-2842 + media.ChangeTrashedState(false, parentId); Save(media, userId); //Ensure that Path and Level is updated on children - var children = GetChildren(media.Id); + var children = GetChildren(media.Id).ToList(); //No need to enumerate twice? if (children.Any()) { var parentPath = media.Path; var parentLevel = media.Level; - var updatedDescendents = UpdatePathAndLevelOnChildren(children, parentPath, parentLevel); + var updatedDescendents = UpdatePathAndLevelOnChildren(children, parentPath, parentLevel).ToList(); + //Update trashed state for descendants -> #U4-2842 + foreach (var descendant in updatedDescendents) + { + descendant.ChangeTrashedState(false, descendant.ParentId); + } Save(updatedDescendents, userId); } @@ -910,7 +916,7 @@ namespace Umbraco.Core.Services child.Level = parentLevel + 1; list.Add(child); - var grandkids = GetChildren(child.Id); + var grandkids = GetChildren(child.Id).ToList(); //No need to enumerate twice? if (grandkids.Any()) { list.AddRange(UpdatePathAndLevelOnChildren(grandkids, child.Path, child.Level)); From 31f146fc6f1f62916041f2a58ff6826280069182 Mon Sep 17 00:00:00 2001 From: Mads Krohn Date: Fri, 13 Sep 2013 10:29:03 +0200 Subject: [PATCH 0013/1046] Updates to the media trashed state handling Tweaked the logic for determining when a media item is trashed or not. Added unit tests. Removed unnecessary comments. --- src/Umbraco.Core/Services/MediaService.cs | 33 +++++---- src/Umbraco.Tests/Services/BaseServiceTest.cs | 24 +++++++ .../Services/MediaServiceTests.cs | 72 +++++++++++++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + 4 files changed, 116 insertions(+), 14 deletions(-) create mode 100644 src/Umbraco.Tests/Services/MediaServiceTests.cs diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index fbf6d0f67b..c45bbb1353 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -479,23 +479,22 @@ namespace Umbraco.Core.Services if (Moving.IsRaisedEventCancelled(new MoveEventArgs(media, parentId), this)) return; - //If we aren't moving to recycle bin, we must be moving from recycle bin -> #U4-2842 - media.ChangeTrashedState(false, parentId); + media.ParentId = parentId; + if (media.Trashed) + { + media.ChangeTrashedState(false, parentId); + } Save(media, userId); - //Ensure that Path and Level is updated on children - var children = GetChildren(media.Id).ToList(); //No need to enumerate twice? + //Ensure that relevant properties are updated on children + var children = GetChildren(media.Id); if (children.Any()) { var parentPath = media.Path; var parentLevel = media.Level; - var updatedDescendents = UpdatePathAndLevelOnChildren(children, parentPath, parentLevel).ToList(); - //Update trashed state for descendants -> #U4-2842 - foreach (var descendant in updatedDescendents) - { - descendant.ChangeTrashedState(false, descendant.ParentId); - } - Save(updatedDescendents, userId); + var parentTrashed = media.Trashed; + var updatedDescendants = UpdatePropertiesOnChildren(children, parentPath, parentLevel, parentTrashed); + Save(updatedDescendants, userId); } Moved.RaiseEvent(new MoveEventArgs(media, false, parentId), this); @@ -901,25 +900,31 @@ namespace Umbraco.Core.Services /// /// Updates the Path and Level on a collection of objects - /// based on the Parent's Path and Level. + /// based on the Parent's Path and Level. Also change the trashed state if relevant. /// /// Collection of objects to update /// Path of the Parent media /// Level of the Parent media + /// Indicates whether the Parent is trashed or not /// Collection of updated objects - private IEnumerable UpdatePathAndLevelOnChildren(IEnumerable children, string parentPath, int parentLevel) + private IEnumerable UpdatePropertiesOnChildren(IEnumerable children, string parentPath, int parentLevel, bool parentTrashed) { var list = new List(); foreach (var child in children) { child.Path = string.Concat(parentPath, ",", child.Id); child.Level = parentLevel + 1; + if (parentTrashed != child.Trashed) + { + child.ChangeTrashedState(parentTrashed, child.ParentId); + } + list.Add(child); var grandkids = GetChildren(child.Id).ToList(); //No need to enumerate twice? if (grandkids.Any()) { - list.AddRange(UpdatePathAndLevelOnChildren(grandkids, child.Path, child.Level)); + list.AddRange(UpdatePropertiesOnChildren(grandkids, child.Path, child.Level, child.Trashed)); } } return list; diff --git a/src/Umbraco.Tests/Services/BaseServiceTest.cs b/src/Umbraco.Tests/Services/BaseServiceTest.cs index cd4318b5a6..23487eb582 100644 --- a/src/Umbraco.Tests/Services/BaseServiceTest.cs +++ b/src/Umbraco.Tests/Services/BaseServiceTest.cs @@ -51,6 +51,30 @@ namespace Umbraco.Tests.Services Content trashed = MockedContent.CreateSimpleContent(contentType, "Text Page Deleted", -20); trashed.Trashed = true; ServiceContext.ContentService.Save(trashed, 0); + + //Create and Save folder-Media -> 1050 + var folderMediaType = ServiceContext.ContentTypeService.GetMediaType(1031); + var folder = MockedMedia.CreateMediaFolder(folderMediaType, -1); + ServiceContext.MediaService.Save(folder); + + //Create and Save folder-Media -> 1051 + var folder2 = MockedMedia.CreateMediaFolder(folderMediaType, -1); + ServiceContext.MediaService.Save(folder2); + + //Create and Save image-Media -> 1052 + var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); + var image = (Media)MockedMedia.CreateMediaImage(imageMediaType, 1050); + ServiceContext.MediaService.Save(image); + + //Create and Save folder-Media that is trashed -> 1053 + var folderTrashed = (Media)MockedMedia.CreateMediaFolder(folderMediaType, -21); + folderTrashed.Trashed = true; + ServiceContext.MediaService.Save(folderTrashed); + + //Create and Save image-Media child of folderTrashed -> 1054 + var imageTrashed = (Media)MockedMedia.CreateMediaImage(imageMediaType, folderTrashed.Id); + imageTrashed.Trashed = true; + ServiceContext.MediaService.Save(imageTrashed); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Services/MediaServiceTests.cs b/src/Umbraco.Tests/Services/MediaServiceTests.cs new file mode 100644 index 0000000000..658bff9de9 --- /dev/null +++ b/src/Umbraco.Tests/Services/MediaServiceTests.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; + +namespace Umbraco.Tests.Services +{ + [TestFixture, RequiresSTA] + public class MediaServiceTests : BaseServiceTest + { + [SetUp] + public override void Initialize() + { + base.Initialize(); + } + + [TearDown] + public override void TearDown() + { + base.TearDown(); + } + + [Test] + public void Can_Move_Media() + { + // Arrange + var mediaService = ServiceContext.MediaService; + var media = mediaService.GetById(1052); + + // Act + mediaService.Move(media, 1051); + + // Assert + Assert.That(media.ParentId, Is.EqualTo(1051)); + Assert.That(media.Trashed, Is.False); + } + + [Test] + public void Can_Move_Media_To_RecycleBin() + { + // Arrange + var mediaService = ServiceContext.MediaService; + var media = mediaService.GetById(1050); + + // Act + mediaService.MoveToRecycleBin(media); + + // Assert + Assert.That(media.ParentId, Is.EqualTo(-21)); + Assert.That(media.Trashed, Is.True); + } + + [Test] + public void Can_Move_Media_From_RecycleBin() + { + // Arrange + var mediaService = ServiceContext.MediaService; + var media = mediaService.GetById(1053); + + // Act - moving out of recycle bin + mediaService.Move(media, 1050); + var mediaChild = mediaService.GetById(1054); + + // Assert + Assert.That(media.ParentId, Is.EqualTo(1050)); + Assert.That(media.Trashed, Is.False); + Assert.That(mediaChild.ParentId, Is.EqualTo(1053)); + Assert.That(mediaChild.Trashed, Is.False); + } + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 032fcb8bfb..186b0736f5 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -200,6 +200,7 @@ + From 0fe33c120b73b13e47a9e6d9b4b24b66dda9529f Mon Sep 17 00:00:00 2001 From: Mads Krohn Date: Fri, 13 Sep 2013 10:42:08 +0200 Subject: [PATCH 0014/1046] Removing unwanted ToList() --- src/Umbraco.Core/Services/MediaService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index c45bbb1353..09df9f6297 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -921,7 +921,7 @@ namespace Umbraco.Core.Services list.Add(child); - var grandkids = GetChildren(child.Id).ToList(); //No need to enumerate twice? + var grandkids = GetChildren(child.Id); if (grandkids.Any()) { list.AddRange(UpdatePropertiesOnChildren(grandkids, child.Path, child.Level, child.Trashed)); From cdf5ddf539bd20633e30987da9b59917d45d28d1 Mon Sep 17 00:00:00 2001 From: Marc Brooks Date: Mon, 30 Sep 2013 18:23:56 -0500 Subject: [PATCH 0015/1046] Added cacheKeyBuilder optional argument to allow the key used for caching to be contextual. when building cached partials that work with many different instances of the same partial view (e.g. using the same partial more than once on a page with different ViewDataDictionary or different model), it is necessary to either name each instance differently (ugh), or not cache. By adding an optional argument to the CachedPartial Html helper method, we can vary the cache-key depending on the context of insertion. For maximum flexibility, pass the model and the viewData to the function so it can use that data to format a cacheKey suffix. A typical use would be something like: @Html.CachedPartial( "EventListings" , FeedRepository.getFeed(new CalendarEventsModel { StartingDate = DateTime.UtcNow.Date , Count = 10 }) , 0 , contextualKeyBuilder: (model, viewData) => { return (model as CalendarEventsModel).StartingDate.ToString(); } ) --- src/Umbraco.Web/HtmlHelperRenderExtensions.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs index 2a009dfaa2..d8af4eecf0 100644 --- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs @@ -82,7 +82,8 @@ namespace Umbraco.Web int cachedSeconds, bool cacheByPage = false, bool cacheByMember = false, - ViewDataDictionary viewData = null) + ViewDataDictionary viewData = null, + Func contextualKeyBuilder = null) { var cacheKey = new StringBuilder(partialViewName); if (cacheByPage) @@ -97,7 +98,12 @@ namespace Umbraco.Web { var currentMember = Member.GetCurrentMember(); cacheKey.AppendFormat("m{0}-", currentMember == null ? 0 : currentMember.Id); - } + } + if (contextualKeyBuilder != null) + { + var contextualKey = contextualKeyBuilder(model, viewData); + cacheKey.Append("c{0}-", contextualKey); + } return ApplicationContext.Current.ApplicationCache.CachedPartialView(htmlHelper, partialViewName, model, cachedSeconds, cacheKey.ToString(), viewData); } @@ -813,4 +819,4 @@ namespace Umbraco.Web #endregion } -} \ No newline at end of file +} From b200cb463e6be0b63c3fa637abcd3c75dabbc805 Mon Sep 17 00:00:00 2001 From: Marc Brooks Date: Mon, 30 Sep 2013 18:28:45 -0500 Subject: [PATCH 0016/1046] Make CacheHelperExtensions public so we can call CachedPartialView and ClearPartialViewCache In order to allow extensions to the logic and usage of the CachedPartial HtmlHelper, expose CachedPartialView and ClearPartialViewCache by making the wrapping static class public. --- src/Umbraco.Web/CacheHelperExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/CacheHelperExtensions.cs b/src/Umbraco.Web/CacheHelperExtensions.cs index 780f841190..ab96a0ee6a 100644 --- a/src/Umbraco.Web/CacheHelperExtensions.cs +++ b/src/Umbraco.Web/CacheHelperExtensions.cs @@ -16,7 +16,7 @@ namespace Umbraco.Web /// /// Extension methods for the cache helper /// - internal static class CacheHelperExtensions + public static class CacheHelperExtensions { /// /// Application event handler to bind to events to clear the cache for the cache helper extensions @@ -84,4 +84,4 @@ namespace Umbraco.Web cacheHelper.ClearCacheByKeySearch(PartialViewCacheKey); } } -} \ No newline at end of file +} From c74cdb28d6309d2d610e2500ff4e0f0c6b18fd27 Mon Sep 17 00:00:00 2001 From: mortenbock Date: Thu, 3 Oct 2013 11:01:37 +0200 Subject: [PATCH 0017/1046] Testing the wrapping of view engines --- .../Profiling/ProfilingViewEngine.cs | 10 +-- .../BootManagers/WebBootManagerTester.cs | 78 +++++++++++++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 5 ++ src/Umbraco.Web/WebBootManager.cs | 23 ++++-- 4 files changed, 103 insertions(+), 13 deletions(-) create mode 100644 src/Umbraco.Tests/BootManagers/WebBootManagerTester.cs diff --git a/src/Umbraco.Core/Profiling/ProfilingViewEngine.cs b/src/Umbraco.Core/Profiling/ProfilingViewEngine.cs index 5df2b6f811..5daceb34e1 100644 --- a/src/Umbraco.Core/Profiling/ProfilingViewEngine.cs +++ b/src/Umbraco.Core/Profiling/ProfilingViewEngine.cs @@ -4,12 +4,12 @@ namespace Umbraco.Core.Profiling { public class ProfilingViewEngine: IViewEngine { - private readonly IViewEngine _inner; + internal readonly IViewEngine Inner; private readonly string _name; public ProfilingViewEngine(IViewEngine inner) { - _inner = inner; + Inner = inner; _name = inner.GetType().Name; } @@ -17,7 +17,7 @@ namespace Umbraco.Core.Profiling { using (ProfilerResolver.Current.Profiler.Step(string.Format("{0}.FindPartialView, {1}, {2}", _name, partialViewName, useCache))) { - return WrapResult(_inner.FindPartialView(controllerContext, partialViewName, useCache)); + return WrapResult(Inner.FindPartialView(controllerContext, partialViewName, useCache)); } } @@ -25,7 +25,7 @@ namespace Umbraco.Core.Profiling { using (ProfilerResolver.Current.Profiler.Step(string.Format("{0}.FindView, {1}, {2}, {3}", _name, viewName, masterName, useCache))) { - return WrapResult(_inner.FindView(controllerContext, viewName, masterName, useCache)); + return WrapResult(Inner.FindView(controllerContext, viewName, masterName, useCache)); } } @@ -41,7 +41,7 @@ namespace Umbraco.Core.Profiling { using (ProfilerResolver.Current.Profiler.Step(string.Format("{0}.ReleaseView, {1}", _name, view.GetType().Name))) { - _inner.ReleaseView(controllerContext, view); + Inner.ReleaseView(controllerContext, view); } } } diff --git a/src/Umbraco.Tests/BootManagers/WebBootManagerTester.cs b/src/Umbraco.Tests/BootManagers/WebBootManagerTester.cs new file mode 100644 index 0000000000..b3c02a058b --- /dev/null +++ b/src/Umbraco.Tests/BootManagers/WebBootManagerTester.cs @@ -0,0 +1,78 @@ +using System.Collections.Generic; +using System.Web.Mvc; +using Microsoft.Web.Mvc; +using NUnit.Framework; +using Umbraco.Core.Profiling; +using Umbraco.Web; +using Umbraco.Web.Mvc; + +namespace Umbraco.Tests.BootManagers +{ + [TestFixture] + public class WebBootManagerTester + { + [Test] + public void WrapViewEngines_HasEngines_WrapsAll() + { + IList engines = new List + { + new FixedWebFormViewEngine(), + new FixedRazorViewEngine(), + new RenderViewEngine(), + new PluginViewEngine() + }; + + WebBootManager.WrapViewEngines(engines); + + Assert.That(engines.Count, Is.EqualTo(4)); + Assert.That(engines[0], Is.InstanceOf()); + Assert.That(engines[1], Is.InstanceOf()); + Assert.That(engines[2], Is.InstanceOf()); + Assert.That(engines[3], Is.InstanceOf()); + } + + [Test] + public void WrapViewEngines_HasEngines_KeepsSortOrder() + { + IList engines = new List + { + new FixedWebFormViewEngine(), + new FixedRazorViewEngine(), + new RenderViewEngine(), + new PluginViewEngine() + }; + + WebBootManager.WrapViewEngines(engines); + + Assert.That(engines.Count, Is.EqualTo(4)); + Assert.That(((ProfilingViewEngine)engines[0]).Inner, Is.InstanceOf()); + Assert.That(((ProfilingViewEngine)engines[1]).Inner, Is.InstanceOf()); + Assert.That(((ProfilingViewEngine)engines[2]).Inner, Is.InstanceOf()); + Assert.That(((ProfilingViewEngine)engines[3]).Inner, Is.InstanceOf()); + } + + + [Test] + public void WrapViewEngines_HasProfiledEngine_AddsSameInstance() + { + var profiledEngine = new ProfilingViewEngine(new FixedRazorViewEngine()); + IList engines = new List + { + profiledEngine + }; + + WebBootManager.WrapViewEngines(engines); + + Assert.That(engines[0], Is.SameAs(profiledEngine)); + } + + [Test] + public void WrapViewEngines_CollectionIsNull_DoesNotThrow() + { + IList engines = null; + Assert.DoesNotThrow(() => WebBootManager.WrapViewEngines(engines)); + Assert.That(engines, Is.Null); + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 469caa926c..0354a7a322 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -68,6 +68,10 @@ True ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + + False + ..\Umbraco.Web.UI\bin\Microsoft.Web.Mvc.FixedDisplayModes.dll + ..\packages\Moq.4.1.1309.1617\lib\net40\Moq.dll @@ -146,6 +150,7 @@ + diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index 4c9757fe95..e908d5618c 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -128,13 +128,7 @@ namespace Umbraco.Web public override IBootManager Complete(Action afterComplete) { //Wrap viewengines in the profiling engine - IViewEngine[] engines = ViewEngines.Engines.Select(e => e).ToArray(); - ViewEngines.Engines.Clear(); - foreach (var engine in engines) - { - var wrappedEngine = engine is ProfilingViewEngine ? engine : new ProfilingViewEngine(engine); - ViewEngines.Engines.Add(wrappedEngine); - } + WrapViewEngines(ViewEngines.Engines); //set routes CreateRoutes(); @@ -147,7 +141,20 @@ namespace Umbraco.Web return this; } - /// + internal static void WrapViewEngines(IList viewEngines) + { + if (viewEngines == null || viewEngines.Count == 0) return; + + var origninaleEngines = viewEngines.Select(e => e).ToArray(); + viewEngines.Clear(); + foreach (var engine in origninaleEngines) + { + var wrappedEngine = engine is ProfilingViewEngine ? engine : new ProfilingViewEngine(engine); + viewEngines.Add(wrappedEngine); + } + } + + /// /// Creates the routes /// protected internal void CreateRoutes() From b6cf18234178b9e96d2c203301a4abe648e3c080 Mon Sep 17 00:00:00 2001 From: wtct Date: Wed, 9 Oct 2013 17:52:05 +0200 Subject: [PATCH 0018/1046] Affect the order of domains --- src/umbraco.cms/businesslogic/web/Domain.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/umbraco.cms/businesslogic/web/Domain.cs b/src/umbraco.cms/businesslogic/web/Domain.cs index f5bcb46090..57156f0858 100644 --- a/src/umbraco.cms/businesslogic/web/Domain.cs +++ b/src/umbraco.cms/businesslogic/web/Domain.cs @@ -151,8 +151,7 @@ namespace umbraco.cms.businesslogic.web () => { var result = new List(); - using (var dr = SqlHelper.ExecuteReader( - "select id, domainName from umbracoDomains")) + using (var dr = SqlHelper.ExecuteReader("SELECT Id, DomainName FFROM umbracoDomains ORDER BY Id")) { while (dr.Read()) { From e1caf3a470726cc922dafca9fe0b79818dfb34d6 Mon Sep 17 00:00:00 2001 From: wtct Date: Thu, 10 Oct 2013 14:45:18 +0200 Subject: [PATCH 0019/1046] U4-3091 --- src/umbraco.cms/businesslogic/Content.cs | 2 +- src/umbraco.cms/businesslogic/web/Document.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/umbraco.cms/businesslogic/Content.cs b/src/umbraco.cms/businesslogic/Content.cs index 786a7b2f6b..df2c8291b9 100644 --- a/src/umbraco.cms/businesslogic/Content.cs +++ b/src/umbraco.cms/businesslogic/Content.cs @@ -397,7 +397,7 @@ namespace umbraco.cms.businesslogic { var props = this.GenericProperties; foreach (property.Property p in props) - if (p != null) + if (p != null && !string.IsNullOrEmpty(p.Value.ToString())) x.AppendChild(p.ToXml(xd)); // attributes diff --git a/src/umbraco.cms/businesslogic/web/Document.cs b/src/umbraco.cms/businesslogic/web/Document.cs index 66e6db5080..b65f0f902c 100644 --- a/src/umbraco.cms/businesslogic/web/Document.cs +++ b/src/umbraco.cms/businesslogic/web/Document.cs @@ -1293,7 +1293,7 @@ namespace umbraco.cms.businesslogic.web public override void XmlPopulate(XmlDocument xd, ref XmlNode x, bool Deep) { string urlName = this.Content.GetUrlSegment().ToLower(); - foreach (Property p in GenericProperties.Where(p => p != null)) + foreach (Property p in GenericProperties.Where(p => p != null && !string.IsNullOrEmpty(p.Value.ToString()))) x.AppendChild(p.ToXml(xd)); // attributes From b5c3fb1728fde4862fff8c6c089296a4207da26a Mon Sep 17 00:00:00 2001 From: wtct Date: Fri, 11 Oct 2013 09:16:18 +0200 Subject: [PATCH 0020/1046] FFROM to FROM --- src/umbraco.cms/businesslogic/web/Domain.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/umbraco.cms/businesslogic/web/Domain.cs b/src/umbraco.cms/businesslogic/web/Domain.cs index 57156f0858..68268b9bb8 100644 --- a/src/umbraco.cms/businesslogic/web/Domain.cs +++ b/src/umbraco.cms/businesslogic/web/Domain.cs @@ -151,7 +151,7 @@ namespace umbraco.cms.businesslogic.web () => { var result = new List(); - using (var dr = SqlHelper.ExecuteReader("SELECT Id, DomainName FFROM umbracoDomains ORDER BY Id")) + using (var dr = SqlHelper.ExecuteReader("SELECT Id, DomainName FROM umbracoDomains ORDER BY Id")) { while (dr.Read()) { From 3285e161c02afa269f692cedce09abec09f2d7a9 Mon Sep 17 00:00:00 2001 From: Steve Temple Date: Tue, 15 Oct 2013 17:09:51 +0100 Subject: [PATCH 0021/1046] Fix issue with error http://our.umbraco.org/forum/developers/api-questions/41483-Umbraco-6-MVC-RenderRouteHandlerHandlePostedValues-Sequence-contains-more-than-one-matching-element when multiple routes share the same controller --- src/Umbraco.Web/Mvc/RenderRouteHandler.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index 6756847980..b4c1f4b25d 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -200,6 +200,7 @@ namespace Umbraco.Web.Mvc .SingleOrDefault(x => x.Defaults != null && x.Defaults.ContainsKey("controller") && x.Defaults["controller"].ToString() == postedInfo.ControllerName && + x.Defaults["action"].ToString().ToLower() == postedInfo.ActionName.ToLower() && !x.DataTokens.ContainsKey("area")); } else From 4236473b14a69ce9fe46bb7405b2daa057a6daac Mon Sep 17 00:00:00 2001 From: Steve Temple Date: Tue, 15 Oct 2013 17:15:53 +0100 Subject: [PATCH 0022/1046] Tidied up tabs to match Umbraco standard --- src/Umbraco.Web/Mvc/RenderRouteHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index b4c1f4b25d..4b0ed7db11 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -200,7 +200,7 @@ namespace Umbraco.Web.Mvc .SingleOrDefault(x => x.Defaults != null && x.Defaults.ContainsKey("controller") && x.Defaults["controller"].ToString() == postedInfo.ControllerName && - x.Defaults["action"].ToString().ToLower() == postedInfo.ActionName.ToLower() && + String.Equals(x.Defaults["action"].ToString(), postedInfo.ActionName, StringComparison.CurrentCultureIgnoreCase) && !x.DataTokens.ContainsKey("area")); } else From e06e50c4fce8875df0b70f5302ac289395ef3813 Mon Sep 17 00:00:00 2001 From: Steve Temple Date: Tue, 15 Oct 2013 17:15:53 +0100 Subject: [PATCH 0023/1046] Tidied up tabs to match Umbraco standard and nicer string comparison --- src/Umbraco.Web/Mvc/RenderRouteHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index b4c1f4b25d..4b0ed7db11 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -200,7 +200,7 @@ namespace Umbraco.Web.Mvc .SingleOrDefault(x => x.Defaults != null && x.Defaults.ContainsKey("controller") && x.Defaults["controller"].ToString() == postedInfo.ControllerName && - x.Defaults["action"].ToString().ToLower() == postedInfo.ActionName.ToLower() && + String.Equals(x.Defaults["action"].ToString(), postedInfo.ActionName, StringComparison.CurrentCultureIgnoreCase) && !x.DataTokens.ContainsKey("area")); } else From f5029122258a27efb0ffa7d9590ac411a97ea7b2 Mon Sep 17 00:00:00 2001 From: Steve Temple Date: Tue, 15 Oct 2013 23:51:39 +0100 Subject: [PATCH 0024/1046] Updated to use invariant culture --- src/Umbraco.Web/Mvc/RenderRouteHandler.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index 4b0ed7db11..7f0c7bc3ce 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -198,10 +198,10 @@ namespace Umbraco.Web.Mvc //find the controller in the route table without an area surfaceRoute = RouteTable.Routes.OfType() .SingleOrDefault(x => x.Defaults != null && - x.Defaults.ContainsKey("controller") && - x.Defaults["controller"].ToString() == postedInfo.ControllerName && - String.Equals(x.Defaults["action"].ToString(), postedInfo.ActionName, StringComparison.CurrentCultureIgnoreCase) && - !x.DataTokens.ContainsKey("area")); + x.Defaults.ContainsKey("controller") && + x.Defaults["controller"].ToString() == postedInfo.ControllerName && + String.Equals(x.Defaults["action"].ToString(), postedInfo.ActionName, StringComparison.InvariantCultureIgnoreCase) && + !x.DataTokens.ContainsKey("area")); } else { From e738738e542190f03c12126abf2ec4cc56cc9108 Mon Sep 17 00:00:00 2001 From: Steve Temple Date: Wed, 16 Oct 2013 12:02:20 +0100 Subject: [PATCH 0025/1046] Only check actions if more than 1 route matches & ignore case when comparing controller names --- src/Umbraco.Web/Mvc/RenderRouteHandler.cs | 27 ++++++++++++++++------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index 7f0c7bc3ce..38d21d9577 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -195,15 +195,26 @@ namespace Umbraco.Web.Mvc Route surfaceRoute; if (postedInfo.Area.IsNullOrWhiteSpace()) { - //find the controller in the route table without an area - surfaceRoute = RouteTable.Routes.OfType() - .SingleOrDefault(x => x.Defaults != null && - x.Defaults.ContainsKey("controller") && - x.Defaults["controller"].ToString() == postedInfo.ControllerName && - String.Equals(x.Defaults["action"].ToString(), postedInfo.ActionName, StringComparison.InvariantCultureIgnoreCase) && - !x.DataTokens.ContainsKey("area")); + //find the controller in the route table without an area + var surfaceRoutes = RouteTable.Routes.OfType() + .Where(x => x.Defaults != null && + x.Defaults.ContainsKey("controller") && + String.Equals(x.Defaults["controller"].ToString(), postedInfo.ControllerName, StringComparison.InvariantCultureIgnoreCase) && + !x.DataTokens.ContainsKey("area")).ToList(); + + // If more than one route is found, find one with a matching action + if (surfaceRoutes.Count() > 1) + { + surfaceRoute = surfaceRoutes.SingleOrDefault(x => + String.Equals(x.Defaults["action"].ToString(), postedInfo.ActionName, StringComparison.InvariantCultureIgnoreCase)); + } + else + { + surfaceRoute = surfaceRoutes.SingleOrDefault(); + } + } - else + else { //find the controller in the route table with the specified area surfaceRoute = RouteTable.Routes.OfType() From 3fe99a402e2e03f7648b1153b2de53193014f152 Mon Sep 17 00:00:00 2001 From: Lars-Erik Aabech Date: Wed, 16 Oct 2013 15:45:39 +0200 Subject: [PATCH 0026/1046] Should make the PublishedContentExtensionTests green when ran together with everything else in TeamCity --- .../PublishedContentExtensionTests.cs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentExtensionTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentExtensionTests.cs index 98ee00c2cd..93502f288d 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentExtensionTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentExtensionTests.cs @@ -18,19 +18,7 @@ namespace Umbraco.Tests.PublishedContent protected override DatabaseBehavior DatabaseTestBehavior { - get { return DatabaseBehavior.NewDbFileAndSchemaPerFixture; } - } - - [SetUp] - public override void Initialize() - { - base.Initialize(); - } - - [TearDown] - public override void TearDown() - { - base.TearDown(); + get { return DatabaseBehavior.NewSchemaPerFixture; } } protected override string GetXmlContent(int templateId) From ea239e61220d993fb1e5adc5f70024a85aae28ac Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Wed, 16 Oct 2013 15:53:56 +0200 Subject: [PATCH 0027/1046] Missing languages in RTE insert link dialogue Fixes this issue: http://issues.umbraco.org/issue/U4-2551 --- .../umbraco_client/tinymce3/plugins/umbracolink/langs/da_dlg.js | 1 + .../umbraco_client/tinymce3/plugins/umbracolink/langs/de_dlg.js | 1 + .../umbraco_client/tinymce3/plugins/umbracolink/langs/en_dlg.js | 2 +- .../tinymce3/plugins/umbracolink/langs/en_us_dlg.js | 2 +- .../umbraco_client/tinymce3/plugins/umbracolink/langs/fi_dlg.js | 1 + .../umbraco_client/tinymce3/plugins/umbracolink/langs/fr_dlg.js | 1 + .../umbraco_client/tinymce3/plugins/umbracolink/langs/he_dlg.js | 2 +- .../umbraco_client/tinymce3/plugins/umbracolink/langs/it_dlg.js | 1 + .../umbraco_client/tinymce3/plugins/umbracolink/langs/ja_dlg.js | 2 +- .../umbraco_client/tinymce3/plugins/umbracolink/langs/nl_dlg.js | 1 + .../umbraco_client/tinymce3/plugins/umbracolink/langs/no_dlg.js | 1 + .../umbraco_client/tinymce3/plugins/umbracolink/langs/pl_dlg.js | 1 + .../umbraco_client/tinymce3/plugins/umbracolink/langs/pt_dlg.js | 1 + .../umbraco_client/tinymce3/plugins/umbracolink/langs/sv_dlg.js | 2 +- .../umbraco_client/tinymce3/plugins/umbracolink/langs/zh_dlg.js | 2 +- 15 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/da_dlg.js create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/de_dlg.js create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/fi_dlg.js create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/fr_dlg.js create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/it_dlg.js create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/nl_dlg.js create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/no_dlg.js create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/pl_dlg.js create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/pt_dlg.js diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/da_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/da_dlg.js new file mode 100644 index 0000000000..06f7fe3d83 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/da_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('da.advlink_dlg',{"target_name":"Destinationsnavn",classes:"Klasser",style:"Stil",id:"Id","popup_position":"Position (X/Y)",langdir:"Sprogretning","popup_size":"St\u00f8rrelse","popup_dependent":"Afh\u00e6ngig (Kun Mozilla/Firefox)","popup_resizable":"Lad det v\u00e6re muligt at \u00e6ndre st\u00f8rrelsen p\u00e5 vinduet","popup_location":"Vis adresselinje","popup_menubar":"Vis menulinje","popup_toolbar":"Vis v\u00e6rkt\u00f8jslinjer","popup_statusbar":"Vis statuslinje","popup_scrollbars":"Vis rullepanel","popup_return":"Inds\u00e6t \'return false\'","popup_name":"Vinduesnavn","popup_url":"Popup URL",popup:"Javascript popup","target_blank":"\u00c5ben i nyt vindue","target_top":"\u00c5ben i \u00f8verste vindue / ramme (erstatter alle rammer)","target_parent":"\u00c5ben i overliggende vindue / ramme","target_same":"\u00c5ben i dette vindue / ramme","anchor_names":"Ankre","popup_opts":"Indstillinger","advanced_props":"Avancerede egenskaber","event_props":"H\u00e6ndelser","popup_props":"Popup egenskaber","general_props":"Generelle egenskaber","advanced_tab":"Advanceret","events_tab":"H\u00e6ndelser","popup_tab":"Popup","general_tab":"Generelt",list:"Liste over links","is_external":"Den URL, der er indtastet, ser ud til at v\u00e6re et eksternt link. Vil du have tilf\u00f8jet det p\u00e5kr\u00e6vede http:// foran?","is_email":"Den URL, der er indtastet, ser ud til at v\u00e6re en emailadresse. Vil du have tilf\u00f8jet det p\u00e5kr\u00e6vede mailto: foran?",titlefield:"Titel",target:"M\u00e5l",url:"Link URL",title:"Inds\u00e6t/rediger link","link_list":"Liste over links",rtl:"H\u00f8jre mod venstre",ltr:"Venstre mod h\u00f8jre",accesskey:"Genvejstast",tabindex:"Tabindex",rev:"Relativ destination til side",rel:"Relativ side til destination",mime:"Destinations-MIME-type",encoding:"Destinationstegns\u00e6t",langcode:"Sprogkode","target_langcode":"Destinationssprog",width:"Bredde",height:"H\u00f8jde"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/de_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/de_dlg.js new file mode 100644 index 0000000000..bb0d3e35b3 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/de_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('de.advlink_dlg',{"target_name":"Name der Zielseite",classes:"Klassen",style:"Format",id:"ID","popup_position":"Position (X/Y)",langdir:"Schriftrichtung","popup_size":"Gr\u00f6\u00dfe","popup_dependent":"Vom Elternfenster abh\u00e4ngig
(nur Mozilla/Firefox) ","popup_resizable":"Vergr\u00f6\u00dfern des Fenster zulassen","popup_location":"Adressleiste anzeigen","popup_menubar":"Browsermen\u00fc anzeigen","popup_toolbar":"Werkzeugleisten anzeigen","popup_statusbar":"Statusleiste anzeigen","popup_scrollbars":"Scrollbalken anzeigen","popup_return":"Link trotz Popup folgen","popup_name":"Name des Fensters","popup_url":"Popup-Adresse",popup:"JavaScript-Popup","target_blank":"In neuem Fenster \u00f6ffnen","target_top":"Im obersten Frame \u00f6ffnen (sprengt das Frameset)","target_parent":"Im \u00fcbergeordneten Fenster/Frame \u00f6ffnen","target_same":"Im selben Fenster/Frame \u00f6ffnen","anchor_names":"Anker","popup_opts":"Optionen","advanced_props":"Erweiterte Eigenschaften","event_props":"Ereignisse","popup_props":"Popup-Eigenschaften","general_props":"Allemeine Eigenschaften","advanced_tab":"Erweitert","events_tab":"Ereignisse","popup_tab":"Popup","general_tab":"Allgemein",list:"Linkliste","is_external":"Diese Adresse scheint ein externer Link zu sein. M\u00f6chten Sie das dazu ben\u00f6tigte \"http://\" voranstellen?","is_email":"Diese Adresse scheint eine E-Mail-Adresse zu sein. M\u00f6chten Sie das dazu ben\u00f6tigte \"mailto:\" voranstellen?",titlefield:"Titel",target:"Fenster",url:"Adresse",title:"Link einf\u00fcgen/bearbeiten","link_list":"Linkliste",rtl:"Rechts nach links",ltr:"Links nach rechts",accesskey:"Tastenk\u00fcrzel",tabindex:"Tabindex",rev:"Beziehung des Linkziels zur Seite",rel:"Beziehung der Seite zum Linkziel",mime:"MIME-Type der Zielseite",encoding:"Zeichenkodierung der Zielseite",langcode:"Sprachcode","target_langcode":"Sprache der Zielseite",width:"Breite",height:"H\u00f6he"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/en_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/en_dlg.js index a11f69cbf5..3169a56580 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/en_dlg.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/en_dlg.js @@ -1 +1 @@ -tinyMCE.addI18n('en.advlink_dlg', { "target_name": "Target Name", classes: "Classes", style: "Style", id: "ID", "popup_position": "Position (X/Y)", langdir: "Language Direction", "popup_size": "Size", "popup_dependent": "Dependent (Mozilla/Firefox Only)", "popup_resizable": "Make Window Resizable", "popup_location": "Show Location Bar", "popup_menubar": "Show Menu Bar", "popup_toolbar": "Show Toolbars", "popup_statusbar": "Show Status Bar", "popup_scrollbars": "Show Scrollbars", "popup_return": "Insert \'return false\'", "popup_name": "Window Name", "popup_url": "Popup URL", popup: "JavaScript Popup", "target_blank": "Open in New Window", "target_top": "Open in Top Frame (Replaces All Frames)", "target_parent": "Open in Parent Window/Frame", "target_same": "Open in This Window/Frame", "anchor_names": "Anchors", "popup_opts": "Options", "advanced_props": "Advanced Properties", "event_props": "Events", "popup_props": "Popup Properties", "general_props": "General Properties", "advanced_tab": "Advanced", "events_tab": "Events", "popup_tab": "Popup", "general_tab": "General", list: "Link List", "is_external": "The URL you entered seems to be an external link. Do you want to add the required http:// prefix?", "is_email": "The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?", titlefield: "Title", target: "Target", url: "Link URL", title: "Insert/Edit Link", "link_list": "Link List", rtl: "Right to Left", ltr: "Left to Right", accesskey: "AccessKey", tabindex: "TabIndex", rev: "Relationship Target to Page", rel: "Relationship Page to Target", mime: "Target MIME Type", encoding: "Target Character Encoding", langcode: "Language Code", "target_langcode": "Target Language", width: "Width", height: "Height" }); \ No newline at end of file +tinyMCE.addI18n('en.advlink_dlg',{"target_name":"Target Name",classes:"Classes",style:"Style",id:"ID","popup_position":"Position (X/Y)",langdir:"Language Direction","popup_size":"Size","popup_dependent":"Dependent (Mozilla/Firefox Only)","popup_resizable":"Make Window Resizable","popup_location":"Show Location Bar","popup_menubar":"Show Menu Bar","popup_toolbar":"Show Toolbars","popup_statusbar":"Show Status Bar","popup_scrollbars":"Show Scrollbars","popup_return":"Insert \'return false\'","popup_name":"Window Name","popup_url":"Popup URL",popup:"JavaScript Popup","target_blank":"Open in New Window","target_top":"Open in Top Frame (Replaces All Frames)","target_parent":"Open in Parent Window/Frame","target_same":"Open in This Window/Frame","anchor_names":"Anchors","popup_opts":"Options","advanced_props":"Advanced Properties","event_props":"Events","popup_props":"Popup Properties","general_props":"General Properties","advanced_tab":"Advanced","events_tab":"Events","popup_tab":"Popup","general_tab":"General",list:"Link List","is_external":"The URL you entered seems to be an external link. Do you want to add the required http:// prefix?","is_email":"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?",titlefield:"Title",target:"Target",url:"Link URL",title:"Insert/Edit Link","link_list":"Link List",rtl:"Right to Left",ltr:"Left to Right",accesskey:"AccessKey",tabindex:"TabIndex",rev:"Relationship Target to Page",rel:"Relationship Page to Target",mime:"Target MIME Type",encoding:"Target Character Encoding",langcode:"Language Code","target_langcode":"Target Language",width:"Width",height:"Height"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/en_us_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/en_us_dlg.js index 65703ee223..2112e7ce3a 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/en_us_dlg.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/en_us_dlg.js @@ -1 +1 @@ -tinyMCE.addI18n('en_us.advlink_dlg', { "target_name": "Target Name", classes: "Classes", style: "Style", id: "ID", "popup_position": "Position (X/Y)", langdir: "Language Direction", "popup_size": "Size", "popup_dependent": "Dependent (Mozilla/Firefox Only)", "popup_resizable": "Make Window Resizable", "popup_location": "Show Location Bar", "popup_menubar": "Show Menu Bar", "popup_toolbar": "Show Toolbars", "popup_statusbar": "Show Status Bar", "popup_scrollbars": "Show Scrollbars", "popup_return": "Insert \'return false\'", "popup_name": "Window Name", "popup_url": "Popup URL", popup: "JavaScript Popup", "target_blank": "Open in New Window", "target_top": "Open in Top Frame (Replaces All Frames)", "target_parent": "Open in Parent Window/Frame", "target_same": "Open in This Window/Frame", "anchor_names": "Anchors", "popup_opts": "Options", "advanced_props": "Advanced Properties", "event_props": "Events", "popup_props": "Popup Properties", "general_props": "General Properties", "advanced_tab": "Advanced", "events_tab": "Events", "popup_tab": "Popup", "general_tab": "General", list: "Link List", "is_external": "The URL you entered seems to be an external link. Do you want to add the required http:// prefix?", "is_email": "The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?", titlefield: "Title", target: "Target", url: "Link URL", title: "Insert/Edit Link", "link_list": "Link List", rtl: "Right to Left", ltr: "Left to Right", accesskey: "AccessKey", tabindex: "TabIndex", rev: "Relationship Target to Page", rel: "Relationship Page to Target", mime: "Target MIME Type", encoding: "Target Character Encoding", langcode: "Language Code", "target_langcode": "Target Language", width: "Width", height: "Height" }); \ No newline at end of file +tinyMCE.addI18n('en_us.advlink_dlg',{"target_name":"Target Name",classes:"Classes",style:"Style",id:"ID","popup_position":"Position (X/Y)",langdir:"Language Direction","popup_size":"Size","popup_dependent":"Dependent (Mozilla/Firefox Only)","popup_resizable":"Make Window Resizable","popup_location":"Show Location Bar","popup_menubar":"Show Menu Bar","popup_toolbar":"Show Toolbars","popup_statusbar":"Show Status Bar","popup_scrollbars":"Show Scrollbars","popup_return":"Insert \'return false\'","popup_name":"Window Name","popup_url":"Popup URL",popup:"JavaScript Popup","target_blank":"Open in New Window","target_top":"Open in Top Frame (Replaces All Frames)","target_parent":"Open in Parent Window/Frame","target_same":"Open in This Window/Frame","anchor_names":"Anchors","popup_opts":"Options","advanced_props":"Advanced Properties","event_props":"Events","popup_props":"Popup Properties","general_props":"General Properties","advanced_tab":"Advanced","events_tab":"Events","popup_tab":"Popup","general_tab":"General",list:"Link List","is_external":"The URL you entered seems to be an external link. Do you want to add the required http:// prefix?","is_email":"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?",titlefield:"Title",target:"Target",url:"Link URL",title:"Insert/Edit Link","link_list":"Link List",rtl:"Right to Left",ltr:"Left to Right",accesskey:"AccessKey",tabindex:"TabIndex",rev:"Relationship Target to Page",rel:"Relationship Page to Target",mime:"Target MIME Type",encoding:"Target Character Encoding",langcode:"Language Code","target_langcode":"Target Language",width:"Width",height:"Height"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/fi_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/fi_dlg.js new file mode 100644 index 0000000000..e49488e733 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/fi_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('fi.advlink_dlg',{"target_name":"Kohteen nimi",classes:"Luokat",style:"Tyyli",id:"Id","popup_position":"Sijainti (X/Y)",langdir:"Kielen suunta","popup_size":"Koko","popup_dependent":"Riippuvainen (vain Mozilla/Firefox)","popup_resizable":"Tee ikkunan koko muokattavaksi","popup_location":"N\u00e4yt\u00e4 sijaintipalkki","popup_menubar":"N\u00e4yt\u00e4 valikkopalkki","popup_toolbar":"N\u00e4yt\u00e4 ty\u00f6kalut","popup_statusbar":"N\u00e4yt\u00e4 tilapalkki","popup_scrollbars":"N\u00e4yt\u00e4 vierityspalkit","popup_return":"Lis\u00e4\u00e4 \'return false\'","popup_name":"Ikkunan nimi","popup_url":"Ponnahdusikkunan URL",popup:"JavaScript-ponnahdusikkuna","target_blank":"Avaa uudessa ikkunassa","target_top":"Avaa ylimm\u00e4ss\u00e4 ruudussa (korvaa kaikki ruudut)","target_parent":"Avaa ylemm\u00e4ss\u00e4 ikkunassa","target_same":"Avaa t\u00e4ss\u00e4 ikkunassa","anchor_names":"Ankkurit","popup_opts":"Valinta","advanced_props":"Edistyneet asetukset","event_props":"Tapahtumat (events)","popup_props":"Ponnahdusikkunan asetukset","general_props":"Yleiset asetukset","advanced_tab":"Edistynyt","events_tab":"Tapahtumat","popup_tab":"Ponnahdusikkuna","general_tab":"Yleiset",list:"Linkkilista","is_external":"Sy\u00f6tt\u00e4m\u00e4si URL n\u00e4ytt\u00e4\u00e4 olevan sivuston ulkoinen osoite, haluatko lis\u00e4t\u00e4 http://-etuliitteen?","is_email":"Sy\u00f6tt\u00e4m\u00e4si URL n\u00e4ytt\u00e4\u00e4 olevan s\u00e4hk\u00f6postiosoite, haluatko lis\u00e4t\u00e4 mailto:-etuliitteen?",titlefield:"Otsikko",target:"Kohde (target)",url:"Linkin URL",title:"Lis\u00e4\u00e4/muokkaa linkki\u00e4","link_list":"Linkkilista",rtl:"Oikealta vasemmalle",ltr:"Vasemmalta oikealle",accesskey:"Pikan\u00e4pp\u00e4in",tabindex:"Tabulaattori-indeksi",rev:"Kohteen suhde sivuun",rel:"Sivun suhde kohteeseen",mime:"Kohteen MIME-tyyppi",encoding:"Kohteen merkist\u00f6koodaus",langcode:"Kielen koodi","target_langcode":"Kohteen kieli",width:"Leveys",height:"Korkeus"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/fr_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/fr_dlg.js new file mode 100644 index 0000000000..38e5a7858f --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/fr_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('fr.advlink_dlg',{"target_name":"Nom de la cible",classes:"Classes",style:"Style",id:"Id","popup_position":"Position (X/Y)",langdir:"Sens de lecture","popup_size":"Taille","popup_dependent":"D\u00e9pendante (seulement sous Mozilla/Firefox)","popup_resizable":"Autoriser le redimensionnement de la fen\u00eatre","popup_location":"Afficher la barre d\'adresse","popup_menubar":"Afficher la barre de menu","popup_toolbar":"Afficher la barre d\'outils","popup_statusbar":"Afficher la barre d\'\u00e9tat","popup_scrollbars":"Afficher les ascenseurs","popup_return":"Ins\u00e9rer \'return false\'","popup_name":"Nom de la fen\u00eatre","popup_url":"URL de la popup",popup:"Popup Javascript","target_blank":"Ouvrir dans une nouvelle fen\u00eatre","target_top":"Ouvrir dans le cadre principal (remplace tous les cadres)","target_parent":"Ouvrir dans la fen\u00eatre / le cadre parent","target_same":"Ouvrir dans cette fen\u00eatre / dans ce cadre","anchor_names":"Ancres","popup_opts":"Options","advanced_props":"Propri\u00e9t\u00e9s avanc\u00e9es","event_props":"\u00c9v\u00e8nements","popup_props":"Propri\u00e9t\u00e9s de la popup","general_props":"Propri\u00e9t\u00e9s g\u00e9n\u00e9rales","advanced_tab":"Avanc\u00e9","events_tab":"\u00c9v\u00e8nements","popup_tab":"Popup","general_tab":"G\u00e9n\u00e9ral",list:"Liste de liens","is_external":"L\'URL que vous avez saisie semble \u00eatre une adresse web externe. Souhaitez-vous ajouter le pr\u00e9fixe \u00ab http:// \u00bb ?","is_email":"L\'URL que vous avez saisie semble \u00eatre une adresse e-mail, souhaitez-vous ajouter le pr\u00e9fixe \u00ab mailto: \u00bb ?",titlefield:"Titre",target:"Cible",url:"URL du lien",title:"Ins\u00e9rer / \u00e9diter un lien","link_list":"Liste des liens",rtl:"Droite \u00e0 gauche",ltr:"Gauche \u00e0 droite",accesskey:"Touche d\'acc\u00e8s rapide",tabindex:"Tabindex",rev:"Relation de la cible \u00e0 la page",rel:"Relation de la page \u00e0 la cible",mime:"Type MIME de la cible",encoding:"Encodage de la cible",langcode:"Code de la langue","target_langcode":"Langue de la cible",width:"Largeur",height:"Hauteur"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/he_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/he_dlg.js index d0ef8b987b..7ea21bdaae 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/he_dlg.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/he_dlg.js @@ -1 +1 @@ -tinyMCE.addI18n('he.advlink_dlg', { "target_name": "Target Name", classes: "Classes", style: "Style", id: "ID", "popup_position": "Position (X/Y)", langdir: "Language Direction", "popup_size": "Size", "popup_dependent": "Dependent (Mozilla/Firefox Only)", "popup_resizable": "Make Window Resizable", "popup_location": "Show Location Bar", "popup_menubar": "Show Menu Bar", "popup_toolbar": "Show Toolbars", "popup_statusbar": "Show Status Bar", "popup_scrollbars": "Show Scrollbars", "popup_return": "Insert \'return false\'", "popup_name": "Window Name", "popup_url": "Popup URL", popup: "JavaScript Popup", "target_blank": "Open in New Window", "target_top": "Open in Top Frame (Replaces All Frames)", "target_parent": "Open in Parent Window/Frame", "target_same": "Open in This Window/Frame", "anchor_names": "Anchors", "popup_opts": "Options", "advanced_props": "Advanced Properties", "event_props": "Events", "popup_props": "Popup Properties", "general_props": "General Properties", "advanced_tab": "Advanced", "events_tab": "Events", "popup_tab": "Popup", "general_tab": "General", list: "Link List", "is_external": "The URL you entered seems to be an external link. Do you want to add the required http:// prefix?", "is_email": "The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?", titlefield: "Title", target: "Target", url: "Link URL", title: "Insert/Edit Link", "link_list": "Link List", rtl: "Right to Left", ltr: "Left to Right", accesskey: "AccessKey", tabindex: "TabIndex", rev: "Relationship Target to Page", rel: "Relationship Page to Target", mime: "Target MIME Type", encoding: "Target Character Encoding", langcode: "Language Code", "target_langcode": "Target Language", width: "Width", height: "Height" }); \ No newline at end of file +tinyMCE.addI18n('he.advlink_dlg',{"target_name":"Target name",classes:"Classes",style:"\u05e1\u05d2\u05e0\u05d5\u05df",id:"\u05de\u05e1\u05e4\u05e8 \u05e1\u05d9\u05d3\u05d5\u05e8\u05d9","popup_position":"\u05de\u05d9\u05e7\u05d5\u05dd (X/Y)",langdir:"\u05db\u05d9\u05d5\u05d5\u05df \u05d4\u05e9\u05e4\u05d4","popup_size":"\u05d2\u05d5\u05d3\u05dc","popup_dependent":"Dependent (Mozilla/Firefox only)","popup_resizable":"\u05d7\u05dc\u05d5\u05df \u05d3\u05d9\u05e0\u05d0\u05de\u05d9(resizable)","popup_location":"\u05d4\u05e6\u05d2\u05ea location bar ","popup_menubar":"\u05d4\u05e6\u05d2\u05ea \u05ea\u05e4\u05e8\u05d9\u05d8","popup_toolbar":"\u05d4\u05e6\u05d2\u05ea \u05e1\u05e8\u05d2\u05dc\u05d9 \u05db\u05dc\u05d9\u05dd","popup_statusbar":"\u05d4\u05e6\u05d2\u05ea \u05e9\u05d5\u05e8\u05ea \u05e1\u05d8\u05d0\u05d8\u05d5\u05e1","popup_scrollbars":"\u05d4\u05e6\u05d2\u05ea \u05e4\u05e1 \u05d2\u05dc\u05d9\u05dc\u05d4","popup_return":"\u05d9\u05e9 \u05dc\u05d4\u05db\u05e0\u05d9\u05e1 \'return false\'","popup_name":"\u05e9\u05dd \u05d4\u05d7\u05dc\u05d5\u05df","popup_url":"\u05d7\u05dc\u05d5\u05df \u05de\u05d5\u05e7\u05e4\u05e5 URL",popup:"\u05d7\u05dc\u05d5\u05df \u05de\u05d5\u05e7\u05e4\u05e5 javascript","target_blank":"\u05e4\u05ea\u05d9\u05d7\u05d4 \u05d1\u05d7\u05dc\u05d5\u05df \u05d7\u05d3\u05e9","target_top":"\u05e4\u05ea\u05d9\u05d7\u05d4 \u05d1\u05d7\u05dc\u05d5\u05df \u05d4\u05d1\u05df \u05d4\u05e8\u05d0\u05e9\u05d9(\u05de\u05d7\u05dc\u05d9\u05e3 \u05d0\u05ea \u05db\u05dc \u05d7\u05dc\u05d5\u05e0\u05d5\u05ea \u05d4\u05d1\u05e0\u05d9\u05dd)","target_parent":"\u05e4\u05ea\u05d9\u05d7\u05d4 \u05d1\u05dc\u05d5\u05df \u05d4\u05d0\u05d1\u05d0/\u05d7\u05dc\u05d5\u05df \u05d1\u05df","target_same":"\u05e4\u05ea\u05d9\u05d7\u05d4 \u05d1\u05d7\u05dc\u05d5\u05df \u05d7\u05d3\u05e9/\u05d7\u05dc\u05d5\u05df \u05d1\u05df","anchor_names":"\u05e7\u05d9\u05e9\u05d5\u05e8 \u05dc\u05e1\u05d9\u05de\u05e0\u05d9\u05d4","popup_opts":"\u05d0\u05e4\u05e9\u05e8\u05d5\u05d9\u05d5\u05ea","advanced_props":"\u05ea\u05db\u05d5\u05e0\u05d5\u05ea \u05de\u05ea\u05e7\u05d3\u05de\u05d5\u05ea","event_props":"\u05de\u05d0\u05d5\u05e8\u05e2\u05d5\u05ea","popup_props":"\u05ea\u05db\u05d5\u05e0\u05d5\u05ea \u05d7\u05dc\u05d5\u05df \u05de\u05d5\u05e7\u05e4\u05e5","general_props":"\u05ea\u05db\u05d5\u05e0\u05d5\u05ea \u05db\u05dc\u05dc\u05d9\u05d5\u05ea","advanced_tab":"\u05de\u05ea\u05e7\u05d3\u05dd","events_tab":"\u05d0\u05e8\u05d5\u05e2\u05d9\u05dd","popup_tab":"\u05d7\u05dc\u05d5\u05df \u05de\u05d5\u05e7\u05e4\u05e5","general_tab":"\u05db\u05dc\u05dc\u05d9",list:"\u05e8\u05e9\u05d9\u05de\u05ea \u05e7\u05d9\u05e9\u05d5\u05e8\u05d9\u05dd","is_external":"\u05db\u05ea\u05d5\u05d1\u05ea \u05d4-URL \u05e9\u05d4\u05d5\u05db\u05e0\u05e1\u05d4 \u05d4\u05d9\u05d0 \u05db\u05db\u05dc \u05d4\u05e0\u05e8\u05d0\u05d4 \u05e7\u05d9\u05e9\u05d5\u05e8 \u05d7\u05d9\u05e6\u05d5\u05e0\u05d9 \u05d4\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05d5\u05e1\u05d9\u05e3 \u05d0\u05ea \u05d4\u05e7\u05d9\u05d3\u05d5\u05de\u05ea http:// \u05d4\u05e0\u05d3\u05e8\u05e9\u05ea?","is_email":"\u05db\u05ea\u05d5\u05d1\u05ea \u05d4-URL \u05e9\u05d4\u05d5\u05db\u05e0\u05e1\u05d4 \u05d4\u05d9\u05d0 \u05db\u05db\u05dc \u05d4\u05e0\u05e8\u05d0\u05d4 \u05db\u05ea\u05d5\u05d1\u05ea \u05de\u05d9\u05d9\u05dc \u05d4\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05d5\u05e1\u05d9\u05e3 \u05d0\u05ea \u05d4\u05e7\u05d9\u05d3\u05d5\u05de\u05ea MAILTO \u05d4\u05e0\u05d3\u05e8\u05e9\u05ea?",titlefield:"\u05db\u05d5\u05ea\u05e8\u05ea \u05d4\u05e7\u05d9\u05e9\u05d5\u05e8",target:"\u05d9\u05e2\u05d3",url:"\u05db\u05ea\u05d5\u05d1\u05ea \u05d4\u05e7\u05d9\u05e9\u05d5\u05e8",title:"\u05d4\u05d5\u05e1\u05e4\u05d4/\u05e2\u05e8\u05d9\u05db\u05ea \u05e7\u05d9\u05e9\u05d5\u05e8","link_list":"\u05e8\u05e9\u05d9\u05de\u05ea \u05e7\u05d9\u05e9\u05d5\u05e8\u05d9\u05dd",rtl:"\u05de\u05d9\u05de\u05d9\u05df \u05dc\u05e9\u05de\u05d0\u05dc",ltr:"\u05de\u05e9\u05de\u05d0\u05dc \u05dc\u05d9\u05de\u05d9\u05df",accesskey:"Accesskey",tabindex:"Tabindex",rev:"Relationship target to page",rel:"Relationship page to target",mime:"Target MIME type",encoding:"Target character encoding",langcode:"\u05e7\u05d5\u05d3 \u05d4\u05e9\u05e4\u05d4","target_langcode":"Target language",width:"\u05e8\u05d5\u05d7\u05d1",height:"\u05d2\u05d5\u05d1\u05d4"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/it_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/it_dlg.js new file mode 100644 index 0000000000..bf19659d05 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/it_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('it.advlink_dlg',{"target_name":"Nome target",classes:"Classe",style:"Stile",id:"Id","popup_position":"Posizione (X/Y)",langdir:"Direzione del testo","popup_size":"Dimensioni","popup_dependent":"Dipendente (Solo in Mozilla/Firefox)","popup_resizable":"Rendi la finestra ridimensionabile","popup_location":"Mostra barra navigazione","popup_menubar":"Mostra barra menu","popup_toolbar":"Mostra barre strumenti","popup_statusbar":"Mostra barra di stato","popup_scrollbars":"Mostra barre di scorrimento","popup_return":"Inserisci \'return false\'","popup_name":"Nome finestra","popup_url":"URL Popup",popup:"Popup Javascript","target_blank":"Apri in una nuova finestra","target_top":"Apri nella cornice superiore (sostituisce tutte le cornici)","target_parent":"Apri nella finestra / cornice genitore","target_same":"Apri in questa finestra / cornice","anchor_names":"Ancore","popup_opts":"Opzioni","advanced_props":"Propriet\u00e0 avanzate","event_props":"Eventi","popup_props":"Propriet\u00e0 popup","general_props":"Propriet\u00e0 generali","advanced_tab":"Avanzate","events_tab":"Eventi","popup_tab":"Popup","general_tab":"Generale",list:"Lista collegamenti","is_external":"L\'URL inserito sembra essere un link esterno. Aggiungere il necessario prefisso http:// ?","is_email":"L\'URL inserito sembra essere un indirizzo email. Aggiungere il necessario prefisso mailto: ?",titlefield:"Titolo",target:"Target",url:"URL collegamento",title:"Inserisci/modifica link","link_list":"Lista collegamenti",rtl:"Destra verso sinistra",ltr:"Sinistra verso destra",accesskey:"Carattere di accesso",tabindex:"Indice tabulazione",rev:"Relazione da target a pagina",rel:"Relazione da pagina a target",mime:"Tipo MIME del target",encoding:"Codifica carattere del target",langcode:"Lingua","target_langcode":"Lingua del target",width:"Larghezza",height:"Altezza"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/ja_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/ja_dlg.js index 91df8efc11..68ebcd2e6e 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/ja_dlg.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/ja_dlg.js @@ -1 +1 @@ -tinyMCE.addI18n('ja.advlink_dlg',{"target_name":"ターゲットの名前",classes:"クラス",style:"スタイル",id:"ID","popup_position":"位置 (X/Y)",langdir:"文章の方向","popup_size":"大きさ","popup_dependent":"依存(MozillaとFirefoxだけ)","popup_resizable":"ウインドウのサイズ変更を許可","popup_location":"アドレスバーを表示","popup_menubar":"メニューバーを表示","popup_toolbar":"ツールバーを表示","popup_statusbar":"ステータスバーを表示","popup_scrollbars":"スクロールバーを表示","popup_return":"\'return false\'を挿入","popup_name":"ウインドウの名前","popup_url":"ポップアップのURL",popup:"Javascriptポップアップ","target_blank":"新しいウインドウで開く","target_top":"トップのフレームで開く(すべてのフレームを置き換え)","target_parent":"親ウインドウ/親フレームで開く","target_same":"このウインドウ/フレームで開く","anchor_names":"アンカー","popup_opts":"オプション","advanced_props":"高度な属性","event_props":"イベント","popup_props":"ポップアップ","general_props":"一般","advanced_tab":"専門的","events_tab":"イベント","popup_tab":"ポップアップ","general_tab":"一般",list:"リンクの一覧","is_external":"入力したURLは外部のリンクのようです。リンクに http:// を追加しますか?","is_email":"入力したURLは電子メールアドレスのようです。リンクに mailto: を追加しますか?",titlefield:"タイトル",target:"ターゲット",url:"リンクのURL",title:"リンクの挿入/編集","link_list":"リンクの一覧",rtl:"右から左",ltr:"左から右",accesskey:"アクセスキー",tabindex:"タブインデックス",rev:"ターゲットからページの関係",rel:"ページからターゲットの関係",mime:"ターゲットのMIMEタイプ",encoding:"ターゲットの文字エンコーディング",langcode:"言語コード","target_langcode":"ターゲットの言語",width:"幅",height:"高さ"}); \ No newline at end of file +tinyMCE.addI18n('ja.advlink_dlg',{"target_name":"\u30bf\u30fc\u30b2\u30c3\u30c8\u306e\u540d\u524d",classes:"\u30af\u30e9\u30b9",style:"\u30b9\u30bf\u30a4\u30eb",id:"ID","popup_position":"\u4f4d\u7f6e (X/Y)",langdir:"\u6587\u7ae0\u306e\u65b9\u5411","popup_size":"\u5927\u304d\u3055","popup_dependent":"\u4f9d\u5b58(Mozilla\u3068Firefox\u3060\u3051)","popup_resizable":"\u30a6\u30a4\u30f3\u30c9\u30a6\u306e\u30b5\u30a4\u30ba\u5909\u66f4\u3092\u8a31\u53ef","popup_location":"\u30a2\u30c9\u30ec\u30b9\u30d0\u30fc\u3092\u8868\u793a","popup_menubar":"\u30e1\u30cb\u30e5\u30fc\u30d0\u30fc\u3092\u8868\u793a","popup_toolbar":"\u30c4\u30fc\u30eb\u30d0\u30fc\u3092\u8868\u793a","popup_statusbar":"\u30b9\u30c6\u30fc\u30bf\u30b9\u30d0\u30fc\u3092\u8868\u793a","popup_scrollbars":"\u30b9\u30af\u30ed\u30fc\u30eb\u30d0\u30fc\u3092\u8868\u793a","popup_return":"\'return false\'\u3092\u633f\u5165","popup_name":"\u30a6\u30a4\u30f3\u30c9\u30a6\u306e\u540d\u524d","popup_url":"\u30dd\u30c3\u30d7\u30a2\u30c3\u30d7\u306eURL",popup:"Javascript\u30dd\u30c3\u30d7\u30a2\u30c3\u30d7","target_blank":"\u65b0\u3057\u3044\u30a6\u30a4\u30f3\u30c9\u30a6\u3067\u958b\u304f","target_top":"\u30c8\u30c3\u30d7\u306e\u30d5\u30ec\u30fc\u30e0\u3067\u958b\u304f(\u3059\u3079\u3066\u306e\u30d5\u30ec\u30fc\u30e0\u3092\u7f6e\u304d\u63db\u3048)","target_parent":"\u89aa\u30a6\u30a4\u30f3\u30c9\u30a6/\u89aa\u30d5\u30ec\u30fc\u30e0\u3067\u958b\u304f","target_same":"\u3053\u306e\u30a6\u30a4\u30f3\u30c9\u30a6/\u30d5\u30ec\u30fc\u30e0\u3067\u958b\u304f","anchor_names":"\u30a2\u30f3\u30ab\u30fc","popup_opts":"\u30aa\u30d7\u30b7\u30e7\u30f3","advanced_props":"\u9ad8\u5ea6\u306a\u5c5e\u6027","event_props":"\u30a4\u30d9\u30f3\u30c8","popup_props":"\u30dd\u30c3\u30d7\u30a2\u30c3\u30d7","general_props":"\u4e00\u822c","advanced_tab":"\u5c02\u9580\u7684","events_tab":"\u30a4\u30d9\u30f3\u30c8","popup_tab":"\u30dd\u30c3\u30d7\u30a2\u30c3\u30d7","general_tab":"\u4e00\u822c",list:"\u30ea\u30f3\u30af\u306e\u4e00\u89a7","is_external":"\u5165\u529b\u3057\u305fURL\u306f\u5916\u90e8\u306e\u30ea\u30f3\u30af\u306e\u3088\u3046\u3067\u3059\u3002\u30ea\u30f3\u30af\u306b http:// \u3092\u8ffd\u52a0\u3057\u307e\u3059\u304b?","is_email":"\u5165\u529b\u3057\u305fURL\u306f\u96fb\u5b50\u30e1\u30fc\u30eb\u30a2\u30c9\u30ec\u30b9\u306e\u3088\u3046\u3067\u3059\u3002\u30ea\u30f3\u30af\u306b mailto: \u3092\u8ffd\u52a0\u3057\u307e\u3059\u304b?",titlefield:"\u30bf\u30a4\u30c8\u30eb",target:"\u30bf\u30fc\u30b2\u30c3\u30c8",url:"\u30ea\u30f3\u30af\u306eURL",title:"\u30ea\u30f3\u30af\u306e\u633f\u5165/\u7de8\u96c6","link_list":"\u30ea\u30f3\u30af\u306e\u4e00\u89a7",rtl:"\u53f3\u304b\u3089\u5de6",ltr:"\u5de6\u304b\u3089\u53f3",accesskey:"\u30a2\u30af\u30bb\u30b9\u30ad\u30fc",tabindex:"\u30bf\u30d6\u30a4\u30f3\u30c7\u30c3\u30af\u30b9",rev:"\u30bf\u30fc\u30b2\u30c3\u30c8\u304b\u3089\u30da\u30fc\u30b8\u306e\u95a2\u4fc2",rel:"\u30da\u30fc\u30b8\u304b\u3089\u30bf\u30fc\u30b2\u30c3\u30c8\u306e\u95a2\u4fc2",mime:"\u30bf\u30fc\u30b2\u30c3\u30c8\u306eMIME\u30bf\u30a4\u30d7",encoding:"\u30bf\u30fc\u30b2\u30c3\u30c8\u306e\u6587\u5b57\u30a8\u30f3\u30b3\u30fc\u30c7\u30a3\u30f3\u30b0",langcode:"\u8a00\u8a9e\u30b3\u30fc\u30c9","target_langcode":"\u30bf\u30fc\u30b2\u30c3\u30c8\u306e\u8a00\u8a9e",width:"\u5e45",height:"\u9ad8\u3055"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/nl_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/nl_dlg.js new file mode 100644 index 0000000000..b2924758b0 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/nl_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('nl.advlink_dlg',{"target_name":"Doel",classes:"Klasses",style:"Stijl",id:"Id","popup_position":"Positie (X/Y)",langdir:"Taalrichting","popup_size":"Grootte","popup_dependent":"Afhankelijk (Alleen Mozilla/Firefox)","popup_resizable":"Aanpasbaar venster","popup_location":"Lokatiebalk weergeven","popup_menubar":"Menubalk weergeven","popup_toolbar":"Werkbalk weergeven","popup_statusbar":"Statusbalk weergeven","popup_scrollbars":"Scrollbalken weergeven","popup_return":"\'return false\' invoegen","popup_name":"Vensternaam","popup_url":"Popup URL",popup:"Javascript popup","target_blank":"In nieuw venster openen","target_top":"In bovenste frame openen (vervangt gehele pagina)","target_parent":"In bovenliggend venster / frame openen","target_same":"In dit venster / frame openen","anchor_names":"Ankers","popup_opts":"Opties","advanced_props":"Geavanceerde eigenschappen","event_props":"Gebeurtenissen","popup_props":"Popup eigenschappen","general_props":"Algemene eigenschappen","advanced_tab":"Geavanceerd","events_tab":"Gebeurtenissen","popup_tab":"Popup","general_tab":"Algemeen",list:"Lijst","is_external":"De ingevoerde URL lijkt op een externe link. Wilt u de vereiste http:// tekst voorvoegen?","is_email":"De ingevoerde URL lijkt op een e-mailadres. Wilt u de vereiste mailto: tekst voorvoegen?",titlefield:"Titel",target:"Doel",url:"URL",title:"Link invoegen/bewerken","link_list":"Lijst",rtl:"Van rechts naar links",ltr:"Van links naar rechts",accesskey:"Toegangstoets",tabindex:"Tabvolgorde",rev:"Relatie van doel tot pagina",rel:"Relatie van pagina tot doel",mime:"MIME type",encoding:"Taalcodering",langcode:"Taalcode","target_langcode":"Taal",width:"Breedte",height:"Hoogte"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/no_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/no_dlg.js new file mode 100644 index 0000000000..1a333095d3 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/no_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('no.advlink_dlg',{"target_name":"M\u00e5lnavn",classes:"Klasse",style:"Stil",id:"Id","popup_position":"Posisjon (X/Y)",langdir:"Skriftretning","popup_size":"St\u00f8rrelse","popup_dependent":"Avhengig vindu (kun i Mozilla/Firefox)","popup_resizable":"Gj\u00f8r vinduet skalerbart","popup_location":"Vis plasseringslinje","popup_menubar":"Vis menylinje","popup_toolbar":"Vis verkt\u00f8ylinjer","popup_statusbar":"Vis statusline","popup_scrollbars":"Vis rullefelt","popup_return":"Sett inn \\\'return false\\\'","popup_name":"Navn p\u00e5 vindu","popup_url":"Popup URL",popup:"Javascript popup","target_blank":"\u00c5pne i nytt vindu","target_top":"\u00c5pne i toppvindu (erstatter alle rammer)","target_parent":"\u00c5pne i overordnet vindu/ramme","target_same":"\u00c5pne i samme vindu/ramme","anchor_names":"Anker","popup_opts":"Innstillinger","advanced_props":"Avanserte egenskaper","event_props":"Hendelser","popup_props":"Popupegenskaper","general_props":"Generelle egenskaper","advanced_tab":"Avansert","events_tab":"Hendelser","popup_tab":"Popup","general_tab":"Generelt",list:"Liste over lenker","is_external":"URLen du skrev inn ser ut til \u00e5 v\u00e6re en ekstern lenke. \u00d8nsker du \u00e5 legge til obligatorisk http://-prefiks?","is_email":"URLen du skrev inn ser ut til \u00e5 v\u00e6re Epost adresse. \u00d8nsker du \u00e5 legge til obligatorisk mailto:-prefiks?",titlefield:"Tittel",target:"M\u00e5l",url:"Lenke URL",title:"Sett inn / rediger lenke","link_list":"Liste over lenker",rtl:"H\u00f8yre mot venstre",ltr:"Venstre mot h\u00f8yre",accesskey:"Hurtigtast",tabindex:"Tabulatorindeks",rev:"Forholdet mellom m\u00e5l og side",rel:"Forholdet mellom side og m\u00e5l",mime:"M\u00e5l MIME type",encoding:"M\u00e5l karakter koding",langcode:"Spr\u00e5kkode","target_langcode":"M\u00e5lspr\u00e5k",width:"Bredde",height:"H\u00f8yde"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/pl_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/pl_dlg.js new file mode 100644 index 0000000000..d529d7ad17 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/pl_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('pl.advlink_dlg',{"target_name":"Nazwa celu",classes:"Klasy",style:"Styl",id:"Id","popup_position":"Pozycja (X/Y)",langdir:"Kierunek czytania tekstu","popup_size":"Rozmiar","popup_dependent":"Zale\u017cny (Mozilla/Firefox wy\u0142\u0105cznie)","popup_resizable":"Stw\u00f3rz okno z mo\u017cliwo\u015bci\u0105 zmiany rozmiaru","popup_location":"Poka\u017c pasek adresu","popup_menubar":"Poka\u017c pasek menu","popup_toolbar":"Poka\u017c narz\u0119dzia","popup_statusbar":"Poka\u017c pasek statusu","popup_scrollbars":"Poka\u017c paski przewijania","popup_return":"Wstaw \'return false\'","popup_name":"Nazwa okna","popup_url":"URL okna",popup:"Wyskakuj\u0105ce okno","target_blank":"Otw\u00f3rz w nowym oknie","target_top":"Otw\u00f3rz w g\u00f3rnej ramce (zamie\u0144 wszystkie ramki)","target_parent":"Otw\u00f3rz w nadrz\u0119dnym oknie / ramce","target_same":"Otw\u00f3rz w tym oknie / ramce","anchor_names":"Kotwice","popup_opts":"Opcje","advanced_props":"Zaawansowae w\u0142a\u015bciwo\u015bci","event_props":"Zdarzenia","popup_props":"W\u0142a\u015bciwo\u015bci okna","general_props":"W\u0142a\u015bciwo\u015bci og\u00f3lne","advanced_tab":"Zaawansowane","events_tab":"Zdarzenia","popup_tab":"Popup","general_tab":"Og\u00f3lne",list:"Lista link\u00f3w","is_external":"Podany adres wydaje si\u0119 by\u0107 zewn\u0119trznym linkiem, czy chcesz doda\u0107 wymagany prefiks http://?","is_email":"Podany adres wydaje si\u0119 by\u0107 adresem emailowym, czy chcesz doda\u0107 wymagany prefiks mailto:?",titlefield:"Tytu\u0142",target:"Cel",url:"URL linka",title:"Wstaw/edytuj link","link_list":"Lista odno\u015bnik\u00f3w",rtl:"Kierunek z prawej do lewej",ltr:"Kierunek z lewej do prawej",accesskey:"Klawisz skr\u00f3tu",tabindex:"Numer tab",rev:"Relacje celu do strony",rel:"Relacje strony do celu",mime:"Docelowy typ MIME",encoding:"Kodowanie znak\u00f3w celu",langcode:"Kod j\u0119zyka","target_langcode":"Docelowy kod j\u0119zyka",width:"Szeroko\u015b\u0107",height:"Wysoko\u015b\u0107"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/pt_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/pt_dlg.js new file mode 100644 index 0000000000..8167855442 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/pt_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('pt.advlink_dlg',{"target_name":"Nome do alvo",classes:"Classes",style:"Estilo",id:"Id","popup_position":"Posi\u00e7\u00e3o (X/Y)",langdir:"Dire\u00e7\u00e3o do texto","popup_size":"Tamanho","popup_dependent":"Dependente (Mozilla/Firefox apenas)","popup_resizable":"Permitir altera\u00e7\u00e3o do tamanho da janela","popup_location":"Mostrar a barra de endere\u00e7os","popup_menubar":"Mostrar a barra de menu","popup_toolbar":"Mostrar a barra de ferramentas","popup_statusbar":"Mostrar a barra de status","popup_scrollbars":"Mostrar as barras de scroll","popup_return":"Inserir \"return false\"","popup_name":"Nome da janela","popup_url":"URL do popup",popup:"Popup javascript","target_blank":"Abrir numa nova janela","target_top":"Abrir na p\u00e1gina inteira (substitui todos os quadros)","target_parent":"Abrir na janela/quadro pai","target_same":"Abrir nesta janela/quadro","anchor_names":"\u00c2ncoras","popup_opts":"Op\u00e7\u00f5es","advanced_props":"Propriedades avan\u00e7adas","event_props":"Eventos","popup_props":"Propriedades de popup","general_props":"Propriedades gerais","advanced_tab":"Avan\u00e7ado","events_tab":"Eventos","popup_tab":"Popup","general_tab":"Geral",list:"Lista de hyperlinks","is_external":"A URL digitada parece conduzir a um link externo. Deseja acrescentar o prefixo necess\u00e1rio http://?","is_email":"A URL digitada parece ser um endere\u00e7o de e-mail. Deseja acrescentar o prefixo necess\u00e1rio mailto:?",titlefield:"T\u00edtulo",target:"Alvo",url:"URL do hyperlink",title:"Inserir/editar hyperlink","link_list":"Lista de hyperlinks",rtl:"Da direita para a esquerda",ltr:"Da esquerda para a direita",accesskey:"Chave de acesso",tabindex:"Tabindex",rev:"Rela\u00e7\u00e3o alvo/p\u00e1gina",rel:"Rela\u00e7\u00e3o p\u00e1gina/alvo",mime:"Tipo MIME alvo",encoding:"Codifica\u00e7\u00e3o de caracteres",langcode:"C\u00f3digo do idioma","target_langcode":"Idioma alvo",width:"Largura",height:"Altura"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/sv_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/sv_dlg.js index 0d665f99e1..8a6194472a 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/sv_dlg.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/sv_dlg.js @@ -1 +1 @@ -tinyMCE.addI18n('sv.advlink_dlg', { "target_name": "Målets namn", classes: "Klasser", style: "Stil", id: "ID", "popup_position": "Position (X/Y)", langdir: "Språkmål", "popup_size": "Storlek", "popup_dependent": "Dependent (Mozilla/Firefox Only)", "popup_resizable": "Make Window Resizable", "popup_location": "Show Location Bar", "popup_menubar": "Show Menu Bar", "popup_toolbar": "Show Toolbars", "popup_statusbar": "Show Status Bar", "popup_scrollbars": "Show Scrollbars", "popup_return": "Insert \'return false\'", "popup_name": "Window Name", "popup_url": "Popup URL", popup: "JavaScript Popup", "target_blank": "Öppna i ett nytt fönster", "target_top": "Öppna i toppramen", "target_parent": "Öppna i nuvarande fönster/ram", "target_same": "Öppna i detta fönster/ram", "anchor_names": "Ankare", "popup_opts": "Options", "advanced_props": "Advanced Properties", "event_props": "Events", "popup_props": "Popup Properties", "general_props": "General Properties", "advanced_tab": "Advanced", "events_tab": "Events", "popup_tab": "Popup", "general_tab": "General", list: "Link List", "is_external": "The URL you entered seems to be an external link. Do you want to add the required http:// prefix?", "is_email": "The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?", titlefield: "Title", target: "Mål", url: "Länkens URL", title: "Infoga/redigera länk", "link_list": "Link List", rtl: "Right to Left", ltr: "Left to Right", accesskey: "AccessKey", tabindex: "TabIndex", rev: "Relationship Target to Page", rel: "Relationship Page to Target", mime: "Target MIME Type", encoding: "Target Character Encoding", langcode: "Språkkod", "target_langcode": "Målspråk", width: "Bredd", height: "Höjd" }); \ No newline at end of file +tinyMCE.addI18n('sv.advlink_dlg',{"target_name":"M\u00e5lnamn",classes:"Klasser",style:"Stil",id:"Id","popup_position":"Position (x/y)",langdir:"Skriftriktning","popup_size":"Storlek","popup_dependent":"Beroende av (Mozilla/Firefox enbart)","popup_resizable":"Skalbart f\u00f6nster","popup_location":"Adressraden","popup_menubar":"Menyrad","popup_toolbar":"Verktygsf\u00e4lt","popup_statusbar":"Statusf\u00e4lt","popup_scrollbars":"Rullningslister","popup_return":"Infoga \'return false\'","popup_name":"F\u00f6nsternamn","popup_url":"Popup URL",popup:"Javascript popup","target_blank":"\u00d6ppna i nytt f\u00f6nster","target_top":"\u00d6ppna i toppramen (ers\u00e4tter alla ramar)","target_parent":"\u00d6ppna i \u00f6verliggande f\u00f6nster/ram","target_same":"\u00d6ppna i detta f\u00f6nster/ram","anchor_names":"Bokm\u00e4rken","popup_opts":"Inst\u00e4llningar","advanced_props":"Avancerade inst\u00e4llningar","event_props":"H\u00e4ndelser","popup_props":"Popup-inst\u00e4llningar","general_props":"Generella inst\u00e4llningar","advanced_tab":"Avancerat","events_tab":"H\u00e4ndelser","popup_tab":"Popup","general_tab":"Generellt",list:"L\u00e4nklista","is_external":"L\u00e4nken du angav verkar vara en extern adress. Vill du infoga http:// prefixet p\u00e5 l\u00e4nken?","is_email":"L\u00e4nken du angav verkar vara en e-post adress. Vill du infoga mailto: prefixet p\u00e5 l\u00e4nken?",titlefield:"Titel",target:"M\u00e5l",url:"L\u00e4nkens URL",title:"Infoga/redigera l\u00e4nk","link_list":"L\u00e4nklista",rtl:"H\u00f6ger till v\u00e4nster",ltr:"V\u00e4nster till h\u00f6ger",accesskey:"Snabbtangent",tabindex:"Tabbindex",rev:"Omv\u00e4nd relation (rev)",rel:"Relation (rel attribut)",mime:"MIME type",encoding:"Teckenformattering",langcode:"Spr\u00e5kkod","target_langcode":"M\u00e5lspr\u00e5k",width:"Bredd",height:"H\u00f6jd"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/zh_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/zh_dlg.js index bec669b5f2..fb228f5942 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/zh_dlg.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/zh_dlg.js @@ -1 +1 @@ -tinyMCE.addI18n('zh.advlink_dlg', { "target_name": "目标名称", classes: "类", style: "样式", id: "ID", "popup_position": "位置 (X/Y)", langdir: "语言书写方向", "popup_size": "尺寸", "popup_dependent": "依赖 (仅限Mozilla/Firefox)", "popup_resizable": "窗口大小可调", "popup_location": "显示地址栏", "popup_menubar": "显示菜单栏", "popup_toolbar": "显示工具栏", "popup_statusbar": "显示状态栏", "popup_scrollbars": "显示滚动条", "popup_return": "插入 \'return false\'", "popup_name": "窗口名称", "popup_url": "URL", popup: "JavaScript 弹出窗口", "target_blank": "在新窗口中打开", "target_top": "在顶部位置打开(替换掉所有Frames)", "target_parent": "在父级窗口或位置中打开", "target_same": "在该窗口或位置打开", "anchor_names": "锚点", "popup_opts": "选项", "advanced_props": "高级属性", "event_props": "事件", "popup_props": "弹窗属性", "general_props": "普通属性", "advanced_tab": "高级", "events_tab": "事件", "popup_tab": "弹窗", "general_tab": "普通", list: "链接列表", "is_external": "您输入的好像是外部链接,您是否想在前面添加http://?", "is_email": "您输入的好像是邮箱地址,您是否想在前面添加mailto:?", titlefield: "标题", target: "目标", url: "链接URL", title: "插入/编辑链接", "link_list": "链接列表", rtl: "从右到左", ltr: "从左到右", accesskey: "访问键", tabindex: "Tab索引", rev: "目标至页面关系", rel: "页面至目标关系", mime: "目标MIME类型", encoding: "目标语言编码", langcode: "语言代码", "target_langcode": "目标语言", width: "宽", height: "高" }); \ No newline at end of file +tinyMCE.addI18n('zh-cn.advlink_dlg',{"target_name":"\u76ee\u6807\u540d\u79f0",classes:"\u7c7b\u522b",style:"\u6837\u5f0f",id:"ID","popup_position":"\u4f4d\u7f6e(X/Y)",langdir:"\u8bed\u8a00\u4e66\u5199\u65b9\u5411","popup_size":"\u5927\u5c0f","popup_dependent":"\u9650\u5236(\u4ec5\u652f\u6301Mozilla/Firefox)","popup_resizable":"\u7a97\u53e3\u53ef\u8c03\u6574\u5927\u5c0f","popup_location":"\u663e\u793a\u5730\u5740\u680f","popup_menubar":"\u663e\u793a\u83dc\u5355\u680f","popup_toolbar":"\u663e\u793a\u5de5\u5177\u680f","popup_statusbar":"\u663e\u793a\u72b6\u6001\u680f","popup_scrollbars":"\u663e\u793a\u6eda\u52a8\u6761","popup_return":"\u63d2\u5165\'return false\'","popup_name":"\u7a97\u53e3\u540d\u79f0","popup_url":"\u5f39\u51faURL",popup:"Javascript\u5f39\u7a97","target_blank":"\u5728\u65b0\u7a97\u53e3\u6253\u5f00","target_top":"\u5728\u9876\u90e8\u6846\u67b6\u6253\u5f00\uff08\u91cd\u7f6e\u6240\u6709\u6846\u67b6\uff09","target_parent":"\u5728\u7236\u7a97\u53e3/\u6846\u67b6\u6253\u5f00","target_same":"\u5728\u5f53\u524d\u7a97\u53e3/\u6846\u67b6\u6253\u5f00","anchor_names":"\u951a","popup_opts":"\u9009\u9879","advanced_props":"\u9ad8\u7ea7\u5c5e\u6027","event_props":"\u4e8b\u4ef6","popup_props":"\u5f39\u51fa\u5c5e\u6027","general_props":"\u666e\u901a\u5c5e\u6027","advanced_tab":"\u9ad8\u7ea7","events_tab":"\u4e8b\u4ef6","popup_tab":"\u5f39\u51fa","general_tab":"\u666e\u901a",list:"\u94fe\u63a5\u5217\u8868","is_external":"\u60a8\u8f93\u5165\u7684URL\u662f\u4e00\u4e2a\u5916\u90e8\u94fe\u63a5\uff0c\u662f\u5426\u8981\u52a0\u4e0a\"http://\"\u524d\u7f00\uff1f","is_email":"\u60a8\u8f93\u5165URL\u662f\u7535\u5b50\u90ae\u4ef6\u5730\u5740\uff0c\u662f\u5426\u9700\u8981\u52a0\"mailto:\"\u524d\u7f00\uff1f",titlefield:"\u6807\u9898",target:"\u6253\u5f00\u65b9\u5f0f",url:"\u8d85\u94fe\u63a5URL",title:"\u63d2\u5165/\u7f16\u8f91 \u8d85\u94fe\u63a5","link_list":"\u94fe\u63a5\u5217\u8868",rtl:"\u4ece\u53f3\u5230\u5de6",ltr:"\u4ece\u5de6\u5230\u53f3",accesskey:"\u5feb\u6377\u952e",tabindex:"Tab\u7d22\u5f15",rev:"\u76ee\u6807\u5230\u7f51\u9875\u7684\u5173\u7cfb",rel:"\u7f51\u9875\u5230\u76ee\u6807\u7684\u5173\u7cfb",mime:"\u76ee\u6807MIME\u7c7b\u578b",encoding:"\u76ee\u6807\u8bed\u8a00\u7f16\u7801",langcode:"\u8bed\u8a00\u7f16\u7801","target_langcode":"\u76ee\u6807\u8bed\u8a00",width:"Width",height:"Height"}); \ No newline at end of file From 3abd4df8bd27631a797b8108228cca2180e55e19 Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Wed, 16 Oct 2013 16:01:17 +0200 Subject: [PATCH 0028/1046] Enabled lists plugin in tinymce by default Fixes http://issues.umbraco.org/issue/U4-3023 --- src/Umbraco.Web.UI/config/tinyMceConfig.Release.config | 1 + src/Umbraco.Web.UI/config/tinyMceConfig.config | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI/config/tinyMceConfig.Release.config b/src/Umbraco.Web.UI/config/tinyMceConfig.Release.config index ae2aa171f9..579dc77ba1 100644 --- a/src/Umbraco.Web.UI/config/tinyMceConfig.Release.config +++ b/src/Umbraco.Web.UI/config/tinyMceConfig.Release.config @@ -208,6 +208,7 @@ inlinepopups noneditable table + lists umbracomacro umbracoimg advlink diff --git a/src/Umbraco.Web.UI/config/tinyMceConfig.config b/src/Umbraco.Web.UI/config/tinyMceConfig.config index 0a3982b343..2c3f2484f6 100644 --- a/src/Umbraco.Web.UI/config/tinyMceConfig.config +++ b/src/Umbraco.Web.UI/config/tinyMceConfig.config @@ -211,6 +211,7 @@ inlinepopups noneditable table + lists umbracomacro advlink From 1923b68594c8c9d2a37aa86d9ee81734abc088a2 Mon Sep 17 00:00:00 2001 From: yawka Date: Thu, 7 Nov 2013 12:36:35 +0200 Subject: [PATCH 0029/1046] Fix 2034, add check for whitespaces --- src/Umbraco.Core/IO/IOHelper.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs index 22dc0814d5..bb23cde56e 100644 --- a/src/Umbraco.Core/IO/IOHelper.cs +++ b/src/Umbraco.Core/IO/IOHelper.cs @@ -43,7 +43,10 @@ namespace Umbraco.Core.IO //Replaces tildes with the root dir public static string ResolveUrl(string virtualPath) { - if (virtualPath.StartsWith("~")) + if (string.IsNullOrWhiteSpace(virtualPath)) + return string.Empty; + + if (virtualPath.StartsWith("~")) return virtualPath.Replace("~", SystemDirectories.Root).Replace("//", "/"); else if (Uri.IsWellFormedUriString(virtualPath, UriKind.Absolute)) return virtualPath; From 451d5eb4843d321d70a541ad96748a3a3a4262b9 Mon Sep 17 00:00:00 2001 From: mkariti Date: Thu, 7 Nov 2013 14:40:26 +0200 Subject: [PATCH 0030/1046] U4-415 --- .../tinymce3/themes/umbraco/skins/umbraco/content.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/themes/umbraco/skins/umbraco/content.css b/src/Umbraco.Web.UI/umbraco_client/tinymce3/themes/umbraco/skins/umbraco/content.css index adb3483d5c..4cc9238ea7 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/themes/umbraco/skins/umbraco/content.css +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/themes/umbraco/skins/umbraco/content.css @@ -1,4 +1,4 @@ -body, td, pre {color:#000; font-family:Verdana, Arial, Helvetica, sans-serif; font-size:10px; margin:8px;} +body, td, pre {color:#000; font-family:Verdana, Arial, Helvetica, sans-serif; font-size:12px; margin:8px;} body {background:#FFF;} body.mceForceColors {background:#FFF; color:#000;} h1 {font-size: 2em} From e0da5a9cb29dcae56f51cdc96504ed132e7511a3 Mon Sep 17 00:00:00 2001 From: Simon Dingley Date: Thu, 7 Nov 2013 12:45:35 +0000 Subject: [PATCH 0031/1046] Groups jQuery dependencies using the ClientDependency Group property --- src/Umbraco.Web.UI/umbraco/umbraco.aspx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/umbraco.aspx b/src/Umbraco.Web.UI/umbraco/umbraco.aspx index 348a5a9143..9e2f51aa73 100644 --- a/src/Umbraco.Web.UI/umbraco/umbraco.aspx +++ b/src/Umbraco.Web.UI/umbraco/umbraco.aspx @@ -25,11 +25,11 @@ + Priority="0" Group="0" /> + Priority="1" Group="0" /> + PathNameAlias="UmbracoClient" Priority="2" Group="0" /> - + + PathNameAlias="UmbracoClient" Priority="10" Group="0" /> + PathNameAlias="UmbracoClient" Priority="1" Group="0" /> From a0106ff8111053b41c7dd89ea7f657b029030296 Mon Sep 17 00:00:00 2001 From: Craig Cronin Date: Thu, 7 Nov 2013 12:50:26 +0000 Subject: [PATCH 0032/1046] Fixed as per issue comments, but also implemented for another 3 overloads --- src/Umbraco.Web/HtmlHelperRenderExtensions.cs | 8 ++++---- src/umbraco.sln | 3 --- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs index 2a009dfaa2..f434b70656 100644 --- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs @@ -296,7 +296,7 @@ namespace Umbraco.Web object htmlAttributes, FormMethod method) { - return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, htmlAttributes.ToDictionary(), method); + return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes), method); } /// @@ -312,7 +312,7 @@ namespace Umbraco.Web object additionalRouteVals, object htmlAttributes) { - return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, htmlAttributes.ToDictionary()); + return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)); } /// @@ -480,7 +480,7 @@ namespace Umbraco.Web object htmlAttributes, FormMethod method) { - return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, htmlAttributes.ToDictionary(), method); + return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes), method); } /// @@ -496,7 +496,7 @@ namespace Umbraco.Web object additionalRouteVals, object htmlAttributes) { - return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, htmlAttributes.ToDictionary()); + return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)); } /// diff --git a/src/umbraco.sln b/src/umbraco.sln index 0b7902d964..ba7a350194 100644 --- a/src/umbraco.sln +++ b/src/umbraco.sln @@ -157,7 +157,4 @@ Global {73529637-28F5-419C-A6BB-D094E39DE614} = {DD32977B-EF54-475B-9A1B-B97A502C6E58} {B555AAE6-0F56-442F-AC9F-EF497DB38DE7} = {DD32977B-EF54-475B-9A1B-B97A502C6E58} EndGlobalSection - GlobalSection(Performance) = preSolution - HasPerformanceSessions = true - EndGlobalSection EndGlobal From baed01518cbc2e7fa4791a0d2aaccfee348882a7 Mon Sep 17 00:00:00 2001 From: Simon Dingley Date: Thu, 7 Nov 2013 13:21:01 +0000 Subject: [PATCH 0033/1046] Group jQueryTree script files --- .../umbraco/controls/Tree/TreeControl.ascx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/controls/Tree/TreeControl.ascx b/src/Umbraco.Web.UI/umbraco/controls/Tree/TreeControl.ascx index 41ac545d6f..fddfefe8c2 100644 --- a/src/Umbraco.Web.UI/umbraco/controls/Tree/TreeControl.ascx +++ b/src/Umbraco.Web.UI/umbraco/controls/Tree/TreeControl.ascx @@ -9,12 +9,12 @@ - - - + + + - - + + From 02e167c1f678720e5b82ae696e675ee30ca50e1f Mon Sep 17 00:00:00 2001 From: Tim Payne Date: Thu, 7 Nov 2013 14:20:47 +0000 Subject: [PATCH 0034/1046] Fixes issue U4-3063 where copying a document type uses the rename for the node alias, but not the title, resulting in two Doctypes with the same name, but different aliases, rather than different names AND aliases. --- .../umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs index ad8410da85..8987635447 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs @@ -146,8 +146,9 @@ namespace umbraco.dialogs var contentType = contentTypeService.GetContentType( int.Parse(Request.GetItemAsString("id"))); - var alias = rename.Text.Replace("'", "''"); + var alias = rename.Text.Trim().Replace("'", "''"); var clone = ((Umbraco.Core.Models.ContentType) contentType).Clone(alias); + clone.Name = rename.Text.Trim(); contentTypeService.Save(clone); var returnUrl = string.Format("{0}/settings/editNodeTypeNew.aspx?id={1}", SystemDirectories.Umbraco, clone.Id); From e9d12ae0d8b8457f97e7989e14e7e7fe4070dcc3 Mon Sep 17 00:00:00 2001 From: Simon Dingley Date: Thu, 7 Nov 2013 14:23:53 +0000 Subject: [PATCH 0035/1046] Revert "Group jQueryTree script files" This reverts commit baed01518cbc2e7fa4791a0d2aaccfee348882a7. --- .../umbraco/controls/Tree/TreeControl.ascx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/controls/Tree/TreeControl.ascx b/src/Umbraco.Web.UI/umbraco/controls/Tree/TreeControl.ascx index fddfefe8c2..41ac545d6f 100644 --- a/src/Umbraco.Web.UI/umbraco/controls/Tree/TreeControl.ascx +++ b/src/Umbraco.Web.UI/umbraco/controls/Tree/TreeControl.ascx @@ -9,12 +9,12 @@ - - - + + + - - + + From 7cd01fc50b4d744e8760995184fce07c3afc9439 Mon Sep 17 00:00:00 2001 From: mattbrailsford Date: Thu, 7 Nov 2013 14:35:44 +0000 Subject: [PATCH 0036/1046] Added ValidateRequest="false" on package installer page to allow user controls which postback html / code. --- src/Umbraco.Web.UI/umbraco/developer/Packages/installer.aspx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/umbraco/developer/Packages/installer.aspx b/src/Umbraco.Web.UI/umbraco/developer/Packages/installer.aspx index e271bf7413..d9d9896b30 100644 --- a/src/Umbraco.Web.UI/umbraco/developer/Packages/installer.aspx +++ b/src/Umbraco.Web.UI/umbraco/developer/Packages/installer.aspx @@ -1,6 +1,6 @@ <%@ Page Language="c#" MasterPageFile="../../masterpages/umbracoPage.Master" CodeBehind="installer.aspx.cs" AutoEventWireup="True" Inherits="umbraco.presentation.developer.packages.Installer" - Trace="false" %> + Trace="false" ValidateRequest="false" %> <%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> From 516beedbbbedc3a0c3815a5293a6ac4d4639fc39 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 7 Nov 2013 14:48:20 +0000 Subject: [PATCH 0037/1046] Fixes: U4-3452 Update the MIT License URL (Packager) --- src/umbraco.cms/businesslogic/Packager/data.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/umbraco.cms/businesslogic/Packager/data.cs b/src/umbraco.cms/businesslogic/Packager/data.cs index 6f0df33b4d..a950212c47 100644 --- a/src/umbraco.cms/businesslogic/Packager/data.cs +++ b/src/umbraco.cms/businesslogic/Packager/data.cs @@ -116,8 +116,8 @@ namespace umbraco.cms.businesslogic.packager instance.Attributes.Append(xmlHelper.addAttribute(Source, "skinRepoGuid", "")); XmlElement license = Source.CreateElement("license"); - license.InnerText = "MIT license"; - license.Attributes.Append(xmlHelper.addAttribute(Source, "url", "http://www.opensource.org/licenses/mit-license.php")); + license.InnerText = "MIT License"; + license.Attributes.Append(xmlHelper.addAttribute(Source, "url", "http://opensource.org/licenses/MIT")); instance.AppendChild(license); XmlElement author = Source.CreateElement("author"); From a5782a40afe0cc862c02657c42f95042cf882348 Mon Sep 17 00:00:00 2001 From: dipunm Date: Thu, 7 Nov 2013 14:59:00 +0000 Subject: [PATCH 0038/1046] Missing languages in RTE insert link dialog. Also included files in csproj file to ensure proper build. --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 9 +++++++++ .../tinymce3/plugins/umbracolink/langs/da_dlg.js | 1 + .../tinymce3/plugins/umbracolink/langs/de_dlg.js | 1 + .../tinymce3/plugins/umbracolink/langs/fi_dlg.js | 1 + .../tinymce3/plugins/umbracolink/langs/fr_dlg.js | 1 + .../tinymce3/plugins/umbracolink/langs/it_dlg.js | 1 + .../tinymce3/plugins/umbracolink/langs/nl_dlg.js | 1 + .../tinymce3/plugins/umbracolink/langs/no_dlg.js | 1 + .../tinymce3/plugins/umbracolink/langs/pl_dlg.js | 1 + .../tinymce3/plugins/umbracolink/langs/pt_dlg.js | 1 + 10 files changed, 18 insertions(+) create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/da_dlg.js create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/de_dlg.js create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/fi_dlg.js create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/fr_dlg.js create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/it_dlg.js create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/nl_dlg.js create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/no_dlg.js create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/pl_dlg.js create mode 100644 src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/pt_dlg.js diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index d717813eb2..dfcfcb9e2c 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -1337,10 +1337,19 @@ + + + + + + + + + diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/da_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/da_dlg.js new file mode 100644 index 0000000000..06f7fe3d83 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/da_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('da.advlink_dlg',{"target_name":"Destinationsnavn",classes:"Klasser",style:"Stil",id:"Id","popup_position":"Position (X/Y)",langdir:"Sprogretning","popup_size":"St\u00f8rrelse","popup_dependent":"Afh\u00e6ngig (Kun Mozilla/Firefox)","popup_resizable":"Lad det v\u00e6re muligt at \u00e6ndre st\u00f8rrelsen p\u00e5 vinduet","popup_location":"Vis adresselinje","popup_menubar":"Vis menulinje","popup_toolbar":"Vis v\u00e6rkt\u00f8jslinjer","popup_statusbar":"Vis statuslinje","popup_scrollbars":"Vis rullepanel","popup_return":"Inds\u00e6t \'return false\'","popup_name":"Vinduesnavn","popup_url":"Popup URL",popup:"Javascript popup","target_blank":"\u00c5ben i nyt vindue","target_top":"\u00c5ben i \u00f8verste vindue / ramme (erstatter alle rammer)","target_parent":"\u00c5ben i overliggende vindue / ramme","target_same":"\u00c5ben i dette vindue / ramme","anchor_names":"Ankre","popup_opts":"Indstillinger","advanced_props":"Avancerede egenskaber","event_props":"H\u00e6ndelser","popup_props":"Popup egenskaber","general_props":"Generelle egenskaber","advanced_tab":"Advanceret","events_tab":"H\u00e6ndelser","popup_tab":"Popup","general_tab":"Generelt",list:"Liste over links","is_external":"Den URL, der er indtastet, ser ud til at v\u00e6re et eksternt link. Vil du have tilf\u00f8jet det p\u00e5kr\u00e6vede http:// foran?","is_email":"Den URL, der er indtastet, ser ud til at v\u00e6re en emailadresse. Vil du have tilf\u00f8jet det p\u00e5kr\u00e6vede mailto: foran?",titlefield:"Titel",target:"M\u00e5l",url:"Link URL",title:"Inds\u00e6t/rediger link","link_list":"Liste over links",rtl:"H\u00f8jre mod venstre",ltr:"Venstre mod h\u00f8jre",accesskey:"Genvejstast",tabindex:"Tabindex",rev:"Relativ destination til side",rel:"Relativ side til destination",mime:"Destinations-MIME-type",encoding:"Destinationstegns\u00e6t",langcode:"Sprogkode","target_langcode":"Destinationssprog",width:"Bredde",height:"H\u00f8jde"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/de_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/de_dlg.js new file mode 100644 index 0000000000..bb0d3e35b3 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/de_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('de.advlink_dlg',{"target_name":"Name der Zielseite",classes:"Klassen",style:"Format",id:"ID","popup_position":"Position (X/Y)",langdir:"Schriftrichtung","popup_size":"Gr\u00f6\u00dfe","popup_dependent":"Vom Elternfenster abh\u00e4ngig
(nur Mozilla/Firefox) ","popup_resizable":"Vergr\u00f6\u00dfern des Fenster zulassen","popup_location":"Adressleiste anzeigen","popup_menubar":"Browsermen\u00fc anzeigen","popup_toolbar":"Werkzeugleisten anzeigen","popup_statusbar":"Statusleiste anzeigen","popup_scrollbars":"Scrollbalken anzeigen","popup_return":"Link trotz Popup folgen","popup_name":"Name des Fensters","popup_url":"Popup-Adresse",popup:"JavaScript-Popup","target_blank":"In neuem Fenster \u00f6ffnen","target_top":"Im obersten Frame \u00f6ffnen (sprengt das Frameset)","target_parent":"Im \u00fcbergeordneten Fenster/Frame \u00f6ffnen","target_same":"Im selben Fenster/Frame \u00f6ffnen","anchor_names":"Anker","popup_opts":"Optionen","advanced_props":"Erweiterte Eigenschaften","event_props":"Ereignisse","popup_props":"Popup-Eigenschaften","general_props":"Allemeine Eigenschaften","advanced_tab":"Erweitert","events_tab":"Ereignisse","popup_tab":"Popup","general_tab":"Allgemein",list:"Linkliste","is_external":"Diese Adresse scheint ein externer Link zu sein. M\u00f6chten Sie das dazu ben\u00f6tigte \"http://\" voranstellen?","is_email":"Diese Adresse scheint eine E-Mail-Adresse zu sein. M\u00f6chten Sie das dazu ben\u00f6tigte \"mailto:\" voranstellen?",titlefield:"Titel",target:"Fenster",url:"Adresse",title:"Link einf\u00fcgen/bearbeiten","link_list":"Linkliste",rtl:"Rechts nach links",ltr:"Links nach rechts",accesskey:"Tastenk\u00fcrzel",tabindex:"Tabindex",rev:"Beziehung des Linkziels zur Seite",rel:"Beziehung der Seite zum Linkziel",mime:"MIME-Type der Zielseite",encoding:"Zeichenkodierung der Zielseite",langcode:"Sprachcode","target_langcode":"Sprache der Zielseite",width:"Breite",height:"H\u00f6he"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/fi_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/fi_dlg.js new file mode 100644 index 0000000000..e49488e733 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/fi_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('fi.advlink_dlg',{"target_name":"Kohteen nimi",classes:"Luokat",style:"Tyyli",id:"Id","popup_position":"Sijainti (X/Y)",langdir:"Kielen suunta","popup_size":"Koko","popup_dependent":"Riippuvainen (vain Mozilla/Firefox)","popup_resizable":"Tee ikkunan koko muokattavaksi","popup_location":"N\u00e4yt\u00e4 sijaintipalkki","popup_menubar":"N\u00e4yt\u00e4 valikkopalkki","popup_toolbar":"N\u00e4yt\u00e4 ty\u00f6kalut","popup_statusbar":"N\u00e4yt\u00e4 tilapalkki","popup_scrollbars":"N\u00e4yt\u00e4 vierityspalkit","popup_return":"Lis\u00e4\u00e4 \'return false\'","popup_name":"Ikkunan nimi","popup_url":"Ponnahdusikkunan URL",popup:"JavaScript-ponnahdusikkuna","target_blank":"Avaa uudessa ikkunassa","target_top":"Avaa ylimm\u00e4ss\u00e4 ruudussa (korvaa kaikki ruudut)","target_parent":"Avaa ylemm\u00e4ss\u00e4 ikkunassa","target_same":"Avaa t\u00e4ss\u00e4 ikkunassa","anchor_names":"Ankkurit","popup_opts":"Valinta","advanced_props":"Edistyneet asetukset","event_props":"Tapahtumat (events)","popup_props":"Ponnahdusikkunan asetukset","general_props":"Yleiset asetukset","advanced_tab":"Edistynyt","events_tab":"Tapahtumat","popup_tab":"Ponnahdusikkuna","general_tab":"Yleiset",list:"Linkkilista","is_external":"Sy\u00f6tt\u00e4m\u00e4si URL n\u00e4ytt\u00e4\u00e4 olevan sivuston ulkoinen osoite, haluatko lis\u00e4t\u00e4 http://-etuliitteen?","is_email":"Sy\u00f6tt\u00e4m\u00e4si URL n\u00e4ytt\u00e4\u00e4 olevan s\u00e4hk\u00f6postiosoite, haluatko lis\u00e4t\u00e4 mailto:-etuliitteen?",titlefield:"Otsikko",target:"Kohde (target)",url:"Linkin URL",title:"Lis\u00e4\u00e4/muokkaa linkki\u00e4","link_list":"Linkkilista",rtl:"Oikealta vasemmalle",ltr:"Vasemmalta oikealle",accesskey:"Pikan\u00e4pp\u00e4in",tabindex:"Tabulaattori-indeksi",rev:"Kohteen suhde sivuun",rel:"Sivun suhde kohteeseen",mime:"Kohteen MIME-tyyppi",encoding:"Kohteen merkist\u00f6koodaus",langcode:"Kielen koodi","target_langcode":"Kohteen kieli",width:"Leveys",height:"Korkeus"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/fr_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/fr_dlg.js new file mode 100644 index 0000000000..38e5a7858f --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/fr_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('fr.advlink_dlg',{"target_name":"Nom de la cible",classes:"Classes",style:"Style",id:"Id","popup_position":"Position (X/Y)",langdir:"Sens de lecture","popup_size":"Taille","popup_dependent":"D\u00e9pendante (seulement sous Mozilla/Firefox)","popup_resizable":"Autoriser le redimensionnement de la fen\u00eatre","popup_location":"Afficher la barre d\'adresse","popup_menubar":"Afficher la barre de menu","popup_toolbar":"Afficher la barre d\'outils","popup_statusbar":"Afficher la barre d\'\u00e9tat","popup_scrollbars":"Afficher les ascenseurs","popup_return":"Ins\u00e9rer \'return false\'","popup_name":"Nom de la fen\u00eatre","popup_url":"URL de la popup",popup:"Popup Javascript","target_blank":"Ouvrir dans une nouvelle fen\u00eatre","target_top":"Ouvrir dans le cadre principal (remplace tous les cadres)","target_parent":"Ouvrir dans la fen\u00eatre / le cadre parent","target_same":"Ouvrir dans cette fen\u00eatre / dans ce cadre","anchor_names":"Ancres","popup_opts":"Options","advanced_props":"Propri\u00e9t\u00e9s avanc\u00e9es","event_props":"\u00c9v\u00e8nements","popup_props":"Propri\u00e9t\u00e9s de la popup","general_props":"Propri\u00e9t\u00e9s g\u00e9n\u00e9rales","advanced_tab":"Avanc\u00e9","events_tab":"\u00c9v\u00e8nements","popup_tab":"Popup","general_tab":"G\u00e9n\u00e9ral",list:"Liste de liens","is_external":"L\'URL que vous avez saisie semble \u00eatre une adresse web externe. Souhaitez-vous ajouter le pr\u00e9fixe \u00ab http:// \u00bb ?","is_email":"L\'URL que vous avez saisie semble \u00eatre une adresse e-mail, souhaitez-vous ajouter le pr\u00e9fixe \u00ab mailto: \u00bb ?",titlefield:"Titre",target:"Cible",url:"URL du lien",title:"Ins\u00e9rer / \u00e9diter un lien","link_list":"Liste des liens",rtl:"Droite \u00e0 gauche",ltr:"Gauche \u00e0 droite",accesskey:"Touche d\'acc\u00e8s rapide",tabindex:"Tabindex",rev:"Relation de la cible \u00e0 la page",rel:"Relation de la page \u00e0 la cible",mime:"Type MIME de la cible",encoding:"Encodage de la cible",langcode:"Code de la langue","target_langcode":"Langue de la cible",width:"Largeur",height:"Hauteur"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/it_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/it_dlg.js new file mode 100644 index 0000000000..bf19659d05 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/it_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('it.advlink_dlg',{"target_name":"Nome target",classes:"Classe",style:"Stile",id:"Id","popup_position":"Posizione (X/Y)",langdir:"Direzione del testo","popup_size":"Dimensioni","popup_dependent":"Dipendente (Solo in Mozilla/Firefox)","popup_resizable":"Rendi la finestra ridimensionabile","popup_location":"Mostra barra navigazione","popup_menubar":"Mostra barra menu","popup_toolbar":"Mostra barre strumenti","popup_statusbar":"Mostra barra di stato","popup_scrollbars":"Mostra barre di scorrimento","popup_return":"Inserisci \'return false\'","popup_name":"Nome finestra","popup_url":"URL Popup",popup:"Popup Javascript","target_blank":"Apri in una nuova finestra","target_top":"Apri nella cornice superiore (sostituisce tutte le cornici)","target_parent":"Apri nella finestra / cornice genitore","target_same":"Apri in questa finestra / cornice","anchor_names":"Ancore","popup_opts":"Opzioni","advanced_props":"Propriet\u00e0 avanzate","event_props":"Eventi","popup_props":"Propriet\u00e0 popup","general_props":"Propriet\u00e0 generali","advanced_tab":"Avanzate","events_tab":"Eventi","popup_tab":"Popup","general_tab":"Generale",list:"Lista collegamenti","is_external":"L\'URL inserito sembra essere un link esterno. Aggiungere il necessario prefisso http:// ?","is_email":"L\'URL inserito sembra essere un indirizzo email. Aggiungere il necessario prefisso mailto: ?",titlefield:"Titolo",target:"Target",url:"URL collegamento",title:"Inserisci/modifica link","link_list":"Lista collegamenti",rtl:"Destra verso sinistra",ltr:"Sinistra verso destra",accesskey:"Carattere di accesso",tabindex:"Indice tabulazione",rev:"Relazione da target a pagina",rel:"Relazione da pagina a target",mime:"Tipo MIME del target",encoding:"Codifica carattere del target",langcode:"Lingua","target_langcode":"Lingua del target",width:"Larghezza",height:"Altezza"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/nl_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/nl_dlg.js new file mode 100644 index 0000000000..b2924758b0 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/nl_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('nl.advlink_dlg',{"target_name":"Doel",classes:"Klasses",style:"Stijl",id:"Id","popup_position":"Positie (X/Y)",langdir:"Taalrichting","popup_size":"Grootte","popup_dependent":"Afhankelijk (Alleen Mozilla/Firefox)","popup_resizable":"Aanpasbaar venster","popup_location":"Lokatiebalk weergeven","popup_menubar":"Menubalk weergeven","popup_toolbar":"Werkbalk weergeven","popup_statusbar":"Statusbalk weergeven","popup_scrollbars":"Scrollbalken weergeven","popup_return":"\'return false\' invoegen","popup_name":"Vensternaam","popup_url":"Popup URL",popup:"Javascript popup","target_blank":"In nieuw venster openen","target_top":"In bovenste frame openen (vervangt gehele pagina)","target_parent":"In bovenliggend venster / frame openen","target_same":"In dit venster / frame openen","anchor_names":"Ankers","popup_opts":"Opties","advanced_props":"Geavanceerde eigenschappen","event_props":"Gebeurtenissen","popup_props":"Popup eigenschappen","general_props":"Algemene eigenschappen","advanced_tab":"Geavanceerd","events_tab":"Gebeurtenissen","popup_tab":"Popup","general_tab":"Algemeen",list:"Lijst","is_external":"De ingevoerde URL lijkt op een externe link. Wilt u de vereiste http:// tekst voorvoegen?","is_email":"De ingevoerde URL lijkt op een e-mailadres. Wilt u de vereiste mailto: tekst voorvoegen?",titlefield:"Titel",target:"Doel",url:"URL",title:"Link invoegen/bewerken","link_list":"Lijst",rtl:"Van rechts naar links",ltr:"Van links naar rechts",accesskey:"Toegangstoets",tabindex:"Tabvolgorde",rev:"Relatie van doel tot pagina",rel:"Relatie van pagina tot doel",mime:"MIME type",encoding:"Taalcodering",langcode:"Taalcode","target_langcode":"Taal",width:"Breedte",height:"Hoogte"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/no_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/no_dlg.js new file mode 100644 index 0000000000..1a333095d3 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/no_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('no.advlink_dlg',{"target_name":"M\u00e5lnavn",classes:"Klasse",style:"Stil",id:"Id","popup_position":"Posisjon (X/Y)",langdir:"Skriftretning","popup_size":"St\u00f8rrelse","popup_dependent":"Avhengig vindu (kun i Mozilla/Firefox)","popup_resizable":"Gj\u00f8r vinduet skalerbart","popup_location":"Vis plasseringslinje","popup_menubar":"Vis menylinje","popup_toolbar":"Vis verkt\u00f8ylinjer","popup_statusbar":"Vis statusline","popup_scrollbars":"Vis rullefelt","popup_return":"Sett inn \\\'return false\\\'","popup_name":"Navn p\u00e5 vindu","popup_url":"Popup URL",popup:"Javascript popup","target_blank":"\u00c5pne i nytt vindu","target_top":"\u00c5pne i toppvindu (erstatter alle rammer)","target_parent":"\u00c5pne i overordnet vindu/ramme","target_same":"\u00c5pne i samme vindu/ramme","anchor_names":"Anker","popup_opts":"Innstillinger","advanced_props":"Avanserte egenskaper","event_props":"Hendelser","popup_props":"Popupegenskaper","general_props":"Generelle egenskaper","advanced_tab":"Avansert","events_tab":"Hendelser","popup_tab":"Popup","general_tab":"Generelt",list:"Liste over lenker","is_external":"URLen du skrev inn ser ut til \u00e5 v\u00e6re en ekstern lenke. \u00d8nsker du \u00e5 legge til obligatorisk http://-prefiks?","is_email":"URLen du skrev inn ser ut til \u00e5 v\u00e6re Epost adresse. \u00d8nsker du \u00e5 legge til obligatorisk mailto:-prefiks?",titlefield:"Tittel",target:"M\u00e5l",url:"Lenke URL",title:"Sett inn / rediger lenke","link_list":"Liste over lenker",rtl:"H\u00f8yre mot venstre",ltr:"Venstre mot h\u00f8yre",accesskey:"Hurtigtast",tabindex:"Tabulatorindeks",rev:"Forholdet mellom m\u00e5l og side",rel:"Forholdet mellom side og m\u00e5l",mime:"M\u00e5l MIME type",encoding:"M\u00e5l karakter koding",langcode:"Spr\u00e5kkode","target_langcode":"M\u00e5lspr\u00e5k",width:"Bredde",height:"H\u00f8yde"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/pl_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/pl_dlg.js new file mode 100644 index 0000000000..d529d7ad17 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/pl_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('pl.advlink_dlg',{"target_name":"Nazwa celu",classes:"Klasy",style:"Styl",id:"Id","popup_position":"Pozycja (X/Y)",langdir:"Kierunek czytania tekstu","popup_size":"Rozmiar","popup_dependent":"Zale\u017cny (Mozilla/Firefox wy\u0142\u0105cznie)","popup_resizable":"Stw\u00f3rz okno z mo\u017cliwo\u015bci\u0105 zmiany rozmiaru","popup_location":"Poka\u017c pasek adresu","popup_menubar":"Poka\u017c pasek menu","popup_toolbar":"Poka\u017c narz\u0119dzia","popup_statusbar":"Poka\u017c pasek statusu","popup_scrollbars":"Poka\u017c paski przewijania","popup_return":"Wstaw \'return false\'","popup_name":"Nazwa okna","popup_url":"URL okna",popup:"Wyskakuj\u0105ce okno","target_blank":"Otw\u00f3rz w nowym oknie","target_top":"Otw\u00f3rz w g\u00f3rnej ramce (zamie\u0144 wszystkie ramki)","target_parent":"Otw\u00f3rz w nadrz\u0119dnym oknie / ramce","target_same":"Otw\u00f3rz w tym oknie / ramce","anchor_names":"Kotwice","popup_opts":"Opcje","advanced_props":"Zaawansowae w\u0142a\u015bciwo\u015bci","event_props":"Zdarzenia","popup_props":"W\u0142a\u015bciwo\u015bci okna","general_props":"W\u0142a\u015bciwo\u015bci og\u00f3lne","advanced_tab":"Zaawansowane","events_tab":"Zdarzenia","popup_tab":"Popup","general_tab":"Og\u00f3lne",list:"Lista link\u00f3w","is_external":"Podany adres wydaje si\u0119 by\u0107 zewn\u0119trznym linkiem, czy chcesz doda\u0107 wymagany prefiks http://?","is_email":"Podany adres wydaje si\u0119 by\u0107 adresem emailowym, czy chcesz doda\u0107 wymagany prefiks mailto:?",titlefield:"Tytu\u0142",target:"Cel",url:"URL linka",title:"Wstaw/edytuj link","link_list":"Lista odno\u015bnik\u00f3w",rtl:"Kierunek z prawej do lewej",ltr:"Kierunek z lewej do prawej",accesskey:"Klawisz skr\u00f3tu",tabindex:"Numer tab",rev:"Relacje celu do strony",rel:"Relacje strony do celu",mime:"Docelowy typ MIME",encoding:"Kodowanie znak\u00f3w celu",langcode:"Kod j\u0119zyka","target_langcode":"Docelowy kod j\u0119zyka",width:"Szeroko\u015b\u0107",height:"Wysoko\u015b\u0107"}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/pt_dlg.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/pt_dlg.js new file mode 100644 index 0000000000..8167855442 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/umbracolink/langs/pt_dlg.js @@ -0,0 +1 @@ +tinyMCE.addI18n('pt.advlink_dlg',{"target_name":"Nome do alvo",classes:"Classes",style:"Estilo",id:"Id","popup_position":"Posi\u00e7\u00e3o (X/Y)",langdir:"Dire\u00e7\u00e3o do texto","popup_size":"Tamanho","popup_dependent":"Dependente (Mozilla/Firefox apenas)","popup_resizable":"Permitir altera\u00e7\u00e3o do tamanho da janela","popup_location":"Mostrar a barra de endere\u00e7os","popup_menubar":"Mostrar a barra de menu","popup_toolbar":"Mostrar a barra de ferramentas","popup_statusbar":"Mostrar a barra de status","popup_scrollbars":"Mostrar as barras de scroll","popup_return":"Inserir \"return false\"","popup_name":"Nome da janela","popup_url":"URL do popup",popup:"Popup javascript","target_blank":"Abrir numa nova janela","target_top":"Abrir na p\u00e1gina inteira (substitui todos os quadros)","target_parent":"Abrir na janela/quadro pai","target_same":"Abrir nesta janela/quadro","anchor_names":"\u00c2ncoras","popup_opts":"Op\u00e7\u00f5es","advanced_props":"Propriedades avan\u00e7adas","event_props":"Eventos","popup_props":"Propriedades de popup","general_props":"Propriedades gerais","advanced_tab":"Avan\u00e7ado","events_tab":"Eventos","popup_tab":"Popup","general_tab":"Geral",list:"Lista de hyperlinks","is_external":"A URL digitada parece conduzir a um link externo. Deseja acrescentar o prefixo necess\u00e1rio http://?","is_email":"A URL digitada parece ser um endere\u00e7o de e-mail. Deseja acrescentar o prefixo necess\u00e1rio mailto:?",titlefield:"T\u00edtulo",target:"Alvo",url:"URL do hyperlink",title:"Inserir/editar hyperlink","link_list":"Lista de hyperlinks",rtl:"Da direita para a esquerda",ltr:"Da esquerda para a direita",accesskey:"Chave de acesso",tabindex:"Tabindex",rev:"Rela\u00e7\u00e3o alvo/p\u00e1gina",rel:"Rela\u00e7\u00e3o p\u00e1gina/alvo",mime:"Tipo MIME alvo",encoding:"Codifica\u00e7\u00e3o de caracteres",langcode:"C\u00f3digo do idioma","target_langcode":"Idioma alvo",width:"Largura",height:"Altura"}); \ No newline at end of file From 8e89d1314264d54e4ef38900d3e13a79ad8020c5 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 7 Nov 2013 15:41:34 +0000 Subject: [PATCH 0039/1046] Fixes: U4-2006 Domain.GetDomain(string DomainName) is case-sensitive --- src/umbraco.cms/businesslogic/web/Domain.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/umbraco.cms/businesslogic/web/Domain.cs b/src/umbraco.cms/businesslogic/web/Domain.cs index f5bcb46090..62ec9f3128 100644 --- a/src/umbraco.cms/businesslogic/web/Domain.cs +++ b/src/umbraco.cms/businesslogic/web/Domain.cs @@ -177,7 +177,7 @@ namespace umbraco.cms.businesslogic.web public static Domain GetDomain(string DomainName) { - return GetDomains().FirstOrDefault(d => d.Name == DomainName); + return GetDomains().FirstOrDefault(d => d.Name.InvariantEquals(DomainName)); } public static int GetRootFromDomain(string DomainName) From 62a7e0acd01cdd9c28e503d1800cad80e7ab3d48 Mon Sep 17 00:00:00 2001 From: "adang@thecogworks.co.uk" Date: Thu, 7 Nov 2013 15:44:55 +0000 Subject: [PATCH 0040/1046] Replaced HttpContext.Current.Cache with HttpRuntime.Cache --- .../umbraco.presentation/umbraco/uQuery/uQuery-Nodes.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/uQuery-Nodes.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/uQuery-Nodes.cs index 30dbe63c58..5fbb56a5a9 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/uQuery-Nodes.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/uQuery-Nodes.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Web; using System.Xml.XPath; -using umbraco; using umbraco.NodeFactory; using Umbraco.Core; @@ -37,17 +36,17 @@ namespace umbraco XPathExpression xPathExpression; // Check to see if XPathExpression is in the cache - if (HttpContext.Current.Cache[xpath] == null) - { + if (HttpRuntime.Cache[xpath] == null) + { // Build Compiled XPath expression xPathExpression = xPathNavigator.Compile(xpath); // Store in Cache - HttpContext.Current.Cache[xpath] = xPathExpression; + HttpRuntime.Cache[xpath] = xPathExpression; } else // Get from Cache { - xPathExpression = (XPathExpression)HttpContext.Current.Cache[xpath]; + xPathExpression = (XPathExpression)HttpRuntime.Cache[xpath]; } // [LK] Interested in exploring options to call custom extension methods in XPath expressions. From f16aa38fff1f0be192b3a5961c2486caa46dd89a Mon Sep 17 00:00:00 2001 From: Tim Payne Date: Thu, 7 Nov 2013 15:51:58 +0000 Subject: [PATCH 0041/1046] Fixes issue U4-2757 where generic properties are not ordered correctly. --- .../umbraco.presentation/umbraco/controls/ContentControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentControl.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentControl.cs index 589682a18c..f334110c8f 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentControl.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentControl.cs @@ -178,7 +178,7 @@ namespace umbraco.controls //if the property is not in a tab, add it to the general tab var props = _content.GenericProperties; - foreach (Property p in props) + foreach (Property p in props.OrderBy(x => x.PropertyType.SortOrder)) { if (inTab[p.PropertyType.Id.ToString()] == null) AddControlNew(p, tpProp, ui.Text("general", "properties", null)); From 347d41c8414588221c8dd847c730454a7dff348a Mon Sep 17 00:00:00 2001 From: Tim Payne Date: Thu, 7 Nov 2013 16:06:16 +0000 Subject: [PATCH 0042/1046] Fixes issue U4-2760, and makes the installed packages list by name in alphabetical order to make it easier to see what's installed. --- .../umbraco.presentation/umbraco/Trees/loadPackages.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadPackages.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadPackages.cs index cbdc3f0a90..cfd3777ba0 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadPackages.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadPackages.cs @@ -55,7 +55,7 @@ namespace umbraco .OrderByDescending(x => Version.TryParse(x.Data.Version, out v) ? v : new Version()) .GroupBy(x => x.Data.Name) .Select(x => x.First()) - .OrderBy(x => x.Data.Id); + .OrderBy(x => x.Data.Name); foreach (var p in uniquePackages) { var xNode = XmlTreeNode.Create(this); From d963917556663bef4da155381c0db2248f010aca Mon Sep 17 00:00:00 2001 From: mattbrailsford Date: Thu, 7 Nov 2013 16:30:23 +0000 Subject: [PATCH 0043/1046] Added SortOrder to XML for document types (used by export) Added ability for doctype importer to set the sort order if a sort order element is available --- src/Umbraco.Core/Services/PackagingService.cs | 9 +++++++++ src/umbraco.cms/businesslogic/web/DocumentType.cs | 1 + 2 files changed, 10 insertions(+) diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index efa6ae9fe6..c33a0235f4 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -586,9 +586,18 @@ namespace Umbraco.Core.Services { var id = tab.Element("Id").Value;//Do we need to use this for tracking? var caption = tab.Element("Caption").Value; + if (contentType.PropertyGroups.Contains(caption) == false) { contentType.AddPropertyGroup(caption); + + int sortOrder; + if(tab.Element("SortOrder") != null && + int.TryParse(tab.Element("SortOrder").Value, out sortOrder)) + { + // Override the sort order with the imported value + contentType.PropertyGroups[caption].SortOrder = sortOrder; + } } } } diff --git a/src/umbraco.cms/businesslogic/web/DocumentType.cs b/src/umbraco.cms/businesslogic/web/DocumentType.cs index 31d5d89e14..154e374c04 100644 --- a/src/umbraco.cms/businesslogic/web/DocumentType.cs +++ b/src/umbraco.cms/businesslogic/web/DocumentType.cs @@ -547,6 +547,7 @@ namespace umbraco.cms.businesslogic.web var tabx = xd.CreateElement("Tab"); tabx.AppendChild(XmlHelper.AddTextNode(xd, "Id", propertyTypeGroup.Id.ToString())); tabx.AppendChild(XmlHelper.AddTextNode(xd, "Caption", propertyTypeGroup.Name)); + tabx.AppendChild(XmlHelper.AddTextNode(xd, "SortOrder", propertyTypeGroup.SortOrder.ToString())); tabs.AppendChild(tabx); } } From 807df434f63ec3341fe909592bd2c47c4cff98e4 Mon Sep 17 00:00:00 2001 From: yawka Date: Thu, 7 Nov 2013 18:39:02 +0200 Subject: [PATCH 0044/1046] Add validation for regular expression property --- src/Umbraco.Core/Models/PropertyType.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index 3b4915862a..cfeb9e832d 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -361,8 +361,16 @@ namespace Umbraco.Core.Models //Check against Regular Expression for Legacy DataTypes - Validation exists and value is not null: if(string.IsNullOrEmpty(ValidationRegExp) == false && (value != null && string.IsNullOrEmpty(value.ToString()) == false)) { - var regexPattern = new Regex(ValidationRegExp); - return regexPattern.IsMatch(value.ToString()); + try + { + var regexPattern = new Regex(ValidationRegExp); + return regexPattern.IsMatch(value.ToString()); + } + catch + { + throw new Exception(string .Format("Invalid validation expression on property {0}",this.Alias)); + } + } //TODO Add PropertyEditor validation when its relevant to introduce From e0a10080b1d69eb1e701d187e6a3f571facc2731 Mon Sep 17 00:00:00 2001 From: yawka Date: Thu, 7 Nov 2013 18:39:27 +0200 Subject: [PATCH 0045/1046] Add UI validation for regular expression property --- .../GenericProperties/GenericProperty.ascx | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/controls/GenericProperties/GenericProperty.ascx b/src/Umbraco.Web.UI/umbraco/controls/GenericProperties/GenericProperty.ascx index 4cdad45578..bd20a7d269 100644 --- a/src/Umbraco.Web.UI/umbraco/controls/GenericProperties/GenericProperty.ascx +++ b/src/Umbraco.Web.UI/umbraco/controls/GenericProperties/GenericProperty.ascx @@ -25,33 +25,34 @@ Edit "" - - + + - + - + - + - + - +
+
Search for a regular expression
- +
@@ -69,4 +70,13 @@ duplicatePropertyNameAsSafeAlias('<%=tbName.ClientID%>', '<%=tbAlias.ClientID%>'); } }); + function ValidateValidation(sender, args) { + try { + var patt = new RegExp(args.Value); + args.IsValid = true; + + } catch (e) { + args.IsValid = false; + } + } \ No newline at end of file From d227aa19b83a209e379ac3677afa796dc8cdb75a Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Thu, 7 Nov 2013 17:56:12 +0100 Subject: [PATCH 0046/1046] Leaving the original internal method intact --- src/Umbraco.Core/Persistence/PetaPocoExtensions.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs b/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs index 7ae81aaa16..2c204daba8 100644 --- a/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs +++ b/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs @@ -336,6 +336,14 @@ namespace Umbraco.Core.Persistence creation.UninstallDatabaseSchema(); } + internal static void CreateDatabaseSchemaDo(this Database db, bool guardConfiguration) + { + if (guardConfiguration && ApplicationContext.Current.IsConfigured) + throw new Exception("Umbraco is already configured!"); + + CreateDatabaseSchemaDo(db); + } + internal static void CreateDatabaseSchemaDo(this Database db) { NewTable += PetaPocoExtensions_NewTable; From 9fa17ef6cb2839ae489af91e4712d996fff47d52 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Thu, 7 Nov 2013 17:56:38 +0100 Subject: [PATCH 0047/1046] Initial datalayer refactoring work on the legacy User class --- src/umbraco.businesslogic/User.cs | 149 +++++++++++++++--------------- 1 file changed, 77 insertions(+), 72 deletions(-) diff --git a/src/umbraco.businesslogic/User.cs b/src/umbraco.businesslogic/User.cs index 5da83d2a6d..f6772c9575 100644 --- a/src/umbraco.businesslogic/User.cs +++ b/src/umbraco.businesslogic/User.cs @@ -4,6 +4,7 @@ using System.Web.Caching; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Logging; +using Umbraco.Core.Models.Rdbms; using umbraco.DataLayer; using System.Collections.Generic; using System.Linq; @@ -32,6 +33,7 @@ namespace umbraco.BusinessLogic private Hashtable _notifications = new Hashtable(); private bool _notificationsInitialized = false; + [Obsolete("Obsolete, For querying the database use the new UmbracoDatabase object ApplicationContext.Current.DatabaseContext.Database", false)] private static ISqlHelper SqlHelper { get { return Application.SqlHelper; } @@ -79,28 +81,24 @@ namespace umbraco.BusinessLogic { _id = ID; - using (IRecordsReader dr = SqlHelper.ExecuteReader( - "Select userNoConsole, userDisabled, userType,startStructureID, startMediaId, userName,userLogin,userEmail,userDefaultPermissions, userLanguage, defaultToLiveEditing from umbracoUser where id = @id", - SqlHelper.CreateParameter("@id", ID))) + var dto = ApplicationContext.Current.DatabaseContext.Database.FirstOrDefault("WHERE id = @id", new { id = ID}); + if (dto != null) { - if (dr.Read()) - { - _userNoConsole = dr.GetBoolean("usernoconsole"); - _userDisabled = dr.GetBoolean("userDisabled"); - _name = dr.GetString("userName"); - _loginname = dr.GetString("userLogin"); - _email = dr.GetString("userEmail"); - _language = dr.GetString("userLanguage"); - _startnodeid = dr.GetInt("startStructureID"); - if (!dr.IsNull("startMediaId")) - _startmediaid = dr.GetInt("startMediaID"); - _usertype = UserType.GetUserType(dr.GetShort("UserType")); - _defaultToLiveEditing = dr.GetBoolean("defaultToLiveEditing"); - } - else - { - throw new ArgumentException("No User exists with ID " + ID.ToString()); - } + _userNoConsole = dto.NoConsole; + _userDisabled = dto.Disabled; + _name = dto.UserName; + _loginname = dto.Login; + _email = dto.Email; + _language = dto.UserLanguage; + _startnodeid = dto.ContentStartId; + if (dto.MediaStartId.HasValue) + _startmediaid = dto.MediaStartId.Value; + _usertype = UserType.GetUserType(dto.Type); + _defaultToLiveEditing = dto.DefaultToLiveEditing; + } + else + { + throw new ArgumentException("No User exists with ID " + ID); } _isInitialized = true; } @@ -121,14 +119,17 @@ namespace umbraco.BusinessLogic { get { - if (!_isInitialized) + if (_isInitialized == false) setupUser(_id); return _name; } set { _name = value; - SqlHelper.ExecuteNonQuery("Update umbracoUser set UserName = @userName where id = @id", SqlHelper.CreateParameter("@userName", value), SqlHelper.CreateParameter("@id", Id)); + + ApplicationContext.Current.DatabaseContext.Database.Update( + "SET UserName = @userName WHERE id = @id", new { userName = value, id = Id}); + FlushFromCache(); } } @@ -141,14 +142,17 @@ namespace umbraco.BusinessLogic { get { - if (!_isInitialized) + if (_isInitialized == false) setupUser(_id); return _email; } set { _email = value; - SqlHelper.ExecuteNonQuery("Update umbracoUser set UserEmail = @email where id = @id", SqlHelper.CreateParameter("@id", this.Id), SqlHelper.CreateParameter("@email", value)); + + ApplicationContext.Current.DatabaseContext.Database.Update( + "SET UserEmail = @email WHERE id = @id", new { email = value, id = Id }); + FlushFromCache(); } } @@ -161,14 +165,17 @@ namespace umbraco.BusinessLogic { get { - if (!_isInitialized) + if (_isInitialized == false) setupUser(_id); return _language; } set { _language = value; - SqlHelper.ExecuteNonQuery("Update umbracoUser set userLanguage = @language where id = @id", SqlHelper.CreateParameter("@language", value), SqlHelper.CreateParameter("@id", Id)); + + ApplicationContext.Current.DatabaseContext.Database.Update( + "SET userLanguage = @language WHERE id = @id", new { language = value, id = Id }); + FlushFromCache(); } } @@ -185,7 +192,9 @@ namespace umbraco.BusinessLogic } set { - SqlHelper.ExecuteNonQuery("Update umbracoUser set UserPassword = @pw where id = @id", SqlHelper.CreateParameter("@pw", value), SqlHelper.CreateParameter("@id", Id)); + ApplicationContext.Current.DatabaseContext.Database.Update( + "SET UserPassword = @pw WHERE id = @id", new { pw = value, id = Id }); + FlushFromCache(); } } @@ -196,9 +205,8 @@ namespace umbraco.BusinessLogic /// public string GetPassword() { - return - SqlHelper.ExecuteScalar("select UserPassword from umbracoUser where id = @id", - SqlHelper.CreateParameter("@id", this.Id)); + return ApplicationContext.Current.DatabaseContext.Database.ExecuteScalar( + "SELECT UserPassword FROM umbracoUser WHERE id = @id", new {id = Id}); } /// @@ -214,11 +222,10 @@ namespace umbraco.BusinessLogic public bool ValidatePassword(string password) { - string userLogin = - SqlHelper.ExecuteScalar("select userLogin from umbracoUser where userLogin = @login and UserPassword = @pw", - SqlHelper.CreateParameter("@pw", password), - SqlHelper.CreateParameter("@login", LoginName) - ); + var userLogin = ApplicationContext.Current.DatabaseContext.Database.ExecuteScalar( + "SELECT userLogin FROM umbracoUser WHERE userLogin = @login AND UserPasword = @password", + new {login = LoginName, password = password}); + return userLogin == this.LoginName; } @@ -251,20 +258,19 @@ namespace umbraco.BusinessLogic /// public List GetApplications() { - if (!_isInitialized) + if (_isInitialized == false) setupUser(_id); var allApps = Application.getAll(); var apps = new List(); - using (IRecordsReader appIcons = SqlHelper.ExecuteReader("select app from umbracoUser2app where [user] = @userID", SqlHelper.CreateParameter("@userID", this.Id))) + var dtos = ApplicationContext.Current.DatabaseContext.Database.Fetch( + "SELECT * FROM umbracoUser2app WHERE [user] = @userID", new {userID = Id}); + foreach (var dto in dtos) { - while (appIcons.Read()) - { - var app = allApps.SingleOrDefault(x => x.alias == appIcons.GetString("app")); - if(app != null) - apps.Add(app); - } + var app = allApps.SingleOrDefault(x => x.alias == dto.AppAlias); + if (app != null) + apps.Add(app); } return apps; @@ -278,21 +284,25 @@ namespace umbraco.BusinessLogic { get { - if (!_isInitialized) + if (_isInitialized == false) setupUser(_id); return _loginname; } set { - if (!ensureUniqueLoginName(value, this)) + if (EnsureUniqueLoginName(value, this) == false) throw new Exception(String.Format("A user with the login '{0}' already exists", value)); + _loginname = value; - SqlHelper.ExecuteNonQuery("Update umbracoUser set UserLogin = @login where id = @id", SqlHelper.CreateParameter("@login", value), SqlHelper.CreateParameter("@id", Id)); + + ApplicationContext.Current.DatabaseContext.Database.Update( + "SET UserLogin = @login WHERE id = @id", new { login = value, id = Id }); + FlushFromCache(); } } - private static bool ensureUniqueLoginName(string loginName, User currentUser) + private static bool EnsureUniqueLoginName(string loginName, User currentUser) { User[] u = User.getAllByLoginName(loginName); if (u.Length != 0) @@ -329,7 +339,9 @@ namespace umbraco.BusinessLogic consoleCheckSql = "and userNoConsole = 0 "; object tmp = SqlHelper.ExecuteScalar( - "select id from umbracoUser where userDisabled = 0 " + consoleCheckSql + " and userLogin = @login and userPassword = @pw", SqlHelper.CreateParameter("@login", lname), SqlHelper.CreateParameter("@pw", passw) + "select id from umbracoUser where userDisabled = 0 " + consoleCheckSql + " and userLogin = @login and userPassword = @pw", + SqlHelper.CreateParameter("@login", lname), + SqlHelper.CreateParameter("@pw", passw) ); // Logging @@ -349,17 +361,17 @@ namespace umbraco.BusinessLogic { get { - if (!_isInitialized) + if (_isInitialized == false) setupUser(_id); return _usertype; } set { _usertype = value; - SqlHelper.ExecuteNonQuery( - @"Update umbracoUser set userType = @type where id = @id", - SqlHelper.CreateParameter("@type", value.Id), - SqlHelper.CreateParameter("@id", Id)); + + ApplicationContext.Current.DatabaseContext.Database.Update( + "SET userType = @type WHERE id = @id", new { type = value.Id, id = Id }); + FlushFromCache(); } } @@ -371,11 +383,9 @@ namespace umbraco.BusinessLogic /// public static User[] getAll() { + IRecordsReader dr = SqlHelper.ExecuteReader("Select id from umbracoUser"); - IRecordsReader dr; - dr = SqlHelper.ExecuteReader("Select id from umbracoUser"); - - List users = new List(); + var users = new List(); while (dr.Read()) { @@ -424,23 +434,18 @@ namespace umbraco.BusinessLogic /// public static User[] getAllByEmail(string email, bool useExactMatch) { - List retVal = new List(); - System.Collections.ArrayList tmpContainer = new System.Collections.ArrayList(); - - IRecordsReader dr; + var retVal = new List(); + var tmpContainer = new ArrayList(); - if (useExactMatch == true) - { - dr = SqlHelper.ExecuteReader("Select id from umbracoUser where userEmail = @email", SqlHelper.CreateParameter("@email", email)); - } - else - { - dr = SqlHelper.ExecuteReader("Select id from umbracoUser where userEmail LIKE {0} @email", SqlHelper.CreateParameter("@email", String.Format("%{0}%", email))); - } + IRecordsReader dr = useExactMatch + ? SqlHelper.ExecuteReader("Select id from umbracoUser where userEmail = @email", + SqlHelper.CreateParameter("@email", email)) + : SqlHelper.ExecuteReader("Select id from umbracoUser where userEmail LIKE {0} @email", + SqlHelper.CreateParameter("@email", String.Format("%{0}%", email))); while (dr.Read()) { - retVal.Add(BusinessLogic.User.GetUser(dr.GetInt("id"))); + retVal.Add(GetUser(dr.GetInt("id"))); } dr.Close(); @@ -569,7 +574,7 @@ namespace umbraco.BusinessLogic /// The ut. public static void Update(int id, string name, string lname, string email, UserType ut) { - if (!ensureUniqueLoginName(lname, User.GetUser(id))) + if (!EnsureUniqueLoginName(lname, User.GetUser(id))) throw new Exception(String.Format("A user with the login '{0}' already exists", lname)); From 0debf9c34faa05201b52577755a97a229c8a61be Mon Sep 17 00:00:00 2001 From: yawka Date: Thu, 7 Nov 2013 19:05:50 +0200 Subject: [PATCH 0048/1046] Add check is path is whitespace --- src/Umbraco.Core/Configuration/GlobalSettings.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Umbraco.Core/Configuration/GlobalSettings.cs b/src/Umbraco.Core/Configuration/GlobalSettings.cs index 4526ece5f3..3dbc99fba9 100644 --- a/src/Umbraco.Core/Configuration/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/GlobalSettings.cs @@ -704,6 +704,8 @@ namespace Umbraco.Core.Configuration StartsWithContainer _newReservedList = new StartsWithContainer(); foreach (string reservedUrl in _reservedUrlsCache.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries)) { + if (string.IsNullOrWhiteSpace(reservedUrl)) + continue; //resolves the url to support tilde chars string reservedUrlTrimmed = IOHelper.ResolveUrl(reservedUrl).Trim().ToLower(); if (reservedUrlTrimmed.Length > 0) @@ -713,6 +715,8 @@ namespace Umbraco.Core.Configuration foreach (string reservedPath in _reservedPathsCache.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries)) { bool trimEnd = !reservedPath.EndsWith("/"); + if (string.IsNullOrWhiteSpace(reservedPath)) + continue; //resolves the url to support tilde chars string reservedPathTrimmed = IOHelper.ResolveUrl(reservedPath).Trim().ToLower(); From 59391f58ec7a8d55fe21dd861b9cbcac5522121c Mon Sep 17 00:00:00 2001 From: yawka Date: Thu, 7 Nov 2013 19:06:25 +0200 Subject: [PATCH 0049/1046] Remove previous fix, and move it to IsReservedPathOrUrl --- src/Umbraco.Core/IO/IOHelper.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs index bb23cde56e..9f917f18a7 100644 --- a/src/Umbraco.Core/IO/IOHelper.cs +++ b/src/Umbraco.Core/IO/IOHelper.cs @@ -43,9 +43,6 @@ namespace Umbraco.Core.IO //Replaces tildes with the root dir public static string ResolveUrl(string virtualPath) { - if (string.IsNullOrWhiteSpace(virtualPath)) - return string.Empty; - if (virtualPath.StartsWith("~")) return virtualPath.Replace("~", SystemDirectories.Root).Replace("//", "/"); else if (Uri.IsWellFormedUriString(virtualPath, UriKind.Absolute)) From aacb86bf92da5493276fcdeca84e43ff1c428205 Mon Sep 17 00:00:00 2001 From: yawka Date: Thu, 7 Nov 2013 19:09:50 +0200 Subject: [PATCH 0050/1046] Add trim to all urls --- src/Umbraco.Core/Configuration/GlobalSettings.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Configuration/GlobalSettings.cs b/src/Umbraco.Core/Configuration/GlobalSettings.cs index 3dbc99fba9..d3c1fcb2ae 100644 --- a/src/Umbraco.Core/Configuration/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/GlobalSettings.cs @@ -706,8 +706,10 @@ namespace Umbraco.Core.Configuration { if (string.IsNullOrWhiteSpace(reservedUrl)) continue; + + //resolves the url to support tilde chars - string reservedUrlTrimmed = IOHelper.ResolveUrl(reservedUrl).Trim().ToLower(); + string reservedUrlTrimmed = IOHelper.ResolveUrl(reservedUrl.Trim()).Trim().ToLower(); if (reservedUrlTrimmed.Length > 0) _newReservedList.Add(reservedUrlTrimmed); } @@ -717,8 +719,9 @@ namespace Umbraco.Core.Configuration bool trimEnd = !reservedPath.EndsWith("/"); if (string.IsNullOrWhiteSpace(reservedPath)) continue; + //resolves the url to support tilde chars - string reservedPathTrimmed = IOHelper.ResolveUrl(reservedPath).Trim().ToLower(); + string reservedPathTrimmed = IOHelper.ResolveUrl(reservedPath.Trim()).Trim().ToLower(); if (reservedPathTrimmed.Length > 0) _newReservedList.Add(reservedPathTrimmed + (reservedPathTrimmed.EndsWith("/") ? "" : "/")); From 80b5cfbb07e013e57067a7894ee21f8411c213c6 Mon Sep 17 00:00:00 2001 From: OptimisticCoder Date: Sat, 9 Nov 2013 14:37:22 +0000 Subject: [PATCH 0051/1046] U4-451 Inconsistent line endings in template files created by Umbraco Changed all "\n"'s to Environment.NewLine Changed a string that was being appended in that method to a StringBuilder to be more efficient. Cleaned up old commented out code from the method. --- .../template/MasterpageHelper.cs | 66 ++++--------------- 1 file changed, 12 insertions(+), 54 deletions(-) diff --git a/src/umbraco.cms/businesslogic/template/MasterpageHelper.cs b/src/umbraco.cms/businesslogic/template/MasterpageHelper.cs index af7cb213ba..c72bba8fb1 100644 --- a/src/umbraco.cms/businesslogic/template/MasterpageHelper.cs +++ b/src/umbraco.cms/businesslogic/template/MasterpageHelper.cs @@ -68,7 +68,8 @@ namespace umbraco.cms.businesslogic.template internal static string CreateDefaultMasterPageContent(Template template, string currentAlias) { - string design = GetMasterPageHeader(template) + "\n"; + StringBuilder design = new StringBuilder(); + design.Append(GetMasterPageHeader(template) + Environment.NewLine); if (template.HasMasterTemplate) { @@ -76,65 +77,22 @@ namespace umbraco.cms.businesslogic.template foreach (string cpId in master.contentPlaceholderIds()) { - design += "\n\t\n\n\n"; + design.Append("" + + Environment.NewLine + + Environment.NewLine + + "" + + Environment.NewLine + + Environment.NewLine); } } else { - design += GetMasterContentElement(template) + "\n"; - design += template.Design; - design += "\n" + Environment.NewLine; + design.Append(GetMasterContentElement(template) + Environment.NewLine); + design.Append(template.Design + Environment.NewLine); + design.Append("" + Environment.NewLine); } - return design; - - - /* - var masterPageContent = template.Design; - - if (!IsMasterPageSyntax(masterPageContent)) - masterPageContent = ConvertToMasterPageSyntax(template); - - // Add header to master page if it doesn't exist - if (!masterPageContent.TrimStart().StartsWith("<%@")) - { - masterPageContent = GetMasterPageHeader(template) + "\n" + masterPageContent; - } - else - { - // verify that the masterpage attribute is the same as the masterpage - string masterHeader = - masterPageContent.Substring(0, masterPageContent.IndexOf("%>") + 2).Trim( - Environment.NewLine.ToCharArray()); - - // find the masterpagefile attribute - MatchCollection m = Regex.Matches(masterHeader, "(?\\S*)=\"(?[^\"]*)\"", - RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - - foreach (Match attributeSet in m) - { - if (attributeSet.Groups["attributeName"].Value.ToLower() == "masterpagefile") - { - // validate the masterpagefile - string currentMasterPageFile = attributeSet.Groups["attributeValue"].Value; - string currentMasterTemplateFile = ParentTemplatePath(template); - - if (currentMasterPageFile != currentMasterTemplateFile) - { - masterPageContent = - masterPageContent.Replace( - attributeSet.Groups["attributeName"].Value + "=\"" + currentMasterPageFile + "\"", - attributeSet.Groups["attributeName"].Value + "=\"" + currentMasterTemplateFile + - "\""); - } - } - } - - } - - return masterPageContent; - * */ + return design.ToString(); } internal static string UpdateMasterPageContent(Template template, string currentAlias) From c83844468d9a30f73424a3f9e34674a63370fb32 Mon Sep 17 00:00:00 2001 From: OptimisticCoder Date: Sat, 9 Nov 2013 15:18:43 +0000 Subject: [PATCH 0052/1046] U4-3039 Import Document Type leaves files in App_Data Added File.Delete to clean up temp files. Added try/catch round it and logging if it fails for any reason. --- .../umbraco/dialogs/importDocumenttype.aspx.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/importDocumenttype.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/importDocumenttype.aspx.cs index 23005d62d2..27c1724bff 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/importDocumenttype.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/importDocumenttype.aspx.cs @@ -80,6 +80,16 @@ namespace umbraco.presentation.umbraco.dialogs if (contentType != null) dtNameConfirm.Text = contentType.Name; + // Try to clean up the temporary file. + try + { + System.IO.File.Delete(tempFile.Value); + } + catch(Exception ex) + { + Umbraco.Core.Logging.LogHelper.Error(typeof(importDocumentType), "Error cleaning up temporary udt file in App_Data: " + ex.Message, ex); + } + Wizard.Visible = false; Confirm.Visible = false; done.Visible = true; From 282da69d61d8b9e0d8a28454c519d850dc4120ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20St=C3=B6cker?= Date: Wed, 13 Nov 2013 13:27:56 +0100 Subject: [PATCH 0053/1046] Fixed minor typos --- src/Umbraco.Web.UI/umbraco/config/lang/de.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/de.xml b/src/Umbraco.Web.UI/umbraco/config/lang/de.xml index e1b0c1c929..a1aac87d1a 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/de.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/de.xml @@ -225,8 +225,8 @@ '%0%' hat ein falsches Format - ACHTUNG! Obwohl CodeMirror in den Einstellungen aktiviert ist bleibt das Modul wegen mangelnder Stabilität in Internet Explorer deaktiviert. - Bitte geben Sie die Bezeichnung und den Alias des neuen Dokumenttyps ein! + ACHTUNG! Obwohl CodeMirror in den Einstellungen aktiviert ist, bleibt das Modul wegen mangelnder Stabilität in Internet Explorer deaktiviert. + Bitte geben Sie die Bezeichnung und den Alias des neuen Dokumenttyps ein. Es besteht ein Problem mit den Lese-/Schreibrechten auf eine Datei oder einen Ordner Bitte geben Sie einen Titel ein Bitte wählen Sie einen Typ @@ -633,7 +633,7 @@ Ihr freundlicher Umbraco-Robot Sortierung abgeschlossen. Ziehen Sie die Elemente an ihre gewünschte neue Position. - Bitte warten, die Seiten werden sortiert. Das kann einen Moment dauern.Bitte schließen Sie dieses Fenster nicht, bis der Sortiervorgang abgeschlossen ist. + Bitte warten, die Seiten werden sortiert. Das kann einen Moment dauern. Bitte schließen Sie dieses Fenster nicht, bis der Sortiervorgang abgeschlossen ist. Das Veröffentlichen wurde von einem individuellen Ereignishandler abgebrochen From 376fe0bf55e3d9881d6382276a1b035236dde9f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20St=C3=B6cker?= Date: Thu, 14 Nov 2013 22:22:25 +0100 Subject: [PATCH 0054/1046] changed wording to correct one in context of content wording was wrong until today, needed a completely different meaning in the context of the content section; ironically it's just "Alias" now ;) --- src/Umbraco.Web.UI/umbraco/config/lang/de.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/de.xml b/src/Umbraco.Web.UI/umbraco/config/lang/de.xml index a1aac87d1a..a8b473fefd 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/de.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/de.xml @@ -89,8 +89,8 @@ Über dieses Dokument - Alternative URLs - (Wie würden Sie das Bild über dem Telefon beschreiben?) + Alias + (Wie würden Sie das Bild über das Telefon beschreiben?) Alternative Links Klicken, um das Dokument zu bearbeiten Erstellt von From 997e0e5492818a5e5eefcfa3b5debd42f68ebb92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20St=C3=B6cker?= Date: Mon, 18 Nov 2013 23:39:20 +0100 Subject: [PATCH 0055/1046] Initial German v7 translation/localization Should cover all of the already localized strings for v7 (as well as 6.x - no obsolete items removed for now). --- src/Umbraco.Web.UI/umbraco/config/lang/de.xml | 71 +++++++++++++------ 1 file changed, 51 insertions(+), 20 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/de.xml b/src/Umbraco.Web.UI/umbraco/config/lang/de.xml index d2a16639ad..3721090de9 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/de.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/de.xml @@ -15,8 +15,6 @@ Deaktivieren Papierkorb leeren Dokumenttyp exportieren - Export zu .NET - Export zu .NET Dokumenttyp importieren Paket importieren 'Canvas'-Modus starten @@ -86,11 +84,13 @@ Stil anzeigen Tabelle einfügen Die Vorschaufunktion ist deaktiviert, da keine Vorlage zugewiesen ist + Auswählen + Etwas anders machen Über dieses Dokument - Alternative URLs - (Wie würden Sie das Bild über dem Telefon beschreiben?) + Alias + (Wie würden Sie das Bild über das Telefon beschreiben?) Alternative Links Klicken, um das Dokument zu bearbeiten Erstellt von @@ -124,11 +124,21 @@ Link zum Dokument Verweis auf Medienobjekt(e) Ups! Dieses Dokument ist veröffentlicht aber nicht im internen Cache aufzufinden: Systemfehler. + Ursprünglicher Autor + Aktualisiert von + Erstellungszeitpunkt des Dokuments + Veröffentlichung widerrufen am + Letzter Änderungszeitpunkt des Dokuments + Mitglied der Gruppe(n) + Kein Mitglied der Gruppe(n) + Untergeordnete Elemente An welcher Stellen wollen Sie das Element erstellen Erstellen unter Wählen Sie einen Namen und einen Typ + Es stehen keine erlaubten Dokumenttypen zur Verfügung. Sie müssen diese in den Einstellungen (unter "Dokumenttypen") aktivieren. + Es stehen keine erlaubten Medientypen zur Verfügung. Sie müssen diese in den Einstellungen (unter "Medientypen") aktivieren. Website anzeigen @@ -197,6 +207,7 @@ Neue Registerkarte Registerkarte Illustration + Listenansicht aktivieren Vorgabewert hinzufügen @@ -225,8 +236,8 @@ '%0%' hat ein falsches Format - ACHTUNG! Obwohl CodeMirror in den Einstellungen aktiviert ist bleibt das Modul wegen mangelnder Stabilität in Internet Explorer deaktiviert. - Bitte geben Sie die Bezeichnung und den Alias des neuen Dokumenttyps ein! + ACHTUNG! Obwohl CodeMirror in den Einstellungen aktiviert ist, bleibt das Modul wegen mangelnder Stabilität in Internet Explorer deaktiviert. + Bitte geben Sie die Bezeichnung und den Alias des neuen Dokumenttyps ein. Es besteht ein Problem mit den Lese-/Schreibrechten auf eine Datei oder einen Ordner Bitte geben Sie einen Titel ein Bitte wählen Sie einen Typ @@ -336,6 +347,10 @@ Breite Ja Ordner + Aktionen + Mehr + Pflichtangabe + Suchergebnisse Hintergrundfarbe @@ -372,16 +387,14 @@ <strong>Der Standard-Benutzer wurde deaktiviert oder hat keinen Zugriff auf Umbraco.</strong></p><p>Es sind keine weiteren Aktionen notwendig. Klicken Sie auf <b>Weiter</b> um fortzufahren. <strong>Das Kennwort des Standard-Benutzers wurde seit der Installation verändert.</strong></p><p>Es sind keine weiteren Aktionen notwendig. Klicken Sie auf <b>Weiter</b> um fortzufahren. Das Kennwort wurde geändert! - - <p> - Bei der Installation von Umbraco erzeugt einen Standard-Benutzer mit dem Login-Namen <strong>'admin'</strong> und dem Kennwort <strong>'default'</strong>. - <strong>WICHTIG:</strong> Das Standardkennwort sollte auf ein eigenes Kennwort geändert werden. + <p> + Bei der Installation von Umbraco wurde ein Standard-Benutzer mit dem Login-Namen <strong>'admin'</strong> und dem Kennwort <strong>'default'</strong> erstellt. + <strong>WICHTIG:</strong> Das Kennwort sollte auf ein sicheres, eigenes Kennwort geändert werden. </p> <p> Das Kennwort des Standard-Benutzers wird jetzt geprüft und im Anschluss werden eventuell notwendige Änderungen vorschlagen. - </p> - - Schauen Sie sich für einen tollen Start unsere Einführungsvideos an. + </p> + Schauen Sie sich die Einführungsvideos für einen schnellen und einfachen Start an. Mit der Installation stimmen Sie der angezeigten Lizenz für diese Software zu. Bitte beachten Sie, dass diese Umbraco-Distribution aus zwei Lizenzen besteht. Einer freien Open-Source MIT-Lizenz für das Framework und der Umbraco-Freeware-Lizenz für die Verwaltungsoberfläche. Noch nicht installiert. Betroffene Verzeichnisse und Dateien @@ -449,11 +462,18 @@ Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die Sie haben keine Tätigkeiten mehr durchgeführt und werden automatisch abgemeldet in - Erneuern Sie um Ihre Arbeit zu speichern... + Erneuern Sie, um Ihre Arbeit zu speichern ... <p style="text-align:right;">&copy; 2001 - %0% <br /><a href="http://umbraco.org" style="text-decoration: none" target="_blank">umbraco.org</a></p> - Willkommen zu Umbraco, bitte geben Sie Ihren Benutzernamen und Ihr Kennwort ein: + Einen wunderbaren Sonntag + Frohen freundlichen Freitag + Donnerwetter Donnerstag + Schönen Montag + Einen großartigen Dienstag + Wunderbaren Mittwoch + Wunderbaren sonnigen Samstag + Hier anmelden: Dashboard @@ -472,6 +492,7 @@ Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die Das ausgewählte Element kann nicht zu einem seiner eigenen Unterelemente verschoben werden. Diese Aktion ist nicht erlaubt, da Sie unzureichende Berechtigungen für mindestens ein untergeordnetes Element haben. Kopierte Elemente mit dem Original verknüpfen + Dieses Element kann nicht auf der obersten Ebene platziert werden. Bearbeiten Sie Ihre Benachrichtigungseinstellungen für '%0%' @@ -575,9 +596,10 @@ Ihr freundlicher Umbraco-Robot Mit <em>Ok</em> wird <strong>%0%</strong> veröffentlicht und auf der Website sichtbar.<br/><br />Sie können dieses Element mitsamt seinen untergeordneten Elementen veröffentlichen, indem Sie <em>Unveröffentlichte Unterelemente einschließen</em> aktivieren. %0% konnte nicht veröffentlicht werden, da einige Daten die Gültigkeitsprüfung nicht bestanden haben. %0% kann nicht veröffentlicht werden, da das übergeordnete Dokument nicht veröffentlicht ist. + %0% kann nicht veröffentlicht werden, da die Veröffentlichung zeitlich geplant ist. - Neuer exerner Link + Neuer externer Link Neuer interner Link Link hinzufügen Beschriftung @@ -601,9 +623,9 @@ Ihr freundlicher Umbraco-Robot Skript bearbeiten - Concierge + Umbraco Concierge Inhalte - Courier + Umbraco Courier Entwickler Konfigurationsassistent Medien @@ -613,6 +635,8 @@ Ihr freundlicher Umbraco-Robot Statistiken Übersetzung Benutzer + Umbraco Contour + Hilfe Standardvorlage @@ -629,11 +653,12 @@ Ihr freundlicher Umbraco-Robot Dieser Dokumenttyp verwendet Masterdokumenttyp aktiviert als Masterdokumenttyp. Register vom Masterdokumenttyp werden nicht angezeigt und können nur im Masterdokumenttyp selbst bearbeitet werden + Für dieses Register sind keine Eigenschaften definiert. Klicken Sie oben auf "neue Eigenschaft hinzufügen", um eine neue Eigenschaft hinzuzufügen. Sortierung abgeschlossen. Ziehen Sie die Elemente an ihre gewünschte neue Position. - Bitte warten, die Seiten werden sortiert. Das kann einen Moment dauern.Bitte schließen Sie dieses Fenster nicht, bis der Sortiervorgang abgeschlossen ist. + Bitte warten, die Seiten werden sortiert. Das kann einen Moment dauern. Bitte schließen Sie dieses Fenster nicht, bis der Sortiervorgang abgeschlossen ist. Das Veröffentlichen wurde von einem individuellen Ereignishandler abgebrochen @@ -688,6 +713,7 @@ Ihr freundlicher Umbraco-Robot Partielle Ansicht nicht gespeichert Fehler beim Speichern der Datei. Benutzertyp gepsichert + Medium fehlerfrei gespeichert Gewünschter CSS-Selektor, zum Beispiel 'h1', '.bigHeader' oder 'p.infoText' @@ -736,6 +762,7 @@ Ihr freundlicher Umbraco-Robot Datum und Zeit mit Trennzeichen: Benutzerdefinierte Felder Standardfelder + Kodierung Ihre Aufgaben @@ -754,7 +781,7 @@ das Dokument '%1%' wurde von '%2%' zur Übersetzung in '%5%' freigegeben. Zum Bearbeiten verwenden Sie bitte diesen Link: http://%3%/umbraco/translation/details.aspx?id=%4%. -Sie können sich auch alle anstehenden Übersetzungen gesammelt im Umbraco-Verwaltungsbereich anzeigen lassen: http://%3%/umbraco +Sie können sich auch alle anstehenden Übersetzungen gesammelt im Umbraco-Verwaltungsbereich anzeigen lassen: http://%3%/umbraco/umbraco.aspx Einen schönen Tag wünscht Ihr freundlicher Umbraco-Robot @@ -849,5 +876,9 @@ Ihr freundlicher Umbraco-Robot Neues Kennwort Neues Kennwort (Bestätigung) Bitte bestätigen Sie das neue Kennwort + Kennwort zurücksetzen + Ihr Profil + Ihr Verlauf + Sitzung läuft ab in From f8375857c21e1b650acedbd34499d0270d3a38a2 Mon Sep 17 00:00:00 2001 From: Kalle Ekstrand Date: Tue, 26 Nov 2013 11:08:55 +0100 Subject: [PATCH 0056/1046] Updates to the swedish language file for v7 --- src/Umbraco.Web.UI/umbraco/config/lang/sv.xml | 173 ++++++++++++------ 1 file changed, 122 insertions(+), 51 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml b/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml index 4c57efb4bb..06b1861743 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml @@ -1,20 +1,25 @@  - + Kalle Ekstrand, Toxic Interactive Solutions AB http://www.toxic.se + Arkivera formulär Hantera domännamn Hantera versioner Surfa på sidan + Ändra dokumenttyp Kopiera Skapa Skapa paket + Standardvärde Ta bort Avaktivera Töm papperskorgen + Exportera formulär Exportera dokumenttyp + Importera formulär Importera dokumenttyp Importera paket Redigera i Canvas @@ -32,17 +37,18 @@ Sortera Skicka för publicering Översätt + Avarkivera formulär Avpublicera Uppdatera Lägg till nytt domännamn Domännamn - Har skapat domännamnet '%0%' - Har tagit bort domännamnet '%0%' - Domänen %0% är redan tillagd + Har skapat domännamnet '{0}' + Har tagit bort domännamnet '{0}' + Domänen {0} är redan tillagd t.ex.: dittdomannamn.se, www.dittdomannamn.se - Domännamnet '%0%' har uppdaterats + Domännamnet '{0}' har uppdaterats Domänen är redan tilldelad Ärv Ogiltigt domännamn @@ -82,20 +88,49 @@ Spara Spara och publicera Spara och skicka för godkännande + Välj + Välj aktuell mapp Förhandsgranska Förhandsgranskning är avstängt på grund av att det inte finns någon mall tilldelad + Gör någonting annat Välj stil Visa stil Infoga tabell + + För att ändra vald dokumenttyp, välj först i listan av giltiga typer för denna platsen. + Konfirmera sedan och/eller ändra mappningen av egenskaper från aktuell dokumenttyp till den nya, och klicka sedan på spara. + Innehållet har blivit publicerat på nytt + Aktuell egenskap + Aktuell typ + Dokumenttypen kan inte ändras, eftersom det inte finns några giltiga val för denna plats. + Dokumenttypen är ändrad + Mappningsegenskaper + Mappning till egenskap + Ny sidmall + Ny egenskap + ingen + Innehåll + Välj ny dokumenttyp + Dokumenttypen på valt innehåll har ändrats till [new type] utan problem och följande egenskaper är mappade: + till + Kunde inte slutföra egenskapsmappningen då en eller flera egenskaper har en eller flera mappningar definierade. + Enbart giltiga alternativa egenskaper visas för platsen + + + Du har inte konfigurerat några giltiga färger + Om denna sida Alternativ länk (hur skulle du beskriva denna bild för någon över telefon) Alternativa länkar + Underliggande noder Klicka för att redigera detta objekt Skapad av + Ursprunglig författare Skapad + Datum/tid som dokumentet skapades Dokumenttyp Redigering Ta bort @@ -105,12 +140,14 @@ Länk till medieobjekt Mediatyp Medlemsgrupp + Medlem av grupp(er) Roll Medlemstyp Inget datum valt Sidnamn + Ej medlem av grupp(er) Egenskaper - Detta dokument är publicerat men syns inte eftersom den överordnade sidan '%0%' inte är publicerad + Detta dokument är publicerat men syns inte eftersom den överordnade sidan '{0}' inte är publicerad Oops: detta dokument är publicerat men finns inte i cacheminnet (internt fel) Publicera Publiceringsstatus @@ -122,13 +159,18 @@ Titel (valfritt) Typ Avpublicera + Avpubliceras Senast redigerad + Datum/tid detta dokument ändrats + Uppdaterad av Ta bort fil Länk till dokument - Var vill du skapa den nya %0% + Var vill du skapa den nya {0} Skapa här + "dokumenttyper".]]> + "mediatyper".]]> Välj typ och rubrik @@ -146,7 +188,7 @@ Stäng fönstret Är du säker på att du vill ta bort Är du säker på att du vill avaktivera - Kryssa i denna ruta för att bekräfta att %0% objekt tas bort + Kryssa i denna ruta för att bekräfta att {0} objekt tas bort Är du säker? Är du säker? Klipp ut @@ -187,7 +229,7 @@ Se cachat objekt - Redigera de olika översättningarna för ordboksinlägget '%0%' nedan. Du kan lägga till ytterligare språk under 'språk' i menyn till vänster. + Redigera de olika översättningarna för ordboksinlägget '{0}' nedan. Du kan lägga till ytterligare språk under 'språk' i menyn till vänster. Språknamn @@ -195,6 +237,7 @@ Skapa Ta bort flik Beskrivning + Aktivera listvy Ny flik Flik Miniatyrbild @@ -215,15 +258,15 @@ Informationen har sparats, men innan du kan publicera denna sida måste du åtgärda följande fel: Det går inte att byta lösenord i den medlemshanterare du har valt (EnablePasswordRetrieval måste vara satt till 'true'). - %0% redan finns + {0} redan finns Följande fel inträffade: Följande fel inträffade: - Lösenordet måste bestå av minst %0% tecken varav minst %1% är icke-alfanumeriska tecken (t.ex. %, #, !, @). - %0% måste vara ett heltal - %0% under %1% är ett obligatoriskt fält - %0% är ett obligatoriskt fält - %0% under %1% har ett felaktigt format - %0% har ett felaktigt format + Lösenordet måste bestå av minst {0} tecken varav minst {1} är icke-alfanumeriska tecken (t.ex. %, #, !, @). + {0} måste vara ett heltal + {0} under {1} är ett obligatoriskt fält + {0} är ett obligatoriskt fält + {0} under {1} har ett felaktigt format + {0} har ett felaktigt format Även om CodeMirror är aktiverad i konfigurationen, så är den avaktiverad i Internet Explorer på grund av att den inte är tillräckligt stabil @@ -246,6 +289,7 @@ Om Åtgärd + Händelser Lägg till Alias Är du säker? @@ -295,6 +339,7 @@ Logga ut Logga ut Makro + Fler Flytta Namn Nytt @@ -315,9 +360,11 @@ Återstående Döp om Förnya + Obligatorisk Försök igen Rättigheter Sök + Sökresultat Server Visa Vilken sida skall visas när formuläret är skickat @@ -353,12 +400,12 @@ Kunde inte spara filen web.config. Vänligen ändra databasanslutnings-inställningarna manuellt. Din databas har lokaliserats och är identifierad som Databaskonfiguration - installera]]> - Nästa för att fortsätta.]]> + installera]]> + Nästa för att fortsätta.]]> Databasen kunde inte hittas! Kontrollera att informationen i databasanslutnings-inställningarna i filen "web.config" är rätt.

För att fortsätta måste du redigera filen "web.config" (du kan använda Visual Studio eller din favorit text-redigerare), bläddra till slutet, lägg till databasanslutnings-inställningarna för din databas i fältet som heter "umbracoDbDSN" och spara filen.

Klicka på Försök igen knappen när du är klar.
> Mer information om att redigera web.config hittar du här.

]]>
Eventuellt kan du behöva kontakta ditt webb-hotell. Om du installerar på en lokal maskin eller server kan du få informationen från din systemadministratör.]]> - Tryck Uppgradera knappen för att uppgradera din databas till Umbraco %0%

Du behöver inte vara orolig. Inget innehåll kommer att raderas och efteråt kommer allt att fungera som vanligt!

]]>
- Tryck Nästa för att fortsätta.]]> + Tryck Uppgradera knappen för att uppgradera din databas till Umbraco {0}

Du behöver inte vara orolig. Inget innehåll kommer att raderas och efteråt kommer allt att fungera som vanligt!

]]>
+ Tryck Nästa för att fortsätta.]]> Nästa för att fortsätta med konfigurationsguiden]]> Lösenordet på standardanvändaren måste bytas!]]> Standardanvändaren har avaktiverats eller har inte åtkomst till umbraco!

Du behöver inte göra något ytterligare här. Klicka Next för att fortsätta.]]> @@ -402,13 +449,13 @@ Tack för att du valde umbraco Besök din nya webbplats Du installerade Runway, så varför inte se hur din nya webbplats ser ut.]]> Ytterligare hjälp och information Få hjälp från våra prisbelönta community, bläddra i dokumentationen eller titta på några gratis videor om hur man bygger en enkel webbplats, hur du använder paket eller en snabbguide till umbracos terminologi]]> - Umbraco %0% är installerat och klart för användning - /web.config filen och ändra AppSettingsnyckeln umbracoConfigurationStatus på slutet till '%0%']]> + Umbraco {0} är installerat och klart för användning + /web.config filen och ändra AppSettingsnyckeln umbracoConfigurationStatus på slutet till '{0}']]> börja omedelbart genom att klicka på "Starta Umbraco"-knappen nedan.
Om du är en ny umbraco användarekan du hitta massor av resurser på våra kom igång sidor.]]>
Starta Umbraco För att administrera din webbplats öppnar du bara umbraco back office och börjar lägga till innehåll, uppdatera mallar och stilmallar eller lägga till nya funktioner.]]> Anslutningen till databasen misslyckades. Se - umbraco %0% antingen för en ny installation eller en uppgradering från version 3.0.

Tryck på "next" för att börja.]]>
+ umbraco {0} antingen för en ny installation eller en uppgradering från version 3.0.

Tryck på "next" för att börja.]]>
Umbraco Version 3 Umbraco Version 4 @@ -421,20 +468,27 @@ Förnya nu för att spara ditt arbete - © 2001 - %0%
umbraco.org

]]>
- Välkommen till umbraco, skriv ditt användarnamn och lösenord i textfälten nedan: + © 2001 - {0}
umbraco.org

]]>
+ Happy super sunday + Happy manic monday + Happy tremendous tuesday + Happy wonderfull wednesday + Happy thunder thursday + Happy friendly friday + Happy shiny saturday + Logga in nedan - Redigeringsyta + Översikt Sektioner Innehåll Välj sida ovan... - %0% har kopierats till %1% - Ange mål att kopiera sidan %0% till nedan - %0% har flyttats till %1% - Ange vart sidan %0% skall flyttas till nedan + {0} har kopierats till {1} + Ange mål att kopiera sidan {0} till nedan + {0} har flyttats till {1} + Ange vart sidan {0} skall flyttas till nedan är nu roten för ditt nya innehåll. Klicka 'ok' nedan. Du har inte valt någon sida än. Välj en sida i listan ovan och klicka sedan 'fortsätt'. Aktuell nod får inte existera i roten @@ -444,10 +498,10 @@ Relatera kopierat objekt till orginalet - Inställningar för notifieringar gällande %0% - Hej %0% Detta mail skickas till dig automatiskt för att meddela att '%1%' har utförts på sidan '{2}' av användaren '{3}' Gå till http://{4}/actions/editContent.aspx?id={5} för att redigera. - Hej %0%

Detta mail skickas till dig automatiskt för att meddela att '%1%' har utförts på sidan '{2}' av användaren '{3}'

Sammanfattning av uppdateringen:

{6}

]]>
- [%0%] Meddelande för att informera om att %1% har utförts på {2} + Inställningar för notifieringar gällande {0} + Hej {0} Detta mail skickas till dig automatiskt för att meddela att '{1}' har utförts på sidan '{2}' av användaren '{3}' Gå till http://{4}/actions/editContent.aspx?id={5} för att redigera. + Hej {0}

Detta mail skickas till dig automatiskt för att meddela att '{1}' har utförts på sidan '{2}' av användaren '{3}'

Sammanfattning av uppdateringen:

{6}

]]>
+ [{0}] Meddelande för att informera om att {1} har utförts på {2} Notifieringar @@ -482,6 +536,14 @@ Klistra in texten helt utan formatering Klistra in texen och ta bort specialformatering (rekommenderas) + + Fyll i ett namn... + Skriv för att filtrera... + Namnge {0}... + Fyll i ditt lösenord + Skriv för att söka... + Fyll i ditt lösenord + Rollbaserat lösenordsskydd Då används umbracos medlemsgrupper.]]> @@ -489,8 +551,8 @@ Sida med felmeddelande Används när en användare är inloggad, men saknar rättigheter att se sidan Välj hur du vill lösenordsskydda sidan - %0% är nu lösenordsskyddad - Lösenordsskyddet är nu borttaget på %0% + {0} är nu lösenordsskyddad + Lösenordsskyddet är nu borttaget på {0} Inloggningssida Välj sidan med inloggningsformuläret Ta bort lösenordsskydd @@ -501,16 +563,17 @@ Välj detta alternativ om du vill skydda sidan med ett enkelt användarnamn och lösenord. Alla loggar då in med samma inloggningsuppgifter. - %0% kunde inte publiceras på grund av att ett tredjepartstillägg avbröt publiceringen. - %0% kan inte publiceras, på grund av att överordnad nod inte är publicerad. - + + {0} kunde inte publiceras på grund av att ett tredjepartstillägg avbröt publiceringen. + {0} kan inte publiceras, på grund av att överordnad nod inte är publicerad. + {0} kunde inte publiceras på grund av följande orsaker: {1} passerade inte valideringen. Inkludera opublicerade undersidor Publicering pågår - vänligen vänta... - %0% av %1% sidor har publicerats... - %0% har publicerats - %0% och underliggande sidor har publicerats - Publicera %0% och alla dess underordnade sidor - ok för att publicera %0%. Därmed blir innehållet publikt.

Du kan publicera denna sida och alla dess undersidor genom att kryssa i publicera alla undersidor. ]]>
+ {0} av {1} sidor har publicerats... + {0} har publicerats + {0} och underliggande sidor har publicerats + Publicera {0} och alla dess underordnade sidor + ok för att publicera {0}. Därmed blir innehållet publikt.

Du kan publicera denna sida och alla dess undersidor genom att kryssa i publicera alla undersidor. ]]>
Lägg till extern länk @@ -539,8 +602,10 @@ Concierge Innehåll + Contour Courier Utvecklare + Hjälp Umbraco Configuration Wizard Media Medlemmar @@ -559,6 +624,7 @@ För att importera en dokumenttyp, leta upp ".udt"-filen på din hårddisk genom att klicka på "Browse"-knappen och sedan på "Importera" (du får bekräfta ditt val i nästa steg). Namn på ny flik Nodtyp + Inga egenskaper definierade i denna sektion. Klicka på "Lägg till ny egenskap" länken vid toppen för att skapa en ny egenskap. Typ Stilmall Egenskap för stilmall @@ -575,12 +641,12 @@ Publiceringen avbröts av ett tredjepartstillägg Egenskapstyp finns redan Egenskapstyp skapad - Datatyp: %1%]]> + Datatyp: {1}]]> Egenskapstypen har tagits bort Innehållstypen har sparats Ny flik skapad Fliken har tagits bort - Fliken med id: %0% har tagits bort + Fliken med id: {0} har tagits bort Innehållet är avpublicerat Stilmallen kunde inte sparas Stilmallen sparades @@ -654,6 +720,7 @@ Byter radbrytningar mot html-taggen &lt;br&gt; Anpassade fält Ja, endast datum + Omkodning Formatera som datum HTML-omkodning Ersätter specialtecken med deras HTML-motsvarigheter. @@ -684,13 +751,13 @@ Ladda hem DTD för XML Fält Inkludera undersidor - Hej %0%. Detta är ett automatisk mail skickat for att informera dig om att det finns en översättningsförfrågan på dokument '%1' till '{5}' skickad av {2}. För att redigere, besök http://{3}/translation/details.aspx?id={4}. För att få en översikt över dina översättningsuppgigter loggar du in i umbraco på: http://{3} - [%0%] Översättningsuppgit för %1% + Hej {0}. Detta är ett automatisk mail skickat for att informera dig om att det finns en översättningsförfrågan på dokument '%1' till '{5}' skickad av {2}. För att redigere, besök http://{3}/translation/details.aspx?id={4}. För att få en översikt över dina översättningsuppgigter loggar du in i umbraco på: http://{3} + [{0}] Översättningsuppgit för {1} Hittade inga användare som är översättare. Vänligen skapa en användare som är översättare innan du börjar skicka innehåll för översättning Arbetsuppgifter som du har skapat som du har skapat. För att se en detaljvy med kommentarer, klicka på "Detaljer" eller på sidans namn. Du kan också ladda ned sidan i XML-format genom att klicka på länken "Ladda ned XML".
För att markera ett översättningsjobb som avslutat, gå till detaljvyn och klicka på knappen "Stäng".]]>
- Sidan '%0%' har skickats för översättning - Skicka sidan '%0%' för översättning + Sidan '{0}' har skickats för översättning + Skicka sidan '{0}' för översättning Tilldelat av Arbetsuppgift öppnad Totalt antal ord @@ -734,7 +801,7 @@ Ny uppdatering tillgänglig - %0% är klart, klicka här för att ladda ner + {0} är klart, klicka här för att ladda ner Ingen kontakt med server Fel vid kontroll av uppdatering. Se trace-stack för mer information. @@ -768,12 +835,16 @@ Ersätt rättigheterna på underliggande noder Du redigerar nu rättigheterna för sidorna: Välj de sidor vars rättigheter du vill redigera + Återställ lösenord Sök igenom alla undernoder + Sessionen går ut Startnod i innehåll Användarens namn Användarrättigheter Användartyp Användartyper Skribent + Din nuvarande historik + Din profil
\ No newline at end of file From 48dd6198994945b96e3fbe04d475b66856a70382 Mon Sep 17 00:00:00 2001 From: Kalle Ekstrand Date: Thu, 28 Nov 2013 14:34:22 +0100 Subject: [PATCH 0057/1046] Minor changes --- src/Umbraco.Web.UI/umbraco/config/lang/sv.xml | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml b/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml index 06b1861743..91e08eb95c 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml @@ -44,11 +44,11 @@ Lägg till nytt domännamn Domännamn - Har skapat domännamnet '{0}' - Har tagit bort domännamnet '{0}' + Har skapat domännamnet {0} + Har tagit bort domännamnet {0} Domänen {0} är redan tillagd t.ex.: dittdomannamn.se, www.dittdomannamn.se - Domännamnet '{0}' har uppdaterats + Domännamnet {0} har uppdaterats Domänen är redan tilldelad Ärv Ogiltigt domännamn @@ -92,7 +92,7 @@ Välj aktuell mapp Förhandsgranska Förhandsgranskning är avstängt på grund av att det inte finns någon mall tilldelad - Gör någonting annat + Ångra Välj stil Visa stil Infoga tabell @@ -122,7 +122,7 @@ Om denna sida - Alternativ länk + Alias (hur skulle du beskriva denna bild för någon över telefon) Alternativa länkar Underliggande noder @@ -147,7 +147,7 @@ Sidnamn Ej medlem av grupp(er) Egenskaper - Detta dokument är publicerat men syns inte eftersom den överordnade sidan '{0}' inte är publicerad + Detta dokument är publicerat men syns inte eftersom den överordnade sidan {0} inte är publicerad Oops: detta dokument är publicerat men finns inte i cacheminnet (internt fel) Publicera Publiceringsstatus @@ -168,7 +168,7 @@ Var vill du skapa den nya {0} - Skapa här + Skapa innehåll under "dokumenttyper".]]> "mediatyper".]]> Välj typ och rubrik @@ -229,7 +229,7 @@ Se cachat objekt - Redigera de olika översättningarna för ordboksinlägget '{0}' nedan. Du kan lägga till ytterligare språk under 'språk' i menyn till vänster. + Redigera de olika översättningarna för ordboksinlägget {0} nedan. Du kan lägga till ytterligare språk under 'språk' i menyn till vänster. Språknamn @@ -450,7 +450,7 @@ Besök din nya webbplats Du installerade Runway, så varför inte se hur din nya webbplats ser ut.]]> Ytterligare hjälp och information Få hjälp från våra prisbelönta community, bläddra i dokumentationen eller titta på några gratis videor om hur man bygger en enkel webbplats, hur du använder paket eller en snabbguide till umbracos terminologi]]> Umbraco {0} är installerat och klart för användning - /web.config filen och ändra AppSettingsnyckeln umbracoConfigurationStatus på slutet till '{0}']]> + /web.config filen och ändra AppSettingsnyckeln umbracoConfigurationStatus på slutet till {0}]]> börja omedelbart genom att klicka på "Starta Umbraco"-knappen nedan.
Om du är en ny umbraco användarekan du hitta massor av resurser på våra kom igång sidor.]]>
Starta Umbraco För att administrera din webbplats öppnar du bara umbraco back office och börjar lägga till innehåll, uppdatera mallar och stilmallar eller lägga till nya funktioner.]]> Anslutningen till databasen misslyckades. @@ -756,8 +756,8 @@ Hittade inga användare som är översättare. Vänligen skapa en användare som är översättare innan du börjar skicka innehåll för översättning Arbetsuppgifter som du har skapat som du har skapat. För att se en detaljvy med kommentarer, klicka på "Detaljer" eller på sidans namn. Du kan också ladda ned sidan i XML-format genom att klicka på länken "Ladda ned XML".
För att markera ett översättningsjobb som avslutat, gå till detaljvyn och klicka på knappen "Stäng".]]>
- Sidan '{0}' har skickats för översättning - Skicka sidan '{0}' för översättning + Sidan {0} har skickats för översättning + Skicka sidan {0} för översättning Tilldelat av Arbetsuppgift öppnad Totalt antal ord From 24fcfd9fcb39943118e6086c87f7ce45c45e91e1 Mon Sep 17 00:00:00 2001 From: Kalle Ekstrand Date: Thu, 28 Nov 2013 15:07:33 +0100 Subject: [PATCH 0058/1046] Minor changes --- src/Umbraco.Web.UI/umbraco/config/lang/sv.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml b/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml index 91e08eb95c..ff957c1b54 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml @@ -81,7 +81,7 @@ Infoga länk Infoga intern länk (ankare) Punktlista - Numerad lista + Numrerad lista Infoga macro Infoga bild Ändra relation From cacf87be2d2c5f128d1b3cd1dc1615879c4d8355 Mon Sep 17 00:00:00 2001 From: esunxray Date: Fri, 29 Nov 2013 21:26:16 +0800 Subject: [PATCH 0059/1046] Update zh.xml update Chinese file for 6.2, this can be merged into V7. --- src/Umbraco.Web.UI/umbraco/config/lang/zh.xml | 110 ++++++++++++------ 1 file changed, 77 insertions(+), 33 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml b/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml index a586389379..472ac1252c 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml @@ -1,4 +1,4 @@ - + 孙柱梁 @@ -8,6 +8,7 @@ 管理主机名 跟踪审计 浏览节点 + 改变文档类型 复制 创建 创建扩展包 @@ -36,19 +37,30 @@ 更新 + 禁止访问 添加域名 + 移除 + 错误的节点 + 域名重复 域名 + 语言 新域名 '%0%' 已创建 域名 '%0%' 已删除 域名 '%0%' 已使用 https://www.example.com/、example.com/en、……使用 * 代表任意域名,
- 只需要设置语言部分即可。]]> -
+ 只需要设置语言部分即可。]]> + 域名 '%0%' 已更新 域名错误 编辑当前域名 + 继承 + 语言 + + 也可以从父节点继承。]]> + + 域名 查看 @@ -80,6 +92,26 @@ 显示样式 插入表格 + + 要更改所选节点的文档类型,先在列表中选择合适的文档类型。 + 然后设置当前文档类型到新文档类型的各字段间的对应映射关系并保存。 + 内容已被重新发布 + 当前属性 + 当前类型 + 不能改变文档类型,因为没有可替代的类型。 + 文档类型已更改 + 要映射的字段 + 映射字段 + 新模板 + 新类型 + + 内容 + 选择新的文档类型 + 选中文档的类型已被成功更改为[new type],以下字段被映射: + + 不能完成字段映射,因为存在一个字段映射至多字段的问题。 + 仅显示可作为替代的文档类型。 + 关于本页 别名 @@ -103,6 +135,7 @@ 页标题 属性 该文档不可见,因为其上级 '%0%' 未发布。 + 该文档已发布,但是没有更新至缓存(内部错误) 发布 发布状态 发布于 @@ -116,6 +149,8 @@ 最近编辑 移除文件 链接到文档 + 会员组成员 + 非会员组成员 您想在哪里创建 %0% @@ -217,6 +252,7 @@ %0% 格式不正确 + 该文件类型已被管理员禁用 注意,尽管配置中允许CodeMirror,但是它在IE上不够稳定,所以无法在IE运行。 请为新的属性类型填写名称和别名! 权限有问题,访问指定文件或文件夹失败! @@ -349,7 +385,7 @@

您可以自行编辑“web.config”文件,键名为 “umbracoDbDSN”

当自行编辑后,单击重试按钮
。 - 如何编辑web.config

+ 如何编辑web.config

]]> 如有必要,请联系您的系统管理员。 @@ -414,7 +450,7 @@ 您刚刚安装了一个干净的系统,要继续吗? “Runway”已安装 + 在顶部选择您想要的功能模块
这是我们推荐的模块,您也可以查看 全部模块 ]]>
仅推荐高级用户使用 @@ -482,6 +518,7 @@ 尚未选择节点,请选择一个节点点击“确定”。 类型不符不允许选择 该项不能移到其子项 + 当前节点不能建在根节点下 您在子项的权限不够,不允许该操作。 复本和原本建立关联 @@ -498,32 +535,32 @@ ]]>
%0%:

-

您好!这是一封自动发送的邮件,告诉您任务'%1%'已在'%2%'被用户'%3%'执行

- -

-

更新概况:

- - %6% -
-

+

您好!这是一封自动发送的邮件,告诉您任务'%1%'已在'%2%'被用户'%3%'执行

+ +

+

更新概况:

+ + %6% +
+

- + -

祝您愉快!

- 该信息由系统自动发送 -

+

祝您愉快!

+ 该信息由系统自动发送 +

]]>
在 %2%,[%0%] 关于 %1% 的通告已执行。 通知 @@ -558,6 +595,7 @@ 更新说明 扩展包有可用的更新,您可以从程序库网站更新。 版本 + 版本历史 访问扩展包网站 @@ -585,6 +623,9 @@ 如果您只希望提供一个用户名和密码就能访问 + + + @@ -655,6 +696,7 @@ 选项卡 选项卡标题 选项卡 + 没有字段设置在该标签页 排序完成。 @@ -684,6 +726,8 @@ 请发布以使更改生效 提交审核 更改已提交审核 + 媒体已保存 + 媒体已保存 会员已保存 样式表属性已保存 样式表已保存 @@ -709,10 +753,10 @@ XSLT无法保存,请检查权限。 XSLT已保存 XSLT无错误 - Partial视图已保存 - Partial视图保存,无错误。 - Partial视图未保存 - Partial视图因为错误未能保存 + 片段视图已保存 + 片段视图保存,无错误。 + 片段视图未保存 + 片段视图因为错误未能保存 使用CSS语法,如:h1、.redHeader、.blueTex。 From 3f1b057f45d09ef00c77e8ee093e92a97c3ff7f1 Mon Sep 17 00:00:00 2001 From: Jeavon Leopold Date: Thu, 5 Dec 2013 09:06:01 +0000 Subject: [PATCH 0060/1046] Fixes U4-3247 - v6.1.6 RTE view/edit HTML doesn't work with IE 11 by upgrading TinyMce to v3.5.10 --- .../umbraco_client/tinymce3/license.txt | 2 +- .../plugins/autolink/editor_plugin.js | 2 +- .../plugins/autolink/editor_plugin_src.js | 4 +- .../plugins/example/editor_plugin_src.js | 2 +- .../plugins/fullscreen/editor_plugin.js | 2 +- .../plugins/fullscreen/editor_plugin_src.js | 131 +++++-- .../plugins/fullscreen/fullscreen.htm | 25 +- .../plugins/inlinepopups/editor_plugin.js | 2 +- .../plugins/inlinepopups/editor_plugin_src.js | 26 +- .../tinymce3/plugins/media/js/media.js | 44 ++- .../tinymce3/plugins/paste/editor_plugin.js | 2 +- .../plugins/paste/editor_plugin_src.js | 10 +- .../plugins/searchreplace/js/searchreplace.js | 6 + .../plugins/spellchecker/editor_plugin.js | 2 +- .../plugins/spellchecker/editor_plugin_src.js | 45 ++- .../tinymce3/plugins/table/editor_plugin.js | 2 +- .../plugins/table/editor_plugin_src.js | 10 +- .../tinymce3/plugins/table/js/row.js | 17 + .../tinymce3/plugins/table/js/table.js | 14 +- .../tinymce3/plugins/table/row.htm | 30 +- .../themes/advanced/editor_template.js | 2 +- .../themes/advanced/editor_template_src.js | 2 +- .../umbraco_client/tinymce3/tiny_mce.js | 2 +- .../umbraco_client/tinymce3/tiny_mce_popup.js | 2 +- .../umbraco_client/tinymce3/tiny_mce_src.js | 341 ++++++++++++------ 25 files changed, 511 insertions(+), 216 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/license.txt b/src/Umbraco.Web.UI/umbraco_client/tinymce3/license.txt index 60d6d4c8f5..5a25342994 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/license.txt +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/license.txt @@ -20,7 +20,7 @@ free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether +can use it too, but we suggest you first think carefuly about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/autolink/editor_plugin.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/autolink/editor_plugin.js index d1c3502ace..71d86bbecb 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/autolink/editor_plugin.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/autolink/editor_plugin.js @@ -1 +1 @@ -(function(){tinymce.create("tinymce.plugins.AutolinkPlugin",{init:function(a,b){var c=this;a.onKeyDown.addToTop(function(d,f){if(f.keyCode==13){return c.handleEnter(d)}});if(tinyMCE.isIE){return}a.onKeyPress.add(function(d,f){if(f.which==41){return c.handleEclipse(d)}});a.onKeyUp.add(function(d,f){if(f.keyCode==32){return c.handleSpacebar(d)}})},handleEclipse:function(a){this.parseCurrentLine(a,-1,"(",true)},handleSpacebar:function(a){this.parseCurrentLine(a,0,"",true)},handleEnter:function(a){this.parseCurrentLine(a,-1,"",false)},parseCurrentLine:function(i,d,b,g){var a,f,c,n,k,m,h,e,j;a=i.selection.getRng(true).cloneRange();if(a.startOffset<5){e=a.endContainer.previousSibling;if(e==null){if(a.endContainer.firstChild==null||a.endContainer.firstChild.nextSibling==null){return}e=a.endContainer.firstChild.nextSibling}j=e.length;a.setStart(e,j);a.setEnd(e,j);if(a.endOffset<5){return}f=a.endOffset;n=e}else{n=a.endContainer;if(n.nodeType!=3&&n.firstChild){while(n.nodeType!=3&&n.firstChild){n=n.firstChild}if(n.nodeType==3){a.setStart(n,0);a.setEnd(n,n.nodeValue.length)}}if(a.endOffset==1){f=2}else{f=a.endOffset-1-d}}c=f;do{a.setStart(n,f-2);a.setEnd(n,f-1);f-=1}while(a.toString()!=" "&&a.toString()!=""&&a.toString().charCodeAt(0)!=160&&(f-2)>=0&&a.toString()!=b);if(a.toString()==b||a.toString().charCodeAt(0)==160){a.setStart(n,f);a.setEnd(n,c);f+=1}else{if(a.startOffset==0){a.setStart(n,0);a.setEnd(n,c)}else{a.setStart(n,f);a.setEnd(n,c)}}var m=a.toString();if(m.charAt(m.length-1)=="."){a.setEnd(n,c-1)}m=a.toString();h=m.match(/^(https?:\/\/|ssh:\/\/|ftp:\/\/|file:\/|www\.|(?:mailto:)?[A-Z0-9._%+-]+@)(.+)$/i);if(h){if(h[1]=="www."){h[1]="http://www."}else{if(/@$/.test(h[1])&&!/^mailto:/.test(h[1])){h[1]="mailto:"+h[1]}}k=i.selection.getBookmark();i.selection.setRng(a);tinyMCE.execCommand("createlink",false,h[1]+h[2]);i.selection.moveToBookmark(k);i.nodeChanged();if(tinyMCE.isWebKit){i.selection.collapse(false);var l=Math.min(n.length,c+1);a.setStart(n,l);a.setEnd(n,l);i.selection.setRng(a)}}},getInfo:function(){return{longname:"Autolink",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autolink",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("autolink",tinymce.plugins.AutolinkPlugin)})(); \ No newline at end of file +(function(){tinymce.create("tinymce.plugins.AutolinkPlugin",{init:function(a,b){var c=this;a.onKeyDown.addToTop(function(d,f){if(f.keyCode==13){return c.handleEnter(d)}});if(tinyMCE.isIE){return}a.onKeyPress.add(function(d,f){if(f.which==41){return c.handleEclipse(d)}});a.onKeyUp.add(function(d,f){if(f.keyCode==32){return c.handleSpacebar(d)}})},handleEclipse:function(a){this.parseCurrentLine(a,-1,"(",true)},handleSpacebar:function(a){this.parseCurrentLine(a,0,"",true)},handleEnter:function(a){this.parseCurrentLine(a,-1,"",false)},parseCurrentLine:function(i,d,b,g){var a,f,c,n,k,m,h,e,j;a=i.selection.getRng(true).cloneRange();if(a.startOffset<5){e=a.endContainer.previousSibling;if(e==null){if(a.endContainer.firstChild==null||a.endContainer.firstChild.nextSibling==null){return}e=a.endContainer.firstChild.nextSibling}j=e.length;a.setStart(e,j);a.setEnd(e,j);if(a.endOffset<5){return}f=a.endOffset;n=e}else{n=a.endContainer;if(n.nodeType!=3&&n.firstChild){while(n.nodeType!=3&&n.firstChild){n=n.firstChild}if(n.nodeType==3){a.setStart(n,0);a.setEnd(n,n.nodeValue.length)}}if(a.endOffset==1){f=2}else{f=a.endOffset-1-d}}c=f;do{a.setStart(n,f>=2?f-2:0);a.setEnd(n,f>=1?f-1:0);f-=1}while(a.toString()!=" "&&a.toString()!=""&&a.toString().charCodeAt(0)!=160&&(f-2)>=0&&a.toString()!=b);if(a.toString()==b||a.toString().charCodeAt(0)==160){a.setStart(n,f);a.setEnd(n,c);f+=1}else{if(a.startOffset==0){a.setStart(n,0);a.setEnd(n,c)}else{a.setStart(n,f);a.setEnd(n,c)}}var m=a.toString();if(m.charAt(m.length-1)=="."){a.setEnd(n,c-1)}m=a.toString();h=m.match(/^(https?:\/\/|ssh:\/\/|ftp:\/\/|file:\/|www\.|(?:mailto:)?[A-Z0-9._%+-]+@)(.+)$/i);if(h){if(h[1]=="www."){h[1]="http://www."}else{if(/@$/.test(h[1])&&!/^mailto:/.test(h[1])){h[1]="mailto:"+h[1]}}k=i.selection.getBookmark();i.selection.setRng(a);tinyMCE.execCommand("createlink",false,h[1]+h[2]);i.selection.moveToBookmark(k);i.nodeChanged();if(tinyMCE.isWebKit){i.selection.collapse(false);var l=Math.min(n.length,c+1);a.setStart(n,l);a.setEnd(n,l);i.selection.setRng(a)}}},getInfo:function(){return{longname:"Autolink",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autolink",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("autolink",tinymce.plugins.AutolinkPlugin)})(); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/autolink/editor_plugin_src.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/autolink/editor_plugin_src.js index c05fbbc09c..5b61f7a20b 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/autolink/editor_plugin_src.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/autolink/editor_plugin_src.js @@ -107,8 +107,8 @@ do { // Move the selection one character backwards. - r.setStart(endContainer, end - 2); - r.setEnd(endContainer, end - 1); + r.setStart(endContainer, end >= 2 ? end - 2 : 0); + r.setEnd(endContainer, end >= 1 ? end - 1 : 0); end -= 1; // Loop until one of the following is found: a blank space,  , delimeter, (end-2) >= 0 diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/example/editor_plugin_src.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/example/editor_plugin_src.js index 9a0e7da15d..0a259471d2 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/example/editor_plugin_src.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/example/editor_plugin_src.js @@ -49,7 +49,7 @@ }, /** - * Creates control instances based in the incomming name. This method is normally not + * Creates control instances based in the incoming name. This method is normally not * needed since the addButton method of the tinymce.Editor class is a more easy way of adding buttons * but you sometimes need to create more complex controls like listboxes, split buttons etc then this * method can be used to create those. diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/fullscreen/editor_plugin.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/fullscreen/editor_plugin.js index a2eb034839..1aa8cc443a 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/fullscreen/editor_plugin.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/fullscreen/editor_plugin.js @@ -1 +1 @@ -(function(){var a=tinymce.DOM;tinymce.create("tinymce.plugins.FullScreenPlugin",{init:function(d,e){var f=this,g={},c,b;f.editor=d;d.addCommand("mceFullScreen",function(){var i,j=a.doc.documentElement;if(d.getParam("fullscreen_is_enabled")){if(d.getParam("fullscreen_new_window")){closeFullscreen()}else{a.win.setTimeout(function(){tinymce.dom.Event.remove(a.win,"resize",f.resizeFunc);tinyMCE.get(d.getParam("fullscreen_editor_id")).setContent(d.getContent());tinyMCE.remove(d);a.remove("mce_fullscreen_container");j.style.overflow=d.getParam("fullscreen_html_overflow");a.setStyle(a.doc.body,"overflow",d.getParam("fullscreen_overflow"));a.win.scrollTo(d.getParam("fullscreen_scrollx"),d.getParam("fullscreen_scrolly"));tinyMCE.settings=tinyMCE.oldSettings},10)}return}if(d.getParam("fullscreen_new_window")){i=a.win.open(e+"/fullscreen.htm","mceFullScreenPopup","fullscreen=yes,menubar=no,toolbar=no,scrollbars=no,resizable=yes,left=0,top=0,width="+screen.availWidth+",height="+screen.availHeight);try{i.resizeTo(screen.availWidth,screen.availHeight)}catch(h){}}else{tinyMCE.oldSettings=tinyMCE.settings;g.fullscreen_overflow=a.getStyle(a.doc.body,"overflow",1)||"auto";g.fullscreen_html_overflow=a.getStyle(j,"overflow",1);c=a.getViewPort();g.fullscreen_scrollx=c.x;g.fullscreen_scrolly=c.y;if(tinymce.isOpera&&g.fullscreen_overflow=="visible"){g.fullscreen_overflow="auto"}if(tinymce.isIE&&g.fullscreen_overflow=="scroll"){g.fullscreen_overflow="auto"}if(tinymce.isIE&&(g.fullscreen_html_overflow=="visible"||g.fullscreen_html_overflow=="scroll")){g.fullscreen_html_overflow="auto"}if(g.fullscreen_overflow=="0px"){g.fullscreen_overflow=""}a.setStyle(a.doc.body,"overflow","hidden");j.style.overflow="hidden";c=a.getViewPort();a.win.scrollTo(0,0);if(tinymce.isIE){c.h-=1}if(tinymce.isIE6||document.compatMode=="BackCompat"){b="absolute;top:"+c.y}else{b="fixed;top:0"}n=a.add(a.doc.body,"div",{id:"mce_fullscreen_container",style:"position:"+b+";left:0;width:"+c.w+"px;height:"+c.h+"px;z-index:200000;"});a.add(n,"div",{id:"mce_fullscreen"});tinymce.each(d.settings,function(k,l){g[l]=k});g.id="mce_fullscreen";g.width=n.clientWidth;g.height=n.clientHeight-15;g.fullscreen_is_enabled=true;g.fullscreen_editor_id=d.id;g.theme_advanced_resizing=false;g.save_onsavecallback=function(){d.setContent(tinyMCE.get(g.id).getContent());d.execCommand("mceSave")};tinymce.each(d.getParam("fullscreen_settings"),function(m,l){g[l]=m});if(g.theme_advanced_toolbar_location==="external"){g.theme_advanced_toolbar_location="top"}f.fullscreenEditor=new tinymce.Editor("mce_fullscreen",g);f.fullscreenEditor.onInit.add(function(){f.fullscreenEditor.setContent(d.getContent());f.fullscreenEditor.focus()});f.fullscreenEditor.render();f.fullscreenElement=new tinymce.dom.Element("mce_fullscreen_container");f.fullscreenElement.update();f.resizeFunc=tinymce.dom.Event.add(a.win,"resize",function(){var o=tinymce.DOM.getViewPort(),l=f.fullscreenEditor,k,m;k=l.dom.getSize(l.getContainer().getElementsByTagName("table")[0]);m=l.dom.getSize(l.getContainer().getElementsByTagName("iframe")[0]);l.theme.resizeTo(o.w-k.w+m.w,o.h-k.h+m.h)})}});d.addButton("fullscreen",{title:"fullscreen.desc",cmd:"mceFullScreen"});d.onNodeChange.add(function(i,h){h.setActive("fullscreen",i.getParam("fullscreen_is_enabled"))})},getInfo:function(){return{longname:"Fullscreen",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/fullscreen",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("fullscreen",tinymce.plugins.FullScreenPlugin)})(); \ No newline at end of file +(function(){var b=tinymce.DOM;var a=function(d,f,e){var c=function(g){var i=d.controlManager.get(g);var h=f.controlManager.get(g);if(i&&h){h.displayColor(i.value)}};c("forecolor");c("backcolor");f.setContent(d.getContent({format:"raw"}),{format:"raw"});f.selection.moveToBookmark(e);if(d.plugins.spellchecker&&f.plugins.spellchecker){f.plugins.spellchecker.setLanguage(d.plugins.spellchecker.selectedLang)}};tinymce.create("tinymce.plugins.FullScreenPlugin",{init:function(i,c){var l=this,m={},k=b.doc.documentElement,d,o,h,g,f,e,j;i.addCommand("mceFullScreen",function(){var q,r;if(i.getParam("fullscreen_is_enabled")){if(i.getParam("fullscreen_new_window")){closeFullscreen()}else{b.win.setTimeout(function(){var t=i;var s=tinyMCE.get(t.getParam("fullscreen_editor_id"));s.plugins.fullscreen.saveState(t);tinyMCE.remove(t)},10)}return}if(i.getParam("fullscreen_new_window")){l.fullscreenSettings={bookmark:i.selection.getBookmark()};q=b.win.open(c+"/fullscreen.htm","mceFullScreenPopup","fullscreen=yes,menubar=no,toolbar=no,scrollbars=no,resizable=yes,left=0,top=0,width="+screen.availWidth+",height="+screen.availHeight);try{q.resizeTo(screen.availWidth,screen.availHeight)}catch(p){}}else{o=b.getStyle(b.doc.body,"overflow",1)||"auto";h=b.getStyle(k,"overflow",1);d=b.getViewPort();g=d.x;f=d.y;if(tinymce.isOpera&&o=="visible"){o="auto"}if(tinymce.isIE&&o=="scroll"){o="auto"}if(tinymce.isIE&&(h=="visible"||h=="scroll")){h="auto"}if(o=="0px"){o=""}b.setStyle(b.doc.body,"overflow","hidden");k.style.overflow="hidden";d=b.getViewPort();b.win.scrollTo(0,0);if(tinymce.isIE){d.h-=1}if(tinymce.isIE6||document.compatMode=="BackCompat"){e="absolute;top:"+d.y}else{e="fixed;top:0"}n=b.add(b.doc.body,"div",{id:"mce_fullscreen_container",style:"position:"+e+";left:0;width:"+d.w+"px;height:"+d.h+"px;z-index:200000;"});b.add(n,"div",{id:"mce_fullscreen"});tinymce.each(i.settings,function(s,t){m[t]=s});m.id="mce_fullscreen";m.width=n.clientWidth;m.height=n.clientHeight-15;m.fullscreen_is_enabled=true;m.fullscreen_editor_id=i.id;m.theme_advanced_resizing=false;m.save_onsavecallback=function(){i.setContent(tinyMCE.get(m.id).getContent());i.execCommand("mceSave")};tinymce.each(i.getParam("fullscreen_settings"),function(t,s){m[s]=t});l.fullscreenSettings={bookmark:i.selection.getBookmark(),fullscreen_overflow:o,fullscreen_html_overflow:h,fullscreen_scrollx:g,fullscreen_scrolly:f};if(m.theme_advanced_toolbar_location==="external"){m.theme_advanced_toolbar_location="top"}tinyMCE.oldSettings=tinyMCE.settings;l.fullscreenEditor=new tinymce.Editor("mce_fullscreen",m);l.fullscreenEditor.onInit.add(function(){l.loadState(l.fullscreenEditor)});l.fullscreenEditor.render();l.fullscreenElement=new tinymce.dom.Element("mce_fullscreen_container");l.fullscreenElement.update();l.resizeFunc=tinymce.dom.Event.add(b.win,"resize",function(){var v=tinymce.DOM.getViewPort(),t=l.fullscreenEditor,s,u;s=t.dom.getSize(t.getContainer().getElementsByTagName("table")[0]);u=t.dom.getSize(t.getContainer().getElementsByTagName("iframe")[0]);t.theme.resizeTo(v.w-s.w+u.w,v.h-s.h+u.h)})}});i.addButton("fullscreen",{title:"fullscreen.desc",cmd:"mceFullScreen"});i.onNodeChange.add(function(q,p){p.setActive("fullscreen",q.getParam("fullscreen_is_enabled"))});l.loadState=function(p){if(!(p&&l.fullscreenSettings)){throw"No fullscreen editor to load to"}a(i,p,l.fullscreenSettings.bookmark);p.focus()};l.saveState=function(q){if(!(q&&l.fullscreenSettings)){throw"No fullscreen editor to restore from"}var p=l.fullscreenSettings;a(q,i,q.selection.getBookmark());if(!i.getParam("fullscreen_new_window")){tinymce.dom.Event.remove(b.win,"resize",l.resizeFunc);delete l.resizeFunc;b.remove("mce_fullscreen_container");b.doc.documentElement.style.overflow=p.fullscreen_html_overflow;b.setStyle(b.doc.body,"overflow",p.fullscreen_overflow);b.win.scrollTo(p.fullscreen_scrollx,p.fullscreen_scrolly)}tinyMCE.settings=tinyMCE.oldSettings;delete tinyMCE.oldSettings;delete l.fullscreenEditor;delete l.fullscreenElement;delete l.fullscreenSettings;b.win.setTimeout(function(){i.selection.moveToBookmark(j);i.focus()},10)}},getInfo:function(){return{longname:"Fullscreen",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/fullscreen",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("fullscreen",tinymce.plugins.FullScreenPlugin)})(); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/fullscreen/editor_plugin_src.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/fullscreen/editor_plugin_src.js index 524b487aa0..bec886f74f 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/fullscreen/editor_plugin_src.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/fullscreen/editor_plugin_src.js @@ -11,29 +11,48 @@ (function() { var DOM = tinymce.DOM; + // State Transfer function + var transferState = function(oldEditor, newEditor, bookmark) { + var transferColorButtonState = function(swapme) { + var c = oldEditor.controlManager.get(swapme); + var newC = newEditor.controlManager.get(swapme); + + if (c && newC) { + newC.displayColor(c.value); + } + + }; + + transferColorButtonState('forecolor'); + transferColorButtonState('backcolor'); + newEditor.setContent(oldEditor.getContent({format : 'raw'}), {format : 'raw'}); + newEditor.selection.moveToBookmark(bookmark); + + if (oldEditor.plugins.spellchecker && newEditor.plugins.spellchecker) { + newEditor.plugins.spellchecker.setLanguage(oldEditor.plugins.spellchecker.selectedLang); + } + }; + tinymce.create('tinymce.plugins.FullScreenPlugin', { init : function(ed, url) { - var t = this, s = {}, vp, posCss; - - t.editor = ed; + var t = this, s = {}, de = DOM.doc.documentElement, vp, fullscreen_overflow, fullscreen_html_overflow, fullscreen_scrollx, fullscreen_scrolly, posCss, bookmark; // Register commands ed.addCommand('mceFullScreen', function() { - var win, de = DOM.doc.documentElement; + var win, oed; if (ed.getParam('fullscreen_is_enabled')) { if (ed.getParam('fullscreen_new_window')) - closeFullscreen(); // Call to close in new window + closeFullscreen(); // Call to close in fullscreen.htm else { DOM.win.setTimeout(function() { - tinymce.dom.Event.remove(DOM.win, 'resize', t.resizeFunc); - tinyMCE.get(ed.getParam('fullscreen_editor_id')).setContent(ed.getContent()); - tinyMCE.remove(ed); - DOM.remove('mce_fullscreen_container'); - de.style.overflow = ed.getParam('fullscreen_html_overflow'); - DOM.setStyle(DOM.doc.body, 'overflow', ed.getParam('fullscreen_overflow')); - DOM.win.scrollTo(ed.getParam('fullscreen_scrollx'), ed.getParam('fullscreen_scrolly')); - tinyMCE.settings = tinyMCE.oldSettings; // Restore old settings + var fullscreenEditor = ed; + + // find the editor that opened this one, execute restore function there + var originalEditor = tinyMCE.get(fullscreenEditor.getParam('fullscreen_editor_id')); + originalEditor.plugins.fullscreen.saveState(fullscreenEditor); + + tinyMCE.remove(fullscreenEditor); }, 10); } @@ -41,6 +60,9 @@ } if (ed.getParam('fullscreen_new_window')) { + t.fullscreenSettings = { + bookmark: ed.selection.getBookmark() + }; win = DOM.win.open(url + "/fullscreen.htm", "mceFullScreenPopup", "fullscreen=yes,menubar=no,toolbar=no,scrollbars=no,resizable=yes,left=0,top=0,width=" + screen.availWidth + ",height=" + screen.availHeight); try { win.resizeTo(screen.availWidth, screen.availHeight); @@ -48,27 +70,26 @@ // Ignore } } else { - tinyMCE.oldSettings = tinyMCE.settings; // Store old settings - s.fullscreen_overflow = DOM.getStyle(DOM.doc.body, 'overflow', 1) || 'auto'; - s.fullscreen_html_overflow = DOM.getStyle(de, 'overflow', 1); + fullscreen_overflow = DOM.getStyle(DOM.doc.body, 'overflow', 1) || 'auto'; + fullscreen_html_overflow = DOM.getStyle(de, 'overflow', 1); vp = DOM.getViewPort(); - s.fullscreen_scrollx = vp.x; - s.fullscreen_scrolly = vp.y; + fullscreen_scrollx = vp.x; + fullscreen_scrolly = vp.y; // Fixes an Opera bug where the scrollbars doesn't reappear - if (tinymce.isOpera && s.fullscreen_overflow == 'visible') - s.fullscreen_overflow = 'auto'; + if (tinymce.isOpera && fullscreen_overflow == 'visible') + fullscreen_overflow = 'auto'; // Fixes an IE bug where horizontal scrollbars would appear - if (tinymce.isIE && s.fullscreen_overflow == 'scroll') - s.fullscreen_overflow = 'auto'; + if (tinymce.isIE && fullscreen_overflow == 'scroll') + fullscreen_overflow = 'auto'; // Fixes an IE bug where the scrollbars doesn't reappear - if (tinymce.isIE && (s.fullscreen_html_overflow == 'visible' || s.fullscreen_html_overflow == 'scroll')) - s.fullscreen_html_overflow = 'auto'; + if (tinymce.isIE && (fullscreen_html_overflow == 'visible' || fullscreen_html_overflow == 'scroll')) + fullscreen_html_overflow = 'auto'; - if (s.fullscreen_overflow == '0px') - s.fullscreen_overflow = ''; + if (fullscreen_overflow == '0px') + fullscreen_overflow = ''; DOM.setStyle(DOM.doc.body, 'overflow', 'hidden'); de.style.overflow = 'hidden'; //Fix for IE6/7 @@ -108,13 +129,21 @@ s[k] = v; }); + t.fullscreenSettings = { + bookmark: ed.selection.getBookmark(), + fullscreen_overflow: fullscreen_overflow, + fullscreen_html_overflow: fullscreen_html_overflow, + fullscreen_scrollx: fullscreen_scrollx, + fullscreen_scrolly: fullscreen_scrolly + }; + if (s.theme_advanced_toolbar_location === 'external') s.theme_advanced_toolbar_location = 'top'; + tinyMCE.oldSettings = tinyMCE.settings; // Store old settings, the Editor constructor overwrites them t.fullscreenEditor = new tinymce.Editor('mce_fullscreen', s); t.fullscreenEditor.onInit.add(function() { - t.fullscreenEditor.setContent(ed.getContent()); - t.fullscreenEditor.focus(); + t.loadState(t.fullscreenEditor); }); t.fullscreenEditor.render(); @@ -141,6 +170,52 @@ ed.onNodeChange.add(function(ed, cm) { cm.setActive('fullscreen', ed.getParam('fullscreen_is_enabled')); }); + + // fullscreenEditor is a param here because in window mode we don't create it + t.loadState = function(fullscreenEditor) { + if (!(fullscreenEditor && t.fullscreenSettings)) { + throw "No fullscreen editor to load to"; + } + + transferState(ed, fullscreenEditor, t.fullscreenSettings.bookmark); + fullscreenEditor.focus(); + + }; + + // fullscreenEditor is a param here because in window mode we don't create it + t.saveState = function(fullscreenEditor) { + if (!(fullscreenEditor && t.fullscreenSettings)) { + throw "No fullscreen editor to restore from"; + } + var settings = t.fullscreenSettings; + + transferState(fullscreenEditor, ed, fullscreenEditor.selection.getBookmark()); + + // cleanup only required if window mode isn't used + if (!ed.getParam('fullscreen_new_window')) { + tinymce.dom.Event.remove(DOM.win, 'resize', t.resizeFunc); + delete t.resizeFunc; + + DOM.remove('mce_fullscreen_container'); + + DOM.doc.documentElement.style.overflow = settings.fullscreen_html_overflow; + DOM.setStyle(DOM.doc.body, 'overflow', settings.fullscreen_overflow); + DOM.win.scrollTo(settings.fullscreen_scrollx, settings.fullscreen_scrolly); + } + tinyMCE.settings = tinyMCE.oldSettings; // Restore old settings + + // clear variables + delete tinyMCE.oldSettings; + delete t.fullscreenEditor; + delete t.fullscreenElement; + delete t.fullscreenSettings; + + // allow the fullscreen editor to be removed before restoring focus and selection + DOM.win.setTimeout(function() { + ed.selection.moveToBookmark(bookmark); + ed.focus(); + }, 10); + }; }, getInfo : function() { diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/fullscreen/fullscreen.htm b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/fullscreen/fullscreen.htm index ffe528e417..baf028b79f 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/fullscreen/fullscreen.htm +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/fullscreen/fullscreen.htm @@ -46,7 +46,7 @@ settings['strict_loading_mode'] = true; settings.save_onsavecallback = function() { - window.opener.tinyMCE.get(oeID).setContent(tinyMCE.get('fullscreenarea').getContent({format : 'raw'}), {format : 'raw'}); + moveContent(); window.opener.tinyMCE.get(oeID).execCommand('mceSave'); window.close(); }; @@ -56,11 +56,15 @@ } function moveContent() { - window.opener.tinyMCE.get(oeID).setContent(tinyMCE.activeEditor.getContent()); + // find the original editor, execute restore state in it's plugin instance + window.opener.tinyMCE.get(oeID).plugins.fullscreen.saveState(tinyMCE.activeEditor); + + // prevent moveContent from being called twice - e.g. if the unloadHandler runs after moveContent() + tinymce.dom.Event.remove(window, "beforeunload", unloadHandler); } function closeFullscreen() { - moveContent(); + // moveContent() will be called by the unload handler window.close(); } @@ -78,17 +82,20 @@ function render() { var e = document.getElementById('fullscreenarea'), vp, ed, ow, oh, dom = tinymce.DOM; - e.value = window.opener.tinyMCE.get(oeID).getContent(); - vp = dom.getViewPort(); settings.width = vp.w; settings.height = vp.h - 15; - tinymce.dom.Event.add(window, 'resize', function() { - var vp = dom.getViewPort(); + settings.oninit = function() { + var ed = tinyMCE.activeEditor; + window.opener.tinyMCE.get(oeID).plugins.fullscreen.loadState(ed); - tinyMCE.activeEditor.theme.resizeTo(vp.w, vp.h); - }); + tinymce.dom.Event.add(window, 'resize', function() { + var vp = dom.getViewPort(); + + tinyMCE.activeEditor.theme.resizeTo(vp.w, vp.h); + }); + } tinyMCE.init(settings); } diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/inlinepopups/editor_plugin.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/inlinepopups/editor_plugin.js index 8bb96f9cbe..2d71a2e174 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/inlinepopups/editor_plugin.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/inlinepopups/editor_plugin.js @@ -1 +1 @@ -(function(){var d=tinymce.DOM,b=tinymce.dom.Element,a=tinymce.dom.Event,e=tinymce.each,c=tinymce.is;tinymce.create("tinymce.plugins.InlinePopups",{init:function(f,g){f.onBeforeRenderUI.add(function(){f.windowManager=new tinymce.InlineWindowManager(f);d.loadCSS(g+"/skins/"+(f.settings.inlinepopups_skin||"clearlooks2")+"/window.css")})},getInfo:function(){return{longname:"InlinePopups",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/inlinepopups",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.create("tinymce.InlineWindowManager:tinymce.WindowManager",{InlineWindowManager:function(f){var g=this;g.parent(f);g.zIndex=300000;g.count=0;g.windows={}},open:function(s,j){var z=this,i,k="",r=z.editor,g=0,v=0,h,m,o,q,l,x,y,n;s=s||{};j=j||{};if(!s.inline){return z.parent(s,j)}n=z._frontWindow();if(n&&d.get(n.id+"_ifr")){n.focussedElement=d.get(n.id+"_ifr").contentWindow.document.activeElement}if(!s.type){z.bookmark=r.selection.getBookmark(1)}i=d.uniqueId();h=d.getViewPort();s.width=parseInt(s.width||320);s.height=parseInt(s.height||240)+(tinymce.isIE?8:0);s.min_width=parseInt(s.min_width||150);s.min_height=parseInt(s.min_height||100);s.max_width=parseInt(s.max_width||2000);s.max_height=parseInt(s.max_height||2000);s.left=s.left||Math.round(Math.max(h.x,h.x+(h.w/2)-(s.width/2)));s.top=s.top||Math.round(Math.max(h.y,h.y+(h.h/2)-(s.height/2)));s.movable=s.resizable=true;j.mce_width=s.width;j.mce_height=s.height;j.mce_inline=true;j.mce_window_id=i;j.mce_auto_focus=s.auto_focus;z.features=s;z.params=j;z.onOpen.dispatch(z,s,j);if(s.type){k+=" mceModal";if(s.type){k+=" mce"+s.type.substring(0,1).toUpperCase()+s.type.substring(1)}s.resizable=false}if(s.statusbar){k+=" mceStatusbar"}if(s.resizable){k+=" mceResizable"}if(s.minimizable){k+=" mceMinimizable"}if(s.maximizable){k+=" mceMaximizable"}if(s.movable){k+=" mceMovable"}z._addAll(d.doc.body,["div",{id:i,role:"dialog","aria-labelledby":s.type?i+"_content":i+"_title","class":(r.settings.inlinepopups_skin||"clearlooks2")+(tinymce.isIE&&window.getSelection?" ie9":""),style:"width:100px;height:100px"},["div",{id:i+"_wrapper","class":"mceWrapper"+k},["div",{id:i+"_top","class":"mceTop"},["div",{"class":"mceLeft"}],["div",{"class":"mceCenter"}],["div",{"class":"mceRight"}],["span",{id:i+"_title"},s.title||""]],["div",{id:i+"_middle","class":"mceMiddle"},["div",{id:i+"_left","class":"mceLeft",tabindex:"0"}],["span",{id:i+"_content"}],["div",{id:i+"_right","class":"mceRight",tabindex:"0"}]],["div",{id:i+"_bottom","class":"mceBottom"},["div",{"class":"mceLeft"}],["div",{"class":"mceCenter"}],["div",{"class":"mceRight"}],["span",{id:i+"_status"},"Content"]],["a",{"class":"mceMove",tabindex:"-1",href:"javascript:;"}],["a",{"class":"mceMin",tabindex:"-1",href:"javascript:;",onmousedown:"return false;"}],["a",{"class":"mceMax",tabindex:"-1",href:"javascript:;",onmousedown:"return false;"}],["a",{"class":"mceMed",tabindex:"-1",href:"javascript:;",onmousedown:"return false;"}],["a",{"class":"mceClose",tabindex:"-1",href:"javascript:;",onmousedown:"return false;"}],["a",{id:i+"_resize_n","class":"mceResize mceResizeN",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_s","class":"mceResize mceResizeS",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_w","class":"mceResize mceResizeW",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_e","class":"mceResize mceResizeE",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_nw","class":"mceResize mceResizeNW",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_ne","class":"mceResize mceResizeNE",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_sw","class":"mceResize mceResizeSW",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_se","class":"mceResize mceResizeSE",tabindex:"-1",href:"javascript:;"}]]]);d.setStyles(i,{top:-10000,left:-10000});if(tinymce.isGecko){d.setStyle(i,"overflow","auto")}if(!s.type){g+=d.get(i+"_left").clientWidth;g+=d.get(i+"_right").clientWidth;v+=d.get(i+"_top").clientHeight;v+=d.get(i+"_bottom").clientHeight}d.setStyles(i,{top:s.top,left:s.left,width:s.width+g,height:s.height+v});y=s.url||s.file;if(y){if(tinymce.relaxedDomain){y+=(y.indexOf("?")==-1?"?":"&")+"mce_rdomain="+tinymce.relaxedDomain}y=tinymce._addVer(y)}if(!s.type){d.add(i+"_content","iframe",{id:i+"_ifr",src:'javascript:""',frameBorder:0,style:"border:0;width:10px;height:10px"});d.setStyles(i+"_ifr",{width:s.width,height:s.height});d.setAttrib(i+"_ifr","src",y)}else{d.add(i+"_wrapper","a",{id:i+"_ok","class":"mceButton mceOk",href:"javascript:;",onmousedown:"return false;"},"Ok");if(s.type=="confirm"){d.add(i+"_wrapper","a",{"class":"mceButton mceCancel",href:"javascript:;",onmousedown:"return false;"},"Cancel")}d.add(i+"_middle","div",{"class":"mceIcon"});d.setHTML(i+"_content",s.content.replace("\n","
"));a.add(i,"keyup",function(f){var p=27;if(f.keyCode===p){s.button_func(false);return a.cancel(f)}});a.add(i,"keydown",function(f){var t,p=9;if(f.keyCode===p){t=d.select("a.mceCancel",i+"_wrapper")[0];if(t&&t!==f.target){t.focus()}else{d.get(i+"_ok").focus()}return a.cancel(f)}})}o=a.add(i,"mousedown",function(t){var u=t.target,f,p;f=z.windows[i];z.focus(i);if(u.nodeName=="A"||u.nodeName=="a"){if(u.className=="mceClose"){z.close(null,i);return a.cancel(t)}else{if(u.className=="mceMax"){f.oldPos=f.element.getXY();f.oldSize=f.element.getSize();p=d.getViewPort();p.w-=2;p.h-=2;f.element.moveTo(p.x,p.y);f.element.resizeTo(p.w,p.h);d.setStyles(i+"_ifr",{width:p.w-f.deltaWidth,height:p.h-f.deltaHeight});d.addClass(i+"_wrapper","mceMaximized")}else{if(u.className=="mceMed"){f.element.moveTo(f.oldPos.x,f.oldPos.y);f.element.resizeTo(f.oldSize.w,f.oldSize.h);f.iframeElement.resizeTo(f.oldSize.w-f.deltaWidth,f.oldSize.h-f.deltaHeight);d.removeClass(i+"_wrapper","mceMaximized")}else{if(u.className=="mceMove"){return z._startDrag(i,t,u.className)}else{if(d.hasClass(u,"mceResize")){return z._startDrag(i,t,u.className.substring(13))}}}}}}});q=a.add(i,"click",function(f){var p=f.target;z.focus(i);if(p.nodeName=="A"||p.nodeName=="a"){switch(p.className){case"mceClose":z.close(null,i);return a.cancel(f);case"mceButton mceOk":case"mceButton mceCancel":s.button_func(p.className=="mceButton mceOk");return a.cancel(f)}}});a.add([i+"_left",i+"_right"],"focus",function(p){var t=d.get(i+"_ifr");if(t){var f=t.contentWindow.document.body;var u=d.select(":input:enabled,*[tabindex=0]",f);if(p.target.id===(i+"_left")){u[u.length-1].focus()}else{u[0].focus()}}else{d.get(i+"_ok").focus()}});x=z.windows[i]={id:i,mousedown_func:o,click_func:q,element:new b(i,{blocker:1,container:r.getContainer()}),iframeElement:new b(i+"_ifr"),features:s,deltaWidth:g,deltaHeight:v};x.iframeElement.on("focus",function(){z.focus(i)});if(z.count==0&&z.editor.getParam("dialog_type","modal")=="modal"){d.add(d.doc.body,"div",{id:"mceModalBlocker","class":(z.editor.settings.inlinepopups_skin||"clearlooks2")+"_modalBlocker",style:{zIndex:z.zIndex-1}});d.show("mceModalBlocker");d.setAttrib(d.doc.body,"aria-hidden","true")}else{d.setStyle("mceModalBlocker","z-index",z.zIndex-1)}if(tinymce.isIE6||/Firefox\/2\./.test(navigator.userAgent)||(tinymce.isIE&&!d.boxModel)){d.setStyles("mceModalBlocker",{position:"absolute",left:h.x,top:h.y,width:h.w-2,height:h.h-2})}d.setAttrib(i,"aria-hidden","false");z.focus(i);z._fixIELayout(i,1);if(d.get(i+"_ok")){d.get(i+"_ok").focus()}z.count++;return x},focus:function(h){var g=this,f;if(f=g.windows[h]){f.zIndex=this.zIndex++;f.element.setStyle("zIndex",f.zIndex);f.element.update();h=h+"_wrapper";d.removeClass(g.lastId,"mceFocus");d.addClass(h,"mceFocus");g.lastId=h;if(f.focussedElement){f.focussedElement.focus()}else{if(d.get(h+"_ok")){d.get(f.id+"_ok").focus()}else{if(d.get(f.id+"_ifr")){d.get(f.id+"_ifr").focus()}}}}},_addAll:function(k,h){var g,l,f=this,j=tinymce.DOM;if(c(h,"string")){k.appendChild(j.doc.createTextNode(h))}else{if(h.length){k=k.appendChild(j.create(h[0],h[1]));for(g=2;gf){g=h;f=h.zIndex}});return g},setTitle:function(f,g){var h;f=this._findId(f);if(h=d.get(f+"_title")){h.innerHTML=d.encode(g)}},alert:function(g,f,j){var i=this,h;h=i.open({title:i,type:"alert",button_func:function(k){if(f){f.call(k||i,k)}i.close(null,h.id)},content:d.encode(i.editor.getLang(g,g)),inline:1,width:400,height:130})},confirm:function(g,f,j){var i=this,h;h=i.open({title:i,type:"confirm",button_func:function(k){if(f){f.call(k||i,k)}i.close(null,h.id)},content:d.encode(i.editor.getLang(g,g)),inline:1,width:400,height:130})},_findId:function(f){var g=this;if(typeof(f)=="string"){return f}e(g.windows,function(h){var i=d.get(h.id+"_ifr");if(i&&f==i.contentWindow){f=h.id;return false}});return f},_fixIELayout:function(i,h){var f,g;if(!tinymce.isIE6){return}e(["n","s","w","e","nw","ne","sw","se"],function(j){var k=d.get(i+"_resize_"+j);d.setStyles(k,{width:h?k.clientWidth:"",height:h?k.clientHeight:"",cursor:d.getStyle(k,"cursor",1)});d.setStyle(i+"_bottom","bottom","-1px");k=0});if(f=this.windows[i]){f.element.hide();f.element.show();e(d.select("div,a",i),function(k,j){if(k.currentStyle.backgroundImage!="none"){g=new Image();g.src=k.currentStyle.backgroundImage.replace(/url\(\"(.+)\"\)/,"$1")}});d.get(i).style.filter=""}}});tinymce.PluginManager.add("inlinepopups",tinymce.plugins.InlinePopups)})(); \ No newline at end of file +(function(){var d=tinymce.DOM,b=tinymce.dom.Element,a=tinymce.dom.Event,e=tinymce.each,c=tinymce.is;tinymce.create("tinymce.plugins.InlinePopups",{init:function(f,g){f.onBeforeRenderUI.add(function(){f.windowManager=new tinymce.InlineWindowManager(f);d.loadCSS(g+"/skins/"+(f.settings.inlinepopups_skin||"clearlooks2")+"/window.css")})},getInfo:function(){return{longname:"InlinePopups",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/inlinepopups",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.create("tinymce.InlineWindowManager:tinymce.WindowManager",{InlineWindowManager:function(f){var g=this;g.parent(f);g.zIndex=300000;g.count=0;g.windows={}},open:function(s,j){var z=this,i,k="",r=z.editor,g=0,v=0,h,m,o,q,l,x,y,n;s=s||{};j=j||{};if(!s.inline){return z.parent(s,j)}n=z._frontWindow();if(n&&d.get(n.id+"_ifr")){n.focussedElement=d.get(n.id+"_ifr").contentWindow.document.activeElement}if(!s.type){z.bookmark=r.selection.getBookmark(1)}i=d.uniqueId("mce_inlinepopups_");h=d.getViewPort();s.width=parseInt(s.width||320);s.height=parseInt(s.height||240)+(tinymce.isIE?8:0);s.min_width=parseInt(s.min_width||150);s.min_height=parseInt(s.min_height||100);s.max_width=parseInt(s.max_width||2000);s.max_height=parseInt(s.max_height||2000);s.left=s.left||Math.round(Math.max(h.x,h.x+(h.w/2)-(s.width/2)));s.top=s.top||Math.round(Math.max(h.y,h.y+(h.h/2)-(s.height/2)));s.movable=s.resizable=true;j.mce_width=s.width;j.mce_height=s.height;j.mce_inline=true;j.mce_window_id=i;j.mce_auto_focus=s.auto_focus;z.features=s;z.params=j;z.onOpen.dispatch(z,s,j);if(s.type){k+=" mceModal";if(s.type){k+=" mce"+s.type.substring(0,1).toUpperCase()+s.type.substring(1)}s.resizable=false}if(s.statusbar){k+=" mceStatusbar"}if(s.resizable){k+=" mceResizable"}if(s.minimizable){k+=" mceMinimizable"}if(s.maximizable){k+=" mceMaximizable"}if(s.movable){k+=" mceMovable"}z._addAll(d.doc.body,["div",{id:i,role:"dialog","aria-labelledby":s.type?i+"_content":i+"_title","class":(r.settings.inlinepopups_skin||"clearlooks2")+(tinymce.isIE&&window.getSelection?" ie9":""),style:"width:100px;height:100px"},["div",{id:i+"_wrapper","class":"mceWrapper"+k},["div",{id:i+"_top","class":"mceTop"},["div",{"class":"mceLeft"}],["div",{"class":"mceCenter"}],["div",{"class":"mceRight"}],["span",{id:i+"_title"},s.title||""]],["div",{id:i+"_middle","class":"mceMiddle"},["div",{id:i+"_left","class":"mceLeft",tabindex:"0"}],["span",{id:i+"_content"}],["div",{id:i+"_right","class":"mceRight",tabindex:"0"}]],["div",{id:i+"_bottom","class":"mceBottom"},["div",{"class":"mceLeft"}],["div",{"class":"mceCenter"}],["div",{"class":"mceRight"}],["span",{id:i+"_status"},"Content"]],["a",{"class":"mceMove",tabindex:"-1",href:"javascript:;"}],["a",{"class":"mceMin",tabindex:"-1",href:"javascript:;",onmousedown:"return false;"}],["a",{"class":"mceMax",tabindex:"-1",href:"javascript:;",onmousedown:"return false;"}],["a",{"class":"mceMed",tabindex:"-1",href:"javascript:;",onmousedown:"return false;"}],["a",{"class":"mceClose",tabindex:"-1",href:"javascript:;",onmousedown:"return false;"}],["a",{id:i+"_resize_n","class":"mceResize mceResizeN",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_s","class":"mceResize mceResizeS",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_w","class":"mceResize mceResizeW",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_e","class":"mceResize mceResizeE",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_nw","class":"mceResize mceResizeNW",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_ne","class":"mceResize mceResizeNE",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_sw","class":"mceResize mceResizeSW",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_se","class":"mceResize mceResizeSE",tabindex:"-1",href:"javascript:;"}]]]);d.setStyles(i,{top:-10000,left:-10000});if(tinymce.isGecko){d.setStyle(i,"overflow","auto")}if(!s.type){g+=d.get(i+"_left").clientWidth;g+=d.get(i+"_right").clientWidth;v+=d.get(i+"_top").clientHeight;v+=d.get(i+"_bottom").clientHeight}d.setStyles(i,{top:s.top,left:s.left,width:s.width+g,height:s.height+v});y=s.url||s.file;if(y){if(tinymce.relaxedDomain){y+=(y.indexOf("?")==-1?"?":"&")+"mce_rdomain="+tinymce.relaxedDomain}y=tinymce._addVer(y)}if(!s.type){d.add(i+"_content","iframe",{id:i+"_ifr",src:'javascript:""',frameBorder:0,style:"border:0;width:10px;height:10px"});d.setStyles(i+"_ifr",{width:s.width,height:s.height});d.setAttrib(i+"_ifr","src",y)}else{d.add(i+"_wrapper","a",{id:i+"_ok","class":"mceButton mceOk",href:"javascript:;",onmousedown:"return false;"},"Ok");if(s.type=="confirm"){d.add(i+"_wrapper","a",{"class":"mceButton mceCancel",href:"javascript:;",onmousedown:"return false;"},"Cancel")}d.add(i+"_middle","div",{"class":"mceIcon"});d.setHTML(i+"_content",s.content.replace("\n","
"));a.add(i,"keyup",function(f){var p=27;if(f.keyCode===p){s.button_func(false);return a.cancel(f)}});a.add(i,"keydown",function(f){var t,p=9;if(f.keyCode===p){t=d.select("a.mceCancel",i+"_wrapper")[0];if(t&&t!==f.target){t.focus()}else{d.get(i+"_ok").focus()}return a.cancel(f)}})}o=a.add(i,"mousedown",function(t){var u=t.target,f,p;f=z.windows[i];z.focus(i);if(u.nodeName=="A"||u.nodeName=="a"){if(u.className=="mceClose"){z.close(null,i);return a.cancel(t)}else{if(u.className=="mceMax"){f.oldPos=f.element.getXY();f.oldSize=f.element.getSize();p=d.getViewPort();p.w-=2;p.h-=2;f.element.moveTo(p.x,p.y);f.element.resizeTo(p.w,p.h);d.setStyles(i+"_ifr",{width:p.w-f.deltaWidth,height:p.h-f.deltaHeight});d.addClass(i+"_wrapper","mceMaximized")}else{if(u.className=="mceMed"){f.element.moveTo(f.oldPos.x,f.oldPos.y);f.element.resizeTo(f.oldSize.w,f.oldSize.h);f.iframeElement.resizeTo(f.oldSize.w-f.deltaWidth,f.oldSize.h-f.deltaHeight);d.removeClass(i+"_wrapper","mceMaximized")}else{if(u.className=="mceMove"){return z._startDrag(i,t,u.className)}else{if(d.hasClass(u,"mceResize")){return z._startDrag(i,t,u.className.substring(13))}}}}}}});q=a.add(i,"click",function(f){var p=f.target;z.focus(i);if(p.nodeName=="A"||p.nodeName=="a"){switch(p.className){case"mceClose":z.close(null,i);return a.cancel(f);case"mceButton mceOk":case"mceButton mceCancel":s.button_func(p.className=="mceButton mceOk");return a.cancel(f)}}});a.add([i+"_left",i+"_right"],"focus",function(p){var t=d.get(i+"_ifr");if(t){var f=t.contentWindow.document.body;var u=d.select(":input:enabled,*[tabindex=0]",f);if(p.target.id===(i+"_left")){u[u.length-1].focus()}else{u[0].focus()}}else{d.get(i+"_ok").focus()}});x=z.windows[i]={id:i,mousedown_func:o,click_func:q,element:new b(i,{blocker:1,container:r.getContainer()}),iframeElement:new b(i+"_ifr"),features:s,deltaWidth:g,deltaHeight:v};x.iframeElement.on("focus",function(){z.focus(i)});if(z.count==0&&z.editor.getParam("dialog_type","modal")=="modal"){d.add(d.doc.body,"div",{id:"mceModalBlocker","class":(z.editor.settings.inlinepopups_skin||"clearlooks2")+"_modalBlocker",style:{zIndex:z.zIndex-1}});d.show("mceModalBlocker");d.setAttrib(d.doc.body,"aria-hidden","true")}else{d.setStyle("mceModalBlocker","z-index",z.zIndex-1)}if(tinymce.isIE6||/Firefox\/2\./.test(navigator.userAgent)||(tinymce.isIE&&!d.boxModel)){d.setStyles("mceModalBlocker",{position:"absolute",left:h.x,top:h.y,width:h.w-2,height:h.h-2})}d.setAttrib(i,"aria-hidden","false");z.focus(i);z._fixIELayout(i,1);if(d.get(i+"_ok")){d.get(i+"_ok").focus()}z.count++;return x},focus:function(h){var g=this,f;if(f=g.windows[h]){f.zIndex=this.zIndex++;f.element.setStyle("zIndex",f.zIndex);f.element.update();h=h+"_wrapper";d.removeClass(g.lastId,"mceFocus");d.addClass(h,"mceFocus");g.lastId=h;if(f.focussedElement){f.focussedElement.focus()}else{if(d.get(h+"_ok")){d.get(f.id+"_ok").focus()}else{if(d.get(f.id+"_ifr")){d.get(f.id+"_ifr").focus()}}}}},_addAll:function(k,h){var g,l,f=this,j=tinymce.DOM;if(c(h,"string")){k.appendChild(j.doc.createTextNode(h))}else{if(h.length){k=k.appendChild(j.create(h[0],h[1]));for(g=2;gf){g=h;f=h.zIndex}});return g},setTitle:function(f,g){var h;f=this._findId(f);if(h=d.get(f+"_title")){h.innerHTML=d.encode(g)}},alert:function(g,f,j){var i=this,h;h=i.open({title:i,type:"alert",button_func:function(k){if(f){f.call(k||i,k)}i.close(null,h.id)},content:d.encode(i.editor.getLang(g,g)),inline:1,width:400,height:130})},confirm:function(g,f,j){var i=this,h;h=i.open({title:i,type:"confirm",button_func:function(k){if(f){f.call(k||i,k)}i.close(null,h.id)},content:d.encode(i.editor.getLang(g,g)),inline:1,width:400,height:130})},_findId:function(f){var g=this;if(typeof(f)=="string"){return f}e(g.windows,function(h){var i=d.get(h.id+"_ifr");if(i&&f==i.contentWindow){f=h.id;return false}});return f},_fixIELayout:function(i,h){var f,g;if(!tinymce.isIE6){return}e(["n","s","w","e","nw","ne","sw","se"],function(j){var k=d.get(i+"_resize_"+j);d.setStyles(k,{width:h?k.clientWidth:"",height:h?k.clientHeight:"",cursor:d.getStyle(k,"cursor",1)});d.setStyle(i+"_bottom","bottom","-1px");k=0});if(f=this.windows[i]){f.element.hide();f.element.show();e(d.select("div,a",i),function(k,j){if(k.currentStyle.backgroundImage!="none"){g=new Image();g.src=k.currentStyle.backgroundImage.replace(/url\(\"(.+)\"\)/,"$1")}});d.get(i).style.filter=""}}});tinymce.PluginManager.add("inlinepopups",tinymce.plugins.InlinePopups)})(); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/inlinepopups/editor_plugin_src.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/inlinepopups/editor_plugin_src.js index 67123ca314..da6ee24932 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/inlinepopups/editor_plugin_src.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/inlinepopups/editor_plugin_src.js @@ -55,12 +55,12 @@ if (parentWindow && DOM.get(parentWindow.id + '_ifr')) { parentWindow.focussedElement = DOM.get(parentWindow.id + '_ifr').contentWindow.document.activeElement; } - + // Only store selection if the type is a normal window if (!f.type) t.bookmark = ed.selection.getBookmark(1); - id = DOM.uniqueId(); + id = DOM.uniqueId("mce_inlinepopups_"); // Use a prefix so this can't conflict with other ids vp = DOM.getViewPort(); f.width = parseInt(f.width || 320); f.height = parseInt(f.height || 240) + (tinymce.isIE ? 8 : 0); @@ -111,17 +111,17 @@ opt += ' mceMovable'; // Create DOM objects - t._addAll(DOM.doc.body, - ['div', {id : id, role : 'dialog', 'aria-labelledby': f.type ? id + '_content' : id + '_title', 'class' : (ed.settings.inlinepopups_skin || 'clearlooks2') + (tinymce.isIE && window.getSelection ? ' ie9' : ''), style : 'width:100px;height:100px'}, + t._addAll(DOM.doc.body, + ['div', {id : id, role : 'dialog', 'aria-labelledby': f.type ? id + '_content' : id + '_title', 'class' : (ed.settings.inlinepopups_skin || 'clearlooks2') + (tinymce.isIE && window.getSelection ? ' ie9' : ''), style : 'width:100px;height:100px'}, ['div', {id : id + '_wrapper', 'class' : 'mceWrapper' + opt}, - ['div', {id : id + '_top', 'class' : 'mceTop'}, + ['div', {id : id + '_top', 'class' : 'mceTop'}, ['div', {'class' : 'mceLeft'}], ['div', {'class' : 'mceCenter'}], ['div', {'class' : 'mceRight'}], ['span', {id : id + '_title'}, f.title || ''] ], - ['div', {id : id + '_middle', 'class' : 'mceMiddle'}, + ['div', {id : id + '_middle', 'class' : 'mceMiddle'}, ['div', {id : id + '_left', 'class' : 'mceLeft', tabindex : '0'}], ['span', {id : id + '_content'}], ['div', {id : id + '_right', 'class' : 'mceRight', tabindex : '0'}] @@ -188,7 +188,7 @@ DOM.add(id + '_middle', 'div', {'class' : 'mceIcon'}); DOM.setHTML(id + '_content', f.content.replace('\n', '
')); - + Event.add(id, 'keyup', function(evt) { var VK_ESCAPE = 27; if (evt.keyCode === VK_ESCAPE) { @@ -268,7 +268,7 @@ } } }); - + // Make sure the tab order loops within the dialog. Event.add([id + '_left', id + '_right'], 'focus', function(evt) { var iframe = DOM.get(id + '_ifr'); @@ -284,7 +284,7 @@ DOM.get(id + '_ok').focus(); } }); - + // Add window w = t.windows[id] = { id : id, @@ -341,7 +341,7 @@ DOM.removeClass(t.lastId, 'mceFocus'); DOM.addClass(id, 'mceFocus'); t.lastId = id; - + if (w.focussedElement) { w.focussedElement.focus(); } else if (DOM.get(id + '_ok')) { @@ -486,7 +486,7 @@ dw = v; } - + if (dh < (v = w.features.min_height - sz.h)) { if (dy !== 0) dy += dh - v; @@ -505,7 +505,7 @@ if (dx + dy !== 0) { if (sx + dx < 0) dx = 0; - + if (sy + dy < 0) dy = 0; @@ -567,7 +567,7 @@ t.focus(fw.id); } }, - + // Find front most window _frontWindow : function() { var fw, ix = 0; diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/media/js/media.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/media/js/media.js index f6a081a632..89cea2a410 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/media/js/media.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/media/js/media.js @@ -295,30 +295,40 @@ } else { src = getVal("src"); - // YouTube *NEW* - if (src.match(/youtu.be\/[a-z1-9.-_]+/)) { + // YouTube Embed + if (src.match(/youtube\.com\/embed\/\w+/)) { data.width = 425; data.height = 350; data.params.frameborder = '0'; data.type = 'iframe'; - src = 'http://www.youtube.com/embed/' + src.match(/youtu.be\/([a-z1-9.-_]+)/)[1]; setVal('src', src); setVal('media_type', data.type); - } + } else { + // YouTube *NEW* + if (src.match(/youtu\.be\/[a-z1-9.-_]+/)) { + data.width = 425; + data.height = 350; + data.params.frameborder = '0'; + data.type = 'iframe'; + src = 'http://www.youtube.com/embed/' + src.match(/youtu.be\/([a-z1-9.-_]+)/)[1]; + setVal('src', src); + setVal('media_type', data.type); + } - // YouTube - if (src.match(/youtube.com(.+)v=([^&]+)/)) { - data.width = 425; - data.height = 350; - data.params.frameborder = '0'; - data.type = 'iframe'; - src = 'http://www.youtube.com/embed/' + src.match(/v=([^&]+)/)[1]; - setVal('src', src); - setVal('media_type', data.type); + // YouTube + if (src.match(/youtube\.com(.+)v=([^&]+)/)) { + data.width = 425; + data.height = 350; + data.params.frameborder = '0'; + data.type = 'iframe'; + src = 'http://www.youtube.com/embed/' + src.match(/v=([^&]+)/)[1]; + setVal('src', src); + setVal('media_type', data.type); + } } // Google video - if (src.match(/video.google.com(.+)docid=([^&]+)/)) { + if (src.match(/video\.google\.com(.+)docid=([^&]+)/)) { data.width = 425; data.height = 326; data.type = 'flash'; @@ -328,7 +338,7 @@ } // Vimeo - if (src.match(/vimeo.com\/([0-9]+)/)) { + if (src.match(/vimeo\.com\/([0-9]+)/)) { data.width = 425; data.height = 350; data.params.frameborder = '0'; @@ -339,7 +349,7 @@ } // stream.cz - if (src.match(/stream.cz\/((?!object).)*\/([0-9]+)/)) { + if (src.match(/stream\.cz\/((?!object).)*\/([0-9]+)/)) { data.width = 425; data.height = 350; data.params.frameborder = '0'; @@ -350,7 +360,7 @@ } // Google maps - if (src.match(/maps.google.([a-z]{2,3})\/maps\/(.+)msid=(.+)/)) { + if (src.match(/maps\.google\.([a-z]{2,3})\/maps\/(.+)msid=(.+)/)) { data.width = 425; data.height = 350; data.params.frameborder = '0'; diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/paste/editor_plugin.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/paste/editor_plugin.js index 0ab05ebbb6..f69f263f99 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/paste/editor_plugin.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/paste/editor_plugin.js @@ -1 +1 @@ -(function(){var c=tinymce.each,a={paste_auto_cleanup_on_paste:true,paste_enable_default_filters:true,paste_block_drop:false,paste_retain_style_properties:"none",paste_strip_class_attributes:"mso",paste_remove_spans:false,paste_remove_styles:false,paste_remove_styles_if_webkit:true,paste_convert_middot_lists:true,paste_convert_headers_to_strong:false,paste_dialog_width:"450",paste_dialog_height:"400",paste_max_consecutive_linebreaks:2,paste_text_use_dialog:false,paste_text_sticky:false,paste_text_sticky_default:false,paste_text_notifyalways:false,paste_text_linebreaktype:"combined",paste_text_replacements:[[/\u2026/g,"..."],[/[\x93\x94\u201c\u201d]/g,'"'],[/[\x60\x91\x92\u2018\u2019]/g,"'"]]};function b(d,e){return d.getParam(e,a[e])}tinymce.create("tinymce.plugins.PastePlugin",{init:function(d,e){var f=this;f.editor=d;f.url=e;f.onPreProcess=new tinymce.util.Dispatcher(f);f.onPostProcess=new tinymce.util.Dispatcher(f);f.onPreProcess.add(f._preProcess);f.onPostProcess.add(f._postProcess);f.onPreProcess.add(function(i,j){d.execCallback("paste_preprocess",i,j)});f.onPostProcess.add(function(i,j){d.execCallback("paste_postprocess",i,j)});d.onKeyDown.addToTop(function(i,j){if(((tinymce.isMac?j.metaKey:j.ctrlKey)&&j.keyCode==86)||(j.shiftKey&&j.keyCode==45)){return false}});d.pasteAsPlainText=b(d,"paste_text_sticky_default");function h(l,j){var k=d.dom,i;f.onPreProcess.dispatch(f,l);l.node=k.create("div",0,l.content);if(tinymce.isGecko){i=d.selection.getRng(true);if(i.startContainer==i.endContainer&&i.startContainer.nodeType==3){if(l.node.childNodes.length===1&&/^(p|h[1-6]|pre)$/i.test(l.node.firstChild.nodeName)&&l.content.indexOf("__MCE_ITEM__")===-1){k.remove(l.node.firstChild,true)}}}f.onPostProcess.dispatch(f,l);l.content=d.serializer.serialize(l.node,{getInner:1,forced_root_block:""});if((!j)&&(d.pasteAsPlainText)){f._insertPlainText(l.content);if(!b(d,"paste_text_sticky")){d.pasteAsPlainText=false;d.controlManager.setActive("pastetext",false)}}else{f._insert(l.content)}}d.addCommand("mceInsertClipboardContent",function(i,j){h(j,true)});if(!b(d,"paste_text_use_dialog")){d.addCommand("mcePasteText",function(j,i){var k=tinymce.util.Cookie;d.pasteAsPlainText=!d.pasteAsPlainText;d.controlManager.setActive("pastetext",d.pasteAsPlainText);if((d.pasteAsPlainText)&&(!k.get("tinymcePasteText"))){if(b(d,"paste_text_sticky")){d.windowManager.alert(d.translate("paste.plaintext_mode_sticky"))}else{d.windowManager.alert(d.translate("paste.plaintext_mode"))}if(!b(d,"paste_text_notifyalways")){k.set("tinymcePasteText","1",new Date(new Date().getFullYear()+1,12,31))}}})}d.addButton("pastetext",{title:"paste.paste_text_desc",cmd:"mcePasteText"});d.addButton("selectall",{title:"paste.selectall_desc",cmd:"selectall"});function g(s){var l,p,j,t,k=d.selection,o=d.dom,q=d.getBody(),i,r;if(s.clipboardData||o.doc.dataTransfer){r=(s.clipboardData||o.doc.dataTransfer).getData("Text");if(d.pasteAsPlainText){s.preventDefault();h({content:o.encode(r).replace(/\r?\n/g,"
")});return}}if(o.get("_mcePaste")){return}l=o.add(q,"div",{id:"_mcePaste","class":"mcePaste","data-mce-bogus":"1"},"\uFEFF\uFEFF");if(q!=d.getDoc().body){i=o.getPos(d.selection.getStart(),q).y}else{i=q.scrollTop+o.getViewPort(d.getWin()).y}o.setStyles(l,{position:"absolute",left:tinymce.isGecko?-40:0,top:i-25,width:1,height:1,overflow:"hidden"});if(tinymce.isIE){t=k.getRng();j=o.doc.body.createTextRange();j.moveToElementText(l);j.execCommand("Paste");o.remove(l);if(l.innerHTML==="\uFEFF\uFEFF"){d.execCommand("mcePasteWord");s.preventDefault();return}k.setRng(t);k.setContent("");setTimeout(function(){h({content:l.innerHTML})},0);return tinymce.dom.Event.cancel(s)}else{function m(n){n.preventDefault()}o.bind(d.getDoc(),"mousedown",m);o.bind(d.getDoc(),"keydown",m);p=d.selection.getRng();l=l.firstChild;j=d.getDoc().createRange();j.setStart(l,0);j.setEnd(l,2);k.setRng(j);window.setTimeout(function(){var u="",n;if(!o.select("div.mcePaste > div.mcePaste").length){n=o.select("div.mcePaste");c(n,function(w){var v=w.firstChild;if(v&&v.nodeName=="DIV"&&v.style.marginTop&&v.style.backgroundColor){o.remove(v,1)}c(o.select("span.Apple-style-span",w),function(x){o.remove(x,1)});c(o.select("br[data-mce-bogus]",w),function(x){o.remove(x)});if(w.parentNode.className!="mcePaste"){u+=w.innerHTML}})}else{u="

"+o.encode(r).replace(/\r?\n\r?\n/g,"

").replace(/\r?\n/g,"
")+"

"}c(o.select("div.mcePaste"),function(v){o.remove(v)});if(p){k.setRng(p)}h({content:u});o.unbind(d.getDoc(),"mousedown",m);o.unbind(d.getDoc(),"keydown",m)},0)}}if(b(d,"paste_auto_cleanup_on_paste")){if(tinymce.isOpera||/Firefox\/2/.test(navigator.userAgent)){d.onKeyDown.addToTop(function(i,j){if(((tinymce.isMac?j.metaKey:j.ctrlKey)&&j.keyCode==86)||(j.shiftKey&&j.keyCode==45)){g(j)}})}else{d.onPaste.addToTop(function(i,j){return g(j)})}}d.onInit.add(function(){d.controlManager.setActive("pastetext",d.pasteAsPlainText);if(b(d,"paste_block_drop")){d.dom.bind(d.getBody(),["dragend","dragover","draggesture","dragdrop","drop","drag"],function(i){i.preventDefault();i.stopPropagation();return false})}});f._legacySupport()},getInfo:function(){return{longname:"Paste text/word",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/paste",version:tinymce.majorVersion+"."+tinymce.minorVersion}},_preProcess:function(g,e){var k=this.editor,j=e.content,p=tinymce.grep,n=tinymce.explode,f=tinymce.trim,l,i;function d(h){c(h,function(o){if(o.constructor==RegExp){j=j.replace(o,"")}else{j=j.replace(o[0],o[1])}})}if(k.settings.paste_enable_default_filters==false){return}if(tinymce.isIE&&document.documentMode>=9&&/<(h[1-6r]|p|div|address|pre|form|table|tbody|thead|tfoot|th|tr|td|li|ol|ul|caption|blockquote|center|dl|dt|dd|dir|fieldset)/.test(e.content)){d([[/(?:
 [\s\r\n]+|
)*(<\/?(h[1-6r]|p|div|address|pre|form|table|tbody|thead|tfoot|th|tr|td|li|ol|ul|caption|blockquote|center|dl|dt|dd|dir|fieldset)[^>]*>)(?:
 [\s\r\n]+|
)*/g,"$1"]]);d([[/

/g,"

"],[/
/g," "],[/

/g,"
"]])}if(/class="?Mso|style="[^"]*\bmso-|w:WordDocument/i.test(j)||e.wordContent){e.wordContent=true;d([/^\s*( )+/gi,/( |]*>)+\s*$/gi]);if(b(k,"paste_convert_headers_to_strong")){j=j.replace(/

]*class="?MsoHeading"?[^>]*>(.*?)<\/p>/gi,"

$1

")}if(b(k,"paste_convert_middot_lists")){d([[//gi,"$&__MCE_ITEM__"],[/(]+(?:mso-list:|:\s*symbol)[^>]+>)/gi,"$1__MCE_ITEM__"],[/(]+(?:MsoListParagraph)[^>]+>)/gi,"$1__MCE_ITEM__"]])}d([//gi,/<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi,[/<(\/?)s>/gi,"<$1strike>"],[/ /gi,"\u00a0"]]);do{l=j.length;j=j.replace(/(<[a-z][^>]*\s)(?:id|name|language|type|on\w+|\w+:\w+)=(?:"[^"]*"|\w+)\s?/gi,"$1")}while(l!=j.length);if(b(k,"paste_retain_style_properties").replace(/^none$/i,"").length==0){j=j.replace(/<\/?span[^>]*>/gi,"")}else{d([[/([\s\u00a0]*)<\/span>/gi,function(o,h){return(h.length>0)?h.replace(/./," ").slice(Math.floor(h.length/2)).split("").join("\u00a0"):""}],[/(<[a-z][^>]*)\sstyle="([^"]*)"/gi,function(t,h,r){var u=[],o=0,q=n(f(r).replace(/"/gi,"'"),";");c(q,function(s){var w,y,z=n(s,":");function x(A){return A+((A!=="0")&&(/\d$/.test(A)))?"px":""}if(z.length==2){w=z[0].toLowerCase();y=z[1].toLowerCase();switch(w){case"mso-padding-alt":case"mso-padding-top-alt":case"mso-padding-right-alt":case"mso-padding-bottom-alt":case"mso-padding-left-alt":case"mso-margin-alt":case"mso-margin-top-alt":case"mso-margin-right-alt":case"mso-margin-bottom-alt":case"mso-margin-left-alt":case"mso-table-layout-alt":case"mso-height":case"mso-width":case"mso-vertical-align-alt":u[o++]=w.replace(/^mso-|-alt$/g,"")+":"+x(y);return;case"horiz-align":u[o++]="text-align:"+y;return;case"vert-align":u[o++]="vertical-align:"+y;return;case"font-color":case"mso-foreground":u[o++]="color:"+y;return;case"mso-background":case"mso-highlight":u[o++]="background:"+y;return;case"mso-default-height":u[o++]="min-height:"+x(y);return;case"mso-default-width":u[o++]="min-width:"+x(y);return;case"mso-padding-between-alt":u[o++]="border-collapse:separate;border-spacing:"+x(y);return;case"text-line-through":if((y=="single")||(y=="double")){u[o++]="text-decoration:line-through"}return;case"mso-zero-height":if(y=="yes"){u[o++]="display:none"}return}if(/^(mso|column|font-emph|lang|layout|line-break|list-image|nav|panose|punct|row|ruby|sep|size|src|tab-|table-border|text-(?!align|decor|indent|trans)|top-bar|version|vnd|word-break)/.test(w)){return}u[o++]=w+":"+z[1]}});if(o>0){return h+' style="'+u.join(";")+'"'}else{return h}}]])}}if(b(k,"paste_convert_headers_to_strong")){d([[/]*>/gi,"

"],[/<\/h[1-6][^>]*>/gi,"

"]])}d([[/Version:[\d.]+\nStartHTML:\d+\nEndHTML:\d+\nStartFragment:\d+\nEndFragment:\d+/gi,""]]);i=b(k,"paste_strip_class_attributes");if(i!=="none"){function m(q,o){if(i==="all"){return""}var h=p(n(o.replace(/^(["'])(.*)\1$/,"$2")," "),function(r){return(/^(?!mso)/i.test(r))});return h.length?' class="'+h.join(" ")+'"':""}j=j.replace(/ class="([^"]+)"/gi,m);j=j.replace(/ class=([\-\w]+)/gi,m)}if(b(k,"paste_remove_spans")){j=j.replace(/<\/?span[^>]*>/gi,"")}e.content=j},_postProcess:function(g,i){var f=this,e=f.editor,h=e.dom,d;if(e.settings.paste_enable_default_filters==false){return}if(i.wordContent){c(h.select("a",i.node),function(j){if(!j.href||j.href.indexOf("#_Toc")!=-1){h.remove(j,1)}});if(b(e,"paste_convert_middot_lists")){f._convertLists(g,i)}d=b(e,"paste_retain_style_properties");if((tinymce.is(d,"string"))&&(d!=="all")&&(d!=="*")){d=tinymce.explode(d.replace(/^none$/i,""));c(h.select("*",i.node),function(m){var n={},k=0,l,o,j;if(d){for(l=0;l0){h.setStyles(m,n)}else{if(m.nodeName=="SPAN"&&!m.className){h.remove(m,true)}}})}}if(b(e,"paste_remove_styles")||(b(e,"paste_remove_styles_if_webkit")&&tinymce.isWebKit)){c(h.select("*[style]",i.node),function(j){j.removeAttribute("style");j.removeAttribute("data-mce-style")})}else{if(tinymce.isWebKit){c(h.select("*",i.node),function(j){j.removeAttribute("data-mce-style")})}}},_convertLists:function(g,e){var i=g.editor.dom,h,l,d=-1,f,m=[],k,j;c(i.select("p",e.node),function(t){var q,u="",s,r,n,o;for(q=t.firstChild;q&&q.nodeType==3;q=q.nextSibling){u+=q.nodeValue}u=t.innerHTML.replace(/<\/?\w+[^>]*>/gi,"").replace(/ /g,"\u00a0");if(/^(__MCE_ITEM__)+[\u2022\u00b7\u00a7\u00d8o\u25CF]\s*\u00a0*/.test(u)){s="ul"}if(/^__MCE_ITEM__\s*\w+\.\s*\u00a0+/.test(u)){s="ol"}if(s){f=parseFloat(t.style.marginLeft||0);if(f>d){m.push(f)}if(!h||s!=k){h=i.create(s);i.insertAfter(h,t)}else{if(f>d){h=l.appendChild(i.create(s))}else{if(f]*>/gi,"");if(s=="ul"&&/^__MCE_ITEM__[\u2022\u00b7\u00a7\u00d8o\u25CF]/.test(p)){i.remove(v)}else{if(/^__MCE_ITEM__[\s\S]*\w+\.( |\u00a0)*\s*/.test(p)){i.remove(v)}}});r=t.innerHTML;if(s=="ul"){r=t.innerHTML.replace(/__MCE_ITEM__/g,"").replace(/^[\u2022\u00b7\u00a7\u00d8o\u25CF]\s*( |\u00a0)+\s*/,"")}else{r=t.innerHTML.replace(/__MCE_ITEM__/g,"").replace(/^\s*\w+\.( |\u00a0)+\s*/,"")}l=h.appendChild(i.create("li",0,r));i.remove(t);d=f;k=s}else{h=d=0}});j=e.node.innerHTML;if(j.indexOf("__MCE_ITEM__")!=-1){e.node.innerHTML=j.replace(/__MCE_ITEM__/g,"")}},_insert:function(f,d){var e=this.editor,g=e.selection.getRng();if(!e.selection.isCollapsed()&&g.startContainer!=g.endContainer){e.getDoc().execCommand("Delete",false,null)}e.execCommand("mceInsertContent",false,f,{skip_undo:d})},_insertPlainText:function(j){var h=this.editor,f=b(h,"paste_text_linebreaktype"),k=b(h,"paste_text_replacements"),g=tinymce.is;function e(m){c(m,function(n){if(n.constructor==RegExp){j=j.replace(n,"")}else{j=j.replace(n[0],n[1])}})}if((typeof(j)==="string")&&(j.length>0)){if(/<(?:p|br|h[1-6]|ul|ol|dl|table|t[rdh]|div|blockquote|fieldset|pre|address|center)[^>]*>/i.test(j)){e([/[\n\r]+/g])}else{e([/\r+/g])}e([[/<\/(?:p|h[1-6]|ul|ol|dl|table|div|blockquote|fieldset|pre|address|center)>/gi,"\n\n"],[/]*>|<\/tr>/gi,"\n"],[/<\/t[dh]>\s*]*>/gi,"\t"],/<[a-z!\/?][^>]*>/gi,[/ /gi," "],[/(?:(?!\n)\s)*(\n+)(?:(?!\n)\s)*/gi,"$1"]]);var d=Number(b(h,"paste_max_consecutive_linebreaks"));if(d>-1){var l=new RegExp("\n{"+(d+1)+",}","g");var i="";while(i.length"]])}else{if(f=="p"){e([[/\n+/g,"

"],[/^(.*<\/p>)(

)$/,"

$1"]])}else{e([[/\n\n/g,"

"],[/^(.*<\/p>)(

)$/,"

$1"],[/\n/g,"
"]])}}}h.execCommand("mceInsertContent",false,j)}},_legacySupport:function(){var e=this,d=e.editor;d.addCommand("mcePasteWord",function(){d.windowManager.open({file:e.url+"/pasteword.htm",width:parseInt(b(d,"paste_dialog_width")),height:parseInt(b(d,"paste_dialog_height")),inline:1})});if(b(d,"paste_text_use_dialog")){d.addCommand("mcePasteText",function(){d.windowManager.open({file:e.url+"/pastetext.htm",width:parseInt(b(d,"paste_dialog_width")),height:parseInt(b(d,"paste_dialog_height")),inline:1})})}d.addButton("pasteword",{title:"paste.paste_word_desc",cmd:"mcePasteWord"})}});tinymce.PluginManager.add("paste",tinymce.plugins.PastePlugin)})(); \ No newline at end of file +(function(){var c=tinymce.each,a={paste_auto_cleanup_on_paste:true,paste_enable_default_filters:true,paste_block_drop:false,paste_retain_style_properties:"none",paste_strip_class_attributes:"mso",paste_remove_spans:false,paste_remove_styles:false,paste_remove_styles_if_webkit:true,paste_convert_middot_lists:true,paste_convert_headers_to_strong:false,paste_dialog_width:"450",paste_dialog_height:"400",paste_max_consecutive_linebreaks:2,paste_text_use_dialog:false,paste_text_sticky:false,paste_text_sticky_default:false,paste_text_notifyalways:false,paste_text_linebreaktype:"combined",paste_text_replacements:[[/\u2026/g,"..."],[/[\x93\x94\u201c\u201d]/g,'"'],[/[\x60\x91\x92\u2018\u2019]/g,"'"]]};function b(d,e){return d.getParam(e,a[e])}tinymce.create("tinymce.plugins.PastePlugin",{init:function(d,e){var f=this;f.editor=d;f.url=e;f.onPreProcess=new tinymce.util.Dispatcher(f);f.onPostProcess=new tinymce.util.Dispatcher(f);f.onPreProcess.add(f._preProcess);f.onPostProcess.add(f._postProcess);f.onPreProcess.add(function(i,j){d.execCallback("paste_preprocess",i,j)});f.onPostProcess.add(function(i,j){d.execCallback("paste_postprocess",i,j)});d.onKeyDown.addToTop(function(i,j){if(((tinymce.isMac?j.metaKey:j.ctrlKey)&&j.keyCode==86)||(j.shiftKey&&j.keyCode==45)){return false}});d.pasteAsPlainText=b(d,"paste_text_sticky_default");function h(l,j){var k=d.dom,i;f.onPreProcess.dispatch(f,l);l.node=k.create("div",0,l.content);if(tinymce.isGecko){i=d.selection.getRng(true);if(i.startContainer==i.endContainer&&i.startContainer.nodeType==3){if(l.node.childNodes.length===1&&/^(p|h[1-6]|pre)$/i.test(l.node.firstChild.nodeName)&&l.content.indexOf("__MCE_ITEM__")===-1){k.remove(l.node.firstChild,true)}}}f.onPostProcess.dispatch(f,l);l.content=d.serializer.serialize(l.node,{getInner:1,forced_root_block:""});if((!j)&&(d.pasteAsPlainText)){f._insertPlainText(l.content);if(!b(d,"paste_text_sticky")){d.pasteAsPlainText=false;d.controlManager.setActive("pastetext",false)}}else{f._insert(l.content)}}d.addCommand("mceInsertClipboardContent",function(i,j){h(j,true)});if(!b(d,"paste_text_use_dialog")){d.addCommand("mcePasteText",function(j,i){var k=tinymce.util.Cookie;d.pasteAsPlainText=!d.pasteAsPlainText;d.controlManager.setActive("pastetext",d.pasteAsPlainText);if((d.pasteAsPlainText)&&(!k.get("tinymcePasteText"))){if(b(d,"paste_text_sticky")){d.windowManager.alert(d.translate("paste.plaintext_mode_sticky"))}else{d.windowManager.alert(d.translate("paste.plaintext_mode"))}if(!b(d,"paste_text_notifyalways")){k.set("tinymcePasteText","1",new Date(new Date().getFullYear()+1,12,31))}}})}d.addButton("pastetext",{title:"paste.paste_text_desc",cmd:"mcePasteText"});d.addButton("selectall",{title:"paste.selectall_desc",cmd:"selectall"});function g(s){var l,p,j,t,k=d.selection,o=d.dom,q=d.getBody(),i,r;if(s.clipboardData||o.doc.dataTransfer){r=(s.clipboardData||o.doc.dataTransfer).getData("Text");if(d.pasteAsPlainText){s.preventDefault();h({content:o.encode(r).replace(/\r?\n/g,"
")});return}}if(o.get("_mcePaste")){return}l=o.add(q,"div",{id:"_mcePaste","class":"mcePaste","data-mce-bogus":"1"},"\uFEFF\uFEFF");if(q!=d.getDoc().body){i=o.getPos(d.selection.getStart(),q).y}else{i=q.scrollTop+o.getViewPort(d.getWin()).y}o.setStyles(l,{position:"absolute",left:tinymce.isGecko?-40:0,top:i-25,width:1,height:1,overflow:"hidden"});if(tinymce.isIE){t=k.getRng();j=o.doc.body.createTextRange();j.moveToElementText(l);j.execCommand("Paste");o.remove(l);if(l.innerHTML==="\uFEFF\uFEFF"){d.execCommand("mcePasteWord");s.preventDefault();return}k.setRng(t);k.setContent("");setTimeout(function(){h({content:l.innerHTML})},0);return tinymce.dom.Event.cancel(s)}else{function m(n){n.preventDefault()}o.bind(d.getDoc(),"mousedown",m);o.bind(d.getDoc(),"keydown",m);p=d.selection.getRng();l=l.firstChild;j=d.getDoc().createRange();j.setStart(l,0);j.setEnd(l,2);k.setRng(j);window.setTimeout(function(){var u="",n;if(!o.select("div.mcePaste > div.mcePaste").length){n=o.select("div.mcePaste");c(n,function(w){var v=w.firstChild;if(v&&v.nodeName=="DIV"&&v.style.marginTop&&v.style.backgroundColor){o.remove(v,1)}c(o.select("span.Apple-style-span",w),function(x){o.remove(x,1)});c(o.select("br[data-mce-bogus]",w),function(x){o.remove(x)});if(w.parentNode.className!="mcePaste"){u+=w.innerHTML}})}else{u="

"+o.encode(r).replace(/\r?\n\r?\n/g,"

").replace(/\r?\n/g,"
")+"

"}c(o.select("div.mcePaste"),function(v){o.remove(v)});if(p){k.setRng(p)}h({content:u});o.unbind(d.getDoc(),"mousedown",m);o.unbind(d.getDoc(),"keydown",m)},0)}}if(b(d,"paste_auto_cleanup_on_paste")){if(tinymce.isOpera||/Firefox\/2/.test(navigator.userAgent)){d.onKeyDown.addToTop(function(i,j){if(((tinymce.isMac?j.metaKey:j.ctrlKey)&&j.keyCode==86)||(j.shiftKey&&j.keyCode==45)){g(j)}})}else{d.onPaste.addToTop(function(i,j){return g(j)})}}d.onInit.add(function(){d.controlManager.setActive("pastetext",d.pasteAsPlainText);if(b(d,"paste_block_drop")){d.dom.bind(d.getBody(),["dragend","dragover","draggesture","dragdrop","drop","drag"],function(i){i.preventDefault();i.stopPropagation();return false})}});f._legacySupport()},getInfo:function(){return{longname:"Paste text/word",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/paste",version:tinymce.majorVersion+"."+tinymce.minorVersion}},_preProcess:function(g,e){var k=this.editor,j=e.content,p=tinymce.grep,n=tinymce.explode,f=tinymce.trim,l,i;function d(h){c(h,function(o){if(o.constructor==RegExp){j=j.replace(o,"")}else{j=j.replace(o[0],o[1])}})}if(k.settings.paste_enable_default_filters==false){return}if(tinymce.isIE&&document.documentMode>=9&&/<(h[1-6r]|p|div|address|pre|form|table|tbody|thead|tfoot|th|tr|td|li|ol|ul|caption|blockquote|center|dl|dt|dd|dir|fieldset)/.test(e.content)){d([[/(?:
 [\s\r\n]+|
)*(<\/?(h[1-6r]|p|div|address|pre|form|table|tbody|thead|tfoot|th|tr|td|li|ol|ul|caption|blockquote|center|dl|dt|dd|dir|fieldset)[^>]*>)(?:
 [\s\r\n]+|
)*/g,"$1"]]);d([[/

/g,"

"],[/
/g," "],[/

/g,"
"]])}if(/class="?Mso|style="[^"]*\bmso-|w:WordDocument/i.test(j)||e.wordContent){e.wordContent=true;d([/^\s*( )+/gi,/( |]*>)+\s*$/gi]);if(b(k,"paste_convert_headers_to_strong")){j=j.replace(/

]*class="?MsoHeading"?[^>]*>(.*?)<\/p>/gi,"

$1

")}if(b(k,"paste_convert_middot_lists")){d([[//gi,"$&__MCE_ITEM__"],[/(]+(?:mso-list:|:\s*symbol)[^>]+>)/gi,"$1__MCE_ITEM__"],[/(]+(?:MsoListParagraph)[^>]+>)/gi,"$1__MCE_ITEM__"]])}d([//gi,/<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi,[/<(\/?)s>/gi,"<$1strike>"],[/ /gi,"\u00a0"]]);do{l=j.length;j=j.replace(/(]*\s)(?:id|name|language|type|on\w+|\w+:\w+)=(?:"[^"]*"|\w+)\s?/gi,"$1");j=j.replace(/(<(ol|ul)[^>]*\s)(?:id|name|language|on\w+|\w+:\w+)=(?:"[^"]*"|\w+)\s?/gi,"$1")}while(l!=j.length);if(b(k,"paste_retain_style_properties").replace(/^none$/i,"").length==0){j=j.replace(/<\/?span[^>]*>/gi,"")}else{d([[/([\s\u00a0]*)<\/span>/gi,function(o,h){return(h.length>0)?h.replace(/./," ").slice(Math.floor(h.length/2)).split("").join("\u00a0"):""}],[/(<[a-z][^>]*)\sstyle="([^"]*)"/gi,function(t,h,r){var u=[],o=0,q=n(f(r).replace(/"/gi,"'"),";");c(q,function(s){var w,y,z=n(s,":");function x(A){return A+((A!=="0")&&(/\d$/.test(A)))?"px":""}if(z.length==2){w=z[0].toLowerCase();y=z[1].toLowerCase();switch(w){case"mso-padding-alt":case"mso-padding-top-alt":case"mso-padding-right-alt":case"mso-padding-bottom-alt":case"mso-padding-left-alt":case"mso-margin-alt":case"mso-margin-top-alt":case"mso-margin-right-alt":case"mso-margin-bottom-alt":case"mso-margin-left-alt":case"mso-table-layout-alt":case"mso-height":case"mso-width":case"mso-vertical-align-alt":u[o++]=w.replace(/^mso-|-alt$/g,"")+":"+x(y);return;case"horiz-align":u[o++]="text-align:"+y;return;case"vert-align":u[o++]="vertical-align:"+y;return;case"font-color":case"mso-foreground":u[o++]="color:"+y;return;case"mso-background":case"mso-highlight":u[o++]="background:"+y;return;case"mso-default-height":u[o++]="min-height:"+x(y);return;case"mso-default-width":u[o++]="min-width:"+x(y);return;case"mso-padding-between-alt":u[o++]="border-collapse:separate;border-spacing:"+x(y);return;case"text-line-through":if((y=="single")||(y=="double")){u[o++]="text-decoration:line-through"}return;case"mso-zero-height":if(y=="yes"){u[o++]="display:none"}return}if(/^(mso|column|font-emph|lang|layout|line-break|list-image|nav|panose|punct|row|ruby|sep|size|src|tab-|table-border|text-(?!align|decor|indent|trans)|top-bar|version|vnd|word-break)/.test(w)){return}u[o++]=w+":"+z[1]}});if(o>0){return h+' style="'+u.join(";")+'"'}else{return h}}]])}}if(b(k,"paste_convert_headers_to_strong")){d([[/]*>/gi,"

"],[/<\/h[1-6][^>]*>/gi,"

"]])}d([[/Version:[\d.]+\nStartHTML:\d+\nEndHTML:\d+\nStartFragment:\d+\nEndFragment:\d+/gi,""]]);i=b(k,"paste_strip_class_attributes");if(i!=="none"){function m(q,o){if(i==="all"){return""}var h=p(n(o.replace(/^(["'])(.*)\1$/,"$2")," "),function(r){return(/^(?!mso)/i.test(r))});return h.length?' class="'+h.join(" ")+'"':""}j=j.replace(/ class="([^"]+)"/gi,m);j=j.replace(/ class=([\-\w]+)/gi,m)}if(b(k,"paste_remove_spans")){j=j.replace(/<\/?span[^>]*>/gi,"")}e.content=j},_postProcess:function(g,i){var f=this,e=f.editor,h=e.dom,d;if(e.settings.paste_enable_default_filters==false){return}if(i.wordContent){c(h.select("a",i.node),function(j){if(!j.href||j.href.indexOf("#_Toc")!=-1){h.remove(j,1)}});if(b(e,"paste_convert_middot_lists")){f._convertLists(g,i)}d=b(e,"paste_retain_style_properties");if((tinymce.is(d,"string"))&&(d!=="all")&&(d!=="*")){d=tinymce.explode(d.replace(/^none$/i,""));c(h.select("*",i.node),function(m){var n={},k=0,l,o,j;if(d){for(l=0;l0){h.setStyles(m,n)}else{if(m.nodeName=="SPAN"&&!m.className){h.remove(m,true)}}})}}if(b(e,"paste_remove_styles")||(b(e,"paste_remove_styles_if_webkit")&&tinymce.isWebKit)){c(h.select("*[style]",i.node),function(j){j.removeAttribute("style");j.removeAttribute("data-mce-style")})}else{if(tinymce.isWebKit){c(h.select("*",i.node),function(j){j.removeAttribute("data-mce-style")})}}},_convertLists:function(g,e){var i=g.editor.dom,h,l,d=-1,f,m=[],k,j;c(i.select("p",e.node),function(t){var q,u="",s,r,n,o;for(q=t.firstChild;q&&q.nodeType==3;q=q.nextSibling){u+=q.nodeValue}u=t.innerHTML.replace(/<\/?\w+[^>]*>/gi,"").replace(/ /g,"\u00a0");if(/^(__MCE_ITEM__)+[\u2022\u00b7\u00a7\u00d8o\u25CF]\s*\u00a0*/.test(u)){s="ul"}if(/^__MCE_ITEM__\s*\w+\.\s*\u00a0+/.test(u)){s="ol"}if(s){f=parseFloat(t.style.marginLeft||0);if(f>d){m.push(f)}if(!h||s!=k){h=i.create(s);i.insertAfter(h,t)}else{if(f>d){h=l.appendChild(i.create(s))}else{if(f]*>/gi,"");if(s=="ul"&&/^__MCE_ITEM__[\u2022\u00b7\u00a7\u00d8o\u25CF]/.test(p)){i.remove(v)}else{if(/^__MCE_ITEM__[\s\S]*\w+\.( |\u00a0)*\s*/.test(p)){i.remove(v)}}});r=t.innerHTML;if(s=="ul"){r=t.innerHTML.replace(/__MCE_ITEM__/g,"").replace(/^[\u2022\u00b7\u00a7\u00d8o\u25CF]\s*( |\u00a0)+\s*/,"")}else{r=t.innerHTML.replace(/__MCE_ITEM__/g,"").replace(/^\s*\w+\.( |\u00a0)+\s*/,"")}l=h.appendChild(i.create("li",0,r));i.remove(t);d=f;k=s}else{h=d=0}});j=e.node.innerHTML;if(j.indexOf("__MCE_ITEM__")!=-1){e.node.innerHTML=j.replace(/__MCE_ITEM__/g,"")}},_insert:function(f,d){var e=this.editor,g=e.selection.getRng();if(!e.selection.isCollapsed()&&g.startContainer!=g.endContainer){e.getDoc().execCommand("Delete",false,null)}e.execCommand("mceInsertContent",false,f,{skip_undo:d})},_insertPlainText:function(j){var h=this.editor,f=b(h,"paste_text_linebreaktype"),k=b(h,"paste_text_replacements"),g=tinymce.is;function e(m){c(m,function(n){if(n.constructor==RegExp){j=j.replace(n,"")}else{j=j.replace(n[0],n[1])}})}if((typeof(j)==="string")&&(j.length>0)){if(/<(?:p|br|h[1-6]|ul|ol|dl|table|t[rdh]|div|blockquote|fieldset|pre|address|center)[^>]*>/i.test(j)){e([/[\n\r]+/g])}else{e([/\r+/g])}e([[/<\/(?:p|h[1-6]|ul|ol|dl|table|div|blockquote|fieldset|pre|address|center)>/gi,"\n\n"],[/]*>|<\/tr>/gi,"\n"],[/<\/t[dh]>\s*]*>/gi,"\t"],/<[a-z!\/?][^>]*>/gi,[/ /gi," "],[/(?:(?!\n)\s)*(\n+)(?:(?!\n)\s)*/gi,"$1"]]);var d=Number(b(h,"paste_max_consecutive_linebreaks"));if(d>-1){var l=new RegExp("\n{"+(d+1)+",}","g");var i="";while(i.length"]])}else{if(f=="p"){e([[/\n+/g,"

"],[/^(.*<\/p>)(

)$/,"

$1"]])}else{e([[/\n\n/g,"

"],[/^(.*<\/p>)(

)$/,"

$1"],[/\n/g,"
"]])}}}h.execCommand("mceInsertContent",false,j)}},_legacySupport:function(){var e=this,d=e.editor;d.addCommand("mcePasteWord",function(){d.windowManager.open({file:e.url+"/pasteword.htm",width:parseInt(b(d,"paste_dialog_width")),height:parseInt(b(d,"paste_dialog_height")),inline:1})});if(b(d,"paste_text_use_dialog")){d.addCommand("mcePasteText",function(){d.windowManager.open({file:e.url+"/pastetext.htm",width:parseInt(b(d,"paste_dialog_width")),height:parseInt(b(d,"paste_dialog_height")),inline:1})})}d.addButton("pasteword",{title:"paste.paste_word_desc",cmd:"mcePasteWord"})}});tinymce.PluginManager.add("paste",tinymce.plugins.PastePlugin)})(); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/paste/editor_plugin_src.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/paste/editor_plugin_src.js index 0154eceb58..6f1734299e 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/paste/editor_plugin_src.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/paste/editor_plugin_src.js @@ -291,7 +291,7 @@ } } - // Check if we should use the new auto process method + // Check if we should use the new auto process method if (getParam(ed, "paste_auto_cleanup_on_paste")) { // Is it's Opera or older FF use key handler if (tinymce.isOpera || /Firefox\/2/.test(navigator.userAgent)) { @@ -354,7 +354,7 @@ h = h.replace(v[0], v[1]); }); } - + if (ed.settings.paste_enable_default_filters == false) { return; } @@ -413,7 +413,9 @@ // If JavaScript had a RegExp look-behind, we could have integrated this with the last process() array and got rid of the loop. But alas, it does not, so we cannot. do { len = h.length; - h = h.replace(/(<[a-z][^>]*\s)(?:id|name|language|type|on\w+|\w+:\w+)=(?:"[^"]*"|\w+)\s?/gi, "$1"); + // Don't remove the type attribute for lists so that non-default list types display correctly. + h = h.replace(/(]*\s)(?:id|name|language|type|on\w+|\w+:\w+)=(?:"[^"]*"|\w+)\s?/gi, "$1"); + h = h.replace(/(<(ol|ul)[^>]*\s)(?:id|name|language|on\w+|\w+:\w+)=(?:"[^"]*"|\w+)\s?/gi, "$1"); } while (len != h.length); // Remove all spans if no styles is to be retained @@ -589,7 +591,7 @@ if (ed.settings.paste_enable_default_filters == false) { return; } - + if (o.wordContent) { // Remove named anchors or TOC links each(dom.select('a', o.node), function(a) { diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/searchreplace/js/searchreplace.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/searchreplace/js/searchreplace.js index 80284b9f3f..eb9b6eea84 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/searchreplace/js/searchreplace.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/searchreplace/js/searchreplace.js @@ -14,6 +14,7 @@ var SearchReplaceDialog = { mcTabs.onChange.add(function(tab_id, panel_id) { t.switchMode(tab_id.substring(0, tab_id.indexOf('_'))); }); + }, switchMode : function(m) { @@ -39,6 +40,11 @@ var SearchReplaceDialog = { searchNext : function(a) { var ed = tinyMCEPopup.editor, se = ed.selection, r = se.getRng(), f, m = this.lastMode, s, b, fl = 0, w = ed.getWin(), wm = ed.windowManager, fo = 0; + if (tinymce.isIE11 && !window.find) { + ed.windowManager.alert("This feature is not available in IE 11+. Upgrade TinyMCE to 4.x to get this functionallity back."); + return; + } + // Get input f = document.forms[0]; s = f[m + '_panel_searchstring'].value; diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/spellchecker/editor_plugin.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/spellchecker/editor_plugin.js index 48549c9239..6b57241a90 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/spellchecker/editor_plugin.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/spellchecker/editor_plugin.js @@ -1 +1 @@ -(function(){var a=tinymce.util.JSONRequest,c=tinymce.each,b=tinymce.DOM;tinymce.create("tinymce.plugins.SpellcheckerPlugin",{getInfo:function(){return{longname:"Spellchecker",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/spellchecker",version:tinymce.majorVersion+"."+tinymce.minorVersion}},init:function(e,f){var g=this,d;g.url=f;g.editor=e;g.rpcUrl=e.getParam("spellchecker_rpc_url","{backend}");if(g.rpcUrl=="{backend}"){if(tinymce.isIE){return}g.hasSupport=true;e.onContextMenu.addToTop(function(h,i){if(g.active){return false}})}e.addCommand("mceSpellCheck",function(){if(g.rpcUrl=="{backend}"){g.editor.getBody().spellcheck=g.active=!g.active;return}if(!g.active){e.setProgressState(1);g._sendRPC("checkWords",[g.selectedLang,g._getWords()],function(h){if(h.length>0){g.active=1;g._markWords(h);e.setProgressState(0);e.nodeChanged()}else{e.setProgressState(0);if(e.getParam("spellchecker_report_no_misspellings",true)){e.windowManager.alert("spellchecker.no_mpell")}}})}else{g._done()}});if(e.settings.content_css!==false){e.contentCSS.push(f+"/css/content.css")}e.onClick.add(g._showMenu,g);e.onContextMenu.add(g._showMenu,g);e.onBeforeGetContent.add(function(){if(g.active){g._removeWords()}});e.onNodeChange.add(function(i,h){h.setActive("spellchecker",g.active)});e.onSetContent.add(function(){g._done()});e.onBeforeGetContent.add(function(){g._done()});e.onBeforeExecCommand.add(function(h,i){if(i=="mceFullScreen"){g._done()}});g.languages={};c(e.getParam("spellchecker_languages","+English=en,Danish=da,Dutch=nl,Finnish=fi,French=fr,German=de,Italian=it,Polish=pl,Portuguese=pt,Spanish=es,Swedish=sv","hash"),function(i,h){if(h.indexOf("+")===0){h=h.substring(1);g.selectedLang=i}g.languages[h]=i})},createControl:function(h,d){var f=this,g,e=f.editor;if(h=="spellchecker"){if(f.rpcUrl=="{backend}"){if(f.hasSupport){g=d.createButton(h,{title:"spellchecker.desc",cmd:"mceSpellCheck",scope:f})}return g}g=d.createSplitButton(h,{title:"spellchecker.desc",cmd:"mceSpellCheck",scope:f});g.onRenderMenu.add(function(j,i){i.add({title:"spellchecker.langs","class":"mceMenuItemTitle"}).setDisabled(1);c(f.languages,function(n,m){var p={icon:1},l;p.onclick=function(){if(n==f.selectedLang){return}l.setSelected(1);f.selectedItem.setSelected(0);f.selectedItem=l;f.selectedLang=n};p.title=m;l=i.add(p);l.setSelected(n==f.selectedLang);if(n==f.selectedLang){f.selectedItem=l}})});return g}},_walk:function(i,g){var h=this.editor.getDoc(),e;if(h.createTreeWalker){e=h.createTreeWalker(i,NodeFilter.SHOW_TEXT,null,false);while((i=e.nextNode())!=null){g.call(this,i)}}else{tinymce.walk(i,g,"childNodes")}},_getSeparators:function(){var e="",d,f=this.editor.getParam("spellchecker_word_separator_chars",'\\s!"#$%&()*+,-./:;<=>?@[]^_{|}\u201d\u201c');for(d=0;d$2");while((s=p.indexOf(""))!=-1){o=p.substring(0,s);if(o.length){r=j.createTextNode(g.decode(o));q.appendChild(r)}p=p.substring(s+10);s=p.indexOf("");o=p.substring(0,s);p=p.substring(s+11);q.appendChild(g.create("span",{"class":"mceItemHiddenSpellWord"},o))}if(p.length){r=j.createTextNode(g.decode(p));q.appendChild(r)}}else{q.innerHTML=p.replace(f,'$1$2')}g.replace(q,t)}});i.setRng(d)},_showMenu:function(h,j){var i=this,h=i.editor,d=i._menu,l,k=h.dom,g=k.getViewPort(h.getWin()),f=j.target;j=0;if(!d){d=h.controlManager.createDropMenu("spellcheckermenu",{"class":"mceNoIcons"});i._menu=d}if(k.hasClass(f,"mceItemHiddenSpellWord")){d.removeAll();d.add({title:"spellchecker.wait","class":"mceMenuItemTitle"}).setDisabled(1);i._sendRPC("getSuggestions",[i.selectedLang,k.decode(f.innerHTML)],function(m){var e;d.removeAll();if(m.length>0){d.add({title:"spellchecker.sug","class":"mceMenuItemTitle"}).setDisabled(1);c(m,function(n){d.add({title:n,onclick:function(){k.replace(h.getDoc().createTextNode(n),f);i._checkDone()}})});d.addSeparator()}else{d.add({title:"spellchecker.no_sug","class":"mceMenuItemTitle"}).setDisabled(1)}if(h.getParam("show_ignore_words",true)){e=i.editor.getParam("spellchecker_enable_ignore_rpc","");d.add({title:"spellchecker.ignore_word",onclick:function(){var n=f.innerHTML;k.remove(f,1);i._checkDone();if(e){h.setProgressState(1);i._sendRPC("ignoreWord",[i.selectedLang,n],function(o){h.setProgressState(0)})}}});d.add({title:"spellchecker.ignore_words",onclick:function(){var n=f.innerHTML;i._removeWords(k.decode(n));i._checkDone();if(e){h.setProgressState(1);i._sendRPC("ignoreWords",[i.selectedLang,n],function(o){h.setProgressState(0)})}}})}if(i.editor.getParam("spellchecker_enable_learn_rpc")){d.add({title:"spellchecker.learn_word",onclick:function(){var n=f.innerHTML;k.remove(f,1);i._checkDone();h.setProgressState(1);i._sendRPC("learnWord",[i.selectedLang,n],function(o){h.setProgressState(0)})}})}d.update()});l=b.getPos(h.getContentAreaContainer());d.settings.offset_x=l.x;d.settings.offset_y=l.y;h.selection.select(f);l=k.getPos(f);d.showMenu(l.x,l.y+f.offsetHeight-g.y);return tinymce.dom.Event.cancel(j)}else{d.hideMenu()}},_checkDone:function(){var e=this,d=e.editor,g=d.dom,f;c(g.select("span"),function(h){if(h&&g.hasClass(h,"mceItemHiddenSpellWord")){f=true;return false}});if(!f){e._done()}},_done:function(){var d=this,e=d.active;if(d.active){d.active=0;d._removeWords();if(d._menu){d._menu.hideMenu()}if(e){d.editor.nodeChanged()}}},_sendRPC:function(e,g,d){var f=this;a.sendRPC({url:f.rpcUrl,method:e,params:g,success:d,error:function(i,h){f.editor.setProgressState(0);f.editor.windowManager.alert(i.errstr||("Error response: "+h.responseText))}})}});tinymce.PluginManager.add("spellchecker",tinymce.plugins.SpellcheckerPlugin)})(); \ No newline at end of file +(function(){var a=tinymce.util.JSONRequest,c=tinymce.each,b=tinymce.DOM;tinymce.create("tinymce.plugins.SpellcheckerPlugin",{getInfo:function(){return{longname:"Spellchecker",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/spellchecker",version:tinymce.majorVersion+"."+tinymce.minorVersion}},init:function(e,f){var g=this,d;g.url=f;g.editor=e;g.rpcUrl=e.getParam("spellchecker_rpc_url","{backend}");if(g.rpcUrl=="{backend}"){if(tinymce.isIE){return}g.hasSupport=true;e.onContextMenu.addToTop(function(h,i){if(g.active){return false}})}e.addCommand("mceSpellCheck",function(){if(g.rpcUrl=="{backend}"){g.editor.getBody().spellcheck=g.active=!g.active;return}if(!g.active){e.setProgressState(1);g._sendRPC("checkWords",[g.selectedLang,g._getWords()],function(h){if(h.length>0){g.active=1;g._markWords(h);e.setProgressState(0);e.nodeChanged()}else{e.setProgressState(0);if(e.getParam("spellchecker_report_no_misspellings",true)){e.windowManager.alert("spellchecker.no_mpell")}}})}else{g._done()}});if(e.settings.content_css!==false){e.contentCSS.push(f+"/css/content.css")}e.onClick.add(g._showMenu,g);e.onContextMenu.add(g._showMenu,g);e.onBeforeGetContent.add(function(){if(g.active){g._removeWords()}});e.onNodeChange.add(function(i,h){h.setActive("spellchecker",g.active)});e.onSetContent.add(function(){g._done()});e.onBeforeGetContent.add(function(){g._done()});e.onBeforeExecCommand.add(function(h,i){if(i=="mceFullScreen"){g._done()}});g.languages={};c(e.getParam("spellchecker_languages","+English=en,Danish=da,Dutch=nl,Finnish=fi,French=fr,German=de,Italian=it,Polish=pl,Portuguese=pt,Spanish=es,Swedish=sv","hash"),function(i,h){if(h.indexOf("+")===0){h=h.substring(1);g.selectedLang=i}g.languages[h]=i})},createControl:function(h,d){var f=this,g,e=f.editor;if(h=="spellchecker"){if(f.rpcUrl=="{backend}"){if(f.hasSupport){g=d.createButton(h,{title:"spellchecker.desc",cmd:"mceSpellCheck",scope:f})}return g}g=d.createSplitButton(h,{title:"spellchecker.desc",cmd:"mceSpellCheck",scope:f});g.onRenderMenu.add(function(j,i){i.add({title:"spellchecker.langs","class":"mceMenuItemTitle"}).setDisabled(1);f.menuItems={};c(f.languages,function(n,m){var p={icon:1},l;p.onclick=function(){if(n==f.selectedLang){return}f._updateMenu(l);f.selectedLang=n};p.title=m;l=i.add(p);l.setSelected(n==f.selectedLang);f.menuItems[n]=l;if(n==f.selectedLang){f.selectedItem=l}})});return g}},setLanguage:function(e){var d=this;if(e==d.selectedLang){return}if(tinymce.grep(d.languages,function(f){return f===e}).length===0){throw"Unknown language: "+e}d.selectedLang=e;if(d.menuItems){d._updateMenu(d.menuItems[e])}if(d.active){d._done()}},_updateMenu:function(d){d.setSelected(1);this.selectedItem.setSelected(0);this.selectedItem=d},_walk:function(i,g){var h=this.editor.getDoc(),e;if(h.createTreeWalker){e=h.createTreeWalker(i,NodeFilter.SHOW_TEXT,null,false);while((i=e.nextNode())!=null){g.call(this,i)}}else{tinymce.walk(i,g,"childNodes")}},_getSeparators:function(){var e="",d,f=this.editor.getParam("spellchecker_word_separator_chars",'\\s!"#$%&()*+,-./:;<=>?@[]^_{|}\u201d\u201c');for(d=0;d$2");while((s=p.indexOf(""))!=-1){o=p.substring(0,s);if(o.length){r=j.createTextNode(g.decode(o));q.appendChild(r)}p=p.substring(s+10);s=p.indexOf("");o=p.substring(0,s);p=p.substring(s+11);q.appendChild(g.create("span",{"class":"mceItemHiddenSpellWord"},o))}if(p.length){r=j.createTextNode(g.decode(p));q.appendChild(r)}}else{q.innerHTML=p.replace(f,'$1$2')}g.replace(q,t)}});i.setRng(d)},_showMenu:function(h,j){var i=this,h=i.editor,d=i._menu,l,k=h.dom,g=k.getViewPort(h.getWin()),f=j.target;j=0;if(!d){d=h.controlManager.createDropMenu("spellcheckermenu",{"class":"mceNoIcons"});i._menu=d}if(k.hasClass(f,"mceItemHiddenSpellWord")){d.removeAll();d.add({title:"spellchecker.wait","class":"mceMenuItemTitle"}).setDisabled(1);i._sendRPC("getSuggestions",[i.selectedLang,k.decode(f.innerHTML)],function(m){var e;d.removeAll();if(m.length>0){d.add({title:"spellchecker.sug","class":"mceMenuItemTitle"}).setDisabled(1);c(m,function(n){d.add({title:n,onclick:function(){k.replace(h.getDoc().createTextNode(n),f);i._checkDone()}})});d.addSeparator()}else{d.add({title:"spellchecker.no_sug","class":"mceMenuItemTitle"}).setDisabled(1)}if(h.getParam("show_ignore_words",true)){e=i.editor.getParam("spellchecker_enable_ignore_rpc","");d.add({title:"spellchecker.ignore_word",onclick:function(){var n=f.innerHTML;k.remove(f,1);i._checkDone();if(e){h.setProgressState(1);i._sendRPC("ignoreWord",[i.selectedLang,n],function(o){h.setProgressState(0)})}}});d.add({title:"spellchecker.ignore_words",onclick:function(){var n=f.innerHTML;i._removeWords(k.decode(n));i._checkDone();if(e){h.setProgressState(1);i._sendRPC("ignoreWords",[i.selectedLang,n],function(o){h.setProgressState(0)})}}})}if(i.editor.getParam("spellchecker_enable_learn_rpc")){d.add({title:"spellchecker.learn_word",onclick:function(){var n=f.innerHTML;k.remove(f,1);i._checkDone();h.setProgressState(1);i._sendRPC("learnWord",[i.selectedLang,n],function(o){h.setProgressState(0)})}})}d.update()});l=b.getPos(h.getContentAreaContainer());d.settings.offset_x=l.x;d.settings.offset_y=l.y;h.selection.select(f);l=k.getPos(f);d.showMenu(l.x,l.y+f.offsetHeight-g.y);return tinymce.dom.Event.cancel(j)}else{d.hideMenu()}},_checkDone:function(){var e=this,d=e.editor,g=d.dom,f;c(g.select("span"),function(h){if(h&&g.hasClass(h,"mceItemHiddenSpellWord")){f=true;return false}});if(!f){e._done()}},_done:function(){var d=this,e=d.active;if(d.active){d.active=0;d._removeWords();if(d._menu){d._menu.hideMenu()}if(e){d.editor.nodeChanged()}}},_sendRPC:function(e,g,d){var f=this;a.sendRPC({url:f.rpcUrl,method:e,params:g,success:d,error:function(i,h){f.editor.setProgressState(0);f.editor.windowManager.alert(i.errstr||("Error response: "+h.responseText))}})}});tinymce.PluginManager.add("spellchecker",tinymce.plugins.SpellcheckerPlugin)})(); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/spellchecker/editor_plugin_src.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/spellchecker/editor_plugin_src.js index 86fdfceb40..5751b0e52c 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/spellchecker/editor_plugin_src.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/spellchecker/editor_plugin_src.js @@ -126,6 +126,7 @@ c.onRenderMenu.add(function(c, m) { m.add({title : 'spellchecker.langs', 'class' : 'mceMenuItemTitle'}).setDisabled(1); + t.menuItems = {}; each(t.languages, function(v, k) { var o = {icon : 1}, mi; @@ -133,27 +134,60 @@ if (v == t.selectedLang) { return; } - mi.setSelected(1); - t.selectedItem.setSelected(0); - t.selectedItem = mi; + t._updateMenu(mi); t.selectedLang = v; }; o.title = k; mi = m.add(o); mi.setSelected(v == t.selectedLang); - + t.menuItems[v] = mi; if (v == t.selectedLang) t.selectedItem = mi; - }) + }); }); + + return c; } }, + setLanguage: function(lang) { + var t = this; + + if (lang == t.selectedLang) { + // allowed + return; + } + + if (tinymce.grep(t.languages, function(v) { return v === lang; }).length === 0) { + throw "Unknown language: " + lang; + } + + t.selectedLang = lang; + + // if the menu has been shown, update it as well + if (t.menuItems) { + t._updateMenu(t.menuItems[lang]); + } + + if (t.active) { + // clear error in the old language. + t._done(); + + // Don't immediately block the UI to check spelling in the new language, this is an API not a user action. + } + }, + // Internal functions + _updateMenu: function(mi) { + mi.setSelected(1); + this.selectedItem.setSelected(0); + this.selectedItem = mi; + }, + _walk : function(n, f) { var d = this.editor.getDoc(), w; @@ -235,6 +269,7 @@ each(nl, function(n) { var node, elem, txt, pos, v = n.nodeValue; + rx.lastIndex = 0; if (rx.test(v)) { // Encode the content v = dom.encode(v); diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/table/editor_plugin.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/table/editor_plugin.js index c4c3264e08..4a92e1b364 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/table/editor_plugin.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/table/editor_plugin.js @@ -1 +1 @@ -(function(d){var e=d.each;function c(g,h){var j=h.ownerDocument,f=j.createRange(),k;f.setStartBefore(h);f.setEnd(g.endContainer,g.endOffset);k=j.createElement("body");k.appendChild(f.cloneContents());return k.innerHTML.replace(/<(br|img|object|embed|input|textarea)[^>]*>/gi,"-").replace(/<[^>]+>/g,"").length==0}function a(g,f){return parseInt(g.getAttribute(f)||1)}function b(H,G,K){var g,L,D,o;t();o=G.getParent(K.getStart(),"th,td");if(o){L=F(o);D=I();o=z(L.x,L.y)}function A(N,M){N=N.cloneNode(M);N.removeAttribute("id");return N}function t(){var M=0;g=[];e(["thead","tbody","tfoot"],function(N){var O=G.select("> "+N+" tr",H);e(O,function(P,Q){Q+=M;e(G.select("> td, > th",P),function(W,R){var S,T,U,V;if(g[Q]){while(g[Q][R]){R++}}U=a(W,"rowspan");V=a(W,"colspan");for(T=Q;T'}return false}},"childNodes");M=A(M,false);s(M,"rowSpan",1);s(M,"colSpan",1);if(N){M.appendChild(N)}else{if(!d.isIE){M.innerHTML='
'}}return M}function q(){var M=G.createRng();e(G.select("tr",H),function(N){if(N.cells.length==0){G.remove(N)}});if(G.select("tr",H).length==0){M.setStartAfter(H);M.setEndAfter(H);K.setRng(M);G.remove(H);return}e(G.select("thead,tbody,tfoot",H),function(N){if(N.rows.length==0){G.remove(N)}});t();row=g[Math.min(g.length-1,L.y)];if(row){K.select(row[Math.min(row.length-1,L.x)].elm,true);K.collapse(true)}}function u(S,Q,U,R){var P,N,M,O,T;P=g[Q][S].elm.parentNode;for(M=1;M<=U;M++){P=G.getNext(P,"tr");if(P){for(N=S;N>=0;N--){T=g[Q+M][N].elm;if(T.parentNode==P){for(O=1;O<=R;O++){G.insertAfter(f(T),T)}break}}if(N==-1){for(O=1;O<=R;O++){P.insertBefore(f(P.cells[0]),P.cells[0])}}}}}function C(){e(g,function(M,N){e(M,function(P,O){var S,R,T,Q;if(j(P)){P=P.elm;S=a(P,"colspan");R=a(P,"rowspan");if(S>1||R>1){s(P,"rowSpan",1);s(P,"colSpan",1);for(Q=0;Q1){s(S,"rowSpan",O+1);continue}}else{if(M>0&&g[M-1][R]){V=g[M-1][R].elm;O=a(V,"rowSpan");if(O>1){s(V,"rowSpan",O+1);continue}}}N=f(S);s(N,"colSpan",S.colSpan);U.appendChild(N);P=S}}if(U.hasChildNodes()){if(!Q){G.insertAfter(U,T)}else{T.parentNode.insertBefore(U,T)}}}function h(N){var O,M;e(g,function(P,Q){e(P,function(S,R){if(j(S)){O=R;if(N){return false}}});if(N){return !O}});e(g,function(S,T){var P,Q,R;if(!S[O]){return}P=S[O].elm;if(P!=M){R=a(P,"colspan");Q=a(P,"rowspan");if(R==1){if(!N){G.insertAfter(f(P),P);u(O,T,Q-1,R)}else{P.parentNode.insertBefore(f(P),P);u(O,T,Q-1,R)}}else{s(P,"colSpan",P.colSpan+1)}M=P}})}function n(){var M=[];e(g,function(N,O){e(N,function(Q,P){if(j(Q)&&d.inArray(M,P)===-1){e(g,function(T){var R=T[P].elm,S;S=a(R,"colSpan");if(S>1){s(R,"colSpan",S-1)}else{G.remove(R)}});M.push(P)}})});q()}function m(){var N;function M(Q){var P,R,O;P=G.getNext(Q,"tr");e(Q.cells,function(S){var T=a(S,"rowSpan");if(T>1){s(S,"rowSpan",T-1);R=F(S);u(R.x,R.y,1,1)}});R=F(Q.cells[0]);e(g[R.y],function(S){var T;S=S.elm;if(S!=O){T=a(S,"rowSpan");if(T<=1){G.remove(S)}else{s(S,"rowSpan",T-1)}O=S}})}N=k();e(N.reverse(),function(O){M(O)});q()}function E(){var M=k();G.remove(M);q();return M}function J(){var M=k();e(M,function(O,N){M[N]=A(O,true)});return M}function B(O,N){var P=k(),M=P[N?0:P.length-1],Q=M.cells.length;e(g,function(S){var R;Q=0;e(S,function(U,T){if(U.real){Q+=U.colspan}if(U.elm.parentNode==M){R=1}});if(R){return false}});if(!N){O.reverse()}e(O,function(T){var S=T.cells.length,R;for(i=0;iN){N=R}if(Q>M){M=Q}if(S.real){U=S.colspan-1;T=S.rowspan-1;if(U){if(R+U>N){N=R+U}}if(T){if(Q+T>M){M=Q+T}}}}})});return{x:N,y:M}}function v(S){var P,O,U,T,N,M,Q,R;D=F(S);if(L&&D){P=Math.min(L.x,D.x);O=Math.min(L.y,D.y);U=Math.max(L.x,D.x);T=Math.max(L.y,D.y);N=U;M=T;for(y=O;y<=M;y++){S=g[y][P];if(!S.real){if(P-(S.colspan-1)N){N=x+Q}}if(R){if(y+R>M){M=y+R}}}}}G.removeClass(G.select("td.mceSelected,th.mceSelected"),"mceSelected");for(y=O;y<=M;y++){for(x=P;x<=N;x++){if(g[y][x]){G.addClass(g[y][x].elm,"mceSelected")}}}}}d.extend(this,{deleteTable:r,split:C,merge:p,insertRow:l,insertCol:h,deleteCols:n,deleteRows:m,cutRows:E,copyRows:J,pasteRows:B,getPos:F,setStartCell:w,setEndCell:v})}d.create("tinymce.plugins.TablePlugin",{init:function(g,h){var f,m,j=true;function l(p){var o=g.selection,n=g.dom.getParent(p||o.getNode(),"table");if(n){return new b(n,g.dom,o)}}function k(){g.getBody().style.webkitUserSelect="";if(j){g.dom.removeClass(g.dom.select("td.mceSelected,th.mceSelected"),"mceSelected");j=false}}e([["table","table.desc","mceInsertTable",true],["delete_table","table.del","mceTableDelete"],["delete_col","table.delete_col_desc","mceTableDeleteCol"],["delete_row","table.delete_row_desc","mceTableDeleteRow"],["col_after","table.col_after_desc","mceTableInsertColAfter"],["col_before","table.col_before_desc","mceTableInsertColBefore"],["row_after","table.row_after_desc","mceTableInsertRowAfter"],["row_before","table.row_before_desc","mceTableInsertRowBefore"],["row_props","table.row_desc","mceTableRowProps",true],["cell_props","table.cell_desc","mceTableCellProps",true],["split_cells","table.split_cells_desc","mceTableSplitCells",true],["merge_cells","table.merge_cells_desc","mceTableMergeCells",true]],function(n){g.addButton(n[0],{title:n[1],cmd:n[2],ui:n[3]})});if(!d.isIE){g.onClick.add(function(n,o){o=o.target;if(o.nodeName==="TABLE"){n.selection.select(o);n.nodeChanged()}})}g.onPreProcess.add(function(o,p){var n,q,r,t=o.dom,s;n=t.select("table",p.node);q=n.length;while(q--){r=n[q];t.setAttrib(r,"data-mce-style","");if((s=t.getAttrib(r,"width"))){t.setStyle(r,"width",s);t.setAttrib(r,"width","")}if((s=t.getAttrib(r,"height"))){t.setStyle(r,"height",s);t.setAttrib(r,"height","")}}});g.onNodeChange.add(function(q,o,s){var r;s=q.selection.getStart();r=q.dom.getParent(s,"td,th,caption");o.setActive("table",s.nodeName==="TABLE"||!!r);if(r&&r.nodeName==="CAPTION"){r=0}o.setDisabled("delete_table",!r);o.setDisabled("delete_col",!r);o.setDisabled("delete_table",!r);o.setDisabled("delete_row",!r);o.setDisabled("col_after",!r);o.setDisabled("col_before",!r);o.setDisabled("row_after",!r);o.setDisabled("row_before",!r);o.setDisabled("row_props",!r);o.setDisabled("cell_props",!r);o.setDisabled("split_cells",!r);o.setDisabled("merge_cells",!r)});g.onInit.add(function(r){var p,t,q=r.dom,u;f=r.windowManager;r.onMouseDown.add(function(w,z){if(z.button!=2){k();t=q.getParent(z.target,"td,th");p=q.getParent(t,"table")}});q.bind(r.getDoc(),"mouseover",function(C){var A,z,B=C.target;if(t&&(u||B!=t)&&(B.nodeName=="TD"||B.nodeName=="TH")){z=q.getParent(B,"table");if(z==p){if(!u){u=l(z);u.setStartCell(t);r.getBody().style.webkitUserSelect="none"}u.setEndCell(B);j=true}A=r.selection.getSel();try{if(A.removeAllRanges){A.removeAllRanges()}else{A.empty()}}catch(w){}C.preventDefault()}});r.onMouseUp.add(function(F,G){var z,B=F.selection,H,I=B.getSel(),w,C,A,E;if(t){if(u){F.getBody().style.webkitUserSelect=""}function D(J,L){var K=new d.dom.TreeWalker(J,J);do{if(J.nodeType==3&&d.trim(J.nodeValue).length!=0){if(L){z.setStart(J,0)}else{z.setEnd(J,J.nodeValue.length)}return}if(J.nodeName=="BR"){if(L){z.setStartBefore(J)}else{z.setEndBefore(J)}return}}while(J=(L?K.next():K.prev()))}H=q.select("td.mceSelected,th.mceSelected");if(H.length>0){z=q.createRng();C=H[0];E=H[H.length-1];z.setStartBefore(C);z.setEndAfter(C);D(C,1);w=new d.dom.TreeWalker(C,q.getParent(H[0],"table"));do{if(C.nodeName=="TD"||C.nodeName=="TH"){if(!q.hasClass(C,"mceSelected")){break}A=C}}while(C=w.next());D(A);B.setRng(z)}F.nodeChanged();t=u=p=null}});r.onKeyUp.add(function(w,z){k()});r.onKeyDown.add(function(w,z){n(w)});r.onMouseDown.add(function(w,z){if(z.button!=2){n(w)}});function o(D,z,A,F){var B=3,G=D.dom.getParent(z.startContainer,"TABLE"),C,w,E;if(G){C=G.parentNode}w=z.startContainer.nodeType==B&&z.startOffset==0&&z.endOffset==0&&F&&(A.nodeName=="TR"||A==C);E=(A.nodeName=="TD"||A.nodeName=="TH")&&!F;return w||E}function n(A){if(!d.isWebKit){return}var z=A.selection.getRng();var C=A.selection.getNode();var B=A.dom.getParent(z.startContainer,"TD,TH");if(!o(A,z,C,B)){return}if(!B){B=C}var w=B.lastChild;while(w.lastChild){w=w.lastChild}z.setEnd(w,w.nodeValue.length);A.selection.setRng(z)}r.plugins.table.fixTableCellSelection=n;if(r&&r.plugins.contextmenu){r.plugins.contextmenu.onContextMenu.add(function(A,w,C){var D,B=r.selection,z=B.getNode()||r.getBody();if(r.dom.getParent(C,"td")||r.dom.getParent(C,"th")||r.dom.select("td.mceSelected,th.mceSelected").length){w.removeAll();if(z.nodeName=="A"&&!r.dom.getAttrib(z,"name")){w.add({title:"advanced.link_desc",icon:"link",cmd:r.plugins.advlink?"mceAdvLink":"mceLink",ui:true});w.add({title:"advanced.unlink_desc",icon:"unlink",cmd:"UnLink"});w.addSeparator()}if(z.nodeName=="IMG"&&z.className.indexOf("mceItem")==-1){w.add({title:"advanced.image_desc",icon:"image",cmd:r.plugins.advimage?"mceAdvImage":"mceImage",ui:true});w.addSeparator()}w.add({title:"table.desc",icon:"table",cmd:"mceInsertTable",value:{action:"insert"}});w.add({title:"table.props_desc",icon:"table_props",cmd:"mceInsertTable"});w.add({title:"table.del",icon:"delete_table",cmd:"mceTableDelete"});w.addSeparator();D=w.addMenu({title:"table.cell"});D.add({title:"table.cell_desc",icon:"cell_props",cmd:"mceTableCellProps"});D.add({title:"table.split_cells_desc",icon:"split_cells",cmd:"mceTableSplitCells"});D.add({title:"table.merge_cells_desc",icon:"merge_cells",cmd:"mceTableMergeCells"});D=w.addMenu({title:"table.row"});D.add({title:"table.row_desc",icon:"row_props",cmd:"mceTableRowProps"});D.add({title:"table.row_before_desc",icon:"row_before",cmd:"mceTableInsertRowBefore"});D.add({title:"table.row_after_desc",icon:"row_after",cmd:"mceTableInsertRowAfter"});D.add({title:"table.delete_row_desc",icon:"delete_row",cmd:"mceTableDeleteRow"});D.addSeparator();D.add({title:"table.cut_row_desc",icon:"cut",cmd:"mceTableCutRow"});D.add({title:"table.copy_row_desc",icon:"copy",cmd:"mceTableCopyRow"});D.add({title:"table.paste_row_before_desc",icon:"paste",cmd:"mceTablePasteRowBefore"}).setDisabled(!m);D.add({title:"table.paste_row_after_desc",icon:"paste",cmd:"mceTablePasteRowAfter"}).setDisabled(!m);D=w.addMenu({title:"table.col"});D.add({title:"table.col_before_desc",icon:"col_before",cmd:"mceTableInsertColBefore"});D.add({title:"table.col_after_desc",icon:"col_after",cmd:"mceTableInsertColAfter"});D.add({title:"table.delete_col_desc",icon:"delete_col",cmd:"mceTableDeleteCol"})}else{w.add({title:"table.desc",icon:"table",cmd:"mceInsertTable"})}})}if(d.isWebKit){function v(C,N){var L=d.VK;var Q=N.keyCode;function O(Y,U,S){var T=Y?"previousSibling":"nextSibling";var Z=C.dom.getParent(U,"tr");var X=Z[T];if(X){z(C,U,X,Y);d.dom.Event.cancel(S);return true}else{var aa=C.dom.getParent(Z,"table");var W=Z.parentNode;var R=W.nodeName.toLowerCase();if(R==="tbody"||R===(Y?"tfoot":"thead")){var V=w(Y,aa,W,"tbody");if(V!==null){return K(Y,V,U,S)}}return M(Y,Z,T,aa,S)}}function w(V,T,U,X){var S=C.dom.select(">"+X,T);var R=S.indexOf(U);if(V&&R===0||!V&&R===S.length-1){return B(V,T)}else{if(R===-1){var W=U.tagName.toLowerCase()==="thead"?0:S.length-1;return S[W]}else{return S[R+(V?-1:1)]}}}function B(U,T){var S=U?"thead":"tfoot";var R=C.dom.select(">"+S,T);return R.length!==0?R[0]:null}function K(V,T,S,U){var R=J(T,V);R&&z(C,S,R,V);d.dom.Event.cancel(U);return true}function M(Y,U,R,X,W){var S=X[R];if(S){F(S);return true}else{var V=C.dom.getParent(X,"td,th");if(V){return O(Y,V,W)}else{var T=J(U,!Y);F(T);return d.dom.Event.cancel(W)}}}function J(S,R){var T=S&&S[R?"lastChild":"firstChild"];return T&&T.nodeName==="BR"?C.dom.getParent(T,"td,th"):T}function F(R){C.selection.setCursorLocation(R,0)}function A(){return Q==L.UP||Q==L.DOWN}function D(R){var T=R.selection.getNode();var S=R.dom.getParent(T,"tr");return S!==null}function P(S){var R=0;var T=S;while(T.previousSibling){T=T.previousSibling;R=R+a(T,"colspan")}return R}function E(T,R){var U=0;var S=0;e(T.children,function(V,W){U=U+a(V,"colspan");S=W;if(U>R){return false}});return S}function z(T,W,Y,V){var X=P(T.dom.getParent(W,"td,th"));var S=E(Y,X);var R=Y.childNodes[S];var U=J(R,V);F(U||R)}function H(R){var T=C.selection.getNode();var U=C.dom.getParent(T,"td,th");var S=C.dom.getParent(R,"td,th");return U&&U!==S&&I(U,S)}function I(S,R){return C.dom.getParent(S,"TABLE")===C.dom.getParent(R,"TABLE")}if(A()&&D(C)){var G=C.selection.getNode();setTimeout(function(){if(H(G)){O(!N.shiftKey&&Q===L.UP,G,N)}},0)}}r.onKeyDown.add(v)}function s(){var w;for(w=r.getBody().lastChild;w&&w.nodeType==3&&!w.nodeValue.length;w=w.previousSibling){}if(w&&w.nodeName=="TABLE"){if(r.settings.forced_root_block){r.dom.add(r.getBody(),r.settings.forced_root_block,null,d.isIE?" ":'
')}else{r.dom.add(r.getBody(),"br",{"data-mce-bogus":"1"})}}}if(d.isGecko){r.onKeyDown.add(function(z,B){var w,A,C=z.dom;if(B.keyCode==37||B.keyCode==38){w=z.selection.getRng();A=C.getParent(w.startContainer,"table");if(A&&z.getBody().firstChild==A){if(c(w,A)){w=C.createRng();w.setStartBefore(A);w.setEndBefore(A);z.selection.setRng(w);B.preventDefault()}}}})}r.onKeyUp.add(s);r.onSetContent.add(s);r.onVisualAid.add(s);r.onPreProcess.add(function(w,A){var z=A.node.lastChild;if(z&&(z.nodeName=="BR"||(z.childNodes.length==1&&(z.firstChild.nodeName=="BR"||z.firstChild.nodeValue=="\u00a0")))&&z.previousSibling&&z.previousSibling.nodeName=="TABLE"){w.dom.remove(z)}});s();r.startContent=r.getContent({format:"raw"})});e({mceTableSplitCells:function(n){n.split()},mceTableMergeCells:function(o){var p,q,n;n=g.dom.getParent(g.selection.getNode(),"th,td");if(n){p=n.rowSpan;q=n.colSpan}if(!g.dom.select("td.mceSelected,th.mceSelected").length){f.open({url:h+"/merge_cells.htm",width:240+parseInt(g.getLang("table.merge_cells_delta_width",0)),height:110+parseInt(g.getLang("table.merge_cells_delta_height",0)),inline:1},{rows:p,cols:q,onaction:function(r){o.merge(n,r.cols,r.rows)},plugin_url:h})}else{o.merge()}},mceTableInsertRowBefore:function(n){n.insertRow(true)},mceTableInsertRowAfter:function(n){n.insertRow()},mceTableInsertColBefore:function(n){n.insertCol(true)},mceTableInsertColAfter:function(n){n.insertCol()},mceTableDeleteCol:function(n){n.deleteCols()},mceTableDeleteRow:function(n){n.deleteRows()},mceTableCutRow:function(n){m=n.cutRows()},mceTableCopyRow:function(n){m=n.copyRows()},mceTablePasteRowBefore:function(n){n.pasteRows(m,true)},mceTablePasteRowAfter:function(n){n.pasteRows(m)},mceTableDelete:function(n){n.deleteTable()}},function(o,n){g.addCommand(n,function(){var p=l();if(p){o(p);g.execCommand("mceRepaint");k()}})});e({mceInsertTable:function(n){f.open({url:h+"/table.htm",width:400+parseInt(g.getLang("table.table_delta_width",0)),height:320+parseInt(g.getLang("table.table_delta_height",0)),inline:1},{plugin_url:h,action:n?n.action:0})},mceTableRowProps:function(){f.open({url:h+"/row.htm",width:400+parseInt(g.getLang("table.rowprops_delta_width",0)),height:295+parseInt(g.getLang("table.rowprops_delta_height",0)),inline:1},{plugin_url:h})},mceTableCellProps:function(){f.open({url:h+"/cell.htm",width:400+parseInt(g.getLang("table.cellprops_delta_width",0)),height:295+parseInt(g.getLang("table.cellprops_delta_height",0)),inline:1},{plugin_url:h})}},function(o,n){g.addCommand(n,function(p,q){o(q)})})}});d.PluginManager.add("table",d.plugins.TablePlugin)})(tinymce); \ No newline at end of file +(function(d){var e=d.each;function c(g,h){var j=h.ownerDocument,f=j.createRange(),k;f.setStartBefore(h);f.setEnd(g.endContainer,g.endOffset);k=j.createElement("body");k.appendChild(f.cloneContents());return k.innerHTML.replace(/<(br|img|object|embed|input|textarea)[^>]*>/gi,"-").replace(/<[^>]+>/g,"").length==0}function a(g,f){return parseInt(g.getAttribute(f)||1)}function b(H,G,K){var g,L,D,o;t();o=G.getParent(K.getStart(),"th,td");if(o){L=F(o);D=I();o=z(L.x,L.y)}function A(N,M){N=N.cloneNode(M);N.removeAttribute("id");return N}function t(){var M=0;g=[];e(["thead","tbody","tfoot"],function(N){var O=G.select("> "+N+" tr",H);e(O,function(P,Q){Q+=M;e(G.select("> td, > th",P),function(W,R){var S,T,U,V;if(g[Q]){while(g[Q][R]){R++}}U=a(W,"rowspan");V=a(W,"colspan");for(T=Q;T'}return false}},"childNodes");M=A(M,false);s(M,"rowSpan",1);s(M,"colSpan",1);if(N){M.appendChild(N)}else{if(!d.isIE||d.isIE11){M.innerHTML='
'}}return M}function q(){var M=G.createRng();e(G.select("tr",H),function(N){if(N.cells.length==0){G.remove(N)}});if(G.select("tr",H).length==0){M.setStartAfter(H);M.setEndAfter(H);K.setRng(M);G.remove(H);return}e(G.select("thead,tbody,tfoot",H),function(N){if(N.rows.length==0){G.remove(N)}});t();row=g[Math.min(g.length-1,L.y)];if(row){K.select(row[Math.min(row.length-1,L.x)].elm,true);K.collapse(true)}}function u(S,Q,U,R){var P,N,M,O,T;P=g[Q][S].elm.parentNode;for(M=1;M<=U;M++){P=G.getNext(P,"tr");if(P){for(N=S;N>=0;N--){T=g[Q+M][N].elm;if(T.parentNode==P){for(O=1;O<=R;O++){G.insertAfter(f(T),T)}break}}if(N==-1){for(O=1;O<=R;O++){P.insertBefore(f(P.cells[0]),P.cells[0])}}}}}function C(){e(g,function(M,N){e(M,function(P,O){var S,R,T,Q;if(j(P)){P=P.elm;S=a(P,"colspan");R=a(P,"rowspan");if(S>1||R>1){s(P,"rowSpan",1);s(P,"colSpan",1);for(Q=0;Q1){s(S,"rowSpan",O+1);continue}}else{if(M>0&&g[M-1][R]){V=g[M-1][R].elm;O=a(V,"rowSpan");if(O>1){s(V,"rowSpan",O+1);continue}}}N=f(S);s(N,"colSpan",S.colSpan);U.appendChild(N);P=S}}if(U.hasChildNodes()){if(!Q){G.insertAfter(U,T)}else{T.parentNode.insertBefore(U,T)}}}function h(N){var O,M;e(g,function(P,Q){e(P,function(S,R){if(j(S)){O=R;if(N){return false}}});if(N){return !O}});e(g,function(S,T){var P,Q,R;if(!S[O]){return}P=S[O].elm;if(P!=M){R=a(P,"colspan");Q=a(P,"rowspan");if(R==1){if(!N){G.insertAfter(f(P),P);u(O,T,Q-1,R)}else{P.parentNode.insertBefore(f(P),P);u(O,T,Q-1,R)}}else{s(P,"colSpan",P.colSpan+1)}M=P}})}function n(){var M=[];e(g,function(N,O){e(N,function(Q,P){if(j(Q)&&d.inArray(M,P)===-1){e(g,function(T){var R=T[P].elm,S;S=a(R,"colSpan");if(S>1){s(R,"colSpan",S-1)}else{G.remove(R)}});M.push(P)}})});q()}function m(){var N;function M(Q){var P,R,O;P=G.getNext(Q,"tr");e(Q.cells,function(S){var T=a(S,"rowSpan");if(T>1){s(S,"rowSpan",T-1);R=F(S);u(R.x,R.y,1,1)}});R=F(Q.cells[0]);e(g[R.y],function(S){var T;S=S.elm;if(S!=O){T=a(S,"rowSpan");if(T<=1){G.remove(S)}else{s(S,"rowSpan",T-1)}O=S}})}N=k();e(N.reverse(),function(O){M(O)});q()}function E(){var M=k();G.remove(M);q();return M}function J(){var M=k();e(M,function(O,N){M[N]=A(O,true)});return M}function B(O,N){if(!O){return}var P=k(),M=P[N?0:P.length-1],Q=M.cells.length;e(g,function(S){var R;Q=0;e(S,function(U,T){if(U.real){Q+=U.colspan}if(U.elm.parentNode==M){R=1}});if(R){return false}});if(!N){O.reverse()}e(O,function(T){var S=T.cells.length,R;for(i=0;iN){N=R}if(Q>M){M=Q}if(S.real){U=S.colspan-1;T=S.rowspan-1;if(U){if(R+U>N){N=R+U}}if(T){if(Q+T>M){M=Q+T}}}}})});return{x:N,y:M}}function v(S){var P,O,U,T,N,M,Q,R;D=F(S);if(L&&D){P=Math.min(L.x,D.x);O=Math.min(L.y,D.y);U=Math.max(L.x,D.x);T=Math.max(L.y,D.y);N=U;M=T;for(y=O;y<=M;y++){S=g[y][P];if(!S.real){if(P-(S.colspan-1)N){N=x+Q}}if(R){if(y+R>M){M=y+R}}}}}G.removeClass(G.select("td.mceSelected,th.mceSelected"),"mceSelected");for(y=O;y<=M;y++){for(x=P;x<=N;x++){if(g[y][x]){G.addClass(g[y][x].elm,"mceSelected")}}}}}d.extend(this,{deleteTable:r,split:C,merge:p,insertRow:l,insertCol:h,deleteCols:n,deleteRows:m,cutRows:E,copyRows:J,pasteRows:B,getPos:F,setStartCell:w,setEndCell:v})}d.create("tinymce.plugins.TablePlugin",{init:function(g,h){var f,m,j=true;function l(p){var o=g.selection,n=g.dom.getParent(p||o.getNode(),"table");if(n){return new b(n,g.dom,o)}}function k(){g.getBody().style.webkitUserSelect="";if(j){g.dom.removeClass(g.dom.select("td.mceSelected,th.mceSelected"),"mceSelected");j=false}}e([["table","table.desc","mceInsertTable",true],["delete_table","table.del","mceTableDelete"],["delete_col","table.delete_col_desc","mceTableDeleteCol"],["delete_row","table.delete_row_desc","mceTableDeleteRow"],["col_after","table.col_after_desc","mceTableInsertColAfter"],["col_before","table.col_before_desc","mceTableInsertColBefore"],["row_after","table.row_after_desc","mceTableInsertRowAfter"],["row_before","table.row_before_desc","mceTableInsertRowBefore"],["row_props","table.row_desc","mceTableRowProps",true],["cell_props","table.cell_desc","mceTableCellProps",true],["split_cells","table.split_cells_desc","mceTableSplitCells",true],["merge_cells","table.merge_cells_desc","mceTableMergeCells",true]],function(n){g.addButton(n[0],{title:n[1],cmd:n[2],ui:n[3]})});if(!d.isIE){g.onClick.add(function(n,o){o=o.target;if(o.nodeName==="TABLE"){n.selection.select(o);n.nodeChanged()}})}g.onPreProcess.add(function(o,p){var n,q,r,t=o.dom,s;n=t.select("table",p.node);q=n.length;while(q--){r=n[q];t.setAttrib(r,"data-mce-style","");if((s=t.getAttrib(r,"width"))){t.setStyle(r,"width",s);t.setAttrib(r,"width","")}if((s=t.getAttrib(r,"height"))){t.setStyle(r,"height",s);t.setAttrib(r,"height","")}}});g.onNodeChange.add(function(q,o,s){var r;s=q.selection.getStart();r=q.dom.getParent(s,"td,th,caption");o.setActive("table",s.nodeName==="TABLE"||!!r);if(r&&r.nodeName==="CAPTION"){r=0}o.setDisabled("delete_table",!r);o.setDisabled("delete_col",!r);o.setDisabled("delete_table",!r);o.setDisabled("delete_row",!r);o.setDisabled("col_after",!r);o.setDisabled("col_before",!r);o.setDisabled("row_after",!r);o.setDisabled("row_before",!r);o.setDisabled("row_props",!r);o.setDisabled("cell_props",!r);o.setDisabled("split_cells",!r);o.setDisabled("merge_cells",!r)});g.onInit.add(function(r){var p,t,q=r.dom,u;f=r.windowManager;r.onMouseDown.add(function(w,z){if(z.button!=2){k();t=q.getParent(z.target,"td,th");p=q.getParent(t,"table")}});q.bind(r.getDoc(),"mouseover",function(C){var A,z,B=C.target;if(t&&(u||B!=t)&&(B.nodeName=="TD"||B.nodeName=="TH")){z=q.getParent(B,"table");if(z==p){if(!u){u=l(z);u.setStartCell(t);r.getBody().style.webkitUserSelect="none"}u.setEndCell(B);j=true}A=r.selection.getSel();try{if(A.removeAllRanges){A.removeAllRanges()}else{A.empty()}}catch(w){}C.preventDefault()}});r.onMouseUp.add(function(F,G){var z,B=F.selection,H,I=B.getSel(),w,C,A,E;if(t){if(u){F.getBody().style.webkitUserSelect=""}function D(J,L){var K=new d.dom.TreeWalker(J,J);do{if(J.nodeType==3&&d.trim(J.nodeValue).length!=0){if(L){z.setStart(J,0)}else{z.setEnd(J,J.nodeValue.length)}return}if(J.nodeName=="BR"){if(L){z.setStartBefore(J)}else{z.setEndBefore(J)}return}}while(J=(L?K.next():K.prev()))}H=q.select("td.mceSelected,th.mceSelected");if(H.length>0){z=q.createRng();C=H[0];E=H[H.length-1];z.setStartBefore(C);z.setEndAfter(C);D(C,1);w=new d.dom.TreeWalker(C,q.getParent(H[0],"table"));do{if(C.nodeName=="TD"||C.nodeName=="TH"){if(!q.hasClass(C,"mceSelected")){break}A=C}}while(C=w.next());D(A);B.setRng(z)}F.nodeChanged();t=u=p=null}});r.onKeyUp.add(function(w,z){k()});r.onKeyDown.add(function(w,z){n(w)});r.onMouseDown.add(function(w,z){if(z.button!=2){n(w)}});function o(D,z,A,F){var B=3,G=D.dom.getParent(z.startContainer,"TABLE"),C,w,E;if(G){C=G.parentNode}w=z.startContainer.nodeType==B&&z.startOffset==0&&z.endOffset==0&&F&&(A.nodeName=="TR"||A==C);E=(A.nodeName=="TD"||A.nodeName=="TH")&&!F;return w||E}function n(A){if(!d.isWebKit){return}var z=A.selection.getRng();var C=A.selection.getNode();var B=A.dom.getParent(z.startContainer,"TD,TH");if(!o(A,z,C,B)){return}if(!B){B=C}var w=B.lastChild;while(w.lastChild){w=w.lastChild}z.setEnd(w,w.nodeValue.length);A.selection.setRng(z)}r.plugins.table.fixTableCellSelection=n;if(r&&r.plugins.contextmenu){r.plugins.contextmenu.onContextMenu.add(function(A,w,C){var D,B=r.selection,z=B.getNode()||r.getBody();if(r.dom.getParent(C,"td")||r.dom.getParent(C,"th")||r.dom.select("td.mceSelected,th.mceSelected").length){w.removeAll();if(z.nodeName=="A"&&!r.dom.getAttrib(z,"name")){w.add({title:"advanced.link_desc",icon:"link",cmd:r.plugins.advlink?"mceAdvLink":"mceLink",ui:true});w.add({title:"advanced.unlink_desc",icon:"unlink",cmd:"UnLink"});w.addSeparator()}if(z.nodeName=="IMG"&&z.className.indexOf("mceItem")==-1){w.add({title:"advanced.image_desc",icon:"image",cmd:r.plugins.advimage?"mceAdvImage":"mceImage",ui:true});w.addSeparator()}w.add({title:"table.desc",icon:"table",cmd:"mceInsertTable",value:{action:"insert"}});w.add({title:"table.props_desc",icon:"table_props",cmd:"mceInsertTable"});w.add({title:"table.del",icon:"delete_table",cmd:"mceTableDelete"});w.addSeparator();D=w.addMenu({title:"table.cell"});D.add({title:"table.cell_desc",icon:"cell_props",cmd:"mceTableCellProps"});D.add({title:"table.split_cells_desc",icon:"split_cells",cmd:"mceTableSplitCells"});D.add({title:"table.merge_cells_desc",icon:"merge_cells",cmd:"mceTableMergeCells"});D=w.addMenu({title:"table.row"});D.add({title:"table.row_desc",icon:"row_props",cmd:"mceTableRowProps"});D.add({title:"table.row_before_desc",icon:"row_before",cmd:"mceTableInsertRowBefore"});D.add({title:"table.row_after_desc",icon:"row_after",cmd:"mceTableInsertRowAfter"});D.add({title:"table.delete_row_desc",icon:"delete_row",cmd:"mceTableDeleteRow"});D.addSeparator();D.add({title:"table.cut_row_desc",icon:"cut",cmd:"mceTableCutRow"});D.add({title:"table.copy_row_desc",icon:"copy",cmd:"mceTableCopyRow"});D.add({title:"table.paste_row_before_desc",icon:"paste",cmd:"mceTablePasteRowBefore"}).setDisabled(!m);D.add({title:"table.paste_row_after_desc",icon:"paste",cmd:"mceTablePasteRowAfter"}).setDisabled(!m);D=w.addMenu({title:"table.col"});D.add({title:"table.col_before_desc",icon:"col_before",cmd:"mceTableInsertColBefore"});D.add({title:"table.col_after_desc",icon:"col_after",cmd:"mceTableInsertColAfter"});D.add({title:"table.delete_col_desc",icon:"delete_col",cmd:"mceTableDeleteCol"})}else{w.add({title:"table.desc",icon:"table",cmd:"mceInsertTable"})}})}if(d.isWebKit){function v(C,N){var L=d.VK;var Q=N.keyCode;function O(Y,U,S){var T=Y?"previousSibling":"nextSibling";var Z=C.dom.getParent(U,"tr");var X=Z[T];if(X){z(C,U,X,Y);d.dom.Event.cancel(S);return true}else{var aa=C.dom.getParent(Z,"table");var W=Z.parentNode;var R=W.nodeName.toLowerCase();if(R==="tbody"||R===(Y?"tfoot":"thead")){var V=w(Y,aa,W,"tbody");if(V!==null){return K(Y,V,U,S)}}return M(Y,Z,T,aa,S)}}function w(V,T,U,X){var S=C.dom.select(">"+X,T);var R=S.indexOf(U);if(V&&R===0||!V&&R===S.length-1){return B(V,T)}else{if(R===-1){var W=U.tagName.toLowerCase()==="thead"?0:S.length-1;return S[W]}else{return S[R+(V?-1:1)]}}}function B(U,T){var S=U?"thead":"tfoot";var R=C.dom.select(">"+S,T);return R.length!==0?R[0]:null}function K(V,T,S,U){var R=J(T,V);R&&z(C,S,R,V);d.dom.Event.cancel(U);return true}function M(Y,U,R,X,W){var S=X[R];if(S){F(S);return true}else{var V=C.dom.getParent(X,"td,th");if(V){return O(Y,V,W)}else{var T=J(U,!Y);F(T);return d.dom.Event.cancel(W)}}}function J(S,R){var T=S&&S[R?"lastChild":"firstChild"];return T&&T.nodeName==="BR"?C.dom.getParent(T,"td,th"):T}function F(R){C.selection.setCursorLocation(R,0)}function A(){return Q==L.UP||Q==L.DOWN}function D(R){var T=R.selection.getNode();var S=R.dom.getParent(T,"tr");return S!==null}function P(S){var R=0;var T=S;while(T.previousSibling){T=T.previousSibling;R=R+a(T,"colspan")}return R}function E(T,R){var U=0;var S=0;e(T.children,function(V,W){U=U+a(V,"colspan");S=W;if(U>R){return false}});return S}function z(T,W,Y,V){var X=P(T.dom.getParent(W,"td,th"));var S=E(Y,X);var R=Y.childNodes[S];var U=J(R,V);F(U||R)}function H(R){var T=C.selection.getNode();var U=C.dom.getParent(T,"td,th");var S=C.dom.getParent(R,"td,th");return U&&U!==S&&I(U,S)}function I(S,R){return C.dom.getParent(S,"TABLE")===C.dom.getParent(R,"TABLE")}if(A()&&D(C)){var G=C.selection.getNode();setTimeout(function(){if(H(G)){O(!N.shiftKey&&Q===L.UP,G,N)}},0)}}r.onKeyDown.add(v)}function s(){var w;for(w=r.getBody().lastChild;w&&w.nodeType==3&&!w.nodeValue.length;w=w.previousSibling){}if(w&&w.nodeName=="TABLE"){if(r.settings.forced_root_block){r.dom.add(r.getBody(),r.settings.forced_root_block,null,d.isIE&&!d.isIE11?" ":'
')}else{r.dom.add(r.getBody(),"br",{"data-mce-bogus":"1"})}}}if(d.isGecko){r.onKeyDown.add(function(z,B){var w,A,C=z.dom;if(B.keyCode==37||B.keyCode==38){w=z.selection.getRng();A=C.getParent(w.startContainer,"table");if(A&&z.getBody().firstChild==A){if(c(w,A)){w=C.createRng();w.setStartBefore(A);w.setEndBefore(A);z.selection.setRng(w);B.preventDefault()}}}})}r.onKeyUp.add(s);r.onSetContent.add(s);r.onVisualAid.add(s);r.onPreProcess.add(function(w,A){var z=A.node.lastChild;if(z&&(z.nodeName=="BR"||(z.childNodes.length==1&&(z.firstChild.nodeName=="BR"||z.firstChild.nodeValue=="\u00a0")))&&z.previousSibling&&z.previousSibling.nodeName=="TABLE"){w.dom.remove(z)}});s();r.startContent=r.getContent({format:"raw"})});e({mceTableSplitCells:function(n){n.split()},mceTableMergeCells:function(o){var p,q,n;n=g.dom.getParent(g.selection.getNode(),"th,td");if(n){p=n.rowSpan;q=n.colSpan}if(!g.dom.select("td.mceSelected,th.mceSelected").length){f.open({url:h+"/merge_cells.htm",width:240+parseInt(g.getLang("table.merge_cells_delta_width",0)),height:110+parseInt(g.getLang("table.merge_cells_delta_height",0)),inline:1},{rows:p,cols:q,onaction:function(r){o.merge(n,r.cols,r.rows)},plugin_url:h})}else{o.merge()}},mceTableInsertRowBefore:function(n){n.insertRow(true)},mceTableInsertRowAfter:function(n){n.insertRow()},mceTableInsertColBefore:function(n){n.insertCol(true)},mceTableInsertColAfter:function(n){n.insertCol()},mceTableDeleteCol:function(n){n.deleteCols()},mceTableDeleteRow:function(n){n.deleteRows()},mceTableCutRow:function(n){m=n.cutRows()},mceTableCopyRow:function(n){m=n.copyRows()},mceTablePasteRowBefore:function(n){n.pasteRows(m,true)},mceTablePasteRowAfter:function(n){n.pasteRows(m)},mceTableDelete:function(n){n.deleteTable()}},function(o,n){g.addCommand(n,function(){var p=l();if(p){o(p);g.execCommand("mceRepaint");k()}})});e({mceInsertTable:function(n){f.open({url:h+"/table.htm",width:400+parseInt(g.getLang("table.table_delta_width",0)),height:320+parseInt(g.getLang("table.table_delta_height",0)),inline:1},{plugin_url:h,action:n?n.action:0})},mceTableRowProps:function(){f.open({url:h+"/row.htm",width:400+parseInt(g.getLang("table.rowprops_delta_width",0)),height:295+parseInt(g.getLang("table.rowprops_delta_height",0)),inline:1},{plugin_url:h})},mceTableCellProps:function(){f.open({url:h+"/cell.htm",width:400+parseInt(g.getLang("table.cellprops_delta_width",0)),height:295+parseInt(g.getLang("table.cellprops_delta_height",0)),inline:1},{plugin_url:h})}},function(o,n){g.addCommand(n,function(p,q){o(q)})})}});d.PluginManager.add("table",d.plugins.TablePlugin)})(tinymce); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/table/editor_plugin_src.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/table/editor_plugin_src.js index dc20b38688..045648376d 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/table/editor_plugin_src.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/table/editor_plugin_src.js @@ -166,7 +166,7 @@ // Add something to the inner node if (curNode) - curNode.innerHTML = tinymce.isIE ? ' ' : '
'; + curNode.innerHTML = tinymce.isIE && !tinymce.isIE11 ? ' ' : '
'; return false; } @@ -179,7 +179,7 @@ if (formatNode) { cell.appendChild(formatNode); } else { - if (!tinymce.isIE) + if (!tinymce.isIE || tinymce.isIE11) cell.innerHTML = '
'; } @@ -566,6 +566,10 @@ }; function pasteRows(rows, before) { + // If we don't have any rows in the clipboard, return immediately + if(!rows) + return; + var selectedRows = getSelectedRows(), targetRow = selectedRows[before ? 0 : selectedRows.length - 1], targetCellCount = targetRow.cells.length; @@ -1245,7 +1249,7 @@ if (last && last.nodeName == 'TABLE') { if (ed.settings.forced_root_block) - ed.dom.add(ed.getBody(), ed.settings.forced_root_block, null, tinymce.isIE ? ' ' : '
'); + ed.dom.add(ed.getBody(), ed.settings.forced_root_block, null, tinymce.isIE && !tinymce.isIE11 ? ' ' : '
'); else ed.dom.add(ed.getBody(), 'br', {'data-mce-bogus': '1'}); } diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/table/js/row.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/table/js/row.js index a13d69592d..0c678de463 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/table/js/row.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/table/js/row.js @@ -25,6 +25,7 @@ function init() { var dir = dom.getAttrib(trElm, 'dir'); selectByValue(formObj, 'rowtype', rowtype); + setActionforRowType(formObj, rowtype); // Any cells selected if (dom.select('td.mceSelected,th.mceSelected', trElm).length == 0) { @@ -234,4 +235,20 @@ function changedColor() { formObj.style.value = dom.serializeStyle(st); } +function changedRowType() { + var formObj = document.forms[0]; + var rowtype = getSelectValue(formObj, 'rowtype'); + + setActionforRowType(formObj, rowtype); + +} + +function setActionforRowType(formObj, rowtype) { + if (rowtype === "tbody") { + formObj.action.disabled = false; + } else { + selectByValue(formObj, 'action', "row"); + formObj.action.disabled = true; + } +} tinyMCEPopup.onInit.add(init); diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/table/js/table.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/table/js/table.js index 1db243b63a..f427f5f87e 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/table/js/table.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/plugins/table/js/table.js @@ -91,7 +91,7 @@ function insertTable() { if (!capEl && caption) { capEl = elm.ownerDocument.createElement('caption'); - if (!tinymce.isIE) + if (!tinymce.isIE || tinymce.isIE11) capEl.innerHTML = '
'; elm.insertBefore(capEl, elm.firstChild); @@ -199,7 +199,7 @@ function insertTable() { html += '>'; if (caption) { - if (!tinymce.isIE) + if (!tinymce.isIE || tinymce.isIE11) html += '
'; else html += ''; @@ -209,7 +209,7 @@ function insertTable() { html += ""; for (var x=0; x - @@ -83,8 +83,8 @@ - - + + @@ -93,25 +93,25 @@ - + + - + + - + + - + +
- + + + -
-
@@ -119,11 +119,11 @@
 
-
@@ -133,7 +133,7 @@
-
diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/themes/advanced/editor_template.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/themes/advanced/editor_template.js index cbae1c885e..4b8d563757 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/themes/advanced/editor_template.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/themes/advanced/editor_template.js @@ -1 +1 @@ -(function(h){var i=h.DOM,g=h.dom.Event,c=h.extend,f=h.each,a=h.util.Cookie,e,d=h.explode;function b(p,m){var k,l,o=p.dom,j="",n,r;previewStyles=p.settings.preview_styles;if(previewStyles===false){return""}if(!previewStyles){previewStyles="font-family font-size font-weight text-decoration text-transform color background-color"}function q(s){return s.replace(/%(\w+)/g,"")}k=m.block||m.inline||"span";l=o.create(k);f(m.styles,function(t,s){t=q(t);if(t){o.setStyle(l,s,t)}});f(m.attributes,function(t,s){t=q(t);if(t){o.setAttrib(l,s,t)}});f(m.classes,function(s){s=q(s);if(!o.hasClass(l,s)){o.addClass(l,s)}});o.setStyles(l,{position:"absolute",left:-65535});p.getBody().appendChild(l);n=o.getStyle(p.getBody(),"fontSize",true);n=/px$/.test(n)?parseInt(n,10):0;f(previewStyles.split(" "),function(s){var t=o.getStyle(l,s,true);if(s=="background-color"&&/transparent|rgba\s*\([^)]+,\s*0\)/.test(t)){t=o.getStyle(p.getBody(),s,true);if(o.toHex(t).toLowerCase()=="#ffffff"){return}}if(s=="font-size"){if(/em|%$/.test(t)){if(n===0){return}t=parseFloat(t,10)/(/%$/.test(t)?100:1);t=(t*n)+"px"}}j+=s+":"+t+";"});o.remove(l);return j}h.ThemeManager.requireLangPack("advanced");h.create("tinymce.themes.AdvancedTheme",{sizes:[8,10,12,14,18,24,36],controls:{bold:["bold_desc","Bold"],italic:["italic_desc","Italic"],underline:["underline_desc","Underline"],strikethrough:["striketrough_desc","Strikethrough"],justifyleft:["justifyleft_desc","JustifyLeft"],justifycenter:["justifycenter_desc","JustifyCenter"],justifyright:["justifyright_desc","JustifyRight"],justifyfull:["justifyfull_desc","JustifyFull"],bullist:["bullist_desc","InsertUnorderedList"],numlist:["numlist_desc","InsertOrderedList"],outdent:["outdent_desc","Outdent"],indent:["indent_desc","Indent"],cut:["cut_desc","Cut"],copy:["copy_desc","Copy"],paste:["paste_desc","Paste"],undo:["undo_desc","Undo"],redo:["redo_desc","Redo"],link:["link_desc","mceLink"],unlink:["unlink_desc","unlink"],image:["image_desc","mceImage"],cleanup:["cleanup_desc","mceCleanup"],help:["help_desc","mceHelp"],code:["code_desc","mceCodeEditor"],hr:["hr_desc","InsertHorizontalRule"],removeformat:["removeformat_desc","RemoveFormat"],sub:["sub_desc","subscript"],sup:["sup_desc","superscript"],forecolor:["forecolor_desc","ForeColor"],forecolorpicker:["forecolor_desc","mceForeColor"],backcolor:["backcolor_desc","HiliteColor"],backcolorpicker:["backcolor_desc","mceBackColor"],charmap:["charmap_desc","mceCharMap"],visualaid:["visualaid_desc","mceToggleVisualAid"],anchor:["anchor_desc","mceInsertAnchor"],newdocument:["newdocument_desc","mceNewDocument"],blockquote:["blockquote_desc","mceBlockQuote"]},stateControls:["bold","italic","underline","strikethrough","bullist","numlist","justifyleft","justifycenter","justifyright","justifyfull","sub","sup","blockquote"],init:function(k,l){var m=this,n,j,p;m.editor=k;m.url=l;m.onResolveName=new h.util.Dispatcher(this);n=k.settings;k.forcedHighContrastMode=k.settings.detect_highcontrast&&m._isHighContrast();k.settings.skin=k.forcedHighContrastMode?"highcontrast":k.settings.skin;if(!n.theme_advanced_buttons1){n=c({theme_advanced_buttons1:"bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,|,styleselect,formatselect",theme_advanced_buttons2:"bullist,numlist,|,outdent,indent,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code",theme_advanced_buttons3:"hr,removeformat,visualaid,|,sub,sup,|,charmap"},n)}m.settings=n=c({theme_advanced_path:true,theme_advanced_toolbar_location:"top",theme_advanced_blockformats:"p,address,pre,h1,h2,h3,h4,h5,h6",theme_advanced_toolbar_align:"left",theme_advanced_statusbar_location:"bottom",theme_advanced_fonts:"Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,zapf dingbats",theme_advanced_more_colors:1,theme_advanced_row_height:23,theme_advanced_resize_horizontal:1,theme_advanced_resizing_use_cookie:1,theme_advanced_font_sizes:"1,2,3,4,5,6,7",theme_advanced_font_selector:"span",theme_advanced_show_current_color:0,readonly:k.settings.readonly},n);if(!n.font_size_style_values){n.font_size_style_values="8pt,10pt,12pt,14pt,18pt,24pt,36pt"}if(h.is(n.theme_advanced_font_sizes,"string")){n.font_size_style_values=h.explode(n.font_size_style_values);n.font_size_classes=h.explode(n.font_size_classes||"");p={};k.settings.theme_advanced_font_sizes=n.theme_advanced_font_sizes;f(k.getParam("theme_advanced_font_sizes","","hash"),function(r,q){var o;if(q==r&&r>=1&&r<=7){q=r+" ("+m.sizes[r-1]+"pt)";o=n.font_size_classes[r-1];r=n.font_size_style_values[r-1]||(m.sizes[r-1]+"pt")}if(/^\s*\./.test(r)){o=r.replace(/\./g,"")}p[q]=o?{"class":o}:{fontSize:r}});n.theme_advanced_font_sizes=p}if((j=n.theme_advanced_path_location)&&j!="none"){n.theme_advanced_statusbar_location=n.theme_advanced_path_location}if(n.theme_advanced_statusbar_location=="none"){n.theme_advanced_statusbar_location=0}if(k.settings.content_css!==false){k.contentCSS.push(k.baseURI.toAbsolute(l+"/skins/"+k.settings.skin+"/content.css"))}k.onInit.add(function(){if(!k.settings.readonly){k.onNodeChange.add(m._nodeChanged,m);k.onKeyUp.add(m._updateUndoStatus,m);k.onMouseUp.add(m._updateUndoStatus,m);k.dom.bind(k.dom.getRoot(),"dragend",function(){m._updateUndoStatus(k)})}});k.onSetProgressState.add(function(r,o,s){var t,u=r.id,q;if(o){m.progressTimer=setTimeout(function(){t=r.getContainer();t=t.insertBefore(i.create("DIV",{style:"position:relative"}),t.firstChild);q=i.get(r.id+"_tbl");i.add(t,"div",{id:u+"_blocker","class":"mceBlocker",style:{width:q.clientWidth+2,height:q.clientHeight+2}});i.add(t,"div",{id:u+"_progress","class":"mceProgress",style:{left:q.clientWidth/2,top:q.clientHeight/2}})},s||0)}else{i.remove(u+"_blocker");i.remove(u+"_progress");clearTimeout(m.progressTimer)}});i.loadCSS(n.editor_css?k.documentBaseURI.toAbsolute(n.editor_css):l+"/skins/"+k.settings.skin+"/ui.css");if(n.skin_variant){i.loadCSS(l+"/skins/"+k.settings.skin+"/ui_"+n.skin_variant+".css")}},_isHighContrast:function(){var j,k=i.add(i.getRoot(),"div",{style:"background-color: rgb(171,239,86);"});j=(i.getStyle(k,"background-color",true)+"").toLowerCase().replace(/ /g,"");i.remove(k);return j!="rgb(171,239,86)"&&j!="#abef56"},createControl:function(m,j){var k,l;if(l=j.createControl(m)){return l}switch(m){case"styleselect":return this._createStyleSelect();case"formatselect":return this._createBlockFormats();case"fontselect":return this._createFontSelect();case"fontsizeselect":return this._createFontSizeSelect();case"forecolor":return this._createForeColorMenu();case"backcolor":return this._createBackColorMenu()}if((k=this.controls[m])){return j.createButton(m,{title:"advanced."+k[0],cmd:k[1],ui:k[2],value:k[3]})}},execCommand:function(l,k,m){var j=this["_"+l];if(j){j.call(this,k,m);return true}return false},_importClasses:function(l){var j=this.editor,k=j.controlManager.get("styleselect");if(k.getLength()==0){f(j.dom.getClasses(),function(q,m){var p="style_"+m,n;n={inline:"span",attributes:{"class":q["class"]},selector:"*"};j.formatter.register(p,n);k.add(q["class"],p,{style:function(){return b(j,n)}})})}},_createStyleSelect:function(o){var l=this,j=l.editor,k=j.controlManager,m;m=k.createListBox("styleselect",{title:"advanced.style_select",onselect:function(q){var r,n=[],p;f(m.items,function(s){n.push(s.value)});j.focus();j.undoManager.add();r=j.formatter.matchAll(n);h.each(r,function(s){if(!q||s==q){if(s){j.formatter.remove(s)}p=true}});if(!p){j.formatter.apply(q)}j.undoManager.add();j.nodeChanged();return false}});j.onPreInit.add(function(){var p=0,n=j.getParam("style_formats");if(n){f(n,function(q){var r,s=0;f(q,function(){s++});if(s>1){r=q.name=q.name||"style_"+(p++);j.formatter.register(r,q);m.add(q.title,r,{style:function(){return b(j,q)}})}else{m.add(q.title)}})}else{f(j.getParam("theme_advanced_styles","","hash"),function(t,s){var r,q;if(t){r="style_"+(p++);q={inline:"span",classes:t,selector:"*"};j.formatter.register(r,q);m.add(l.editor.translate(s),r,{style:function(){return b(j,q)}})}})}});if(m.getLength()==0){m.onPostRender.add(function(p,q){if(!m.NativeListBox){g.add(q.id+"_text","focus",l._importClasses,l);g.add(q.id+"_text","mousedown",l._importClasses,l);g.add(q.id+"_open","focus",l._importClasses,l);g.add(q.id+"_open","mousedown",l._importClasses,l)}else{g.add(q.id,"focus",l._importClasses,l)}})}return m},_createFontSelect:function(){var l,k=this,j=k.editor;l=j.controlManager.createListBox("fontselect",{title:"advanced.fontdefault",onselect:function(m){var n=l.items[l.selectedIndex];if(!m&&n){j.execCommand("FontName",false,n.value);return}j.execCommand("FontName",false,m);l.select(function(o){return m==o});if(n&&n.value==m){l.select(null)}return false}});if(l){f(j.getParam("theme_advanced_fonts",k.settings.theme_advanced_fonts,"hash"),function(n,m){l.add(j.translate(m),n,{style:n.indexOf("dings")==-1?"font-family:"+n:""})})}return l},_createFontSizeSelect:function(){var m=this,k=m.editor,n,l=0,j=[];n=k.controlManager.createListBox("fontsizeselect",{title:"advanced.font_size",onselect:function(o){var p=n.items[n.selectedIndex];if(!o&&p){p=p.value;if(p["class"]){k.formatter.toggle("fontsize_class",{value:p["class"]});k.undoManager.add();k.nodeChanged()}else{k.execCommand("FontSize",false,p.fontSize)}return}if(o["class"]){k.focus();k.undoManager.add();k.formatter.toggle("fontsize_class",{value:o["class"]});k.undoManager.add();k.nodeChanged()}else{k.execCommand("FontSize",false,o.fontSize)}n.select(function(q){return o==q});if(p&&(p.value.fontSize==o.fontSize||p.value["class"]&&p.value["class"]==o["class"])){n.select(null)}return false}});if(n){f(m.settings.theme_advanced_font_sizes,function(p,o){var q=p.fontSize;if(q>=1&&q<=7){q=m.sizes[parseInt(q)-1]+"pt"}n.add(o,p,{style:"font-size:"+q,"class":"mceFontSize"+(l++)+(" "+(p["class"]||""))})})}return n},_createBlockFormats:function(){var l,j={p:"advanced.paragraph",address:"advanced.address",pre:"advanced.pre",h1:"advanced.h1",h2:"advanced.h2",h3:"advanced.h3",h4:"advanced.h4",h5:"advanced.h5",h6:"advanced.h6",div:"advanced.div",blockquote:"advanced.blockquote",code:"advanced.code",dt:"advanced.dt",dd:"advanced.dd",samp:"advanced.samp"},k=this;l=k.editor.controlManager.createListBox("formatselect",{title:"advanced.block",onselect:function(m){k.editor.execCommand("FormatBlock",false,m);return false}});if(l){f(k.editor.getParam("theme_advanced_blockformats",k.settings.theme_advanced_blockformats,"hash"),function(n,m){l.add(k.editor.translate(m!=n?m:j[n]),n,{"class":"mce_formatPreview mce_"+n,style:function(){return b(k.editor,{block:n})}})})}return l},_createForeColorMenu:function(){var n,k=this,l=k.settings,m={},j;if(l.theme_advanced_more_colors){m.more_colors_func=function(){k._mceColorPicker(0,{color:n.value,func:function(o){n.setColor(o)}})}}if(j=l.theme_advanced_text_colors){m.colors=j}if(l.theme_advanced_default_foreground_color){m.default_color=l.theme_advanced_default_foreground_color}m.title="advanced.forecolor_desc";m.cmd="ForeColor";m.scope=this;n=k.editor.controlManager.createColorSplitButton("forecolor",m);return n},_createBackColorMenu:function(){var n,k=this,l=k.settings,m={},j;if(l.theme_advanced_more_colors){m.more_colors_func=function(){k._mceColorPicker(0,{color:n.value,func:function(o){n.setColor(o)}})}}if(j=l.theme_advanced_background_colors){m.colors=j}if(l.theme_advanced_default_background_color){m.default_color=l.theme_advanced_default_background_color}m.title="advanced.backcolor_desc";m.cmd="HiliteColor";m.scope=this;n=k.editor.controlManager.createColorSplitButton("backcolor",m);return n},renderUI:function(l){var q,m,r,w=this,u=w.editor,x=w.settings,v,k,j;if(u.settings){u.settings.aria_label=x.aria_label+u.getLang("advanced.help_shortcut")}q=k=i.create("span",{role:"application","aria-labelledby":u.id+"_voice",id:u.id+"_parent","class":"mceEditor "+u.settings.skin+"Skin"+(x.skin_variant?" "+u.settings.skin+"Skin"+w._ufirst(x.skin_variant):"")+(u.settings.directionality=="rtl"?" mceRtl":"")});i.add(q,"span",{"class":"mceVoiceLabel",style:"display:none;",id:u.id+"_voice"},x.aria_label);if(!i.boxModel){q=i.add(q,"div",{"class":"mceOldBoxModel"})}q=v=i.add(q,"table",{role:"presentation",id:u.id+"_tbl","class":"mceLayout",cellSpacing:0,cellPadding:0});q=r=i.add(q,"tbody");switch((x.theme_advanced_layout_manager||"").toLowerCase()){case"rowlayout":m=w._rowLayout(x,r,l);break;case"customlayout":m=u.execCallback("theme_advanced_custom_layout",x,r,l,k);break;default:m=w._simpleLayout(x,r,l,k)}q=l.targetNode;j=v.rows;i.addClass(j[0],"mceFirst");i.addClass(j[j.length-1],"mceLast");f(i.select("tr",r),function(o){i.addClass(o.firstChild,"mceFirst");i.addClass(o.childNodes[o.childNodes.length-1],"mceLast")});if(i.get(x.theme_advanced_toolbar_container)){i.get(x.theme_advanced_toolbar_container).appendChild(k)}else{i.insertAfter(k,q)}g.add(u.id+"_path_row","click",function(n){n=n.target;if(n.nodeName=="A"){w._sel(n.className.replace(/^.*mcePath_([0-9]+).*$/,"$1"));return false}});if(!u.getParam("accessibility_focus")){g.add(i.add(k,"a",{href:"#"},""),"focus",function(){tinyMCE.get(u.id).focus()})}if(x.theme_advanced_toolbar_location=="external"){l.deltaHeight=0}w.deltaHeight=l.deltaHeight;l.targetNode=null;u.onKeyDown.add(function(p,n){var s=121,o=122;if(n.altKey){if(n.keyCode===s){if(h.isWebKit){window.focus()}w.toolbarGroup.focus();return g.cancel(n)}else{if(n.keyCode===o){i.get(p.id+"_path_row").focus();return g.cancel(n)}}}});u.addShortcut("alt+0","","mceShortcuts",w);return{iframeContainer:m,editorContainer:u.id+"_parent",sizeContainer:v,deltaHeight:l.deltaHeight}},getInfo:function(){return{longname:"Advanced theme",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",version:h.majorVersion+"."+h.minorVersion}},resizeBy:function(j,k){var l=i.get(this.editor.id+"_ifr");this.resizeTo(l.clientWidth+j,l.clientHeight+k)},resizeTo:function(j,n,l){var k=this.editor,m=this.settings,o=i.get(k.id+"_tbl"),p=i.get(k.id+"_ifr");j=Math.max(m.theme_advanced_resizing_min_width||100,j);n=Math.max(m.theme_advanced_resizing_min_height||100,n);j=Math.min(m.theme_advanced_resizing_max_width||65535,j);n=Math.min(m.theme_advanced_resizing_max_height||65535,n);i.setStyle(o,"height","");i.setStyle(p,"height",n);if(m.theme_advanced_resize_horizontal){i.setStyle(o,"width","");i.setStyle(p,"width",j);if(j"));i.setHTML(l,r.join(""))},_addStatusBar:function(p,k){var l,w=this,q=w.editor,x=w.settings,j,u,v,m;l=i.add(p,"tr");l=m=i.add(l,"td",{"class":"mceStatusbar"});l=i.add(l,"div",{id:q.id+"_path_row",role:"group","aria-labelledby":q.id+"_path_voice"});if(x.theme_advanced_path){i.add(l,"span",{id:q.id+"_path_voice"},q.translate("advanced.path"));i.add(l,"span",{},": ")}else{i.add(l,"span",{}," ")}if(x.theme_advanced_resizing){i.add(m,"a",{id:q.id+"_resize",href:"javascript:;",onclick:"return false;","class":"mceResize",tabIndex:"-1"});if(x.theme_advanced_resizing_use_cookie){q.onPostRender.add(function(){var n=a.getHash("TinyMCE_"+q.id+"_size"),r=i.get(q.id+"_tbl");if(!n){return}w.resizeTo(n.cw,n.ch)})}q.onPostRender.add(function(){g.add(q.id+"_resize","click",function(n){n.preventDefault()});g.add(q.id+"_resize","mousedown",function(E){var t,r,s,o,D,A,B,G,n,F,y;function z(H){H.preventDefault();n=B+(H.screenX-D);F=G+(H.screenY-A);w.resizeTo(n,F)}function C(H){g.remove(i.doc,"mousemove",t);g.remove(q.getDoc(),"mousemove",r);g.remove(i.doc,"mouseup",s);g.remove(q.getDoc(),"mouseup",o);n=B+(H.screenX-D);F=G+(H.screenY-A);w.resizeTo(n,F,true);q.nodeChanged()}E.preventDefault();D=E.screenX;A=E.screenY;y=i.get(w.editor.id+"_ifr");B=n=y.clientWidth;G=F=y.clientHeight;t=g.add(i.doc,"mousemove",z);r=g.add(q.getDoc(),"mousemove",z);s=g.add(i.doc,"mouseup",C);o=g.add(q.getDoc(),"mouseup",C)})})}k.deltaHeight-=21;l=p=null},_updateUndoStatus:function(k){var j=k.controlManager,l=k.undoManager;j.setDisabled("undo",!l.hasUndo()&&!l.typing);j.setDisabled("redo",!l.hasRedo())},_nodeChanged:function(o,u,E,r,F){var z=this,D,G=0,y,H,A=z.settings,x,l,w,C,m,k,j;h.each(z.stateControls,function(n){u.setActive(n,o.queryCommandState(z.controls[n][1]))});function q(p){var s,n=F.parents,t=p;if(typeof(p)=="string"){t=function(v){return v.nodeName==p}}for(s=0;s0){H.mark(p)}})}if(H=u.get("formatselect")){D=q(o.dom.isBlock);if(D){H.select(D.nodeName.toLowerCase())}}q(function(p){if(p.nodeName==="SPAN"){if(!x&&p.className){x=p.className}}if(o.dom.is(p,A.theme_advanced_font_selector)){if(!l&&p.style.fontSize){l=p.style.fontSize}if(!w&&p.style.fontFamily){w=p.style.fontFamily.replace(/[\"\']+/g,"").replace(/^([^,]+).*/,"$1").toLowerCase()}if(!C&&p.style.color){C=p.style.color}if(!m&&p.style.backgroundColor){m=p.style.backgroundColor}}return false});if(H=u.get("fontselect")){H.select(function(n){return n.replace(/^([^,]+).*/,"$1").toLowerCase()==w})}if(H=u.get("fontsizeselect")){if(A.theme_advanced_runtime_fontsize&&!l&&!x){l=o.dom.getStyle(E,"fontSize",true)}H.select(function(n){if(n.fontSize&&n.fontSize===l){return true}if(n["class"]&&n["class"]===x){return true}})}if(A.theme_advanced_show_current_color){function B(p,n){if(H=u.get(p)){if(!n){n=H.settings.default_color}if(n!==H.value){H.displayColor(n)}}}B("forecolor",C);B("backcolor",m)}if(A.theme_advanced_show_current_color){function B(p,n){if(H=u.get(p)){if(!n){n=H.settings.default_color}if(n!==H.value){H.displayColor(n)}}}B("forecolor",C);B("backcolor",m)}if(A.theme_advanced_path&&A.theme_advanced_statusbar_location){D=i.get(o.id+"_path")||i.add(o.id+"_path_row","span",{id:o.id+"_path"});if(z.statusKeyboardNavigation){z.statusKeyboardNavigation.destroy();z.statusKeyboardNavigation=null}i.setHTML(D,"");q(function(I){var p=I.nodeName.toLowerCase(),s,v,t="";if(I.nodeType!=1||p==="br"||I.getAttribute("data-mce-bogus")||i.hasClass(I,"mceItemHidden")||i.hasClass(I,"mceItemRemoved")){return}if(h.isIE&&I.scopeName!=="HTML"&&I.scopeName){p=I.scopeName+":"+p}p=p.replace(/mce\:/g,"");switch(p){case"b":p="strong";break;case"i":p="em";break;case"img":if(y=i.getAttrib(I,"src")){t+="src: "+y+" "}break;case"a":if(y=i.getAttrib(I,"name")){t+="name: "+y+" ";p+="#"+y}if(y=i.getAttrib(I,"href")){t+="href: "+y+" "}break;case"font":if(y=i.getAttrib(I,"face")){t+="font: "+y+" "}if(y=i.getAttrib(I,"size")){t+="size: "+y+" "}if(y=i.getAttrib(I,"color")){t+="color: "+y+" "}break;case"span":if(y=i.getAttrib(I,"style")){t+="style: "+y+" "}break}if(y=i.getAttrib(I,"id")){t+="id: "+y+" "}if(y=I.className){y=y.replace(/\b\s*(webkit|mce|Apple-)\w+\s*\b/g,"");if(y){t+="class: "+y+" ";if(o.dom.isBlock(I)||p=="img"||p=="span"){p+="."+y}}}p=p.replace(/(html:)/g,"");p={name:p,node:I,title:t};z.onResolveName.dispatch(z,p);t=p.title;p=p.name;v=i.create("a",{href:"javascript:;",role:"button",onmousedown:"return false;",title:t,"class":"mcePath_"+(G++)},p);if(D.hasChildNodes()){D.insertBefore(i.create("span",{"aria-hidden":"true"},"\u00a0\u00bb "),D.firstChild);D.insertBefore(v,D.firstChild)}else{D.appendChild(v)}},o.getBody());if(i.select("a",D).length>0){z.statusKeyboardNavigation=new h.ui.KeyboardNavigation({root:o.id+"_path_row",items:i.select("a",D),excludeFromTabOrder:true,onCancel:function(){o.focus()}},i)}}},_sel:function(j){this.editor.execCommand("mceSelectNodeDepth",false,j)},_mceInsertAnchor:function(l,k){var j=this.editor;j.windowManager.open({url:this.url+"/anchor.htm",width:320+parseInt(j.getLang("advanced.anchor_delta_width",0)),height:90+parseInt(j.getLang("advanced.anchor_delta_height",0)),inline:true},{theme_url:this.url})},_mceCharMap:function(){var j=this.editor;j.windowManager.open({url:this.url+"/charmap.htm",width:550+parseInt(j.getLang("advanced.charmap_delta_width",0)),height:265+parseInt(j.getLang("advanced.charmap_delta_height",0)),inline:true},{theme_url:this.url})},_mceHelp:function(){var j=this.editor;j.windowManager.open({url:this.url+"/about.htm",width:480,height:380,inline:true},{theme_url:this.url})},_mceShortcuts:function(){var j=this.editor;j.windowManager.open({url:this.url+"/shortcuts.htm",width:480,height:380,inline:true},{theme_url:this.url})},_mceColorPicker:function(l,k){var j=this.editor;k=k||{};j.windowManager.open({url:this.url+"/color_picker.htm",width:375+parseInt(j.getLang("advanced.colorpicker_delta_width",0)),height:250+parseInt(j.getLang("advanced.colorpicker_delta_height",0)),close_previous:false,inline:true},{input_color:k.color,func:k.func,theme_url:this.url})},_mceCodeEditor:function(k,l){var j=this.editor;j.windowManager.open({url:this.url+"/source_editor.htm",width:parseInt(j.getParam("theme_advanced_source_editor_width",720)),height:parseInt(j.getParam("theme_advanced_source_editor_height",580)),inline:true,resizable:true,maximizable:true},{theme_url:this.url})},_mceImage:function(k,l){var j=this.editor;if(j.dom.getAttrib(j.selection.getNode(),"class","").indexOf("mceItem")!=-1){return}j.windowManager.open({url:this.url+"/image.htm",width:355+parseInt(j.getLang("advanced.image_delta_width",0)),height:275+parseInt(j.getLang("advanced.image_delta_height",0)),inline:true},{theme_url:this.url})},_mceLink:function(k,l){var j=this.editor;j.windowManager.open({url:this.url+"/link.htm",width:310+parseInt(j.getLang("advanced.link_delta_width",0)),height:200+parseInt(j.getLang("advanced.link_delta_height",0)),inline:true},{theme_url:this.url})},_mceNewDocument:function(){var j=this.editor;j.windowManager.confirm("advanced.newdocument",function(k){if(k){j.execCommand("mceSetContent",false,"")}})},_mceForeColor:function(){var j=this;this._mceColorPicker(0,{color:j.fgColor,func:function(k){j.fgColor=k;j.editor.execCommand("ForeColor",false,k)}})},_mceBackColor:function(){var j=this;this._mceColorPicker(0,{color:j.bgColor,func:function(k){j.bgColor=k;j.editor.execCommand("HiliteColor",false,k)}})},_ufirst:function(j){return j.substring(0,1).toUpperCase()+j.substring(1)}});h.ThemeManager.add("advanced",h.themes.AdvancedTheme)}(tinymce)); \ No newline at end of file +(function(h){var i=h.DOM,g=h.dom.Event,c=h.extend,f=h.each,a=h.util.Cookie,e,d=h.explode;function b(p,m){var k,l,o=p.dom,j="",n,r;previewStyles=p.settings.preview_styles;if(previewStyles===false){return""}if(!previewStyles){previewStyles="font-family font-size font-weight text-decoration text-transform color background-color"}function q(s){return s.replace(/%(\w+)/g,"")}k=m.block||m.inline||"span";l=o.create(k);f(m.styles,function(t,s){t=q(t);if(t){o.setStyle(l,s,t)}});f(m.attributes,function(t,s){t=q(t);if(t){o.setAttrib(l,s,t)}});f(m.classes,function(s){s=q(s);if(!o.hasClass(l,s)){o.addClass(l,s)}});o.setStyles(l,{position:"absolute",left:-65535});p.getBody().appendChild(l);n=o.getStyle(p.getBody(),"fontSize",true);n=/px$/.test(n)?parseInt(n,10):0;f(previewStyles.split(" "),function(s){var t=o.getStyle(l,s,true);if(s=="background-color"&&/transparent|rgba\s*\([^)]+,\s*0\)/.test(t)){t=o.getStyle(p.getBody(),s,true);if(o.toHex(t).toLowerCase()=="#ffffff"){return}}if(s=="font-size"){if(/em|%$/.test(t)){if(n===0){return}t=parseFloat(t,10)/(/%$/.test(t)?100:1);t=(t*n)+"px"}}j+=s+":"+t+";"});o.remove(l);return j}h.ThemeManager.requireLangPack("advanced");h.create("tinymce.themes.AdvancedTheme",{sizes:[8,10,12,14,18,24,36],controls:{bold:["bold_desc","Bold"],italic:["italic_desc","Italic"],underline:["underline_desc","Underline"],strikethrough:["striketrough_desc","Strikethrough"],justifyleft:["justifyleft_desc","JustifyLeft"],justifycenter:["justifycenter_desc","JustifyCenter"],justifyright:["justifyright_desc","JustifyRight"],justifyfull:["justifyfull_desc","JustifyFull"],bullist:["bullist_desc","InsertUnorderedList"],numlist:["numlist_desc","InsertOrderedList"],outdent:["outdent_desc","Outdent"],indent:["indent_desc","Indent"],cut:["cut_desc","Cut"],copy:["copy_desc","Copy"],paste:["paste_desc","Paste"],undo:["undo_desc","Undo"],redo:["redo_desc","Redo"],link:["link_desc","mceLink"],unlink:["unlink_desc","unlink"],image:["image_desc","mceImage"],cleanup:["cleanup_desc","mceCleanup"],help:["help_desc","mceHelp"],code:["code_desc","mceCodeEditor"],hr:["hr_desc","InsertHorizontalRule"],removeformat:["removeformat_desc","RemoveFormat"],sub:["sub_desc","subscript"],sup:["sup_desc","superscript"],forecolor:["forecolor_desc","ForeColor"],forecolorpicker:["forecolor_desc","mceForeColor"],backcolor:["backcolor_desc","HiliteColor"],backcolorpicker:["backcolor_desc","mceBackColor"],charmap:["charmap_desc","mceCharMap"],visualaid:["visualaid_desc","mceToggleVisualAid"],anchor:["anchor_desc","mceInsertAnchor"],newdocument:["newdocument_desc","mceNewDocument"],blockquote:["blockquote_desc","mceBlockQuote"]},stateControls:["bold","italic","underline","strikethrough","bullist","numlist","justifyleft","justifycenter","justifyright","justifyfull","sub","sup","blockquote"],init:function(k,l){var m=this,n,j,p;m.editor=k;m.url=l;m.onResolveName=new h.util.Dispatcher(this);n=k.settings;k.forcedHighContrastMode=k.settings.detect_highcontrast&&m._isHighContrast();k.settings.skin=k.forcedHighContrastMode?"highcontrast":k.settings.skin;if(!n.theme_advanced_buttons1){n=c({theme_advanced_buttons1:"bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,|,styleselect,formatselect",theme_advanced_buttons2:"bullist,numlist,|,outdent,indent,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code",theme_advanced_buttons3:"hr,removeformat,visualaid,|,sub,sup,|,charmap"},n)}m.settings=n=c({theme_advanced_path:true,theme_advanced_toolbar_location:"top",theme_advanced_blockformats:"p,address,pre,h1,h2,h3,h4,h5,h6",theme_advanced_toolbar_align:"left",theme_advanced_statusbar_location:"bottom",theme_advanced_fonts:"Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,zapf dingbats",theme_advanced_more_colors:1,theme_advanced_row_height:23,theme_advanced_resize_horizontal:1,theme_advanced_resizing_use_cookie:1,theme_advanced_font_sizes:"1,2,3,4,5,6,7",theme_advanced_font_selector:"span",theme_advanced_show_current_color:0,readonly:k.settings.readonly},n);if(!n.font_size_style_values){n.font_size_style_values="8pt,10pt,12pt,14pt,18pt,24pt,36pt"}if(h.is(n.theme_advanced_font_sizes,"string")){n.font_size_style_values=h.explode(n.font_size_style_values);n.font_size_classes=h.explode(n.font_size_classes||"");p={};k.settings.theme_advanced_font_sizes=n.theme_advanced_font_sizes;f(k.getParam("theme_advanced_font_sizes","","hash"),function(r,q){var o;if(q==r&&r>=1&&r<=7){q=r+" ("+m.sizes[r-1]+"pt)";o=n.font_size_classes[r-1];r=n.font_size_style_values[r-1]||(m.sizes[r-1]+"pt")}if(/^\s*\./.test(r)){o=r.replace(/\./g,"")}p[q]=o?{"class":o}:{fontSize:r}});n.theme_advanced_font_sizes=p}if((j=n.theme_advanced_path_location)&&j!="none"){n.theme_advanced_statusbar_location=n.theme_advanced_path_location}if(n.theme_advanced_statusbar_location=="none"){n.theme_advanced_statusbar_location=0}if(k.settings.content_css!==false){k.contentCSS.push(k.baseURI.toAbsolute(l+"/skins/"+k.settings.skin+"/content.css"))}k.onInit.add(function(){if(!k.settings.readonly){k.onNodeChange.add(m._nodeChanged,m);k.onKeyUp.add(m._updateUndoStatus,m);k.onMouseUp.add(m._updateUndoStatus,m);k.dom.bind(k.dom.getRoot(),"dragend",function(){m._updateUndoStatus(k)})}});k.onSetProgressState.add(function(r,o,s){var t,u=r.id,q;if(o){m.progressTimer=setTimeout(function(){t=r.getContainer();t=t.insertBefore(i.create("DIV",{style:"position:relative"}),t.firstChild);q=i.get(r.id+"_tbl");i.add(t,"div",{id:u+"_blocker","class":"mceBlocker",style:{width:q.clientWidth+2,height:q.clientHeight+2}});i.add(t,"div",{id:u+"_progress","class":"mceProgress",style:{left:q.clientWidth/2,top:q.clientHeight/2}})},s||0)}else{i.remove(u+"_blocker");i.remove(u+"_progress");clearTimeout(m.progressTimer)}});i.loadCSS(n.editor_css?k.documentBaseURI.toAbsolute(n.editor_css):l+"/skins/"+k.settings.skin+"/ui.css");if(n.skin_variant){i.loadCSS(l+"/skins/"+k.settings.skin+"/ui_"+n.skin_variant+".css")}},_isHighContrast:function(){var j,k=i.add(i.getRoot(),"div",{style:"background-color: rgb(171,239,86);"});j=(i.getStyle(k,"background-color",true)+"").toLowerCase().replace(/ /g,"");i.remove(k);return j!="rgb(171,239,86)"&&j!="#abef56"},createControl:function(m,j){var k,l;if(l=j.createControl(m)){return l}switch(m){case"styleselect":return this._createStyleSelect();case"formatselect":return this._createBlockFormats();case"fontselect":return this._createFontSelect();case"fontsizeselect":return this._createFontSizeSelect();case"forecolor":return this._createForeColorMenu();case"backcolor":return this._createBackColorMenu()}if((k=this.controls[m])){return j.createButton(m,{title:"advanced."+k[0],cmd:k[1],ui:k[2],value:k[3]})}},execCommand:function(l,k,m){var j=this["_"+l];if(j){j.call(this,k,m);return true}return false},_importClasses:function(l){var j=this.editor,k=j.controlManager.get("styleselect");if(k.getLength()==0){f(j.dom.getClasses(),function(q,m){var p="style_"+m,n;n={inline:"span",attributes:{"class":q["class"]},selector:"*"};j.formatter.register(p,n);k.add(q["class"],p,{style:function(){return b(j,n)}})})}},_createStyleSelect:function(o){var l=this,j=l.editor,k=j.controlManager,m;m=k.createListBox("styleselect",{title:"advanced.style_select",onselect:function(q){var r,n=[],p;f(m.items,function(s){n.push(s.value)});j.focus();j.undoManager.add();r=j.formatter.matchAll(n);h.each(r,function(s){if(!q||s==q){if(s){j.formatter.remove(s)}p=true}});if(!p){j.formatter.apply(q)}j.undoManager.add();j.nodeChanged();return false}});j.onPreInit.add(function(){var p=0,n=j.getParam("style_formats");if(n){f(n,function(q){var r,s=0;f(q,function(){s++});if(s>1){r=q.name=q.name||"style_"+(p++);j.formatter.register(r,q);m.add(q.title,r,{style:function(){return b(j,q)}})}else{m.add(q.title)}})}else{f(j.getParam("theme_advanced_styles","","hash"),function(t,s){var r,q;if(t){r="style_"+(p++);q={inline:"span",classes:t,selector:"*"};j.formatter.register(r,q);m.add(l.editor.translate(s),r,{style:function(){return b(j,q)}})}})}});if(m.getLength()==0){m.onPostRender.add(function(p,q){if(!m.NativeListBox){g.add(q.id+"_text","focus",l._importClasses,l);g.add(q.id+"_text","mousedown",l._importClasses,l);g.add(q.id+"_open","focus",l._importClasses,l);g.add(q.id+"_open","mousedown",l._importClasses,l)}else{g.add(q.id,"focus",l._importClasses,l)}})}return m},_createFontSelect:function(){var l,k=this,j=k.editor;l=j.controlManager.createListBox("fontselect",{title:"advanced.fontdefault",onselect:function(m){var n=l.items[l.selectedIndex];if(!m&&n){j.execCommand("FontName",false,n.value);return}j.execCommand("FontName",false,m);l.select(function(o){return m==o});if(n&&n.value==m){l.select(null)}return false}});if(l){f(j.getParam("theme_advanced_fonts",k.settings.theme_advanced_fonts,"hash"),function(n,m){l.add(j.translate(m),n,{style:n.indexOf("dings")==-1?"font-family:"+n:""})})}return l},_createFontSizeSelect:function(){var m=this,k=m.editor,n,l=0,j=[];n=k.controlManager.createListBox("fontsizeselect",{title:"advanced.font_size",onselect:function(o){var p=n.items[n.selectedIndex];if(!o&&p){p=p.value;if(p["class"]){k.formatter.toggle("fontsize_class",{value:p["class"]});k.undoManager.add();k.nodeChanged()}else{k.execCommand("FontSize",false,p.fontSize)}return}if(o["class"]){k.focus();k.undoManager.add();k.formatter.toggle("fontsize_class",{value:o["class"]});k.undoManager.add();k.nodeChanged()}else{k.execCommand("FontSize",false,o.fontSize)}n.select(function(q){return o==q});if(p&&(p.value.fontSize==o.fontSize||p.value["class"]&&p.value["class"]==o["class"])){n.select(null)}return false}});if(n){f(m.settings.theme_advanced_font_sizes,function(p,o){var q=p.fontSize;if(q>=1&&q<=7){q=m.sizes[parseInt(q)-1]+"pt"}n.add(o,p,{style:"font-size:"+q,"class":"mceFontSize"+(l++)+(" "+(p["class"]||""))})})}return n},_createBlockFormats:function(){var l,j={p:"advanced.paragraph",address:"advanced.address",pre:"advanced.pre",h1:"advanced.h1",h2:"advanced.h2",h3:"advanced.h3",h4:"advanced.h4",h5:"advanced.h5",h6:"advanced.h6",div:"advanced.div",blockquote:"advanced.blockquote",code:"advanced.code",dt:"advanced.dt",dd:"advanced.dd",samp:"advanced.samp"},k=this;l=k.editor.controlManager.createListBox("formatselect",{title:"advanced.block",onselect:function(m){k.editor.execCommand("FormatBlock",false,m);return false}});if(l){f(k.editor.getParam("theme_advanced_blockformats",k.settings.theme_advanced_blockformats,"hash"),function(n,m){l.add(k.editor.translate(m!=n?m:j[n]),n,{"class":"mce_formatPreview mce_"+n,style:function(){return b(k.editor,{block:n})}})})}return l},_createForeColorMenu:function(){var n,k=this,l=k.settings,m={},j;if(l.theme_advanced_more_colors){m.more_colors_func=function(){k._mceColorPicker(0,{color:n.value,func:function(o){n.setColor(o)}})}}if(j=l.theme_advanced_text_colors){m.colors=j}if(l.theme_advanced_default_foreground_color){m.default_color=l.theme_advanced_default_foreground_color}m.title="advanced.forecolor_desc";m.cmd="ForeColor";m.scope=this;n=k.editor.controlManager.createColorSplitButton("forecolor",m);return n},_createBackColorMenu:function(){var n,k=this,l=k.settings,m={},j;if(l.theme_advanced_more_colors){m.more_colors_func=function(){k._mceColorPicker(0,{color:n.value,func:function(o){n.setColor(o)}})}}if(j=l.theme_advanced_background_colors){m.colors=j}if(l.theme_advanced_default_background_color){m.default_color=l.theme_advanced_default_background_color}m.title="advanced.backcolor_desc";m.cmd="HiliteColor";m.scope=this;n=k.editor.controlManager.createColorSplitButton("backcolor",m);return n},renderUI:function(l){var q,m,r,w=this,u=w.editor,x=w.settings,v,k,j;if(u.settings){u.settings.aria_label=x.aria_label+u.getLang("advanced.help_shortcut")}q=k=i.create("span",{role:"application","aria-labelledby":u.id+"_voice",id:u.id+"_parent","class":"mceEditor "+u.settings.skin+"Skin"+(x.skin_variant?" "+u.settings.skin+"Skin"+w._ufirst(x.skin_variant):"")+(u.settings.directionality=="rtl"?" mceRtl":"")});i.add(q,"span",{"class":"mceVoiceLabel",style:"display:none;",id:u.id+"_voice"},x.aria_label);if(!i.boxModel){q=i.add(q,"div",{"class":"mceOldBoxModel"})}q=v=i.add(q,"table",{role:"presentation",id:u.id+"_tbl","class":"mceLayout",cellSpacing:0,cellPadding:0});q=r=i.add(q,"tbody");switch((x.theme_advanced_layout_manager||"").toLowerCase()){case"rowlayout":m=w._rowLayout(x,r,l);break;case"customlayout":m=u.execCallback("theme_advanced_custom_layout",x,r,l,k);break;default:m=w._simpleLayout(x,r,l,k)}q=l.targetNode;j=v.rows;i.addClass(j[0],"mceFirst");i.addClass(j[j.length-1],"mceLast");f(i.select("tr",r),function(o){i.addClass(o.firstChild,"mceFirst");i.addClass(o.childNodes[o.childNodes.length-1],"mceLast")});if(i.get(x.theme_advanced_toolbar_container)){i.get(x.theme_advanced_toolbar_container).appendChild(k)}else{i.insertAfter(k,q)}g.add(u.id+"_path_row","click",function(n){n=n.target;if(n.nodeName=="A"){w._sel(n.className.replace(/^.*mcePath_([0-9]+).*$/,"$1"));return false}});if(!u.getParam("accessibility_focus")){g.add(i.add(k,"a",{href:"#"},""),"focus",function(){tinyMCE.get(u.id).focus()})}if(x.theme_advanced_toolbar_location=="external"){l.deltaHeight=0}w.deltaHeight=l.deltaHeight;l.targetNode=null;u.onKeyDown.add(function(p,n){var s=121,o=122;if(n.altKey){if(n.keyCode===s){if(h.isWebKit){window.focus()}w.toolbarGroup.focus();return g.cancel(n)}else{if(n.keyCode===o){i.get(p.id+"_path_row").focus();return g.cancel(n)}}}});u.addShortcut("alt+0","","mceShortcuts",w);return{iframeContainer:m,editorContainer:u.id+"_parent",sizeContainer:v,deltaHeight:l.deltaHeight}},getInfo:function(){return{longname:"Advanced theme",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",version:h.majorVersion+"."+h.minorVersion}},resizeBy:function(j,k){var l=i.get(this.editor.id+"_ifr");this.resizeTo(l.clientWidth+j,l.clientHeight+k)},resizeTo:function(j,n,l){var k=this.editor,m=this.settings,o=i.get(k.id+"_tbl"),p=i.get(k.id+"_ifr");j=Math.max(m.theme_advanced_resizing_min_width||100,j);n=Math.max(m.theme_advanced_resizing_min_height||100,n);j=Math.min(m.theme_advanced_resizing_max_width||65535,j);n=Math.min(m.theme_advanced_resizing_max_height||65535,n);i.setStyle(o,"height","");i.setStyle(p,"height",n);if(m.theme_advanced_resize_horizontal){i.setStyle(o,"width","");i.setStyle(p,"width",j);if(j"));i.setHTML(l,r.join(""))},_addStatusBar:function(p,k){var l,w=this,q=w.editor,x=w.settings,j,u,v,m;l=i.add(p,"tr");l=m=i.add(l,"td",{"class":"mceStatusbar"});l=i.add(l,"div",{id:q.id+"_path_row",role:"group","aria-labelledby":q.id+"_path_voice"});if(x.theme_advanced_path){i.add(l,"span",{id:q.id+"_path_voice"},q.translate("advanced.path"));i.add(l,"span",{},": ")}else{i.add(l,"span",{}," ")}if(x.theme_advanced_resizing){i.add(m,"a",{id:q.id+"_resize",href:"javascript:;",onclick:"return false;","class":"mceResize",tabIndex:"-1"});if(x.theme_advanced_resizing_use_cookie){q.onPostRender.add(function(){var n=a.getHash("TinyMCE_"+q.id+"_size"),r=i.get(q.id+"_tbl");if(!n){return}w.resizeTo(n.cw,n.ch)})}q.onPostRender.add(function(){g.add(q.id+"_resize","click",function(n){n.preventDefault()});g.add(q.id+"_resize","mousedown",function(E){var t,r,s,o,D,A,B,G,n,F,y;function z(H){H.preventDefault();n=B+(H.screenX-D);F=G+(H.screenY-A);w.resizeTo(n,F)}function C(H){g.remove(i.doc,"mousemove",t);g.remove(q.getDoc(),"mousemove",r);g.remove(i.doc,"mouseup",s);g.remove(q.getDoc(),"mouseup",o);n=B+(H.screenX-D);F=G+(H.screenY-A);w.resizeTo(n,F,true);q.nodeChanged()}E.preventDefault();D=E.screenX;A=E.screenY;y=i.get(w.editor.id+"_ifr");B=n=y.clientWidth;G=F=y.clientHeight;t=g.add(i.doc,"mousemove",z);r=g.add(q.getDoc(),"mousemove",z);s=g.add(i.doc,"mouseup",C);o=g.add(q.getDoc(),"mouseup",C)})})}k.deltaHeight-=21;l=p=null},_updateUndoStatus:function(k){var j=k.controlManager,l=k.undoManager;j.setDisabled("undo",!l.hasUndo()&&!l.typing);j.setDisabled("redo",!l.hasRedo())},_nodeChanged:function(o,u,E,r,F){var z=this,D,G=0,y,H,A=z.settings,x,l,w,C,m,k,j;h.each(z.stateControls,function(n){u.setActive(n,o.queryCommandState(z.controls[n][1]))});function q(p){var s,n=F.parents,t=p;if(typeof(p)=="string"){t=function(v){return v.nodeName==p}}for(s=0;s0){H.mark(p)}})}if(H=u.get("formatselect")){D=q(o.dom.isBlock);if(D){H.select(D.nodeName.toLowerCase())}}q(function(p){if(p.nodeName==="SPAN"){if(!x&&p.className){x=p.className}}if(o.dom.is(p,A.theme_advanced_font_selector)){if(!l&&p.style.fontSize){l=p.style.fontSize}if(!w&&p.style.fontFamily){w=p.style.fontFamily.replace(/[\"\']+/g,"").replace(/^([^,]+).*/,"$1").toLowerCase()}if(!C&&p.style.color){C=p.style.color}if(!m&&p.style.backgroundColor){m=p.style.backgroundColor}}return false});if(H=u.get("fontselect")){H.select(function(n){return n.replace(/^([^,]+).*/,"$1").toLowerCase()==w})}if(H=u.get("fontsizeselect")){if(A.theme_advanced_runtime_fontsize&&!l&&!x){l=o.dom.getStyle(E,"fontSize",true)}H.select(function(n){if(n.fontSize&&n.fontSize===l){return true}if(n["class"]&&n["class"]===x){return true}})}if(A.theme_advanced_show_current_color){function B(p,n){if(H=u.get(p)){if(!n){n=H.settings.default_color}if(n!==H.value){H.displayColor(n)}}}B("forecolor",C);B("backcolor",m)}if(A.theme_advanced_show_current_color){function B(p,n){if(H=u.get(p)){if(!n){n=H.settings.default_color}if(n!==H.value){H.displayColor(n)}}}B("forecolor",C);B("backcolor",m)}if(A.theme_advanced_path&&A.theme_advanced_statusbar_location){D=i.get(o.id+"_path")||i.add(o.id+"_path_row","span",{id:o.id+"_path"});if(z.statusKeyboardNavigation){z.statusKeyboardNavigation.destroy();z.statusKeyboardNavigation=null}i.setHTML(D,"");q(function(I){var p=I.nodeName.toLowerCase(),s,v,t="";if(I.nodeType!=1||p==="br"||I.getAttribute("data-mce-bogus")||i.hasClass(I,"mceItemHidden")||i.hasClass(I,"mceItemRemoved")){return}if(h.isIE&&I.scopeName!=="HTML"&&I.scopeName){p=I.scopeName+":"+p}p=p.replace(/mce\:/g,"");switch(p){case"b":p="strong";break;case"i":p="em";break;case"img":if(y=i.getAttrib(I,"src")){t+="src: "+y+" "}break;case"a":if(y=i.getAttrib(I,"name")){t+="name: "+y+" ";p+="#"+y}if(y=i.getAttrib(I,"href")){t+="href: "+y+" "}break;case"font":if(y=i.getAttrib(I,"face")){t+="font: "+y+" "}if(y=i.getAttrib(I,"size")){t+="size: "+y+" "}if(y=i.getAttrib(I,"color")){t+="color: "+y+" "}break;case"span":if(y=i.getAttrib(I,"style")){t+="style: "+y+" "}break}if(y=i.getAttrib(I,"id")){t+="id: "+y+" "}if(y=I.className){y=y.replace(/\b\s*(webkit|mce|Apple-)\w+\s*\b/g,"");if(y){t+="class: "+y+" ";if(o.dom.isBlock(I)||p=="img"||p=="span"){p+="."+y}}}p=p.replace(/(html:)/g,"");p={name:p,node:I,title:t};z.onResolveName.dispatch(z,p);t=p.title;p=p.name;v=i.create("a",{href:"javascript:;",role:"button",onmousedown:"return false;",title:t,"class":"mcePath_"+(G++)},p);if(D.hasChildNodes()){D.insertBefore(i.create("span",{"aria-hidden":"true"},"\u00a0\u00bb "),D.firstChild);D.insertBefore(v,D.firstChild)}else{D.appendChild(v)}},o.getBody());if(i.select("a",D).length>0){z.statusKeyboardNavigation=new h.ui.KeyboardNavigation({root:o.id+"_path_row",items:i.select("a",D),excludeFromTabOrder:true,onCancel:function(){o.focus()}},i)}}},_sel:function(j){this.editor.execCommand("mceSelectNodeDepth",false,j)},_mceInsertAnchor:function(l,k){var j=this.editor;j.windowManager.open({url:this.url+"/anchor.htm",width:320+parseInt(j.getLang("advanced.anchor_delta_width",0)),height:90+parseInt(j.getLang("advanced.anchor_delta_height",0)),inline:true},{theme_url:this.url})},_mceCharMap:function(){var j=this.editor;j.windowManager.open({url:this.url+"/charmap.htm",width:550+parseInt(j.getLang("advanced.charmap_delta_width",0)),height:265+parseInt(j.getLang("advanced.charmap_delta_height",0)),inline:true},{theme_url:this.url})},_mceHelp:function(){var j=this.editor;j.windowManager.open({url:this.url+"/about.htm",width:480,height:380,inline:true},{theme_url:this.url})},_mceShortcuts:function(){var j=this.editor;j.windowManager.open({url:this.url+"/shortcuts.htm",width:480,height:380,inline:true},{theme_url:this.url})},_mceColorPicker:function(l,k){var j=this.editor;k=k||{};j.windowManager.open({url:this.url+"/color_picker.htm",width:375+parseInt(j.getLang("advanced.colorpicker_delta_width",0)),height:250+parseInt(j.getLang("advanced.colorpicker_delta_height",0)),close_previous:false,inline:true},{input_color:k.color,func:k.func,theme_url:this.url})},_mceCodeEditor:function(k,l){var j=this.editor;j.windowManager.open({url:this.url+"/source_editor.htm",width:parseInt(j.getParam("theme_advanced_source_editor_width",720)),height:parseInt(j.getParam("theme_advanced_source_editor_height",580)),inline:true,resizable:true,maximizable:true},{theme_url:this.url})},_mceImage:function(k,l){var j=this.editor;if(j.dom.getAttrib(j.selection.getNode(),"class","").indexOf("mceItem")!=-1){return}j.windowManager.open({url:this.url+"/image.htm",width:355+parseInt(j.getLang("advanced.image_delta_width",0)),height:275+parseInt(j.getLang("advanced.image_delta_height",0)),inline:true},{theme_url:this.url})},_mceLink:function(k,l){var j=this.editor;j.windowManager.open({url:this.url+"/link.htm",width:310+parseInt(j.getLang("advanced.link_delta_width",0)),height:200+parseInt(j.getLang("advanced.link_delta_height",0)),inline:true},{theme_url:this.url})},_mceNewDocument:function(){var j=this.editor;j.windowManager.confirm("advanced.newdocument",function(k){if(k){j.execCommand("mceSetContent",false,"")}})},_mceForeColor:function(){var j=this;this._mceColorPicker(0,{color:j.fgColor,func:function(k){j.fgColor=k;j.editor.execCommand("ForeColor",false,k)}})},_mceBackColor:function(){var j=this;this._mceColorPicker(0,{color:j.bgColor,func:function(k){j.bgColor=k;j.editor.execCommand("HiliteColor",false,k)}})},_ufirst:function(j){return j.substring(0,1).toUpperCase()+j.substring(1)}});h.ThemeManager.add("advanced",h.themes.AdvancedTheme)}(tinymce)); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/themes/advanced/editor_template_src.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/themes/advanced/editor_template_src.js index 12deb49c45..82166dcb68 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/themes/advanced/editor_template_src.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/themes/advanced/editor_template_src.js @@ -956,7 +956,7 @@ a = s.theme_advanced_toolbar_align.toLowerCase(); a = 'mce' + t._ufirst(a); - n = DOM.add(DOM.add(c, 'tr', {role: 'toolbar'}), 'td', {'class' : 'mceToolbar ' + a, "role":"toolbar"}); + n = DOM.add(DOM.add(c, 'tr', {role: 'presentation'}), 'td', {'class' : 'mceToolbar ' + a, "role":"toolbar"}); // Create toolbar and add the controls for (i=1; (v = s['theme_advanced_buttons' + i]); i++) { diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/tiny_mce.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/tiny_mce.js index 7cad8b783f..208dfcc070 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/tiny_mce.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/tiny_mce.js @@ -1 +1 @@ -(function(e){var a=/^\s*|\s*$/g,b,d="B".replace(/A(.)|B/,"$1")==="$1";var c={majorVersion:"3",minorVersion:"5.7",releaseDate:"2012-09-20",_init:function(){var s=this,q=document,o=navigator,g=o.userAgent,m,f,l,k,j,r;s.isOpera=e.opera&&opera.buildNumber;s.isWebKit=/WebKit/.test(g);s.isIE=!s.isWebKit&&!s.isOpera&&(/MSIE/gi).test(g)&&(/Explorer/gi).test(o.appName);s.isIE6=s.isIE&&/MSIE [56]/.test(g);s.isIE7=s.isIE&&/MSIE [7]/.test(g);s.isIE8=s.isIE&&/MSIE [8]/.test(g);s.isIE9=s.isIE&&/MSIE [9]/.test(g);s.isGecko=!s.isWebKit&&/Gecko/.test(g);s.isMac=g.indexOf("Mac")!=-1;s.isAir=/adobeair/i.test(g);s.isIDevice=/(iPad|iPhone)/.test(g);s.isIOS5=s.isIDevice&&g.match(/AppleWebKit\/(\d*)/)[1]>=534;if(e.tinyMCEPreInit){s.suffix=tinyMCEPreInit.suffix;s.baseURL=tinyMCEPreInit.base;s.query=tinyMCEPreInit.query;return}s.suffix="";f=q.getElementsByTagName("base");for(m=0;m0?b:[f.scope]);if(e===false){break}}a.inDispatch=false;return e}});(function(){var a=tinymce.each;tinymce.create("tinymce.util.URI",{URI:function(e,g){var f=this,i,d,c,h;e=tinymce.trim(e);g=f.settings=g||{};if(/^([\w\-]+):([^\/]{2})/i.test(e)||/^\s*#/.test(e)){f.source=e;return}if(e.indexOf("/")===0&&e.indexOf("//")!==0){e=(g.base_uri?g.base_uri.protocol||"http":"http")+"://mce_host"+e}if(!/^[\w\-]*:?\/\//.test(e)){h=g.base_uri?g.base_uri.path:new tinymce.util.URI(location.href).directory;e=((g.base_uri&&g.base_uri.protocol)||"http")+"://mce_host"+f.toAbsPath(h,e)}e=e.replace(/@@/g,"(mce_at)");e=/^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(e);a(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],function(b,j){var k=e[j];if(k){k=k.replace(/\(mce_at\)/g,"@@")}f[b]=k});c=g.base_uri;if(c){if(!f.protocol){f.protocol=c.protocol}if(!f.userInfo){f.userInfo=c.userInfo}if(!f.port&&f.host==="mce_host"){f.port=c.port}if(!f.host||f.host==="mce_host"){f.host=c.host}f.source=""}},setPath:function(c){var b=this;c=/^(.*?)\/?(\w+)?$/.exec(c);b.path=c[0];b.directory=c[1];b.file=c[2];b.source="";b.getURI()},toRelative:function(b){var d=this,f;if(b==="./"){return b}b=new tinymce.util.URI(b,{base_uri:d});if((b.host!="mce_host"&&d.host!=b.host&&b.host)||d.port!=b.port||d.protocol!=b.protocol){return b.getURI()}var c=d.getURI(),e=b.getURI();if(c==e||(c.charAt(c.length-1)=="/"&&c.substr(0,c.length-1)==e)){return c}f=d.toRelPath(d.path,b.path);if(b.query){f+="?"+b.query}if(b.anchor){f+="#"+b.anchor}return f},toAbsolute:function(b,c){b=new tinymce.util.URI(b,{base_uri:this});return b.getURI(this.host==b.host&&this.protocol==b.protocol?c:0)},toRelPath:function(g,h){var c,f=0,d="",e,b;g=g.substring(0,g.lastIndexOf("/"));g=g.split("/");c=h.split("/");if(g.length>=c.length){for(e=0,b=g.length;e=c.length||g[e]!=c[e]){f=e+1;break}}}if(g.length=g.length||g[e]!=c[e]){f=e+1;break}}}if(f===1){return h}for(e=0,b=g.length-(f-1);e=0;c--){if(f[c].length===0||f[c]==="."){continue}if(f[c]===".."){b++;continue}if(b>0){b--;continue}h.push(f[c])}c=e.length-b;if(c<=0){g=h.reverse().join("/")}else{g=e.slice(0,c).join("/")+"/"+h.reverse().join("/")}if(g.indexOf("/")!==0){g="/"+g}if(d&&g.lastIndexOf("/")!==g.length-1){g+=d}return g},getURI:function(d){var c,b=this;if(!b.source||d){c="";if(!d){if(b.protocol){c+=b.protocol+"://"}if(b.userInfo){c+=b.userInfo+"@"}if(b.host){c+=b.host}if(b.port){c+=":"+b.port}}if(b.path){c+=b.path}if(b.query){c+="?"+b.query}if(b.anchor){c+="#"+b.anchor}b.source=c}return b.source}})})();(function(){var a=tinymce.each;tinymce.create("static tinymce.util.Cookie",{getHash:function(d){var b=this.get(d),c;if(b){a(b.split("&"),function(e){e=e.split("=");c=c||{};c[unescape(e[0])]=unescape(e[1])})}return c},setHash:function(j,b,g,f,i,c){var h="";a(b,function(e,d){h+=(!h?"":"&")+escape(d)+"="+escape(e)});this.set(j,h,g,f,i,c)},get:function(i){var h=document.cookie,g,f=i+"=",d;if(!h){return}d=h.indexOf("; "+f);if(d==-1){d=h.indexOf(f);if(d!==0){return null}}else{d+=2}g=h.indexOf(";",d);if(g==-1){g=h.length}return unescape(h.substring(d+f.length,g))},set:function(i,b,g,f,h,c){document.cookie=i+"="+escape(b)+((g)?"; expires="+g.toGMTString():"")+((f)?"; path="+escape(f):"")+((h)?"; domain="+h:"")+((c)?"; secure":"")},remove:function(c,e,d){var b=new Date();b.setTime(b.getTime()-1000);this.set(c,"",b,e,d)}})})();(function(){function serialize(o,quote){var i,v,t,name;quote=quote||'"';if(o==null){return"null"}t=typeof o;if(t=="string"){v="\bb\tt\nn\ff\rr\"\"''\\\\";return quote+o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g,function(a,b){if(quote==='"'&&a==="'"){return a}i=v.indexOf(b);if(i+1){return"\\"+v.charAt(i+1)}a=b.charCodeAt().toString(16);return"\\u"+"0000".substring(a.length)+a})+quote}if(t=="object"){if(o.hasOwnProperty&&Object.prototype.toString.call(o)==="[object Array]"){for(i=0,v="[";i0?",":"")+serialize(o[i],quote)}return v+"]"}v="{";for(name in o){if(o.hasOwnProperty(name)){v+=typeof o[name]!="function"?(v.length>1?","+quote:quote)+name+quote+":"+serialize(o[name],quote):""}}return v+"}"}return""+o}tinymce.util.JSON={serialize:serialize,parse:function(s){try{return eval("("+s+")")}catch(ex){}}}})();tinymce.create("static tinymce.util.XHR",{send:function(g){var a,e,b=window,h=0;function f(){if(!g.async||a.readyState==4||h++>10000){if(g.success&&h<10000&&a.status==200){g.success.call(g.success_scope,""+a.responseText,a,g)}else{if(g.error){g.error.call(g.error_scope,h>10000?"TIMED_OUT":"GENERAL",a,g)}}a=null}else{b.setTimeout(f,10)}}g.scope=g.scope||this;g.success_scope=g.success_scope||g.scope;g.error_scope=g.error_scope||g.scope;g.async=g.async===false?false:true;g.data=g.data||"";function d(i){a=0;try{a=new ActiveXObject(i)}catch(c){}return a}a=b.XMLHttpRequest?new XMLHttpRequest():d("Microsoft.XMLHTTP")||d("Msxml2.XMLHTTP");if(a){if(a.overrideMimeType){a.overrideMimeType(g.content_type)}a.open(g.type||(g.data?"POST":"GET"),g.url,g.async);if(g.content_type){a.setRequestHeader("Content-Type",g.content_type)}a.setRequestHeader("X-Requested-With","XMLHttpRequest");a.send(g.data);if(!g.async){return f()}e=b.setTimeout(f,10)}}});(function(){var c=tinymce.extend,b=tinymce.util.JSON,a=tinymce.util.XHR;tinymce.create("tinymce.util.JSONRequest",{JSONRequest:function(d){this.settings=c({},d);this.count=0},send:function(f){var e=f.error,d=f.success;f=c(this.settings,f);f.success=function(h,g){h=b.parse(h);if(typeof(h)=="undefined"){h={error:"JSON Parse error."}}if(h.error){e.call(f.error_scope||f.scope,h.error,g)}else{d.call(f.success_scope||f.scope,h.result)}};f.error=function(h,g){if(e){e.call(f.error_scope||f.scope,h,g)}};f.data=b.serialize({id:f.id||"c"+(this.count++),method:f.method,params:f.params});f.content_type="application/json";a.send(f)},"static":{sendRPC:function(d){return new tinymce.util.JSONRequest().send(d)}}})}());(function(a){a.VK={BACKSPACE:8,DELETE:46,DOWN:40,ENTER:13,LEFT:37,RIGHT:39,SPACEBAR:32,TAB:9,UP:38,modifierPressed:function(b){return b.shiftKey||b.ctrlKey||b.altKey},metaKeyPressed:function(b){return a.isMac?b.metaKey:b.ctrlKey&&!b.altKey}}})(tinymce);tinymce.util.Quirks=function(e){var n=tinymce.VK,x=n.BACKSPACE,y=n.DELETE,q=e.dom,I=e.selection,v=e.settings,c=e.parser,u=e.serializer;function d(M,L){try{e.getDoc().execCommand(M,false,L)}catch(K){}}function C(){var K=e.getDoc().documentMode;return K?K:6}function H(K){return K.isDefaultPrevented()}function k(){function K(N){var L,P,M,O;L=I.getRng();P=q.getParent(L.startContainer,q.isBlock);if(N){P=q.getNext(P,q.isBlock)}if(P){M=P.firstChild;while(M&&M.nodeType==3&&M.nodeValue.length===0){M=M.nextSibling}if(M&&M.nodeName==="SPAN"){O=M.cloneNode(false)}}e.getDoc().execCommand(N?"ForwardDelete":"Delete",false,null);P=q.getParent(L.startContainer,q.isBlock);tinymce.each(q.select("span.Apple-style-span,font.Apple-style-span",P),function(Q){var R=I.getBookmark();if(O){q.replace(O.cloneNode(false),Q,true)}else{q.remove(Q,true)}I.moveToBookmark(R)})}e.onKeyDown.add(function(L,N){var M;M=N.keyCode==y;if(!H(N)&&(M||N.keyCode==x)&&!n.modifierPressed(N)){N.preventDefault();K(M)}});e.addCommand("Delete",function(){K()})}function J(){function K(N){var M=q.create("body");var O=N.cloneContents();M.appendChild(O);return I.serializer.serialize(M,{format:"html"})}function L(M){var O=K(M);var P=q.createRng();P.selectNode(e.getBody());var N=K(P);return O===N}e.onKeyDown.add(function(N,P){var O=P.keyCode,M;if(!H(P)&&(O==y||O==x)){M=N.selection.isCollapsed();if(M&&!q.isEmpty(N.getBody())){return}if(tinymce.isIE&&!M){return}if(!M&&!L(N.selection.getRng())){return}N.setContent("");N.selection.setCursorLocation(N.getBody(),0);N.nodeChanged()}})}function A(){e.onKeyDown.add(function(K,L){if(!H(L)&&L.keyCode==65&&n.metaKeyPressed(L)){L.preventDefault();K.execCommand("SelectAll")}})}function B(){if(!e.settings.content_editable){q.bind(e.getDoc(),"focusin",function(K){I.setRng(I.getRng())});q.bind(e.getDoc(),"mousedown",function(K){if(K.target==e.getDoc().documentElement){e.getWin().focus();I.setRng(I.getRng())}})}}function o(){e.onKeyDown.add(function(K,N){if(!H(N)&&N.keyCode===x){if(I.isCollapsed()&&I.getRng(true).startOffset===0){var M=I.getNode();var L=M.previousSibling;if(L&&L.nodeName&&L.nodeName.toLowerCase()==="hr"){q.remove(L);tinymce.dom.Event.cancel(N)}}}})}function b(){if(!Range.prototype.getClientRects){e.onMouseDown.add(function(L,M){if(!H(M)&&M.target.nodeName==="HTML"){var K=L.getBody();K.blur();setTimeout(function(){K.focus()},0)}})}}function F(){e.onClick.add(function(K,L){L=L.target;if(/^(IMG|HR)$/.test(L.nodeName)){I.getSel().setBaseAndExtent(L,0,L,1)}if(L.nodeName=="A"&&q.hasClass(L,"mceItemAnchor")){I.select(L)}K.nodeChanged()})}function E(){function L(){var N=q.getAttribs(I.getStart().cloneNode(false));return function(){var O=I.getStart();if(O!==e.getBody()){q.setAttrib(O,"style",null);tinymce.each(N,function(P){O.setAttributeNode(P.cloneNode(true))})}}}function K(){return !I.isCollapsed()&&q.getParent(I.getStart(),q.isBlock)!=q.getParent(I.getEnd(),q.isBlock)}function M(N,O){O.preventDefault();return false}e.onKeyPress.add(function(N,P){var O;if(!H(P)&&(P.keyCode==8||P.keyCode==46)&&K()){O=L();N.getDoc().execCommand("delete",false,null);O();P.preventDefault();return false}});q.bind(e.getDoc(),"cut",function(O){var N;if(!H(O)&&K()){N=L();e.onKeyUp.addToTop(M);setTimeout(function(){N();e.onKeyUp.remove(M)},0)}})}function l(){var L,K;q.bind(e.getDoc(),"selectionchange",function(){if(K){clearTimeout(K);K=0}K=window.setTimeout(function(){var M=I.getRng();if(!L||!tinymce.dom.RangeUtils.compareRanges(M,L)){e.nodeChanged();L=M}},50)})}function G(){document.body.setAttribute("role","application")}function D(){e.onKeyDown.add(function(K,M){if(!H(M)&&M.keyCode===x){if(I.isCollapsed()&&I.getRng(true).startOffset===0){var L=I.getNode().previousSibling;if(L&&L.nodeName&&L.nodeName.toLowerCase()==="table"){return tinymce.dom.Event.cancel(M)}}}})}function i(){if(C()>7){return}d("RespectVisibilityInDesign",true);e.contentStyles.push(".mceHideBrInPre pre br {display: none}");q.addClass(e.getBody(),"mceHideBrInPre");c.addNodeFilter("pre",function(K,M){var N=K.length,P,L,Q,O;while(N--){P=K[N].getAll("br");L=P.length;while(L--){Q=P[L];O=Q.prev;if(O&&O.type===3&&O.value.charAt(O.value-1)!="\n"){O.value+="\n"}else{Q.parent.insert(new tinymce.html.Node("#text",3),Q,true).value="\n"}}}});u.addNodeFilter("pre",function(K,M){var N=K.length,P,L,Q,O;while(N--){P=K[N].getAll("br");L=P.length;while(L--){Q=P[L];O=Q.prev;if(O&&O.type==3){O.value=O.value.replace(/\r?\n$/,"")}}}})}function g(){q.bind(e.getBody(),"mouseup",function(M){var L,K=I.getNode();if(K.nodeName=="IMG"){if(L=q.getStyle(K,"width")){q.setAttrib(K,"width",L.replace(/[^0-9%]+/g,""));q.setStyle(K,"width","")}if(L=q.getStyle(K,"height")){q.setAttrib(K,"height",L.replace(/[^0-9%]+/g,""));q.setStyle(K,"height","")}}})}function s(){e.onKeyDown.add(function(Q,R){var P,K,L,N,O,S,M;P=R.keyCode==y;if(!H(R)&&(P||R.keyCode==x)&&!n.modifierPressed(R)){K=I.getRng();L=K.startContainer;N=K.startOffset;M=K.collapsed;if(L.nodeType==3&&L.nodeValue.length>0&&((N===0&&!M)||(M&&N===(P?0:1)))){nonEmptyElements=Q.schema.getNonEmptyElements();R.preventDefault();O=q.create("br",{id:"__tmp"});L.parentNode.insertBefore(O,L);Q.getDoc().execCommand(P?"ForwardDelete":"Delete",false,null);L=I.getRng().startContainer;S=L.previousSibling;if(S&&S.nodeType==1&&!q.isBlock(S)&&q.isEmpty(S)&&!nonEmptyElements[S.nodeName.toLowerCase()]){q.remove(S)}q.remove("__tmp")}}})}function f(){e.onKeyDown.add(function(O,P){var M,L,Q,K,N;if(H(P)||P.keyCode!=n.BACKSPACE){return}M=I.getRng();L=M.startContainer;Q=M.startOffset;K=q.getRoot();N=L;if(!M.collapsed||Q!==0){return}while(N&&N.parentNode&&N.parentNode.firstChild==N&&N.parentNode!=K){N=N.parentNode}if(N.tagName==="BLOCKQUOTE"){O.formatter.toggle("blockquote",null,N);M=q.createRng();M.setStart(L,0);M.setEnd(L,0);I.setRng(M)}})}function m(){function K(){e._refreshContentEditable();d("StyleWithCSS",false);d("enableInlineTableEditing",false);if(!v.object_resizing){d("enableObjectResizing",false)}}if(!v.readonly){e.onBeforeExecCommand.add(K);e.onMouseDown.add(K)}}function p(){function K(L,M){tinymce.each(q.select("a"),function(P){var N=P.parentNode,O=q.getRoot();if(N.lastChild===P){while(N&&!q.isBlock(N)){if(N.parentNode.lastChild!==N||N===O){return}N=N.parentNode}q.add(N,"br",{"data-mce-bogus":1})}})}e.onExecCommand.add(function(L,M){if(M==="CreateLink"){K(L)}});e.onSetContent.add(I.onSetContent.add(K))}function z(){if(v.forced_root_block){e.onInit.add(function(){d("DefaultParagraphSeparator",v.forced_root_block)})}}function a(){function K(M,L){if(!M||!L.initial){e.execCommand("mceRepaint")}}e.onUndo.add(K);e.onRedo.add(K);e.onSetContent.add(K)}function r(){e.onKeyDown.add(function(L,M){var K;if(!H(M)&&M.keyCode==x){K=L.getDoc().selection.createRange();if(K&&K.item){M.preventDefault();L.undoManager.beforeChange();q.remove(K.item(0));L.undoManager.add()}}})}function j(){var K;if(C()>=10){K="";tinymce.each("p div h1 h2 h3 h4 h5 h6".split(" "),function(L,M){K+=(M>0?",":"")+L+":empty"});e.contentStyles.push(K+"{padding-right: 1px !important}")}}function h(){var M,L,ac,K,X,aa,Y,ab,N,O,Z,V,U,W=document,S=e.getDoc();if(!v.object_resizing||v.webkit_fake_resize===false){return}d("enableObjectResizing",false);Z={n:[0.5,0,0,-1],e:[1,0.5,1,0],s:[0.5,1,0,1],w:[0,0.5,-1,0],nw:[0,0,-1,-1],ne:[1,0,1,-1],se:[1,1,1,1],sw:[0,1,-1,1]};function Q(ag){var af,ae;af=ag.screenX-aa;ae=ag.screenY-Y;V=af*X[2]+ab;U=ae*X[3]+N;V=V<5?5:V;U=U<5?5:U;if(n.modifierPressed(ag)||(ac.nodeName=="IMG"&&X[2]*X[3]!==0)){V=Math.round(U/O);U=Math.round(V*O)}q.setStyles(K,{width:V,height:U});if(X[2]<0&&K.clientWidth<=V){q.setStyle(K,"left",M+(ab-V))}if(X[3]<0&&K.clientHeight<=U){q.setStyle(K,"top",L+(N-U))}}function ad(){function ae(af,ag){if(ag){if(ac.style[af]||!e.schema.isValid(ac.nodeName.toLowerCase(),af)){q.setStyle(ac,af,ag)}else{q.setAttrib(ac,af,ag)}}}ae("width",V);ae("height",U);q.unbind(S,"mousemove",Q);q.unbind(S,"mouseup",ad);if(W!=S){q.unbind(W,"mousemove",Q);q.unbind(W,"mouseup",ad)}q.remove(K);P(ac)}function P(ah){var af,ag,ae;R();af=q.getPos(ah);M=af.x;L=af.y;ag=ah.offsetWidth;ae=ah.offsetHeight;if(ac!=ah){ac=ah;V=U=0}tinymce.each(Z,function(ak,ai){var aj;aj=q.get("mceResizeHandle"+ai);if(!aj){aj=q.add(S.documentElement,"div",{id:"mceResizeHandle"+ai,"class":"mceResizeHandle",style:"cursor:"+ai+"-resize; margin:0; padding:0"});q.bind(aj,"mousedown",function(al){al.preventDefault();ad();aa=al.screenX;Y=al.screenY;ab=ac.clientWidth;N=ac.clientHeight;O=N/ab;X=ak;K=ac.cloneNode(true);q.addClass(K,"mceClonedResizable");q.setStyles(K,{left:M,top:L,margin:0});S.documentElement.appendChild(K);q.bind(S,"mousemove",Q);q.bind(S,"mouseup",ad);if(W!=S){q.bind(W,"mousemove",Q);q.bind(W,"mouseup",ad)}})}else{q.show(aj)}q.setStyles(aj,{left:(ag*ak[0]+M)-(aj.offsetWidth/2),top:(ae*ak[1]+L)-(aj.offsetHeight/2)})});if(!tinymce.isOpera&&ac.nodeName=="IMG"){ac.setAttribute("data-mce-selected","1")}}function R(){if(ac){ac.removeAttribute("data-mce-selected")}for(var ae in Z){q.hide("mceResizeHandle"+ae)}}e.contentStyles.push(".mceResizeHandle {position: absolute;border: 1px solid black;background: #FFF;width: 5px;height: 5px;z-index: 10000}.mceResizeHandle:hover {background: #000}img[data-mce-selected] {outline: 1px solid black}img.mceClonedResizable, table.mceClonedResizable {position: absolute;outline: 1px dashed black;opacity: .5;z-index: 10000}");function T(){var ae=q.getParent(I.getNode(),"table,img");tinymce.each(q.select("img[data-mce-selected]"),function(af){af.removeAttribute("data-mce-selected")});if(ae){P(ae)}else{R()}}e.onNodeChange.add(T);q.bind(S,"selectionchange",T);e.serializer.addAttributeFilter("data-mce-selected",function(ae,af){var ag=ae.length;while(ag--){ae[ag].attr(af,null)}})}function t(){if(C()<9){c.addNodeFilter("noscript",function(K){var L=K.length,M,N;while(L--){M=K[L];N=M.firstChild;if(N){M.attr("data-mce-innertext",N.value)}}});u.addNodeFilter("noscript",function(K){var L=K.length,M,O,N;while(L--){M=K[L];O=K[L].firstChild;if(O){O.value=tinymce.html.Entities.decode(O.value)}else{N=M.attributes.map["data-mce-innertext"];if(N){M.attr("data-mce-innertext",null);O=new tinymce.html.Node("#text",3);O.value=N;O.raw=true;M.append(O)}}}})}}D();f();J();if(tinymce.isWebKit){s();k();B();F();z();if(tinymce.isIDevice){l()}else{h();A()}}if(tinymce.isIE){o();G();i();g();r();j();t()}if(tinymce.isGecko){o();b();E();m();p();a()}if(tinymce.isOpera){h()}};(function(j){var a,g,d,k=/[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,b=/[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,f=/[<>&\"\']/g,c=/&(#x|#)?([\w]+);/g,i={128:"\u20AC",130:"\u201A",131:"\u0192",132:"\u201E",133:"\u2026",134:"\u2020",135:"\u2021",136:"\u02C6",137:"\u2030",138:"\u0160",139:"\u2039",140:"\u0152",142:"\u017D",145:"\u2018",146:"\u2019",147:"\u201C",148:"\u201D",149:"\u2022",150:"\u2013",151:"\u2014",152:"\u02DC",153:"\u2122",154:"\u0161",155:"\u203A",156:"\u0153",158:"\u017E",159:"\u0178"};g={'"':""","'":"'","<":"<",">":">","&":"&"};d={"<":"<",">":">","&":"&",""":'"',"'":"'"};function h(l){var m;m=document.createElement("div");m.innerHTML=l;return m.textContent||m.innerText||l}function e(m,p){var n,o,l,q={};if(m){m=m.split(",");p=p||10;for(n=0;n1){return"&#"+(((n.charCodeAt(0)-55296)*1024)+(n.charCodeAt(1)-56320)+65536)+";"}return g[n]||"&#"+n.charCodeAt(0)+";"})},encodeNamed:function(n,l,m){m=m||a;return n.replace(l?k:b,function(o){return g[o]||m[o]||o})},getEncodeFunc:function(l,o){var p=j.html.Entities;o=e(o)||a;function m(r,q){return r.replace(q?k:b,function(s){return g[s]||o[s]||"&#"+s.charCodeAt(0)+";"||s})}function n(r,q){return p.encodeNamed(r,q,o)}l=j.makeMap(l.replace(/\+/g,","));if(l.named&&l.numeric){return m}if(l.named){if(o){return n}return p.encodeNamed}if(l.numeric){return p.encodeNumeric}return p.encodeRaw},decode:function(l){return l.replace(c,function(n,m,o){if(m){o=parseInt(o,m.length===2?16:10);if(o>65535){o-=65536;return String.fromCharCode(55296+(o>>10),56320+(o&1023))}else{return i[o]||String.fromCharCode(o)}}return d[n]||a[n]||h(n)})}}})(tinymce);tinymce.html.Styles=function(d,f){var k=/rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,h=/(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,b=/\s*([^:]+):\s*([^;]+);?/g,l=/\s+$/,m=/rgb/,e,g,a={},j;d=d||{};j="\\\" \\' \\; \\: ; : \uFEFF".split(" ");for(g=0;g1?r:"0"+r}return"#"+o(q)+o(p)+o(i)}return{toHex:function(i){return i.replace(k,c)},parse:function(s){var z={},q,n,x,r,v=d.url_converter,y=d.url_converter_scope||this;function p(D,G){var F,C,B,E;F=z[D+"-top"+G];if(!F){return}C=z[D+"-right"+G];if(F!=C){return}B=z[D+"-bottom"+G];if(C!=B){return}E=z[D+"-left"+G];if(B!=E){return}z[D+G]=E;delete z[D+"-top"+G];delete z[D+"-right"+G];delete z[D+"-bottom"+G];delete z[D+"-left"+G]}function u(C){var D=z[C],B;if(!D||D.indexOf(" ")<0){return}D=D.split(" ");B=D.length;while(B--){if(D[B]!==D[0]){return false}}z[C]=D[0];return true}function A(D,C,B,E){if(!u(C)){return}if(!u(B)){return}if(!u(E)){return}z[D]=z[C]+" "+z[B]+" "+z[E];delete z[C];delete z[B];delete z[E]}function t(B){r=true;return a[B]}function i(C,B){if(r){C=C.replace(/\uFEFF[0-9]/g,function(D){return a[D]})}if(!B){C=C.replace(/\\([\'\";:])/g,"$1")}return C}function o(C,B,F,E,G,D){G=G||D;if(G){G=i(G);return"'"+G.replace(/\'/g,"\\'")+"'"}B=i(B||F||E);if(v){B=v.call(y,B,"style")}return"url('"+B.replace(/\'/g,"\\'")+"')"}if(s){s=s.replace(/\\[\"\';:\uFEFF]/g,t).replace(/\"[^\"]+\"|\'[^\']+\'/g,function(B){return B.replace(/[;:]/g,t)});while(q=b.exec(s)){n=q[1].replace(l,"").toLowerCase();x=q[2].replace(l,"");if(n&&x.length>0){if(n==="font-weight"&&x==="700"){x="bold"}else{if(n==="color"||n==="background-color"){x=x.toLowerCase()}}x=x.replace(k,c);x=x.replace(h,o);z[n]=r?i(x,true):x}b.lastIndex=q.index+q[0].length}p("border","");p("border","-width");p("border","-color");p("border","-style");p("padding","");p("margin","");A("border","border-width","border-style","border-color");if(z.border==="medium none"){delete z.border}}return z},serialize:function(p,r){var o="",n,q;function i(t){var x,u,s,v;x=f.styles[t];if(x){for(u=0,s=x.length;u0){o+=(o.length>0?" ":"")+t+": "+v+";"}}}}if(r&&f&&f.styles){i("*");i(r)}else{for(n in p){q=p[n];if(q!==e&&q.length>0){o+=(o.length>0?" ":"")+n+": "+q+";"}}}return o}}};(function(f){var a={},e=f.makeMap,g=f.each;function d(j,i){return j.split(i||",")}function h(m,l){var j,k={};function i(n){return n.replace(/[A-Z]+/g,function(o){return i(m[o])})}for(j in m){if(m.hasOwnProperty(j)){m[j]=i(m[j])}}i(l).replace(/#/g,"#text").replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g,function(q,o,n,p){n=d(n,"|");k[o]={attributes:e(n),attributesOrder:n,children:e(p,"|",{"#comment":{}})}});return k}function b(){var i=a.html5;if(!i){i=a.html5=h({A:"id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup",B:"#|a|abbr|area|audio|b|bdo|br|button|canvas|cite|code|command|datalist|del|dfn|em|embed|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|meta|meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video|wbr",C:"#|a|abbr|area|address|article|aside|audio|b|bdo|blockquote|br|button|canvas|cite|code|command|datalist|del|details|dfn|dialog|div|dl|em|embed|fieldset|figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|menu|meta|meter|nav|noscript|ol|object|output|p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video"},"html[A|manifest][body|head]head[A][base|command|link|meta|noscript|script|style|title]title[A][#]base[A|href|target][]link[A|href|rel|media|type|sizes][]meta[A|http-equiv|name|content|charset][]style[A|type|media|scoped][#]script[A|charset|type|src|defer|async][#]noscript[A][C]body[A][C]section[A][C]nav[A][C]article[A][C]aside[A][C]h1[A][B]h2[A][B]h3[A][B]h4[A][B]h5[A][B]h6[A][B]hgroup[A][h1|h2|h3|h4|h5|h6]header[A][C]footer[A][C]address[A][C]p[A][B]br[A][]pre[A][B]dialog[A][dd|dt]blockquote[A|cite][C]ol[A|start|reversed][li]ul[A][li]li[A|value][C]dl[A][dd|dt]dt[A][B]dd[A][C]a[A|href|target|ping|rel|media|type][B]em[A][B]strong[A][B]small[A][B]cite[A][B]q[A|cite][B]dfn[A][B]abbr[A][B]code[A][B]var[A][B]samp[A][B]kbd[A][B]sub[A][B]sup[A][B]i[A][B]b[A][B]mark[A][B]progress[A|value|max][B]meter[A|value|min|max|low|high|optimum][B]time[A|datetime][B]ruby[A][B|rt|rp]rt[A][B]rp[A][B]bdo[A][B]span[A][B]ins[A|cite|datetime][B]del[A|cite|datetime][B]figure[A][C|legend|figcaption]figcaption[A][C]img[A|alt|src|height|width|usemap|ismap][]iframe[A|name|src|height|width|sandbox|seamless][]embed[A|src|height|width|type][]object[A|data|type|height|width|usemap|name|form|classid][param]param[A|name|value][]details[A|open][C|legend]command[A|type|label|icon|disabled|checked|radiogroup][]menu[A|type|label][C|li]legend[A][C|B]div[A][C]source[A|src|type|media][]audio[A|src|autobuffer|autoplay|loop|controls][source]video[A|src|autobuffer|autoplay|loop|controls|width|height|poster][source]hr[A][]form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]fieldset[A|disabled|form|name][C|legend]label[A|form|for][B]input[A|type|accept|alt|autocomplete|autofocus|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value|name][]button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]datalist[A][B|option]optgroup[A|disabled|label][option]option[A|disabled|selected|label|value][]textarea[A|autofocus|disabled|form|maxlength|name|placeholder|readonly|required|rows|cols|wrap][]keygen[A|autofocus|challenge|disabled|form|keytype|name][]output[A|for|form|name][B]canvas[A|width|height][]map[A|name][B|C]area[A|shape|coords|href|alt|target|media|rel|ping|type][]mathml[A][]svg[A][]table[A|border][caption|colgroup|thead|tfoot|tbody|tr]caption[A][C]colgroup[A|span][col]col[A|span][]thead[A][tr]tfoot[A][tr]tbody[A][tr]tr[A][th|td]th[A|headers|rowspan|colspan|scope][B]td[A|headers|rowspan|colspan][C]wbr[A][]")}return i}function c(){var i=a.html4;if(!i){i=a.html4=h({Z:"H|K|N|O|P",Y:"X|form|R|Q",ZG:"E|span|width|align|char|charoff|valign",X:"p|T|div|U|W|isindex|fieldset|table",ZF:"E|align|char|charoff|valign",W:"pre|hr|blockquote|address|center|noframes",ZE:"abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height",ZD:"[E][S]",U:"ul|ol|dl|menu|dir",ZC:"p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q",T:"h1|h2|h3|h4|h5|h6",ZB:"X|S|Q",S:"R|P",ZA:"a|G|J|M|O|P",R:"a|H|K|N|O",Q:"noscript|P",P:"ins|del|script",O:"input|select|textarea|label|button",N:"M|L",M:"em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym",L:"sub|sup",K:"J|I",J:"tt|i|b|u|s|strike",I:"big|small|font|basefont",H:"G|F",G:"br|span|bdo",F:"object|applet|img|map|iframe",E:"A|B|C",D:"accesskey|tabindex|onfocus|onblur",C:"onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup",B:"lang|xml:lang|dir",A:"id|class|style|title"},"script[id|charset|type|language|src|defer|xml:space][]style[B|id|type|media|title|xml:space][]object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]param[id|name|value|valuetype|type][]p[E|align][#|S]a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]br[A|clear][]span[E][#|S]bdo[A|C|B][#|S]applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]h1[E|align][#|S]img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]map[B|C|A|name][X|form|Q|area]h2[E|align][#|S]iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]h3[E|align][#|S]tt[E][#|S]i[E][#|S]b[E][#|S]u[E][#|S]s[E][#|S]strike[E][#|S]big[E][#|S]small[E][#|S]font[A|B|size|color|face][#|S]basefont[id|size|color|face][]em[E][#|S]strong[E][#|S]dfn[E][#|S]code[E][#|S]q[E|cite][#|S]samp[E][#|S]kbd[E][#|S]var[E][#|S]cite[E][#|S]abbr[E][#|S]acronym[E][#|S]sub[E][#|S]sup[E][#|S]input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]optgroup[E|disabled|label][option]option[E|selected|disabled|label|value][]textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]label[E|for|accesskey|onfocus|onblur][#|S]button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]h4[E|align][#|S]ins[E|cite|datetime][#|Y]h5[E|align][#|S]del[E|cite|datetime][#|Y]h6[E|align][#|S]div[E|align][#|Y]ul[E|type|compact][li]li[E|type|value][#|Y]ol[E|type|compact|start][li]dl[E|compact][dt|dd]dt[E][#|S]dd[E][#|Y]menu[E|compact][li]dir[E|compact][li]pre[E|width|xml:space][#|ZA]hr[E|align|noshade|size|width][]blockquote[E|cite][#|Y]address[E][#|S|p]center[E][#|Y]noframes[E][#|Y]isindex[A|B|prompt][]fieldset[E][#|legend|Y]legend[E|accesskey|align][#|S]table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]caption[E|align][#|S]col[ZG][]colgroup[ZG][col]thead[ZF][tr]tr[ZF|bgcolor][th|td]th[E|ZE][#|Y]form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]noscript[E][#|Y]td[E|ZE][#|Y]tfoot[ZF][tr]tbody[ZF][tr]area[E|D|shape|coords|href|nohref|alt|target][]base[id|href|target][]body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]")}return i}f.html.Schema=function(A){var u=this,s={},k={},j=[],D,y;var o,q,z,r,v,n,p={};function m(F,E,H){var G=A[F];if(!G){G=a[F];if(!G){G=e(E," ",e(E.toUpperCase()," "));G=f.extend(G,H);a[F]=G}}else{G=e(G,",",e(G.toUpperCase()," "))}return G}A=A||{};y=A.schema=="html5"?b():c();if(A.verify_html===false){A.valid_elements="*[*]"}if(A.valid_styles){D={};g(A.valid_styles,function(F,E){D[E]=f.explode(F)})}o=m("whitespace_elements","pre script noscript style textarea");q=m("self_closing_elements","colgroup dd dt li option p td tfoot th thead tr");z=m("short_ended_elements","area base basefont br col frame hr img input isindex link meta param embed source wbr");r=m("boolean_attributes","checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls");n=m("non_empty_elements","td th iframe video audio object",z);textBlockElementsMap=m("text_block_elements","h1 h2 h3 h4 h5 h6 p div address pre form blockquote center dir fieldset header footer article section hgroup aside nav figure");v=m("block_elements","hr table tbody thead tfoot th tr td li ol ul caption dl dt dd noscript menu isindex samp option datalist select optgroup",textBlockElementsMap);function i(E){return new RegExp("^"+E.replace(/([?+*])/g,".$1")+"$")}function C(L){var K,G,Z,V,aa,F,I,U,X,Q,Y,ac,O,J,W,E,S,H,ab,ad,P,T,N=/^([#+\-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,R=/^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,M=/[*?+]/;if(L){L=d(L);if(s["@"]){S=s["@"].attributes;H=s["@"].attributesOrder}for(K=0,G=L.length;K=0){for(U=A.length-1;U>=V;U--){T=A[U];if(T.valid){n.end(T.name)}}A.length=V}}function p(U,T,Y,X,W){var Z,V;T=T.toLowerCase();Y=T in H?T:j(Y||X||W||"");if(v&&!z&&T.indexOf("data-mce-")!==0){Z=P[T];if(!Z&&F){V=F.length;while(V--){Z=F[V];if(Z.pattern.test(T)){break}}if(V===-1){Z=null}}if(!Z){return}if(Z.validValues&&!(Y in Z.validValues)){return}}N.map[T]=Y;N.push({name:T,value:Y})}l=new RegExp("<(?:(?:!--([\\w\\W]*?)-->)|(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|(?:!DOCTYPE([\\w\\W]*?)>)|(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|(?:\\/([^>]+)>)|(?:([A-Za-z0-9\\-\\:\\.]+)((?:\\s+[^\"'>]+(?:(?:\"[^\"]*\")|(?:'[^']*')|[^>]*))*|\\/|\\s+)>))","g");D=/([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:[^\"])*)\")|(?:\'((?:[^\'])*)\')|([^>\s]+)))?/g;K={script:/<\/script[^>]*>/gi,style:/<\/style[^>]*>/gi,noscript:/<\/noscript[^>]*>/gi};M=e.getShortEndedElements();J=c.self_closing_elements||e.getSelfClosingElements();H=e.getBoolAttrs();v=c.validate;s=c.remove_internals;y=c.fix_self_closing;q=a.isIE;o=/^:/;while(g=l.exec(E)){if(G0&&A[A.length-1].name===I){u(I)}if(!v||(m=e.getElementRule(I))){k=true;if(v){P=m.attributes;F=m.attributePatterns}if(R=g[8]){z=R.indexOf("data-mce-type")!==-1;if(z&&s){k=false}N=[];N.map={};R.replace(D,p)}else{N=[];N.map={}}if(v&&!z){S=m.attributesRequired;L=m.attributesDefault;f=m.attributesForced;if(f){Q=f.length;while(Q--){t=f[Q];r=t.name;h=t.value;if(h==="{$uid}"){h="mce_"+x++}N.map[r]=h;N.push({name:r,value:h})}}if(L){Q=L.length;while(Q--){t=L[Q];r=t.name;if(!(r in N.map)){h=t.value;if(h==="{$uid}"){h="mce_"+x++}N.map[r]=h;N.push({name:r,value:h})}}}if(S){Q=S.length;while(Q--){if(S[Q] in N.map){break}}if(Q===-1){k=false}}if(N.map["data-mce-bogus"]){k=false}}if(k){n.start(I,N,O)}}else{k=false}if(B=K[I]){B.lastIndex=G=g.index+g[0].length;if(g=B.exec(E)){if(k){C=E.substr(G,g.index-G)}G=g.index+g[0].length}else{C=E.substr(G);G=E.length}if(k&&C.length>0){n.text(C,true)}if(k){n.end(I)}l.lastIndex=G;continue}if(!O){if(!R||R.indexOf("/")!=R.length-1){A.push({name:I,valid:k})}else{if(k){n.end(I)}}}}else{if(I=g[1]){n.comment(I)}else{if(I=g[2]){n.cdata(I)}else{if(I=g[3]){n.doctype(I)}else{if(I=g[4]){n.pi(I,g[5])}}}}}}G=g.index+g[0].length}if(G=0;Q--){I=A[Q];if(I.valid){n.end(I.name)}}}}})(tinymce);(function(d){var c=/^[ \t\r\n]*$/,e={"#text":3,"#comment":8,"#cdata":4,"#pi":7,"#doctype":10,"#document-fragment":11};function a(k,l,j){var i,h,f=j?"lastChild":"firstChild",g=j?"prev":"next";if(k[f]){return k[f]}if(k!==l){i=k[g];if(i){return i}for(h=k.parent;h&&h!==l;h=h.parent){i=h[g];if(i){return i}}}}function b(f,g){this.name=f;this.type=g;if(g===1){this.attributes=[];this.attributes.map={}}}d.extend(b.prototype,{replace:function(g){var f=this;if(g.parent){g.remove()}f.insert(g,f);f.remove();return f},attr:function(h,l){var f=this,g,j,k;if(typeof h!=="string"){for(j in h){f.attr(j,h[j])}return f}if(g=f.attributes){if(l!==k){if(l===null){if(h in g.map){delete g.map[h];j=g.length;while(j--){if(g[j].name===h){g=g.splice(j,1);return f}}}return f}if(h in g.map){j=g.length;while(j--){if(g[j].name===h){g[j].value=l;break}}}else{g.push({name:h,value:l})}g.map[h]=l;return f}else{return g.map[h]}}},clone:function(){var g=this,n=new b(g.name,g.type),h,f,m,j,k;if(m=g.attributes){k=[];k.map={};for(h=0,f=m.length;h1){x.reverse();A=o=f.filterNode(x[0].clone());for(u=0;u0){Q.value=l;Q=Q.prev}else{O=Q.prev;Q.remove();Q=O}}}function H(O){var P,l={};for(P in O){if(P!=="li"&&P!="p"){l[P]=O[P]}}return l}n=new b.html.SaxParser({validate:z,self_closing_elements:H(h.getSelfClosingElements()),cdata:function(l){B.append(K("#cdata",4)).value=l},text:function(P,l){var O;if(!L){P=P.replace(k," ");if(B.lastChild&&o[B.lastChild.name]){P=P.replace(E,"")}}if(P.length!==0){O=K("#text",3);O.raw=!!l;B.append(O).value=P}},comment:function(l){B.append(K("#comment",8)).value=l},pi:function(l,O){B.append(K(l,7)).value=O;I(B)},doctype:function(O){var l;l=B.append(K("#doctype",10));l.value=O;I(B)},start:function(l,W,P){var U,R,Q,O,S,X,V,T;Q=z?h.getElementRule(l):{};if(Q){U=K(Q.outputName||l,1);U.attributes=W;U.shortEnded=P;B.append(U);T=p[B.name];if(T&&p[U.name]&&!T[U.name]){M.push(U)}R=d.length;while(R--){S=d[R].name;if(S in W.map){F=c[S];if(F){F.push(U)}else{c[S]=[U]}}}if(o[l]){I(U)}if(!P){B=U}if(!L&&s[l]){L=true}}},end:function(l){var S,P,R,O,Q;P=z?h.getElementRule(l):{};if(P){if(o[l]){if(!L){S=B.firstChild;if(S&&S.type===3){R=S.value.replace(E,"");if(R.length>0){S.value=R;S=S.next}else{O=S.next;S.remove();S=O}while(S&&S.type===3){R=S.value;O=S.next;if(R.length===0||y.test(R)){S.remove();S=O}S=O}}S=B.lastChild;if(S&&S.type===3){R=S.value.replace(t,"");if(R.length>0){S.value=R;S=S.prev}else{O=S.prev;S.remove();S=O}while(S&&S.type===3){R=S.value;O=S.prev;if(R.length===0||y.test(R)){S.remove();S=O}S=O}}}}if(L&&s[l]){L=false}if(P.removeEmpty||P.paddEmpty){if(B.isEmpty(u)){if(P.paddEmpty){B.empty().append(new a("#text","3")).value="\u00a0"}else{if(!B.attributes.map.name&&!B.attributes.map.id){Q=B.parent;B.empty().remove();B=Q;return}}}}B=B.parent}}},h);J=B=new a(m.context||g.root_name,11);n.parse(v);if(z&&M.length){if(!m.context){j(M)}else{m.invalid=true}}if(q&&J.name=="body"){G()}if(!m.invalid){for(N in i){F=e[N];A=i[N];x=A.length;while(x--){if(!A[x].parent){A.splice(x,1)}}for(D=0,C=F.length;D0){o=c[c.length-1];if(o.length>0&&o!=="\n"){c.push("\n")}}c.push("<",m);if(k){for(n=0,j=k.length;n0){o=c[c.length-1];if(o.length>0&&o!=="\n"){c.push("\n")}}},end:function(h){var i;c.push("");if(a&&d[h]&&c.length>0){i=c[c.length-1];if(i.length>0&&i!=="\n"){c.push("\n")}}},text:function(i,h){if(i.length>0){c[c.length]=h?i:f(i)}},cdata:function(h){c.push("")},comment:function(h){c.push("")},pi:function(h,i){if(i){c.push("")}else{c.push("")}if(a){c.push("\n")}},doctype:function(h){c.push("",a?"\n":"")},reset:function(){c.length=0},getContent:function(){return c.join("").replace(/\n$/,"")}}};(function(a){a.html.Serializer=function(c,d){var b=this,e=new a.html.Writer(c);c=c||{};c.validate="validate" in c?c.validate:true;b.schema=d=d||new a.html.Schema();b.writer=e;b.serialize=function(h){var g,i;i=c.validate;g={3:function(k,j){e.text(k.value,k.raw)},8:function(j){e.comment(j.value)},7:function(j){e.pi(j.name,j.value)},10:function(j){e.doctype(j.value)},4:function(j){e.cdata(j.value)},11:function(j){if((j=j.firstChild)){do{f(j)}while(j=j.next)}}};e.reset();function f(k){var t=g[k.type],j,o,s,r,p,u,n,m,q;if(!t){j=k.name;o=k.shortEnded;s=k.attributes;if(i&&s&&s.length>1){u=[];u.map={};q=d.getElementRule(k.name);for(n=0,m=q.attributesOrder.length;n=8;k.boxModel=!e.isIE||o.compatMode=="CSS1Compat"||k.stdMode;k.hasOuterHTML="outerHTML" in o.createElement("a");k.settings=l=e.extend({keep_values:false,hex_colors:1},l);k.schema=l.schema;k.styles=new e.html.Styles({url_converter:l.url_converter,url_converter_scope:l.url_converter_scope},l.schema);if(e.isIE6){try{o.execCommand("BackgroundImageCache",false,true)}catch(m){k.cssFlicker=true}}k.fixDoc(o);k.events=l.ownEvents?new e.dom.EventUtils(l.proxy):e.dom.Event;e.addUnload(k.destroy,k);n=l.schema?l.schema.getBlockElements():{};k.isBlock=function(q){var p=q.nodeType;if(p){return !!(p===1&&n[q.nodeName])}return !!n[q]}},fixDoc:function(k){var j=this.settings,i;if(b&&j.schema){("abbr article aside audio canvas details figcaption figure footer header hgroup mark menu meter nav output progress section summary time video").replace(/\w+/g,function(l){k.createElement(l)});for(i in j.schema.getCustomElements()){k.createElement(i)}}},clone:function(k,i){var j=this,m,l;if(!b||k.nodeType!==1||i){return k.cloneNode(i)}l=j.doc;if(!i){m=l.createElement(k.nodeName);g(j.getAttribs(k),function(n){j.setAttrib(m,n.nodeName,j.getAttrib(k,n.nodeName))});return m}return m.firstChild},getRoot:function(){var i=this,j=i.settings;return(j&&i.get(j.root_element))||i.doc.body},getViewPort:function(j){var k,i;j=!j?this.win:j;k=j.document;i=this.boxModel?k.documentElement:k.body;return{x:j.pageXOffset||i.scrollLeft,y:j.pageYOffset||i.scrollTop,w:j.innerWidth||i.clientWidth,h:j.innerHeight||i.clientHeight}},getRect:function(l){var k,i=this,j;l=i.get(l);k=i.getPos(l);j=i.getSize(l);return{x:k.x,y:k.y,w:j.w,h:j.h}},getSize:function(l){var j=this,i,k;l=j.get(l);i=j.getStyle(l,"width");k=j.getStyle(l,"height");if(i.indexOf("px")===-1){i=0}if(k.indexOf("px")===-1){k=0}return{w:parseInt(i,10)||l.offsetWidth||l.clientWidth,h:parseInt(k,10)||l.offsetHeight||l.clientHeight}},getParent:function(k,j,i){return this.getParents(k,j,i,false)},getParents:function(s,m,k,q){var j=this,i,l=j.settings,p=[];s=j.get(s);q=q===undefined;if(l.strict_root){k=k||j.getRoot()}if(d(m,"string")){i=m;if(m==="*"){m=function(o){return o.nodeType==1}}else{m=function(o){return j.is(o,i)}}}while(s){if(s==k||!s.nodeType||s.nodeType===9){break}if(!m||m(s)){if(q){p.push(s)}else{return s}}s=s.parentNode}return q?p:null},get:function(i){var j;if(i&&this.doc&&typeof(i)=="string"){j=i;i=this.doc.getElementById(i);if(i&&i.id!==j){return this.doc.getElementsByName(j)[1]}}return i},getNext:function(j,i){return this._findSib(j,i,"nextSibling")},getPrev:function(j,i){return this._findSib(j,i,"previousSibling")},select:function(k,j){var i=this;return e.dom.Sizzle(k,i.get(j)||i.get(i.settings.root_element)||i.doc,[])},is:function(l,j){var k;if(l.length===undefined){if(j==="*"){return l.nodeType==1}if(c.test(j)){j=j.toLowerCase().split(/,/);l=l.nodeName.toLowerCase();for(k=j.length-1;k>=0;k--){if(j[k]==l){return true}}return false}}return e.dom.Sizzle.matches(j,l.nodeType?[l]:l).length>0},add:function(l,o,i,k,m){var j=this;return this.run(l,function(r){var q,n;q=d(o,"string")?j.doc.createElement(o):o;j.setAttribs(q,i);if(k){if(k.nodeType){q.appendChild(k)}else{j.setHTML(q,k)}}return !m?r.appendChild(q):q})},create:function(k,i,j){return this.add(this.doc.createElement(k),k,i,j,1)},createHTML:function(q,i,m){var p="",l=this,j;p+="<"+q;for(j in i){if(i.hasOwnProperty(j)){p+=" "+j+'="'+l.encode(i[j])+'"'}}if(typeof(m)!="undefined"){return p+">"+m+""}return p+" />"},remove:function(i,j){return this.run(i,function(l){var m,k=l.parentNode;if(!k){return null}if(j){while(m=l.firstChild){if(!e.isIE||m.nodeType!==3||m.nodeValue){k.insertBefore(m,l)}else{l.removeChild(m)}}}return k.removeChild(l)})},setStyle:function(l,i,j){var k=this;return k.run(l,function(o){var n,m;n=o.style;i=i.replace(/-(\D)/g,function(q,p){return p.toUpperCase()});if(k.pixelStyles.test(i)&&(e.is(j,"number")||/^[\-0-9\.]+$/.test(j))){j+="px"}switch(i){case"opacity":if(b){n.filter=j===""?"":"alpha(opacity="+(j*100)+")";if(!l.currentStyle||!l.currentStyle.hasLayout){n.display="inline-block"}}n[i]=n["-moz-opacity"]=n["-khtml-opacity"]=j||"";break;case"float":b?n.styleFloat=j:n.cssFloat=j;break;default:n[i]=j||""}if(k.settings.update_styles){k.setAttrib(o,"data-mce-style")}})},getStyle:function(l,i,k){l=this.get(l);if(!l){return}if(this.doc.defaultView&&k){i=i.replace(/[A-Z]/g,function(m){return"-"+m});try{return this.doc.defaultView.getComputedStyle(l,null).getPropertyValue(i)}catch(j){return null}}i=i.replace(/-(\D)/g,function(n,m){return m.toUpperCase()});if(i=="float"){i=b?"styleFloat":"cssFloat"}if(l.currentStyle&&k){return l.currentStyle[i]}return l.style?l.style[i]:undefined},setStyles:function(l,m){var j=this,k=j.settings,i;i=k.update_styles;k.update_styles=0;g(m,function(o,p){j.setStyle(l,p,o)});k.update_styles=i;if(k.update_styles){j.setAttrib(l,k.cssText)}},removeAllAttribs:function(i){return this.run(i,function(l){var k,j=l.attributes;for(k=j.length-1;k>=0;k--){l.removeAttributeNode(j.item(k))}})},setAttrib:function(k,l,i){var j=this;if(!k||!l){return}if(j.settings.strict){l=l.toLowerCase()}return this.run(k,function(p){var o=j.settings;var m=p.getAttribute(l);if(i!==null){switch(l){case"style":if(!d(i,"string")){g(i,function(q,r){j.setStyle(p,r,q)});return}if(o.keep_values){if(i&&!j._isRes(i)){p.setAttribute("data-mce-style",i,2)}else{p.removeAttribute("data-mce-style",2)}}p.style.cssText=i;break;case"class":p.className=i||"";break;case"src":case"href":if(o.keep_values){if(o.url_converter){i=o.url_converter.call(o.url_converter_scope||j,i,l,p)}j.setAttrib(p,"data-mce-"+l,i,2)}break;case"shape":p.setAttribute("data-mce-style",i);break}}if(d(i)&&i!==null&&i.length!==0){p.setAttribute(l,""+i,2)}else{p.removeAttribute(l,2)}if(tinyMCE.activeEditor&&m!=i){var n=tinyMCE.activeEditor;n.onSetAttrib.dispatch(n,p,l,i)}})},setAttribs:function(j,k){var i=this;return this.run(j,function(l){g(k,function(m,o){i.setAttrib(l,o,m)})})},getAttrib:function(m,o,k){var i,j=this,l;m=j.get(m);if(!m||m.nodeType!==1){return k===l?false:k}if(!d(k)){k=""}if(/^(src|href|style|coords|shape)$/.test(o)){i=m.getAttribute("data-mce-"+o);if(i){return i}}if(b&&j.props[o]){i=m[j.props[o]];i=i&&i.nodeValue?i.nodeValue:i}if(!i){i=m.getAttribute(o,2)}if(/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(o)){if(m[j.props[o]]===true&&i===""){return o}return i?o:""}if(m.nodeName==="FORM"&&m.getAttributeNode(o)){return m.getAttributeNode(o).nodeValue}if(o==="style"){i=i||m.style.cssText;if(i){i=j.serializeStyle(j.parseStyle(i),m.nodeName);if(j.settings.keep_values&&!j._isRes(i)){m.setAttribute("data-mce-style",i)}}}if(f&&o==="class"&&i){i=i.replace(/(apple|webkit)\-[a-z\-]+/gi,"")}if(b){switch(o){case"rowspan":case"colspan":if(i===1){i=""}break;case"size":if(i==="+0"||i===20||i===0){i=""}break;case"width":case"height":case"vspace":case"checked":case"disabled":case"readonly":if(i===0){i=""}break;case"hspace":if(i===-1){i=""}break;case"maxlength":case"tabindex":if(i===32768||i===2147483647||i==="32768"){i=""}break;case"multiple":case"compact":case"noshade":case"nowrap":if(i===65535){return o}return k;case"shape":i=i.toLowerCase();break;default:if(o.indexOf("on")===0&&i){i=e._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/,"$1",""+i)}}}return(i!==l&&i!==null&&i!=="")?""+i:k},getPos:function(q,l){var j=this,i=0,p=0,m,o=j.doc,k;q=j.get(q);l=l||o.body;if(q){if(q.getBoundingClientRect){q=q.getBoundingClientRect();m=j.boxModel?o.documentElement:o.body;i=q.left+(o.documentElement.scrollLeft||o.body.scrollLeft)-m.clientTop;p=q.top+(o.documentElement.scrollTop||o.body.scrollTop)-m.clientLeft;return{x:i,y:p}}k=q;while(k&&k!=l&&k.nodeType){i+=k.offsetLeft||0;p+=k.offsetTop||0;k=k.offsetParent}k=q.parentNode;while(k&&k!=l&&k.nodeType){i-=k.scrollLeft||0;p-=k.scrollTop||0;k=k.parentNode}}return{x:i,y:p}},parseStyle:function(i){return this.styles.parse(i)},serializeStyle:function(j,i){return this.styles.serialize(j,i)},addStyle:function(j){var k=this.doc,i;styleElm=k.getElementById("mceDefaultStyles");if(!styleElm){styleElm=k.createElement("style"),styleElm.id="mceDefaultStyles";styleElm.type="text/css";i=k.getElementsByTagName("head")[0];if(i.firstChild){i.insertBefore(styleElm,i.firstChild)}else{i.appendChild(styleElm)}}if(styleElm.styleSheet){styleElm.styleSheet.cssText+=j}else{styleElm.appendChild(k.createTextNode(j))}},loadCSS:function(i){var k=this,l=k.doc,j;if(!i){i=""}j=l.getElementsByTagName("head")[0];g(i.split(","),function(m){var n;if(k.files[m]){return}k.files[m]=true;n=k.create("link",{rel:"stylesheet",href:e._addVer(m)});if(b&&l.documentMode&&l.recalc){n.onload=function(){if(l.recalc){l.recalc()}n.onload=null}}j.appendChild(n)})},addClass:function(i,j){return this.run(i,function(k){var l;if(!j){return 0}if(this.hasClass(k,j)){return k.className}l=this.removeClass(k,j);return k.className=(l!=""?(l+" "):"")+j})},removeClass:function(k,l){var i=this,j;return i.run(k,function(n){var m;if(i.hasClass(n,l)){if(!j){j=new RegExp("(^|\\s+)"+l+"(\\s+|$)","g")}m=n.className.replace(j," ");m=e.trim(m!=" "?m:"");n.className=m;if(!m){n.removeAttribute("class");n.removeAttribute("className")}return m}return n.className})},hasClass:function(j,i){j=this.get(j);if(!j||!i){return false}return(" "+j.className+" ").indexOf(" "+i+" ")!==-1},show:function(i){return this.setStyle(i,"display","block")},hide:function(i){return this.setStyle(i,"display","none")},isHidden:function(i){i=this.get(i);return !i||i.style.display=="none"||this.getStyle(i,"display")=="none"},uniqueId:function(i){return(!i?"mce_":i)+(this.counter++)},setHTML:function(k,j){var i=this;return i.run(k,function(m){if(b){while(m.firstChild){m.removeChild(m.firstChild)}try{m.innerHTML="
"+j;m.removeChild(m.firstChild)}catch(l){var n=i.create("div");n.innerHTML="
"+j;g(e.grep(n.childNodes),function(p,o){if(o&&m.canHaveHTML){m.appendChild(p)}})}}else{m.innerHTML=j}return j})},getOuterHTML:function(k){var j,i=this;k=i.get(k);if(!k){return null}if(k.nodeType===1&&i.hasOuterHTML){return k.outerHTML}j=(k.ownerDocument||i.doc).createElement("body");j.appendChild(k.cloneNode(true));return j.innerHTML},setOuterHTML:function(l,j,m){var i=this;function k(p,o,r){var s,q;q=r.createElement("body");q.innerHTML=o;s=q.lastChild;while(s){i.insertAfter(s.cloneNode(true),p);s=s.previousSibling}i.remove(p)}return this.run(l,function(o){o=i.get(o);if(o.nodeType==1){m=m||o.ownerDocument||i.doc;if(b){try{if(b&&o.nodeType==1){o.outerHTML=j}else{k(o,j,m)}}catch(n){k(o,j,m)}}else{k(o,j,m)}}})},decode:h.decode,encode:h.encodeAllRaw,insertAfter:function(i,j){j=this.get(j);return this.run(i,function(l){var k,m;k=j.parentNode;m=j.nextSibling;if(m){k.insertBefore(l,m)}else{k.appendChild(l)}return l})},replace:function(m,l,i){var j=this;if(d(l,"array")){m=m.cloneNode(true)}return j.run(l,function(k){if(i){g(e.grep(k.childNodes),function(n){m.appendChild(n)})}return k.parentNode.replaceChild(m,k)})},rename:function(l,i){var k=this,j;if(l.nodeName!=i.toUpperCase()){j=k.create(i);g(k.getAttribs(l),function(m){k.setAttrib(j,m.nodeName,k.getAttrib(l,m.nodeName))});k.replace(j,l,1)}return j||l},findCommonAncestor:function(k,i){var l=k,j;while(l){j=i;while(j&&l!=j){j=j.parentNode}if(l==j){break}l=l.parentNode}if(!l&&k.ownerDocument){return k.ownerDocument.documentElement}return l},toHex:function(i){var k=/^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(i);function j(l){l=parseInt(l,10).toString(16);return l.length>1?l:"0"+l}if(k){i="#"+j(k[1])+j(k[2])+j(k[3]);return i}return i},getClasses:function(){var n=this,j=[],m,o={},p=n.settings.class_filter,l;if(n.classes){return n.classes}function q(i){g(i.imports,function(s){q(s)});g(i.cssRules||i.rules,function(s){switch(s.type||1){case 1:if(s.selectorText){g(s.selectorText.split(","),function(r){r=r.replace(/^\s*|\s*$|^\s\./g,"");if(/\.mce/.test(r)||!/\.[\w\-]+$/.test(r)){return}l=r;r=e._replace(/.*\.([a-z0-9_\-]+).*/i,"$1",r);if(p&&!(r=p(r,l))){return}if(!o[r]){j.push({"class":r});o[r]=1}})}break;case 3:q(s.styleSheet);break}})}try{g(n.doc.styleSheets,q)}catch(k){}if(j.length>0){n.classes=j}return j},run:function(l,k,j){var i=this,m;if(i.doc&&typeof(l)==="string"){l=i.get(l)}if(!l){return false}j=j||this;if(!l.nodeType&&(l.length||l.length===0)){m=[];g(l,function(o,n){if(o){if(typeof(o)=="string"){o=i.doc.getElementById(o)}m.push(k.call(j,o,n))}});return m}return k.call(j,l)},getAttribs:function(j){var i;j=this.get(j);if(!j){return[]}if(b){i=[];if(j.nodeName=="OBJECT"){return j.attributes}if(j.nodeName==="OPTION"&&this.getAttrib(j,"selected")){i.push({specified:1,nodeName:"selected"})}j.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi,"").replace(/[\w:\-]+/gi,function(k){i.push({specified:1,nodeName:k})});return i}return j.attributes},isEmpty:function(m,k){var r=this,o,n,q,j,l,p=0;m=m.firstChild;if(m){j=new e.dom.TreeWalker(m,m.parentNode);k=k||r.schema?r.schema.getNonEmptyElements():null;do{q=m.nodeType;if(q===1){if(m.getAttribute("data-mce-bogus")){continue}l=m.nodeName.toLowerCase();if(k&&k[l]){if(l==="br"){p++;continue}return false}n=r.getAttribs(m);o=m.attributes.length;while(o--){l=m.attributes[o].nodeName;if(l==="name"||l==="data-mce-bookmark"){return false}}}if(q==8){return false}if((q===3&&!a.test(m.nodeValue))){return false}}while(m=j.next())}return p<=1},destroy:function(j){var i=this;i.win=i.doc=i.root=i.events=i.frag=null;if(!j){e.removeUnload(i.destroy)}},createRng:function(){var i=this.doc;return i.createRange?i.createRange():new e.dom.Range(this)},nodeIndex:function(m,n){var i=0,k,l,j;if(m){for(k=m.nodeType,m=m.previousSibling,l=m;m;m=m.previousSibling){j=m.nodeType;if(n&&j==3){if(j==k||!m.nodeValue.length){continue}}i++;k=j}}return i},split:function(m,l,p){var q=this,i=q.createRng(),n,k,o;function j(v){var t,s=v.childNodes,u=v.nodeType;function x(A){var z=A.previousSibling&&A.previousSibling.nodeName=="SPAN";var y=A.nextSibling&&A.nextSibling.nodeName=="SPAN";return z&&y}if(u==1&&v.getAttribute("data-mce-type")=="bookmark"){return}for(t=s.length-1;t>=0;t--){j(s[t])}if(u!=9){if(u==3&&v.nodeValue.length>0){var r=e.trim(v.nodeValue).length;if(!q.isBlock(v.parentNode)||r>0||r===0&&x(v)){return}}else{if(u==1){s=v.childNodes;if(s.length==1&&s[0]&&s[0].nodeType==1&&s[0].getAttribute("data-mce-type")=="bookmark"){v.parentNode.insertBefore(s[0],v)}if(s.length||/^(br|hr|input|img)$/i.test(v.nodeName)){return}}}q.remove(v)}return v}if(m&&l){i.setStart(m.parentNode,q.nodeIndex(m));i.setEnd(l.parentNode,q.nodeIndex(l));n=i.extractContents();i=q.createRng();i.setStart(l.parentNode,q.nodeIndex(l)+1);i.setEnd(m.parentNode,q.nodeIndex(m)+1);k=i.extractContents();o=m.parentNode;o.insertBefore(j(n),m);if(p){o.replaceChild(p,l)}else{o.insertBefore(l,m)}o.insertBefore(j(k),m);q.remove(m);return p||l}},bind:function(l,i,k,j){return this.events.add(l,i,k,j||this)},unbind:function(k,i,j){return this.events.remove(k,i,j)},fire:function(k,j,i){return this.events.fire(k,j,i)},getContentEditable:function(j){var i;if(j.nodeType!=1){return null}i=j.getAttribute("data-mce-contenteditable");if(i&&i!=="inherit"){return i}return j.contentEditable!=="inherit"?j.contentEditable:null},_findSib:function(l,i,j){var k=this,m=i;if(l){if(d(m,"string")){m=function(n){return k.is(n,i)}}for(l=l[j];l;l=l[j]){if(m(l)){return l}}}return null},_isRes:function(i){return/^(top|left|bottom|right|width|height)/i.test(i)||/;\s*(top|left|bottom|right|width|height)/i.test(i)}});e.DOM=new e.dom.DOMUtils(document,{process_html:0})})(tinymce);(function(a){function b(c){var O=this,e=c.doc,U=0,F=1,j=2,E=true,S=false,W="startOffset",h="startContainer",Q="endContainer",A="endOffset",k=tinymce.extend,n=c.nodeIndex;k(O,{startContainer:e,startOffset:0,endContainer:e,endOffset:0,collapsed:E,commonAncestorContainer:e,START_TO_START:0,START_TO_END:1,END_TO_END:2,END_TO_START:3,setStart:q,setEnd:s,setStartBefore:g,setStartAfter:J,setEndBefore:K,setEndAfter:u,collapse:B,selectNode:y,selectNodeContents:G,compareBoundaryPoints:v,deleteContents:p,extractContents:I,cloneContents:d,insertNode:D,surroundContents:N,cloneRange:L,toStringIE:T});function x(){return e.createDocumentFragment()}function q(X,t){C(E,X,t)}function s(X,t){C(S,X,t)}function g(t){q(t.parentNode,n(t))}function J(t){q(t.parentNode,n(t)+1)}function K(t){s(t.parentNode,n(t))}function u(t){s(t.parentNode,n(t)+1)}function B(t){if(t){O[Q]=O[h];O[A]=O[W]}else{O[h]=O[Q];O[W]=O[A]}O.collapsed=E}function y(t){g(t);u(t)}function G(t){q(t,0);s(t,t.nodeType===1?t.childNodes.length:t.nodeValue.length)}function v(aa,t){var ad=O[h],Y=O[W],ac=O[Q],X=O[A],ab=t.startContainer,af=t.startOffset,Z=t.endContainer,ae=t.endOffset;if(aa===0){return H(ad,Y,ab,af)}if(aa===1){return H(ac,X,ab,af)}if(aa===2){return H(ac,X,Z,ae)}if(aa===3){return H(ad,Y,Z,ae)}}function p(){l(j)}function I(){return l(U)}function d(){return l(F)}function D(aa){var X=this[h],t=this[W],Z,Y;if((X.nodeType===3||X.nodeType===4)&&X.nodeValue){if(!t){X.parentNode.insertBefore(aa,X)}else{if(t>=X.nodeValue.length){c.insertAfter(aa,X)}else{Z=X.splitText(t);X.parentNode.insertBefore(aa,Z)}}}else{if(X.childNodes.length>0){Y=X.childNodes[t]}if(Y){X.insertBefore(aa,Y)}else{X.appendChild(aa)}}}function N(X){var t=O.extractContents();O.insertNode(X);X.appendChild(t);O.selectNode(X)}function L(){return k(new b(c),{startContainer:O[h],startOffset:O[W],endContainer:O[Q],endOffset:O[A],collapsed:O.collapsed,commonAncestorContainer:O.commonAncestorContainer})}function P(t,X){var Y;if(t.nodeType==3){return t}if(X<0){return t}Y=t.firstChild;while(Y&&X>0){--X;Y=Y.nextSibling}if(Y){return Y}return t}function m(){return(O[h]==O[Q]&&O[W]==O[A])}function H(Z,ab,X,aa){var ac,Y,t,ad,af,ae;if(Z==X){if(ab==aa){return 0}if(ab0){O.collapse(X)}}else{O.collapse(X)}O.collapsed=m();O.commonAncestorContainer=c.findCommonAncestor(O[h],O[Q])}function l(ad){var ac,Z=0,af=0,X,ab,Y,aa,t,ae;if(O[h]==O[Q]){return f(ad)}for(ac=O[Q],X=ac.parentNode;X;ac=X,X=X.parentNode){if(X==O[h]){return r(ac,ad)}++Z}for(ac=O[h],X=ac.parentNode;X;ac=X,X=X.parentNode){if(X==O[Q]){return V(ac,ad)}++af}ab=af-Z;Y=O[h];while(ab>0){Y=Y.parentNode;ab--}aa=O[Q];while(ab<0){aa=aa.parentNode;ab++}for(t=Y.parentNode,ae=aa.parentNode;t!=ae;t=t.parentNode,ae=ae.parentNode){Y=t;aa=ae}return o(Y,aa,ad)}function f(ac){var ae,af,t,Y,Z,ad,aa,X,ab;if(ac!=j){ae=x()}if(O[W]==O[A]){return ae}if(O[h].nodeType==3){af=O[h].nodeValue;t=af.substring(O[W],O[A]);if(ac!=F){Y=O[h];X=O[W];ab=O[A]-O[W];if(X===0&&ab>=Y.nodeValue.length-1){Y.parentNode.removeChild(Y)}else{Y.deleteData(X,ab)}O.collapse(E)}if(ac==j){return}if(t.length>0){ae.appendChild(e.createTextNode(t))}return ae}Y=P(O[h],O[W]);Z=O[A]-O[W];while(Y&&Z>0){ad=Y.nextSibling;aa=z(Y,ac);if(ae){ae.appendChild(aa)}--Z;Y=ad}if(ac!=F){O.collapse(E)}return ae}function r(ad,aa){var ac,ab,X,t,Z,Y;if(aa!=j){ac=x()}ab=i(ad,aa);if(ac){ac.appendChild(ab)}X=n(ad);t=X-O[W];if(t<=0){if(aa!=F){O.setEndBefore(ad);O.collapse(S)}return ac}ab=ad.previousSibling;while(t>0){Z=ab.previousSibling;Y=z(ab,aa);if(ac){ac.insertBefore(Y,ac.firstChild)}--t;ab=Z}if(aa!=F){O.setEndBefore(ad);O.collapse(S)}return ac}function V(ab,aa){var ad,X,ac,t,Z,Y;if(aa!=j){ad=x()}ac=R(ab,aa);if(ad){ad.appendChild(ac)}X=n(ab);++X;t=O[A]-X;ac=ab.nextSibling;while(ac&&t>0){Z=ac.nextSibling;Y=z(ac,aa);if(ad){ad.appendChild(Y)}--t;ac=Z}if(aa!=F){O.setStartAfter(ab);O.collapse(E)}return ad}function o(ab,t,ae){var Y,ag,aa,ac,ad,X,af,Z;if(ae!=j){ag=x()}Y=R(ab,ae);if(ag){ag.appendChild(Y)}aa=ab.parentNode;ac=n(ab);ad=n(t);++ac;X=ad-ac;af=ab.nextSibling;while(X>0){Z=af.nextSibling;Y=z(af,ae);if(ag){ag.appendChild(Y)}af=Z;--X}Y=i(t,ae);if(ag){ag.appendChild(Y)}if(ae!=F){O.setStartAfter(ab);O.collapse(E)}return ag}function i(ac,ad){var Y=P(O[Q],O[A]-1),ae,ab,aa,t,X,Z=Y!=O[Q];if(Y==ac){return M(Y,Z,S,ad)}ae=Y.parentNode;ab=M(ae,S,S,ad);while(ae){while(Y){aa=Y.previousSibling;t=M(Y,Z,S,ad);if(ad!=j){ab.insertBefore(t,ab.firstChild)}Z=E;Y=aa}if(ae==ac){return ab}Y=ae.previousSibling;ae=ae.parentNode;X=M(ae,S,S,ad);if(ad!=j){X.appendChild(ab)}ab=X}}function R(ac,ad){var Z=P(O[h],O[W]),aa=Z!=O[h],ae,ab,Y,t,X;if(Z==ac){return M(Z,aa,E,ad)}ae=Z.parentNode;ab=M(ae,S,E,ad);while(ae){while(Z){Y=Z.nextSibling;t=M(Z,aa,E,ad);if(ad!=j){ab.appendChild(t)}aa=E;Z=Y}if(ae==ac){return ab}Z=ae.nextSibling;ae=ae.parentNode;X=M(ae,S,E,ad);if(ad!=j){X.appendChild(ab)}ab=X}}function M(t,aa,ad,ae){var Z,Y,ab,X,ac;if(aa){return z(t,ae)}if(t.nodeType==3){Z=t.nodeValue;if(ad){X=O[W];Y=Z.substring(X);ab=Z.substring(0,X)}else{X=O[A];Y=Z.substring(0,X);ab=Z.substring(X)}if(ae!=F){t.nodeValue=ab}if(ae==j){return}ac=c.clone(t,S);ac.nodeValue=Y;return ac}if(ae==j){return}return c.clone(t,S)}function z(X,t){if(t!=j){return t==F?c.clone(X,E):X}X.parentNode.removeChild(X)}function T(){return c.create("body",null,d()).outerText}return O}a.Range=b;b.prototype.toString=function(){return this.toStringIE()}})(tinymce.dom);(function(){function a(d){var b=this,h=d.dom,c=true,f=false;function e(i,j){var k,t=0,q,n,m,l,o,r,p=-1,s;k=i.duplicate();k.collapse(j);s=k.parentElement();if(s.ownerDocument!==d.dom.doc){return}while(s.contentEditable==="false"){s=s.parentNode}if(!s.hasChildNodes()){return{node:s,inside:1}}m=s.children;q=m.length-1;while(t<=q){r=Math.floor((t+q)/2);l=m[r];k.moveToElementText(l);p=k.compareEndPoints(j?"StartToStart":"EndToEnd",i);if(p>0){q=r-1}else{if(p<0){t=r+1}else{return{node:l}}}}if(p<0){if(!l){k.moveToElementText(s);k.collapse(true);l=s;n=true}else{k.collapse(false)}o=0;while(k.compareEndPoints(j?"StartToStart":"StartToEnd",i)!==0){if(k.move("character",1)===0||s!=k.parentElement()){break}o++}}else{k.collapse(true);o=0;while(k.compareEndPoints(j?"StartToStart":"StartToEnd",i)!==0){if(k.move("character",-1)===0||s!=k.parentElement()){break}o++}}return{node:l,position:p,offset:o,inside:n}}function g(){var i=d.getRng(),r=h.createRng(),l,k,p,q,m,j;l=i.item?i.item(0):i.parentElement();if(l.ownerDocument!=h.doc){return r}k=d.isCollapsed();if(i.item){r.setStart(l.parentNode,h.nodeIndex(l));r.setEnd(r.startContainer,r.startOffset+1);return r}function o(A){var u=e(i,A),s,y,z=0,x,v,t;s=u.node;y=u.offset;if(u.inside&&!s.hasChildNodes()){r[A?"setStart":"setEnd"](s,0);return}if(y===v){r[A?"setStartBefore":"setEndAfter"](s);return}if(u.position<0){x=u.inside?s.firstChild:s.nextSibling;if(!x){r[A?"setStartAfter":"setEndAfter"](s);return}if(!y){if(x.nodeType==3){r[A?"setStart":"setEnd"](x,0)}else{r[A?"setStartBefore":"setEndBefore"](x)}return}while(x){t=x.nodeValue;z+=t.length;if(z>=y){s=x;z-=y;z=t.length-z;break}x=x.nextSibling}}else{x=s.previousSibling;if(!x){return r[A?"setStartBefore":"setEndBefore"](s)}if(!y){if(s.nodeType==3){r[A?"setStart":"setEnd"](x,s.nodeValue.length)}else{r[A?"setStartAfter":"setEndAfter"](x)}return}while(x){z+=x.nodeValue.length;if(z>=y){s=x;z-=y;break}x=x.previousSibling}}r[A?"setStart":"setEnd"](s,z)}try{o(true);if(!k){o()}}catch(n){if(n.number==-2147024809){m=b.getBookmark(2);p=i.duplicate();p.collapse(true);l=p.parentElement();if(!k){p=i.duplicate();p.collapse(false);q=p.parentElement();q.innerHTML=q.innerHTML}l.innerHTML=l.innerHTML;b.moveToBookmark(m);i=d.getRng();o(true);if(!k){o()}}else{throw n}}return r}this.getBookmark=function(m){var j=d.getRng(),o,i,l={};function n(u){var t,p,s,r,q=[];t=u.parentNode;p=h.getRoot().parentNode;while(t!=p&&t.nodeType!==9){s=t.children;r=s.length;while(r--){if(u===s[r]){q.push(r);break}}u=t;t=t.parentNode}return q}function k(q){var p;p=e(j,q);if(p){return{position:p.position,offset:p.offset,indexes:n(p.node),inside:p.inside}}}if(m===2){if(!j.item){l.start=k(true);if(!d.isCollapsed()){l.end=k()}}else{l.start={ctrl:true,indexes:n(j.item(0))}}}return l};this.moveToBookmark=function(k){var j,i=h.doc.body;function m(o){var r,q,n,p;r=h.getRoot();for(q=o.length-1;q>=0;q--){p=r.children;n=o[q];if(n<=p.length-1){r=p[n]}}return r}function l(r){var n=k[r?"start":"end"],q,p,o;if(n){q=n.position>0;p=i.createTextRange();p.moveToElementText(m(n.indexes));offset=n.offset;if(offset!==o){p.collapse(n.inside||q);p.moveStart("character",q?-offset:offset)}else{p.collapse(r)}j.setEndPoint(r?"StartToStart":"EndToStart",p);if(r){j.collapse(true)}}}if(k.start){if(k.start.ctrl){j=i.createControlRange();j.addElement(m(k.start.indexes));j.select()}else{j=i.createTextRange();l(true);l();j.select()}}};this.addRange=function(i){var n,l,k,p,v,q,t,s=d.dom.doc,m=s.body,r,u;function j(C){var y,B,x,A,z;x=h.create("a");y=C?k:v;B=C?p:q;A=n.duplicate();if(y==s||y==s.documentElement){y=m;B=0}if(y.nodeType==3){y.parentNode.insertBefore(x,y);A.moveToElementText(x);A.moveStart("character",B);h.remove(x);n.setEndPoint(C?"StartToStart":"EndToEnd",A)}else{z=y.childNodes;if(z.length){if(B>=z.length){h.insertAfter(x,z[z.length-1])}else{y.insertBefore(x,z[B])}A.moveToElementText(x)}else{if(y.canHaveHTML){y.innerHTML="\uFEFF";x=y.firstChild;A.moveToElementText(x);A.collapse(f)}}n.setEndPoint(C?"StartToStart":"EndToEnd",A);h.remove(x)}}k=i.startContainer;p=i.startOffset;v=i.endContainer;q=i.endOffset;n=m.createTextRange();if(k==v&&k.nodeType==1){if(p==q&&!k.hasChildNodes()){if(k.canHaveHTML){t=k.previousSibling;if(t&&!t.hasChildNodes()&&h.isBlock(t)){t.innerHTML="\uFEFF"}else{t=null}k.innerHTML="\uFEFF\uFEFF";n.moveToElementText(k.lastChild);n.select();h.doc.selection.clear();k.innerHTML="";if(t){t.innerHTML=""}return}else{p=h.nodeIndex(k);k=k.parentNode}}if(p==q-1){try{u=k.childNodes[p];l=m.createControlRange();l.addElement(u);l.select();r=d.getRng();if(r.item&&u===r.item(0)){return}}catch(o){}}}j(true);j();n.select()};this.getRangeAt=g}tinymce.dom.TridentSelection=a})();(function(){var n=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,i="sizcache",o=0,r=Object.prototype.toString,h=false,g=true,q=/\\/g,u=/\r\n/g,x=/\W/;[0,0].sort(function(){g=false;return 0});var d=function(C,e,F,G){F=F||[];e=e||document;var I=e;if(e.nodeType!==1&&e.nodeType!==9){return[]}if(!C||typeof C!=="string"){return F}var z,K,N,y,J,M,L,E,B=true,A=d.isXML(e),D=[],H=C;do{n.exec("");z=n.exec(H);if(z){H=z[3];D.push(z[1]);if(z[2]){y=z[3];break}}}while(z);if(D.length>1&&j.exec(C)){if(D.length===2&&k.relative[D[0]]){K=s(D[0]+D[1],e,G)}else{K=k.relative[D[0]]?[e]:d(D.shift(),e);while(D.length){C=D.shift();if(k.relative[C]){C+=D.shift()}K=s(C,K,G)}}}else{if(!G&&D.length>1&&e.nodeType===9&&!A&&k.match.ID.test(D[0])&&!k.match.ID.test(D[D.length-1])){J=d.find(D.shift(),e,A);e=J.expr?d.filter(J.expr,J.set)[0]:J.set[0]}if(e){J=G?{expr:D.pop(),set:l(G)}:d.find(D.pop(),D.length===1&&(D[0]==="~"||D[0]==="+")&&e.parentNode?e.parentNode:e,A);K=J.expr?d.filter(J.expr,J.set):J.set;if(D.length>0){N=l(K)}else{B=false}while(D.length){M=D.pop();L=M;if(!k.relative[M]){M=""}else{L=D.pop()}if(L==null){L=e}k.relative[M](N,L,A)}}else{N=D=[]}}if(!N){N=K}if(!N){d.error(M||C)}if(r.call(N)==="[object Array]"){if(!B){F.push.apply(F,N)}else{if(e&&e.nodeType===1){for(E=0;N[E]!=null;E++){if(N[E]&&(N[E]===true||N[E].nodeType===1&&d.contains(e,N[E]))){F.push(K[E])}}}else{for(E=0;N[E]!=null;E++){if(N[E]&&N[E].nodeType===1){F.push(K[E])}}}}}else{l(N,F)}if(y){d(y,I,F,G);d.uniqueSort(F)}return F};d.uniqueSort=function(y){if(p){h=g;y.sort(p);if(h){for(var e=1;e0};d.find=function(E,e,F){var D,z,B,A,C,y;if(!E){return[]}for(z=0,B=k.order.length;z":function(D,y){var C,B=typeof y==="string",z=0,e=D.length;if(B&&!x.test(y)){y=y.toLowerCase();for(;z=0)){if(!z){e.push(C)}}else{if(z){y[B]=false}}}}return false},ID:function(e){return e[1].replace(q,"")},TAG:function(y,e){return y[1].replace(q,"").toLowerCase()},CHILD:function(e){if(e[1]==="nth"){if(!e[2]){d.error(e[0])}e[2]=e[2].replace(/^\+|\s*/g,"");var y=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(e[2]==="even"&&"2n"||e[2]==="odd"&&"2n+1"||!/\D/.test(e[2])&&"0n+"+e[2]||e[2]);e[2]=(y[1]+(y[2]||1))-0;e[3]=y[3]-0}else{if(e[2]){d.error(e[0])}}e[0]=o++;return e},ATTR:function(B,y,z,e,C,D){var A=B[1]=B[1].replace(q,"");if(!D&&k.attrMap[A]){B[1]=k.attrMap[A]}B[4]=(B[4]||B[5]||"").replace(q,"");if(B[2]==="~="){B[4]=" "+B[4]+" "}return B},PSEUDO:function(B,y,z,e,C){if(B[1]==="not"){if((n.exec(B[3])||"").length>1||/^\w/.test(B[3])){B[3]=d(B[3],null,null,y)}else{var A=d.filter(B[3],y,z,true^C);if(!z){e.push.apply(e,A)}return false}}else{if(k.match.POS.test(B[0])||k.match.CHILD.test(B[0])){return true}}return B},POS:function(e){e.unshift(true);return e}},filters:{enabled:function(e){return e.disabled===false&&e.type!=="hidden"},disabled:function(e){return e.disabled===true},checked:function(e){return e.checked===true},selected:function(e){if(e.parentNode){e.parentNode.selectedIndex}return e.selected===true},parent:function(e){return !!e.firstChild},empty:function(e){return !e.firstChild},has:function(z,y,e){return !!d(e[3],z).length},header:function(e){return(/h\d/i).test(e.nodeName)},text:function(z){var e=z.getAttribute("type"),y=z.type;return z.nodeName.toLowerCase()==="input"&&"text"===y&&(e===y||e===null)},radio:function(e){return e.nodeName.toLowerCase()==="input"&&"radio"===e.type},checkbox:function(e){return e.nodeName.toLowerCase()==="input"&&"checkbox"===e.type},file:function(e){return e.nodeName.toLowerCase()==="input"&&"file"===e.type},password:function(e){return e.nodeName.toLowerCase()==="input"&&"password"===e.type},submit:function(y){var e=y.nodeName.toLowerCase();return(e==="input"||e==="button")&&"submit"===y.type},image:function(e){return e.nodeName.toLowerCase()==="input"&&"image"===e.type},reset:function(y){var e=y.nodeName.toLowerCase();return(e==="input"||e==="button")&&"reset"===y.type},button:function(y){var e=y.nodeName.toLowerCase();return e==="input"&&"button"===y.type||e==="button"},input:function(e){return(/input|select|textarea|button/i).test(e.nodeName)},focus:function(e){return e===e.ownerDocument.activeElement}},setFilters:{first:function(y,e){return e===0},last:function(z,y,e,A){return y===A.length-1},even:function(y,e){return e%2===0},odd:function(y,e){return e%2===1},lt:function(z,y,e){return ye[3]-0},nth:function(z,y,e){return e[3]-0===y},eq:function(z,y,e){return e[3]-0===y}},filter:{PSEUDO:function(z,E,D,F){var e=E[1],y=k.filters[e];if(y){return y(z,D,E,F)}else{if(e==="contains"){return(z.textContent||z.innerText||b([z])||"").indexOf(E[3])>=0}else{if(e==="not"){var A=E[3];for(var C=0,B=A.length;C=0)}}},ID:function(y,e){return y.nodeType===1&&y.getAttribute("id")===e},TAG:function(y,e){return(e==="*"&&y.nodeType===1)||!!y.nodeName&&y.nodeName.toLowerCase()===e},CLASS:function(y,e){return(" "+(y.className||y.getAttribute("class"))+" ").indexOf(e)>-1},ATTR:function(C,A){var z=A[1],e=d.attr?d.attr(C,z):k.attrHandle[z]?k.attrHandle[z](C):C[z]!=null?C[z]:C.getAttribute(z),D=e+"",B=A[2],y=A[4];return e==null?B==="!=":!B&&d.attr?e!=null:B==="="?D===y:B==="*="?D.indexOf(y)>=0:B==="~="?(" "+D+" ").indexOf(y)>=0:!y?D&&e!==false:B==="!="?D!==y:B==="^="?D.indexOf(y)===0:B==="$="?D.substr(D.length-y.length)===y:B==="|="?D===y||D.substr(0,y.length+1)===y+"-":false},POS:function(B,y,z,C){var e=y[2],A=k.setFilters[e];if(A){return A(B,z,y,C)}}}};var j=k.match.POS,c=function(y,e){return"\\"+(e-0+1)};for(var f in k.match){k.match[f]=new RegExp(k.match[f].source+(/(?![^\[]*\])(?![^\(]*\))/.source));k.leftMatch[f]=new RegExp(/(^(?:.|\r|\n)*?)/.source+k.match[f].source.replace(/\\(\d+)/g,c))}k.match.globalPOS=j;var l=function(y,e){y=Array.prototype.slice.call(y,0);if(e){e.push.apply(e,y);return e}return y};try{Array.prototype.slice.call(document.documentElement.childNodes,0)[0].nodeType}catch(v){l=function(B,A){var z=0,y=A||[];if(r.call(B)==="[object Array]"){Array.prototype.push.apply(y,B)}else{if(typeof B.length==="number"){for(var e=B.length;z";e.insertBefore(y,e.firstChild);if(document.getElementById(z)){k.find.ID=function(B,C,D){if(typeof C.getElementById!=="undefined"&&!D){var A=C.getElementById(B[1]);return A?A.id===B[1]||typeof A.getAttributeNode!=="undefined"&&A.getAttributeNode("id").nodeValue===B[1]?[A]:undefined:[]}};k.filter.ID=function(C,A){var B=typeof C.getAttributeNode!=="undefined"&&C.getAttributeNode("id");return C.nodeType===1&&B&&B.nodeValue===A}}e.removeChild(y);e=y=null})();(function(){var e=document.createElement("div");e.appendChild(document.createComment(""));if(e.getElementsByTagName("*").length>0){k.find.TAG=function(y,C){var B=C.getElementsByTagName(y[1]);if(y[1]==="*"){var A=[];for(var z=0;B[z];z++){if(B[z].nodeType===1){A.push(B[z])}}B=A}return B}}e.innerHTML="";if(e.firstChild&&typeof e.firstChild.getAttribute!=="undefined"&&e.firstChild.getAttribute("href")!=="#"){k.attrHandle.href=function(y){return y.getAttribute("href",2)}}e=null})();if(document.querySelectorAll){(function(){var e=d,A=document.createElement("div"),z="__sizzle__";A.innerHTML="

";if(A.querySelectorAll&&A.querySelectorAll(".TEST").length===0){return}d=function(L,C,G,K){C=C||document;if(!K&&!d.isXML(C)){var J=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(L);if(J&&(C.nodeType===1||C.nodeType===9)){if(J[1]){return l(C.getElementsByTagName(L),G)}else{if(J[2]&&k.find.CLASS&&C.getElementsByClassName){return l(C.getElementsByClassName(J[2]),G)}}}if(C.nodeType===9){if(L==="body"&&C.body){return l([C.body],G)}else{if(J&&J[3]){var F=C.getElementById(J[3]);if(F&&F.parentNode){if(F.id===J[3]){return l([F],G)}}else{return l([],G)}}}try{return l(C.querySelectorAll(L),G)}catch(H){}}else{if(C.nodeType===1&&C.nodeName.toLowerCase()!=="object"){var D=C,E=C.getAttribute("id"),B=E||z,N=C.parentNode,M=/^\s*[+~]/.test(L);if(!E){C.setAttribute("id",B)}else{B=B.replace(/'/g,"\\$&")}if(M&&N){C=C.parentNode}try{if(!M||N){return l(C.querySelectorAll("[id='"+B+"'] "+L),G)}}catch(I){}finally{if(!E){D.removeAttribute("id")}}}}}return e(L,C,G,K)};for(var y in e){d[y]=e[y]}A=null})()}(function(){var e=document.documentElement,z=e.matchesSelector||e.mozMatchesSelector||e.webkitMatchesSelector||e.msMatchesSelector;if(z){var B=!z.call(document.createElement("div"),"div"),y=false;try{z.call(document.documentElement,"[test!='']:sizzle")}catch(A){y=true}d.matchesSelector=function(D,F){F=F.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!d.isXML(D)){try{if(y||!k.match.PSEUDO.test(F)&&!/!=/.test(F)){var C=z.call(D,F);if(C||!B||D.document&&D.document.nodeType!==11){return C}}}catch(E){}}return d(F,null,null,[D]).length>0}}})();(function(){var e=document.createElement("div");e.innerHTML="
";if(!e.getElementsByClassName||e.getElementsByClassName("e").length===0){return}e.lastChild.className="e";if(e.getElementsByClassName("e").length===1){return}k.order.splice(1,0,"CLASS");k.find.CLASS=function(y,z,A){if(typeof z.getElementsByClassName!=="undefined"&&!A){return z.getElementsByClassName(y[1])}};e=null})();function a(y,D,C,G,E,F){for(var A=0,z=G.length;A0){B=e;break}}}e=e[y]}G[A]=B}}}if(document.documentElement.contains){d.contains=function(y,e){return y!==e&&(y.contains?y.contains(e):true)}}else{if(document.documentElement.compareDocumentPosition){d.contains=function(y,e){return !!(y.compareDocumentPosition(e)&16)}}else{d.contains=function(){return false}}}d.isXML=function(e){var y=(e?e.ownerDocument||e:0).documentElement;return y?y.nodeName!=="HTML":false};var s=function(z,e,D){var C,E=[],B="",F=e.nodeType?[e]:e;while((C=k.match.PSEUDO.exec(z))){B+=C[0];z=z.replace(k.match.PSEUDO,"")}z=k.relative[z]?z+"*":z;for(var A=0,y=F.length;A"+(i.item?i.item(0).outerHTML:i.htmlText);m.removeChild(m.firstChild)}else{m.innerHTML=i.toString()}}if(/^\s/.test(m.innerHTML)){j=" "}if(/\s+$/.test(m.innerHTML)){l=" "}h.getInner=true;h.content=g.isCollapsed()?"":j+g.serializer.serialize(m,h)+l;g.onGetContent.dispatch(g,h);return h.content},setContent:function(h,j){var o=this,g=o.getRng(),k,l=o.win.document,n,m;j=j||{format:"html"};j.set=true;h=j.content=h;if(!j.no_events){o.onBeforeSetContent.dispatch(o,j)}h=j.content;if(g.insertNode){h+='_';if(g.startContainer==l&&g.endContainer==l){l.body.innerHTML=h}else{g.deleteContents();if(l.body.childNodes.length===0){l.body.innerHTML=h}else{if(g.createContextualFragment){g.insertNode(g.createContextualFragment(h))}else{n=l.createDocumentFragment();m=l.createElement("div");n.appendChild(m);m.outerHTML=h;g.insertNode(n)}}}k=o.dom.get("__caret");g=l.createRange();g.setStartBefore(k);g.setEndBefore(k);o.setRng(g);o.dom.remove("__caret");try{o.setRng(g)}catch(i){}}else{if(g.item){l.execCommand("Delete",false,null);g=o.getRng()}if(/^\s+/.test(h)){g.pasteHTML('_'+h);o.dom.remove("__mce_tmp")}else{g.pasteHTML(h)}}if(!j.no_events){o.onSetContent.dispatch(o,j)}},getStart:function(){var i=this,h=i.getRng(),j,g,l,k;if(h.duplicate||h.item){if(h.item){return h.item(0)}l=h.duplicate();l.collapse(1);j=l.parentElement();if(j.ownerDocument!==i.dom.doc){j=i.dom.getRoot()}g=k=h.parentElement();while(k=k.parentNode){if(k==j){j=g;break}}return j}else{j=h.startContainer;if(j.nodeType==1&&j.hasChildNodes()){j=j.childNodes[Math.min(j.childNodes.length-1,h.startOffset)]}if(j&&j.nodeType==3){return j.parentNode}return j}},getEnd:function(){var h=this,g=h.getRng(),j,i;if(g.duplicate||g.item){if(g.item){return g.item(0)}g=g.duplicate();g.collapse(0);j=g.parentElement();if(j.ownerDocument!==h.dom.doc){j=h.dom.getRoot()}if(j&&j.nodeName=="BODY"){return j.lastChild||j}return j}else{j=g.endContainer;i=g.endOffset;if(j.nodeType==1&&j.hasChildNodes()){j=j.childNodes[i>0?i-1:i]}if(j&&j.nodeType==3){return j.parentNode}return j}},getBookmark:function(s,v){var y=this,n=y.dom,h,k,j,o,i,p,q,m="\uFEFF",x;function g(z,A){var t=0;e(n.select(z),function(C,B){if(C==A){t=B}});return t}function u(t){function z(E){var A,D,C,B=E?"start":"end";A=t[B+"Container"];D=t[B+"Offset"];if(A.nodeType==1&&A.nodeName=="TR"){C=A.childNodes;A=C[Math.min(E?D:D-1,C.length-1)];if(A){D=E?0:A.childNodes.length;t["set"+(E?"Start":"End")](A,D)}}}z(true);z();return t}function l(){var z=y.getRng(true),t=n.getRoot(),A={};function B(E,J){var D=E[J?"startContainer":"endContainer"],I=E[J?"startOffset":"endOffset"],C=[],F,H,G=0;if(D.nodeType==3){if(v){for(F=D.previousSibling;F&&F.nodeType==3;F=F.previousSibling){I+=F.nodeValue.length}}C.push(I)}else{H=D.childNodes;if(I>=H.length&&H.length){G=1;I=Math.max(0,H.length-1)}C.push(y.dom.nodeIndex(H[I],v)+G)}for(;D&&D!=t;D=D.parentNode){C.push(y.dom.nodeIndex(D,v))}return C}A.start=B(z,true);if(!y.isCollapsed()){A.end=B(z)}return A}if(s==2){if(y.tridentSel){return y.tridentSel.getBookmark(s)}return l()}if(s){return{rng:y.getRng()}}h=y.getRng();j=n.uniqueId();o=tinyMCE.activeEditor.selection.isCollapsed();x="overflow:hidden;line-height:0px";if(h.duplicate||h.item){if(!h.item){k=h.duplicate();try{h.collapse();h.pasteHTML(''+m+"");if(!o){k.collapse(false);h.moveToElementText(k.parentElement());if(h.compareEndPoints("StartToEnd",k)===0){k.move("character",-1)}k.pasteHTML(''+m+"")}}catch(r){return null}}else{p=h.item(0);i=p.nodeName;return{name:i,index:g(i,p)}}}else{p=y.getNode();i=p.nodeName;if(i=="IMG"){return{name:i,index:g(i,p)}}k=u(h.cloneRange());if(!o){k.collapse(false);k.insertNode(n.create("span",{"data-mce-type":"bookmark",id:j+"_end",style:x},m))}h=u(h);h.collapse(true);h.insertNode(n.create("span",{"data-mce-type":"bookmark",id:j+"_start",style:x},m))}y.moveToBookmark({id:j,keep:1});return{id:j}},moveToBookmark:function(o){var s=this,m=s.dom,j,i,g,r,k,u,p,q;function h(A){var t=o[A?"start":"end"],x,y,z,v;if(t){z=t[0];for(y=r,x=t.length-1;x>=1;x--){v=y.childNodes;if(t[x]>v.length-1){return}y=v[t[x]]}if(y.nodeType===3){z=Math.min(t[0],y.nodeValue.length)}if(y.nodeType===1){z=Math.min(t[0],y.childNodes.length)}if(A){g.setStart(y,z)}else{g.setEnd(y,z)}}return true}function l(B){var v=m.get(o.id+"_"+B),A,t,y,z,x=o.keep;if(v){A=v.parentNode;if(B=="start"){if(!x){t=m.nodeIndex(v)}else{A=v.firstChild;t=1}k=u=A;p=q=t}else{if(!x){t=m.nodeIndex(v)}else{A=v.firstChild;t=1}u=A;q=t}if(!x){z=v.previousSibling;y=v.nextSibling;e(d.grep(v.childNodes),function(C){if(C.nodeType==3){C.nodeValue=C.nodeValue.replace(/\uFEFF/g,"")}});while(v=m.get(o.id+"_"+B)){m.remove(v,1)}if(z&&y&&z.nodeType==y.nodeType&&z.nodeType==3&&!d.isOpera){t=z.nodeValue.length;z.appendData(y.nodeValue);m.remove(y);if(B=="start"){k=u=z;p=q=t}else{u=z;q=t}}}}}function n(t){if(m.isBlock(t)&&!t.innerHTML&&!b){t.innerHTML='
'}return t}if(o){if(o.start){g=m.createRng();r=m.getRoot();if(s.tridentSel){return s.tridentSel.moveToBookmark(o)}if(h(true)&&h()){s.setRng(g)}}else{if(o.id){l("start");l("end");if(k){g=m.createRng();g.setStart(n(k),p);g.setEnd(n(u),q);s.setRng(g)}}else{if(o.name){s.select(m.select(o.name)[o.index])}else{if(o.rng){s.setRng(o.rng)}}}}}},select:function(l,k){var j=this,m=j.dom,h=m.createRng(),g;function i(n,p){var o=new a(n,n);do{if(n.nodeType==3&&d.trim(n.nodeValue).length!==0){if(p){h.setStart(n,0)}else{h.setEnd(n,n.nodeValue.length)}return}if(n.nodeName=="BR"){if(p){h.setStartBefore(n)}else{h.setEndBefore(n)}return}}while(n=(p?o.next():o.prev()))}if(l){g=m.nodeIndex(l);h.setStart(l.parentNode,g);h.setEnd(l.parentNode,g+1);if(k){i(l,1);i(l)}j.setRng(h)}return l},isCollapsed:function(){var g=this,i=g.getRng(),h=g.getSel();if(!i||i.item){return false}if(i.compareEndPoints){return i.compareEndPoints("StartToEnd",i)===0}return !h||i.collapsed},collapse:function(g){var i=this,h=i.getRng(),j;if(h.item){j=h.item(0);h=i.win.document.body.createTextRange();h.moveToElementText(j)}h.collapse(!!g);i.setRng(h)},getSel:function(){var h=this,g=this.win;return g.getSelection?g.getSelection():g.document.selection},getRng:function(m){var h=this,j,g,l,k=h.win.document;if(m&&h.tridentSel){return h.tridentSel.getRangeAt(0)}try{if(j=h.getSel()){g=j.rangeCount>0?j.getRangeAt(0):(j.createRange?j.createRange():k.createRange())}}catch(i){}if(d.isIE&&g&&g.setStart&&k.selection.createRange().item){l=k.selection.createRange().item(0);g=k.createRange();g.setStartBefore(l);g.setEndAfter(l)}if(!g){g=k.createRange?k.createRange():k.body.createTextRange()}if(g.setStart&&g.startContainer.nodeType===9&&g.collapsed){l=h.dom.getRoot();g.setStart(l,0);g.setEnd(l,0)}if(h.selectedRange&&h.explicitRange){if(g.compareBoundaryPoints(g.START_TO_START,h.selectedRange)===0&&g.compareBoundaryPoints(g.END_TO_END,h.selectedRange)===0){g=h.explicitRange}else{h.selectedRange=null;h.explicitRange=null}}return g},setRng:function(k,g){var j,i=this;if(!i.tridentSel){j=i.getSel();if(j){i.explicitRange=k;try{j.removeAllRanges()}catch(h){}j.addRange(k);if(g===false&&j.extend){j.collapse(k.endContainer,k.endOffset);j.extend(k.startContainer,k.startOffset)}i.selectedRange=j.rangeCount>0?j.getRangeAt(0):null}}else{if(k.cloneRange){try{i.tridentSel.addRange(k);return}catch(h){}}try{k.select()}catch(h){}}},setNode:function(h){var g=this;g.setContent(g.dom.getOuterHTML(h));return h},getNode:function(){var i=this,h=i.getRng(),j=i.getSel(),m,l=h.startContainer,g=h.endContainer;function k(q,o){var p=q;while(q&&q.nodeType===3&&q.length===0){q=o?q.nextSibling:q.previousSibling}return q||p}if(!h){return i.dom.getRoot()}if(h.setStart){m=h.commonAncestorContainer;if(!h.collapsed){if(h.startContainer==h.endContainer){if(h.endOffset-h.startOffset<2){if(h.startContainer.hasChildNodes()){m=h.startContainer.childNodes[h.startOffset]}}}if(l.nodeType===3&&g.nodeType===3){if(l.length===h.startOffset){l=k(l.nextSibling,true)}else{l=l.parentNode}if(h.endOffset===0){g=k(g.previousSibling,false)}else{g=g.parentNode}if(l&&l===g){return l}}}if(m&&m.nodeType==3){return m.parentNode}return m}return h.item?h.item(0):h.parentElement()},getSelectedBlocks:function(p,h){var o=this,k=o.dom,m,l,i,j=[];m=k.getParent(p||o.getStart(),k.isBlock);l=k.getParent(h||o.getEnd(),k.isBlock);if(m){j.push(m)}if(m&&l&&m!=l){i=m;var g=new a(m,k.getRoot());while((i=g.next())&&i!=l){if(k.isBlock(i)){j.push(i)}}}if(l&&m!=l){j.push(l)}return j},isForward:function(){var i=this.dom,g=this.getSel(),j,h;if(!g||g.anchorNode==null||g.focusNode==null){return true}j=i.createRng();j.setStart(g.anchorNode,g.anchorOffset);j.collapse(true);h=i.createRng();h.setStart(g.focusNode,g.focusOffset);h.collapse(true);return j.compareBoundaryPoints(j.START_TO_START,h)<=0},normalize:function(){var h=this,g,m,l,j,i;function k(p){var o,r,n,s=h.dom,u=s.getRoot(),q,t,v;function y(z,A){var B=new a(z,s.getParent(z.parentNode,s.isBlock)||u);while(z=B[A?"prev":"next"]()){if(z.nodeName==="BR"){return true}}}function x(B,z){var C,A;z=z||o;C=new a(z,s.getParent(z.parentNode,s.isBlock)||u);while(q=C[B?"prev":"next"]()){if(q.nodeType===3&&q.nodeValue.length>0){o=q;r=B?q.nodeValue.length:0;m=true;return}if(s.isBlock(q)||t[q.nodeName.toLowerCase()]){return}A=q}if(l&&A){o=A;m=true;r=0}}o=g[(p?"start":"end")+"Container"];r=g[(p?"start":"end")+"Offset"];t=s.schema.getNonEmptyElements();if(o.nodeType===9){o=s.getRoot();r=0}if(o===u){if(p){q=o.childNodes[r>0?r-1:0];if(q){v=q.nodeName.toLowerCase();if(t[q.nodeName]||q.nodeName=="TABLE"){return}}}if(o.hasChildNodes()){o=o.childNodes[Math.min(!p&&r>0?r-1:r,o.childNodes.length-1)];r=0;if(o.hasChildNodes()&&!/TABLE/.test(o.nodeName)){q=o;n=new a(o,u);do{if(q.nodeType===3&&q.nodeValue.length>0){r=p?0:q.nodeValue.length;o=q;m=true;break}if(t[q.nodeName.toLowerCase()]){r=s.nodeIndex(q);o=q.parentNode;if(q.nodeName=="IMG"&&!p){r++}m=true;break}}while(q=(p?n.next():n.prev()))}}}if(l){if(o.nodeType===3&&r===0){x(true)}if(o.nodeType===1){q=o.childNodes[r];if(q&&q.nodeName==="BR"&&!y(q)&&!y(q,true)){x(true,o.childNodes[r])}}}if(p&&!l&&o.nodeType===3&&r===o.nodeValue.length){x(false)}if(m){g["set"+(p?"Start":"End")](o,r)}}if(d.isIE){return}g=h.getRng();l=g.collapsed;k(true);if(!l){k()}if(m){if(l){g.collapse(true)}h.setRng(g,h.isForward())}},selectorChanged:function(g,j){var h=this,i;if(!h.selectorChangedData){h.selectorChangedData={};i={};h.editor.onNodeChange.addToTop(function(l,k,o){var p=h.dom,m=p.getParents(o,null,p.getRoot()),n={};e(h.selectorChangedData,function(r,q){e(m,function(s){if(p.is(s,q)){if(!i[q]){e(r,function(t){t(true,{node:s,selector:q,parents:m})});i[q]=r}n[q]=r;return false}})});e(i,function(r,q){if(!n[q]){delete i[q];e(r,function(s){s(false,{node:o,selector:q,parents:m})})}})})}if(!h.selectorChangedData[g]){h.selectorChangedData[g]=[]}h.selectorChangedData[g].push(j);return h},destroy:function(h){var g=this;g.win=null;if(!h){d.removeUnload(g.destroy)}},_fixIESelection:function(){var h=this.dom,n=h.doc,i=n.body,k,o,g;function j(p,s){var q=i.createTextRange();try{q.moveToPoint(p,s)}catch(r){q=null}return q}function m(q){var p;if(q.button){p=j(q.x,q.y);if(p){if(p.compareEndPoints("StartToStart",o)>0){p.setEndPoint("StartToStart",o)}else{p.setEndPoint("EndToEnd",o)}p.select()}}else{l()}}function l(){var p=n.selection.createRange();if(o&&!p.item&&p.compareEndPoints("StartToEnd",p)===0){o.select()}h.unbind(n,"mouseup",l);h.unbind(n,"mousemove",m);o=k=0}n.documentElement.unselectable=true;h.bind(n,["mousedown","contextmenu"],function(p){if(p.target.nodeName==="HTML"){if(k){l()}g=n.documentElement;if(g.scrollHeight>g.clientHeight){return}k=1;o=j(p.x,p.y);if(o){h.bind(n,"mouseup",l);h.bind(n,"mousemove",m);h.win.focus();o.select()}}})}})})(tinymce);(function(a){a.dom.Serializer=function(e,i,f){var h,b,d=a.isIE,g=a.each,c;if(!e.apply_source_formatting){e.indent=false}i=i||a.DOM;f=f||new a.html.Schema(e);e.entity_encoding=e.entity_encoding||"named";e.remove_trailing_brs="remove_trailing_brs" in e?e.remove_trailing_brs:true;h=new a.util.Dispatcher(self);b=new a.util.Dispatcher(self);c=new a.html.DomParser(e,f);c.addAttributeFilter("src,href,style",function(k,j){var o=k.length,l,q,n="data-mce-"+j,p=e.url_converter,r=e.url_converter_scope,m;while(o--){l=k[o];q=l.attributes.map[n];if(q!==m){l.attr(j,q.length>0?q:null);l.attr(n,null)}else{q=l.attributes.map[j];if(j==="style"){q=i.serializeStyle(i.parseStyle(q),l.name)}else{if(p){q=p.call(r,q,j,l.name)}}l.attr(j,q.length>0?q:null)}}});c.addAttributeFilter("class",function(j,k){var l=j.length,m,n;while(l--){m=j[l];n=m.attr("class").replace(/(?:^|\s)mce(Item\w+|Selected)(?!\S)/g,"");m.attr("class",n.length>0?n:null)}});c.addAttributeFilter("data-mce-type",function(j,l,k){var m=j.length,n;while(m--){n=j[m];if(n.attributes.map["data-mce-type"]==="bookmark"&&!k.cleanup){n.remove()}}});c.addAttributeFilter("data-mce-expando",function(j,l,k){var m=j.length;while(m--){j[m].attr(l,null)}});c.addNodeFilter("noscript",function(j){var k=j.length,l;while(k--){l=j[k].firstChild;if(l){l.value=a.html.Entities.decode(l.value)}}});c.addNodeFilter("script,style",function(k,l){var m=k.length,n,o;function j(p){return p.replace(/()/g,"\n").replace(/^[\r\n]*|[\r\n]*$/g,"").replace(/^\s*(()?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g,"")}while(m--){n=k[m];o=n.firstChild?n.firstChild.value:"";if(l==="script"){n.attr("type",(n.attr("type")||"text/javascript").replace(/^mce\-/,""));if(o.length>0){n.firstChild.value="// "}}else{if(o.length>0){n.firstChild.value=""}}}});c.addNodeFilter("#comment",function(j,k){var l=j.length,m;while(l--){m=j[l];if(m.value.indexOf("[CDATA[")===0){m.name="#cdata";m.type=4;m.value=m.value.replace(/^\[CDATA\[|\]\]$/g,"")}else{if(m.value.indexOf("mce:protected ")===0){m.name="#text";m.type=3;m.raw=true;m.value=unescape(m.value).substr(14)}}}});c.addNodeFilter("xml:namespace,input",function(j,k){var l=j.length,m;while(l--){m=j[l];if(m.type===7){m.remove()}else{if(m.type===1){if(k==="input"&&!("type" in m.attributes.map)){m.attr("type","text")}}}}});if(e.fix_list_elements){c.addNodeFilter("ul,ol",function(k,l){var m=k.length,n,j;while(m--){n=k[m];j=n.parent;if(j.name==="ul"||j.name==="ol"){if(n.prev&&n.prev.name==="li"){n.prev.append(n)}}}})}c.addAttributeFilter("data-mce-src,data-mce-href,data-mce-style",function(j,k){var l=j.length;while(l--){j[l].attr(k,null)}});return{schema:f,addNodeFilter:c.addNodeFilter,addAttributeFilter:c.addAttributeFilter,onPreProcess:h,onPostProcess:b,serialize:function(o,m){var l,p,k,j,n;if(d&&i.select("script,style,select,map").length>0){n=o.innerHTML;o=o.cloneNode(false);i.setHTML(o,n)}else{o=o.cloneNode(true)}l=o.ownerDocument.implementation;if(l.createHTMLDocument){p=l.createHTMLDocument("");g(o.nodeName=="BODY"?o.childNodes:[o],function(q){p.body.appendChild(p.importNode(q,true))});if(o.nodeName!="BODY"){o=p.body.firstChild}else{o=p.body}k=i.doc;i.doc=p}m=m||{};m.format=m.format||"html";if(!m.no_events){m.node=o;h.dispatch(self,m)}j=new a.html.Serializer(e,f);m.content=j.serialize(c.parse(a.trim(m.getInner?o.innerHTML:i.getOuterHTML(o)),m));if(!m.cleanup){m.content=m.content.replace(/\uFEFF/g,"")}if(!m.no_events){b.dispatch(self,m)}if(k){i.doc=k}m.node=null;return m.content},addRules:function(j){f.addValidElements(j)},setRules:function(j){f.setValidElements(j)}}}})(tinymce);(function(a){a.dom.ScriptLoader=function(h){var c=0,k=1,i=2,l={},j=[],e={},d=[],g=0,f;function b(m,v){var x=this,q=a.DOM,s,o,r,n;function p(){q.remove(n);if(s){s.onreadystatechange=s.onload=s=null}v()}function u(){if(typeof(console)!=="undefined"&&console.log){console.log("Failed to load: "+m)}}n=q.uniqueId();if(a.isIE6){o=new a.util.URI(m);r=location;if(o.host==r.hostname&&o.port==r.port&&(o.protocol+":")==r.protocol&&o.protocol.toLowerCase()!="file"){a.util.XHR.send({url:a._addVer(o.getURI()),success:function(y){var t=q.create("script",{type:"text/javascript"});t.text=y;document.getElementsByTagName("head")[0].appendChild(t);q.remove(t);p()},error:u});return}}s=document.createElement("script");s.id=n;s.type="text/javascript";s.src=a._addVer(m);if(!a.isIE){s.onload=p}s.onerror=u;if(!a.isOpera){s.onreadystatechange=function(){var t=s.readyState;if(t=="complete"||t=="loaded"){p()}}}(document.getElementsByTagName("head")[0]||document.body).appendChild(s)}this.isDone=function(m){return l[m]==i};this.markDone=function(m){l[m]=i};this.add=this.load=function(m,q,n){var o,p=l[m];if(p==f){j.push(m);l[m]=c}if(q){if(!e[m]){e[m]=[]}e[m].push({func:q,scope:n||this})}};this.loadQueue=function(n,m){this.loadScripts(j,n,m)};this.loadScripts=function(m,q,p){var o;function n(r){a.each(e[r],function(s){s.func.call(s.scope)});e[r]=f}d.push({func:q,scope:p||this});o=function(){var r=a.grep(m);m.length=0;a.each(r,function(s){if(l[s]==i){n(s);return}if(l[s]!=k){l[s]=k;g++;b(s,function(){l[s]=i;g--;n(s);o()})}});if(!g){a.each(d,function(s){s.func.call(s.scope)});d.length=0}};o()}};a.ScriptLoader=new a.dom.ScriptLoader()})(tinymce);(function(a){a.dom.RangeUtils=function(c){var b="\uFEFF";this.walk=function(d,s){var i=d.startContainer,l=d.startOffset,t=d.endContainer,m=d.endOffset,j,g,o,h,r,q,e;e=c.select("td.mceSelected,th.mceSelected");if(e.length>0){a.each(e,function(u){s([u])});return}function f(u){var v;v=u[0];if(v.nodeType===3&&v===i&&l>=v.nodeValue.length){u.splice(0,1)}v=u[u.length-1];if(m===0&&u.length>0&&v===t&&v.nodeType===3){u.splice(u.length-1,1)}return u}function p(x,v,u){var y=[];for(;x&&x!=u;x=x[v]){y.push(x)}return y}function n(v,u){do{if(v.parentNode==u){return v}v=v.parentNode}while(v)}function k(x,v,y){var u=y?"nextSibling":"previousSibling";for(h=x,r=h.parentNode;h&&h!=v;h=r){r=h.parentNode;q=p(h==x?h:h[u],u);if(q.length){if(!y){q.reverse()}s(f(q))}}}if(i.nodeType==1&&i.hasChildNodes()){i=i.childNodes[l]}if(t.nodeType==1&&t.hasChildNodes()){t=t.childNodes[Math.min(m-1,t.childNodes.length-1)]}if(i==t){return s(f([i]))}j=c.findCommonAncestor(i,t);for(h=i;h;h=h.parentNode){if(h===t){return k(i,j,true)}if(h===j){break}}for(h=t;h;h=h.parentNode){if(h===i){return k(t,j)}if(h===j){break}}g=n(i,j)||i;o=n(t,j)||t;k(i,g,true);q=p(g==i?g:g.nextSibling,"nextSibling",o==t?o.nextSibling:o);if(q.length){s(f(q))}k(t,o)};this.split=function(e){var h=e.startContainer,d=e.startOffset,i=e.endContainer,g=e.endOffset;function f(j,k){return j.splitText(k)}if(h==i&&h.nodeType==3){if(d>0&&dd){g=g-d;h=i=f(i,g).previousSibling;g=i.nodeValue.length;d=0}else{g=0}}}else{if(h.nodeType==3&&d>0&&d0&&g=m.length){r=0}}t=m[r];f.setAttrib(g,"tabindex","-1");f.setAttrib(t.id,"tabindex","0");f.get(t.id).focus();if(e.actOnFocus){e.onAction(t.id)}if(s){a.cancel(s)}};p=function(z){var v=37,u=39,y=38,A=40,r=27,t=14,s=13,x=32;switch(z.keyCode){case v:if(i){q.moveFocus(-1)}break;case u:if(i){q.moveFocus(1)}break;case y:if(o){q.moveFocus(-1)}break;case A:if(o){q.moveFocus(1)}break;case r:if(e.onCancel){e.onCancel();a.cancel(z)}break;case t:case s:case x:if(e.onAction){e.onAction(g);a.cancel(z)}break}};c(m,function(t,r){var s,u;if(!t.id){t.id=f.uniqueId("_mce_item_")}u=f.get(t.id);if(l){f.bind(u,"blur",h);s="-1"}else{s=(r===0?"0":"-1")}u.setAttribute("tabindex",s);f.bind(u,"focus",k)});if(m[0]){g=m[0].id}f.setAttrib(n,"tabindex","-1");var j=f.get(n);f.bind(j,"focus",d);f.bind(j,"keydown",p)}})})(tinymce);(function(c){var b=c.DOM,a=c.is;c.create("tinymce.ui.Control",{Control:function(f,e,d){this.id=f;this.settings=e=e||{};this.rendered=false;this.onRender=new c.util.Dispatcher(this);this.classPrefix="";this.scope=e.scope||this;this.disabled=0;this.active=0;this.editor=d},setAriaProperty:function(f,e){var d=b.get(this.id+"_aria")||b.get(this.id);if(d){b.setAttrib(d,"aria-"+f,!!e)}},focus:function(){b.get(this.id).focus()},setDisabled:function(d){if(d!=this.disabled){this.setAriaProperty("disabled",d);this.setState("Disabled",d);this.setState("Enabled",!d);this.disabled=d}},isDisabled:function(){return this.disabled},setActive:function(d){if(d!=this.active){this.setState("Active",d);this.active=d;this.setAriaProperty("pressed",d)}},isActive:function(){return this.active},setState:function(f,d){var e=b.get(this.id);f=this.classPrefix+f;if(d){b.addClass(e,f)}else{b.removeClass(e,f)}},isRendered:function(){return this.rendered},renderHTML:function(){},renderTo:function(d){b.setHTML(d,this.renderHTML())},postRender:function(){var e=this,d;if(a(e.disabled)){d=e.disabled;e.disabled=-1;e.setDisabled(d)}if(a(e.active)){d=e.active;e.active=-1;e.setActive(d)}},remove:function(){b.remove(this.id);this.destroy()},destroy:function(){c.dom.Event.clear(this.id)}})})(tinymce);tinymce.create("tinymce.ui.Container:tinymce.ui.Control",{Container:function(c,b,a){this.parent(c,b,a);this.controls=[];this.lookup={}},add:function(a){this.lookup[a.id]=a;this.controls.push(a);return a},get:function(a){return this.lookup[a]}});tinymce.create("tinymce.ui.Separator:tinymce.ui.Control",{Separator:function(b,a){this.parent(b,a);this.classPrefix="mceSeparator";this.setDisabled(true)},renderHTML:function(){return tinymce.DOM.createHTML("span",{"class":this.classPrefix,role:"separator","aria-orientation":"vertical",tabindex:"-1"})}});(function(d){var c=d.is,b=d.DOM,e=d.each,a=d.walk;d.create("tinymce.ui.MenuItem:tinymce.ui.Control",{MenuItem:function(g,f){this.parent(g,f);this.classPrefix="mceMenuItem"},setSelected:function(f){this.setState("Selected",f);this.setAriaProperty("checked",!!f);this.selected=f},isSelected:function(){return this.selected},postRender:function(){var f=this;f.parent();if(c(f.selected)){f.setSelected(f.selected)}}})})(tinymce);(function(d){var c=d.is,b=d.DOM,e=d.each,a=d.walk;d.create("tinymce.ui.Menu:tinymce.ui.MenuItem",{Menu:function(h,g){var f=this;f.parent(h,g);f.items={};f.collapsed=false;f.menuCount=0;f.onAddItem=new d.util.Dispatcher(this)},expand:function(g){var f=this;if(g){a(f,function(h){if(h.expand){h.expand()}},"items",f)}f.collapsed=false},collapse:function(g){var f=this;if(g){a(f,function(h){if(h.collapse){h.collapse()}},"items",f)}f.collapsed=true},isCollapsed:function(){return this.collapsed},add:function(f){if(!f.settings){f=new d.ui.MenuItem(f.id||b.uniqueId(),f)}this.onAddItem.dispatch(this,f);return this.items[f.id]=f},addSeparator:function(){return this.add({separator:true})},addMenu:function(f){if(!f.collapse){f=this.createMenu(f)}this.menuCount++;return this.add(f)},hasMenus:function(){return this.menuCount!==0},remove:function(f){delete this.items[f.id]},removeAll:function(){var f=this;a(f,function(g){if(g.removeAll){g.removeAll()}else{g.remove()}g.destroy()},"items",f);f.items={}},createMenu:function(g){var f=new d.ui.Menu(g.id||b.uniqueId(),g);f.onAddItem.add(this.onAddItem.dispatch,this.onAddItem);return f}})})(tinymce);(function(e){var d=e.is,c=e.DOM,f=e.each,a=e.dom.Event,b=e.dom.Element;e.create("tinymce.ui.DropMenu:tinymce.ui.Menu",{DropMenu:function(h,g){g=g||{};g.container=g.container||c.doc.body;g.offset_x=g.offset_x||0;g.offset_y=g.offset_y||0;g.vp_offset_x=g.vp_offset_x||0;g.vp_offset_y=g.vp_offset_y||0;if(d(g.icons)&&!g.icons){g["class"]+=" mceNoIcons"}this.parent(h,g);this.onShowMenu=new e.util.Dispatcher(this);this.onHideMenu=new e.util.Dispatcher(this);this.classPrefix="mceMenu"},createMenu:function(j){var h=this,i=h.settings,g;j.container=j.container||i.container;j.parent=h;j.constrain=j.constrain||i.constrain;j["class"]=j["class"]||i["class"];j.vp_offset_x=j.vp_offset_x||i.vp_offset_x;j.vp_offset_y=j.vp_offset_y||i.vp_offset_y;j.keyboard_focus=i.keyboard_focus;g=new e.ui.DropMenu(j.id||c.uniqueId(),j);g.onAddItem.add(h.onAddItem.dispatch,h.onAddItem);return g},focus:function(){var g=this;if(g.keyboardNav){g.keyboardNav.focus()}},update:function(){var i=this,j=i.settings,g=c.get("menu_"+i.id+"_tbl"),l=c.get("menu_"+i.id+"_co"),h,k;h=j.max_width?Math.min(g.offsetWidth,j.max_width):g.offsetWidth;k=j.max_height?Math.min(g.offsetHeight,j.max_height):g.offsetHeight;if(!c.boxModel){i.element.setStyles({width:h+2,height:k+2})}else{i.element.setStyles({width:h,height:k})}if(j.max_width){c.setStyle(l,"width",h)}if(j.max_height){c.setStyle(l,"height",k);if(g.clientHeightv){p=r?r-u:Math.max(0,(v-A.vp_offset_x)-u)}if((n+A.vp_offset_y+l)>q){n=Math.max(0,(q-A.vp_offset_y)-l)}}c.setStyles(o,{left:p,top:n});z.element.update();z.isMenuVisible=1;z.mouseClickFunc=a.add(o,"click",function(s){var h;s=s.target;if(s&&(s=c.getParent(s,"tr"))&&!c.hasClass(s,m+"ItemSub")){h=z.items[s.id];if(h.isDisabled()){return}k=z;while(k){if(k.hideMenu){k.hideMenu()}k=k.settings.parent}if(h.settings.onclick){h.settings.onclick(s)}return false}});if(z.hasMenus()){z.mouseOverFunc=a.add(o,"mouseover",function(x){var h,t,s;x=x.target;if(x&&(x=c.getParent(x,"tr"))){h=z.items[x.id];if(z.lastMenu){z.lastMenu.collapse(1)}if(h.isDisabled()){return}if(x&&c.hasClass(x,m+"ItemSub")){t=c.getRect(x);h.showMenu((t.x+t.w-i),t.y-i,t.x);z.lastMenu=h;c.addClass(c.get(h.id).firstChild,m+"ItemActive")}}})}a.add(o,"keydown",z._keyHandler,z);z.onShowMenu.dispatch(z);if(A.keyboard_focus){z._setupKeyboardNav()}},hideMenu:function(j){var g=this,i=c.get("menu_"+g.id),h;if(!g.isMenuVisible){return}if(g.keyboardNav){g.keyboardNav.destroy()}a.remove(i,"mouseover",g.mouseOverFunc);a.remove(i,"click",g.mouseClickFunc);a.remove(i,"keydown",g._keyHandler);c.hide(i);g.isMenuVisible=0;if(!j){g.collapse(1)}if(g.element){g.element.hide()}if(h=c.get(g.id)){c.removeClass(h.firstChild,g.classPrefix+"ItemActive")}g.onHideMenu.dispatch(g)},add:function(i){var g=this,h;i=g.parent(i);if(g.isRendered&&(h=c.get("menu_"+g.id))){g._add(c.select("tbody",h)[0],i)}return i},collapse:function(g){this.parent(g);this.hideMenu(1)},remove:function(g){c.remove(g.id);this.destroy();return this.parent(g)},destroy:function(){var g=this,h=c.get("menu_"+g.id);if(g.keyboardNav){g.keyboardNav.destroy()}a.remove(h,"mouseover",g.mouseOverFunc);a.remove(c.select("a",h),"focus",g.mouseOverFunc);a.remove(h,"click",g.mouseClickFunc);a.remove(h,"keydown",g._keyHandler);if(g.element){g.element.remove()}c.remove(h)},renderNode:function(){var i=this,j=i.settings,l,h,k,g;g=c.create("div",{role:"listbox",id:"menu_"+i.id,"class":j["class"],style:"position:absolute;left:0;top:0;z-index:200000;outline:0"});if(i.settings.parent){c.setAttrib(g,"aria-parent","menu_"+i.settings.parent.id)}k=c.add(g,"div",{role:"presentation",id:"menu_"+i.id+"_co","class":i.classPrefix+(j["class"]?" "+j["class"]:"")});i.element=new b("menu_"+i.id,{blocker:1,container:j.container});if(j.menu_line){c.add(k,"span",{"class":i.classPrefix+"Line"})}l=c.add(k,"table",{role:"presentation",id:"menu_"+i.id+"_tbl",border:0,cellPadding:0,cellSpacing:0});h=c.add(l,"tbody");f(i.items,function(m){i._add(h,m)});i.rendered=true;return g},_setupKeyboardNav:function(){var i,h,g=this;i=c.get("menu_"+g.id);h=c.select("a[role=option]","menu_"+g.id);h.splice(0,0,i);g.keyboardNav=new e.ui.KeyboardNavigation({root:"menu_"+g.id,items:h,onCancel:function(){g.hideMenu()},enableUpDown:true});i.focus()},_keyHandler:function(g){var h=this,i;switch(g.keyCode){case 37:if(h.settings.parent){h.hideMenu();h.settings.parent.focus();a.cancel(g)}break;case 39:if(h.mouseOverFunc){h.mouseOverFunc(g)}break}},_add:function(j,h){var i,q=h.settings,p,l,k,m=this.classPrefix,g;if(q.separator){l=c.add(j,"tr",{id:h.id,"class":m+"ItemSeparator"});c.add(l,"td",{"class":m+"ItemSeparator"});if(i=l.previousSibling){c.addClass(i,"mceLast")}return}i=l=c.add(j,"tr",{id:h.id,"class":m+"Item "+m+"ItemEnabled"});i=k=c.add(i,q.titleItem?"th":"td");i=p=c.add(i,"a",{id:h.id+"_aria",role:q.titleItem?"presentation":"option",href:"javascript:;",onclick:"return false;",onmousedown:"return false;"});if(q.parent){c.setAttrib(p,"aria-haspopup","true");c.setAttrib(p,"aria-owns","menu_"+h.id)}c.addClass(k,q["class"]);g=c.add(i,"span",{"class":"mceIcon"+(q.icon?" mce_"+q.icon:"")});if(q.icon_src){c.add(g,"img",{src:q.icon_src})}i=c.add(i,q.element||"span",{"class":"mceText",title:h.settings.title},h.settings.title);if(h.settings.style){if(typeof h.settings.style=="function"){h.settings.style=h.settings.style()}c.setAttrib(i,"style",h.settings.style)}if(j.childNodes.length==1){c.addClass(l,"mceFirst")}if((i=l.previousSibling)&&c.hasClass(i,m+"ItemSeparator")){c.addClass(l,"mceFirst")}if(h.collapse){c.addClass(l,m+"ItemSub")}if(i=l.previousSibling){c.removeClass(i,"mceLast")}c.addClass(l,"mceLast")}})})(tinymce);(function(b){var a=b.DOM;b.create("tinymce.ui.Button:tinymce.ui.Control",{Button:function(e,d,c){this.parent(e,d,c);this.classPrefix="mceButton"},renderHTML:function(){var f=this.classPrefix,e=this.settings,d,c;c=a.encode(e.label||"");d='';if(e.image&&!(this.editor&&this.editor.forcedHighContrastMode)){d+=''+a.encode(e.title)+''+(c?''+c+"":"")}else{d+=''+(c?''+c+"":"")}d+='";d+="";return d},postRender:function(){var d=this,e=d.settings,c;if(b.isIE&&d.editor){b.dom.Event.add(d.id,"mousedown",function(f){var g=d.editor.selection.getNode().nodeName;c=g==="IMG"?d.editor.selection.getBookmark():null})}b.dom.Event.add(d.id,"click",function(f){if(!d.isDisabled()){if(b.isIE&&d.editor&&c!==null){d.editor.selection.moveToBookmark(c)}return e.onclick.call(e.scope,f)}});b.dom.Event.add(d.id,"keyup",function(f){if(!d.isDisabled()&&f.keyCode==b.VK.SPACEBAR){return e.onclick.call(e.scope,f)}})}})})(tinymce);(function(e){var d=e.DOM,b=e.dom.Event,f=e.each,a=e.util.Dispatcher,c;e.create("tinymce.ui.ListBox:tinymce.ui.Control",{ListBox:function(j,i,g){var h=this;h.parent(j,i,g);h.items=[];h.onChange=new a(h);h.onPostRender=new a(h);h.onAdd=new a(h);h.onRenderMenu=new e.util.Dispatcher(this);h.classPrefix="mceListBox";h.marked={}},select:function(h){var g=this,j,i;g.marked={};if(h==c){return g.selectByIndex(-1)}if(h&&typeof(h)=="function"){i=h}else{i=function(k){return k==h}}if(h!=g.selectedValue){f(g.items,function(l,k){if(i(l.value)){j=1;g.selectByIndex(k);return false}});if(!j){g.selectByIndex(-1)}}},selectByIndex:function(g){var i=this,j,k,h;i.marked={};if(g!=i.selectedIndex){j=d.get(i.id+"_text");h=d.get(i.id+"_voiceDesc");k=i.items[g];if(k){i.selectedValue=k.value;i.selectedIndex=g;d.setHTML(j,d.encode(k.title));d.setHTML(h,i.settings.title+" - "+k.title);d.removeClass(j,"mceTitle");d.setAttrib(i.id,"aria-valuenow",k.title)}else{d.setHTML(j,d.encode(i.settings.title));d.setHTML(h,d.encode(i.settings.title));d.addClass(j,"mceTitle");i.selectedValue=i.selectedIndex=null;d.setAttrib(i.id,"aria-valuenow",i.settings.title)}j=0}},mark:function(g){this.marked[g]=true},add:function(j,g,i){var h=this;i=i||{};i=e.extend(i,{title:j,value:g});h.items.push(i);h.onAdd.dispatch(h,i)},getLength:function(){return this.items.length},renderHTML:function(){var j="",g=this,i=g.settings,k=g.classPrefix;j='';j+="";j+="";j+="";return j},showMenu:function(){var h=this,j,i=d.get(this.id),g;if(h.isDisabled()||h.items.length===0){return}if(h.menu&&h.menu.isMenuVisible){return h.hideMenu()}if(!h.isMenuRendered){h.renderMenu();h.isMenuRendered=true}j=d.getPos(i);g=h.menu;g.settings.offset_x=j.x;g.settings.offset_y=j.y;g.settings.keyboard_focus=!e.isOpera;f(h.items,function(k){if(g.items[k.id]){g.items[k.id].setSelected(0)}});f(h.items,function(k){if(g.items[k.id]&&h.marked[k.value]){g.items[k.id].setSelected(1)}if(k.value===h.selectedValue){g.items[k.id].setSelected(1)}});g.showMenu(0,i.clientHeight);b.add(d.doc,"mousedown",h.hideMenu,h);d.addClass(h.id,h.classPrefix+"Selected")},hideMenu:function(h){var g=this;if(g.menu&&g.menu.isMenuVisible){d.removeClass(g.id,g.classPrefix+"Selected");if(h&&h.type=="mousedown"&&(h.target.id==g.id+"_text"||h.target.id==g.id+"_open")){return}if(!h||!d.getParent(h.target,".mceMenu")){d.removeClass(g.id,g.classPrefix+"Selected");b.remove(d.doc,"mousedown",g.hideMenu,g);g.menu.hideMenu()}}},renderMenu:function(){var h=this,g;g=h.settings.control_manager.createDropMenu(h.id+"_menu",{menu_line:1,"class":h.classPrefix+"Menu mceNoIcons",max_width:250,max_height:150});g.onHideMenu.add(function(){h.hideMenu();h.focus()});g.add({title:h.settings.title,"class":"mceMenuItemTitle",onclick:function(){if(h.settings.onselect("")!==false){h.select("")}}});f(h.items,function(i){if(i.value===c){g.add({title:i.title,role:"option","class":"mceMenuItemTitle",onclick:function(){if(h.settings.onselect("")!==false){h.select("")}}})}else{i.id=d.uniqueId();i.role="option";i.onclick=function(){if(h.settings.onselect(i.value)!==false){h.select(i.value)}};g.add(i)}});h.onRenderMenu.dispatch(h,g);h.menu=g},postRender:function(){var g=this,h=g.classPrefix;b.add(g.id,"click",g.showMenu,g);b.add(g.id,"keydown",function(i){if(i.keyCode==32){g.showMenu(i);b.cancel(i)}});b.add(g.id,"focus",function(){if(!g._focused){g.keyDownHandler=b.add(g.id,"keydown",function(i){if(i.keyCode==40){g.showMenu();b.cancel(i)}});g.keyPressHandler=b.add(g.id,"keypress",function(j){var i;if(j.keyCode==13){i=g.selectedValue;g.selectedValue=null;b.cancel(j);g.settings.onselect(i)}})}g._focused=1});b.add(g.id,"blur",function(){b.remove(g.id,"keydown",g.keyDownHandler);b.remove(g.id,"keypress",g.keyPressHandler);g._focused=0});if(e.isIE6||!d.boxModel){b.add(g.id,"mouseover",function(){if(!d.hasClass(g.id,h+"Disabled")){d.addClass(g.id,h+"Hover")}});b.add(g.id,"mouseout",function(){if(!d.hasClass(g.id,h+"Disabled")){d.removeClass(g.id,h+"Hover")}})}g.onPostRender.dispatch(g,d.get(g.id))},destroy:function(){this.parent();b.clear(this.id+"_text");b.clear(this.id+"_open")}})})(tinymce);(function(e){var d=e.DOM,b=e.dom.Event,f=e.each,a=e.util.Dispatcher,c;e.create("tinymce.ui.NativeListBox:tinymce.ui.ListBox",{NativeListBox:function(h,g){this.parent(h,g);this.classPrefix="mceNativeListBox"},setDisabled:function(g){d.get(this.id).disabled=g;this.setAriaProperty("disabled",g)},isDisabled:function(){return d.get(this.id).disabled},select:function(h){var g=this,j,i;if(h==c){return g.selectByIndex(-1)}if(h&&typeof(h)=="function"){i=h}else{i=function(k){return k==h}}if(h!=g.selectedValue){f(g.items,function(l,k){if(i(l.value)){j=1;g.selectByIndex(k);return false}});if(!j){g.selectByIndex(-1)}}},selectByIndex:function(g){d.get(this.id).selectedIndex=g+1;this.selectedValue=this.items[g]?this.items[g].value:null},add:function(k,h,g){var j,i=this;g=g||{};g.value=h;if(i.isRendered()){d.add(d.get(this.id),"option",g,k)}j={title:k,value:h,attribs:g};i.items.push(j);i.onAdd.dispatch(i,j)},getLength:function(){return this.items.length},renderHTML:function(){var i,g=this;i=d.createHTML("option",{value:""},"-- "+g.settings.title+" --");f(g.items,function(h){i+=d.createHTML("option",{value:h.value},h.title)});i=d.createHTML("select",{id:g.id,"class":"mceNativeListBox","aria-labelledby":g.id+"_aria"},i);i+=d.createHTML("span",{id:g.id+"_aria",style:"display: none"},g.settings.title);return i},postRender:function(){var h=this,i,j=true;h.rendered=true;function g(l){var k=h.items[l.target.selectedIndex-1];if(k&&(k=k.value)){h.onChange.dispatch(h,k);if(h.settings.onselect){h.settings.onselect(k)}}}b.add(h.id,"change",g);b.add(h.id,"keydown",function(l){var k;b.remove(h.id,"change",i);j=false;k=b.add(h.id,"blur",function(){if(j){return}j=true;b.add(h.id,"change",g);b.remove(h.id,"blur",k)});if(e.isWebKit&&(l.keyCode==37||l.keyCode==39)){return b.prevent(l)}if(l.keyCode==13||l.keyCode==32){g(l);return b.cancel(l)}});h.onPostRender.dispatch(h,d.get(h.id))}})})(tinymce);(function(c){var b=c.DOM,a=c.dom.Event,d=c.each;c.create("tinymce.ui.MenuButton:tinymce.ui.Button",{MenuButton:function(g,f,e){this.parent(g,f,e);this.onRenderMenu=new c.util.Dispatcher(this);f.menu_container=f.menu_container||b.doc.body},showMenu:function(){var g=this,j,i,h=b.get(g.id),f;if(g.isDisabled()){return}if(!g.isMenuRendered){g.renderMenu();g.isMenuRendered=true}if(g.isMenuVisible){return g.hideMenu()}j=b.getPos(g.settings.menu_container);i=b.getPos(h);f=g.menu;f.settings.offset_x=i.x;f.settings.offset_y=i.y;f.settings.vp_offset_x=i.x;f.settings.vp_offset_y=i.y;f.settings.keyboard_focus=g._focused;f.showMenu(0,h.firstChild.clientHeight);a.add(b.doc,"mousedown",g.hideMenu,g);g.setState("Selected",1);g.isMenuVisible=1},renderMenu:function(){var f=this,e;e=f.settings.control_manager.createDropMenu(f.id+"_menu",{menu_line:1,"class":this.classPrefix+"Menu",icons:f.settings.icons});e.onHideMenu.add(function(){f.hideMenu();f.focus()});f.onRenderMenu.dispatch(f,e);f.menu=e},hideMenu:function(g){var f=this;if(g&&g.type=="mousedown"&&b.getParent(g.target,function(h){return h.id===f.id||h.id===f.id+"_open"})){return}if(!g||!b.getParent(g.target,".mceMenu")){f.setState("Selected",0);a.remove(b.doc,"mousedown",f.hideMenu,f);if(f.menu){f.menu.hideMenu()}}f.isMenuVisible=0},postRender:function(){var e=this,f=e.settings;a.add(e.id,"click",function(){if(!e.isDisabled()){if(f.onclick){f.onclick(e.value)}e.showMenu()}})}})})(tinymce);(function(c){var b=c.DOM,a=c.dom.Event,d=c.each;c.create("tinymce.ui.SplitButton:tinymce.ui.MenuButton",{SplitButton:function(g,f,e){this.parent(g,f,e);this.classPrefix="mceSplitButton"},renderHTML:function(){var i,f=this,g=f.settings,e;i="";if(g.image){e=b.createHTML("img ",{src:g.image,role:"presentation","class":"mceAction "+g["class"]})}else{e=b.createHTML("span",{"class":"mceAction "+g["class"]},"")}e+=b.createHTML("span",{"class":"mceVoiceLabel mceIconOnly",id:f.id+"_voice",style:"display:none;"},g.title);i+=""+b.createHTML("a",{role:"button",id:f.id+"_action",tabindex:"-1",href:"javascript:;","class":"mceAction "+g["class"],onclick:"return false;",onmousedown:"return false;",title:g.title},e)+"";e=b.createHTML("span",{"class":"mceOpen "+g["class"]},'');i+=""+b.createHTML("a",{role:"button",id:f.id+"_open",tabindex:"-1",href:"javascript:;","class":"mceOpen "+g["class"],onclick:"return false;",onmousedown:"return false;",title:g.title},e)+"";i+="";i=b.createHTML("table",{role:"presentation","class":"mceSplitButton mceSplitButtonEnabled "+g["class"],cellpadding:"0",cellspacing:"0",title:g.title},i);return b.createHTML("div",{id:f.id,role:"button",tabindex:"0","aria-labelledby":f.id+"_voice","aria-haspopup":"true"},i)},postRender:function(){var e=this,g=e.settings,f;if(g.onclick){f=function(h){if(!e.isDisabled()){g.onclick(e.value);a.cancel(h)}};a.add(e.id+"_action","click",f);a.add(e.id,["click","keydown"],function(h){var k=32,m=14,i=13,j=38,l=40;if((h.keyCode===32||h.keyCode===13||h.keyCode===14)&&!h.altKey&&!h.ctrlKey&&!h.metaKey){f();a.cancel(h)}else{if(h.type==="click"||h.keyCode===l){e.showMenu();a.cancel(h)}}})}a.add(e.id+"_open","click",function(h){e.showMenu();a.cancel(h)});a.add([e.id,e.id+"_open"],"focus",function(){e._focused=1});a.add([e.id,e.id+"_open"],"blur",function(){e._focused=0});if(c.isIE6||!b.boxModel){a.add(e.id,"mouseover",function(){if(!b.hasClass(e.id,"mceSplitButtonDisabled")){b.addClass(e.id,"mceSplitButtonHover")}});a.add(e.id,"mouseout",function(){if(!b.hasClass(e.id,"mceSplitButtonDisabled")){b.removeClass(e.id,"mceSplitButtonHover")}})}},destroy:function(){this.parent();a.clear(this.id+"_action");a.clear(this.id+"_open");a.clear(this.id)}})})(tinymce);(function(d){var c=d.DOM,a=d.dom.Event,b=d.is,e=d.each;d.create("tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton",{ColorSplitButton:function(i,h,f){var g=this;g.parent(i,h,f);g.settings=h=d.extend({colors:"000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF",grid_width:8,default_color:"#888888"},g.settings);g.onShowMenu=new d.util.Dispatcher(g);g.onHideMenu=new d.util.Dispatcher(g);g.value=h.default_color},showMenu:function(){var f=this,g,j,i,h;if(f.isDisabled()){return}if(!f.isMenuRendered){f.renderMenu();f.isMenuRendered=true}if(f.isMenuVisible){return f.hideMenu()}i=c.get(f.id);c.show(f.id+"_menu");c.addClass(i,"mceSplitButtonSelected");h=c.getPos(i);c.setStyles(f.id+"_menu",{left:h.x,top:h.y+i.firstChild.clientHeight,zIndex:200000});i=0;a.add(c.doc,"mousedown",f.hideMenu,f);f.onShowMenu.dispatch(f);if(f._focused){f._keyHandler=a.add(f.id+"_menu","keydown",function(k){if(k.keyCode==27){f.hideMenu()}});c.select("a",f.id+"_menu")[0].focus()}f.keyboardNav=new d.ui.KeyboardNavigation({root:f.id+"_menu",items:c.select("a",f.id+"_menu"),onCancel:function(){f.hideMenu();f.focus()}});f.keyboardNav.focus();f.isMenuVisible=1},hideMenu:function(g){var f=this;if(f.isMenuVisible){if(g&&g.type=="mousedown"&&c.getParent(g.target,function(h){return h.id===f.id+"_open"})){return}if(!g||!c.getParent(g.target,".mceSplitButtonMenu")){c.removeClass(f.id,"mceSplitButtonSelected");a.remove(c.doc,"mousedown",f.hideMenu,f);a.remove(f.id+"_menu","keydown",f._keyHandler);c.hide(f.id+"_menu")}f.isMenuVisible=0;f.onHideMenu.dispatch();f.keyboardNav.destroy()}},renderMenu:function(){var p=this,h,k=0,q=p.settings,g,j,l,o,f;o=c.add(q.menu_container,"div",{role:"listbox",id:p.id+"_menu","class":q.menu_class+" "+q["class"],style:"position:absolute;left:0;top:-1000px;"});h=c.add(o,"div",{"class":q["class"]+" mceSplitButtonMenu"});c.add(h,"span",{"class":"mceMenuLine"});g=c.add(h,"table",{role:"presentation","class":"mceColorSplitMenu"});j=c.add(g,"tbody");k=0;e(b(q.colors,"array")?q.colors:q.colors.split(","),function(m){m=m.replace(/^#/,"");if(!k--){l=c.add(j,"tr");k=q.grid_width-1}g=c.add(l,"td");var i={href:"javascript:;",style:{backgroundColor:"#"+m},title:p.editor.getLang("colors."+m,m),"data-mce-color":"#"+m};if(!d.isIE){i.role="option"}g=c.add(g,"a",i);if(p.editor.forcedHighContrastMode){g=c.add(g,"canvas",{width:16,height:16,"aria-hidden":"true"});if(g.getContext&&(f=g.getContext("2d"))){f.fillStyle="#"+m;f.fillRect(0,0,16,16)}else{c.remove(g)}}});if(q.more_colors_func){g=c.add(j,"tr");g=c.add(g,"td",{colspan:q.grid_width,"class":"mceMoreColors"});g=c.add(g,"a",{role:"option",id:p.id+"_more",href:"javascript:;",onclick:"return false;","class":"mceMoreColors"},q.more_colors_title);a.add(g,"click",function(i){q.more_colors_func.call(q.more_colors_scope||this);return a.cancel(i)})}c.addClass(h,"mceColorSplitMenu");a.add(p.id+"_menu","mousedown",function(i){return a.cancel(i)});a.add(p.id+"_menu","click",function(i){var m;i=c.getParent(i.target,"a",j);if(i&&i.nodeName.toLowerCase()=="a"&&(m=i.getAttribute("data-mce-color"))){p.setColor(m)}return false});return o},setColor:function(f){this.displayColor(f);this.hideMenu();this.settings.onselect(f)},displayColor:function(g){var f=this;c.setStyle(f.id+"_preview","backgroundColor",g);f.value=g},postRender:function(){var f=this,g=f.id;f.parent();c.add(g+"_action","div",{id:g+"_preview","class":"mceColorPreview"});c.setStyle(f.id+"_preview","backgroundColor",f.value)},destroy:function(){var f=this;f.parent();a.clear(f.id+"_menu");a.clear(f.id+"_more");c.remove(f.id+"_menu");if(f.keyboardNav){f.keyboardNav.destroy()}}})})(tinymce);(function(b){var d=b.DOM,c=b.each,a=b.dom.Event;b.create("tinymce.ui.ToolbarGroup:tinymce.ui.Container",{renderHTML:function(){var f=this,i=[],e=f.controls,j=b.each,g=f.settings;i.push('
');i.push("");i.push('");j(e,function(h){i.push(h.renderHTML())});i.push("");i.push("
");return i.join("")},focus:function(){var e=this;d.get(e.id).focus()},postRender:function(){var f=this,e=[];c(f.controls,function(g){c(g.controls,function(h){if(h.id){e.push(h)}})});f.keyNav=new b.ui.KeyboardNavigation({root:f.id,items:e,onCancel:function(){if(b.isWebKit){d.get(f.editor.id+"_ifr").focus()}f.editor.focus()},excludeFromTabOrder:!f.settings.tab_focus_toolbar})},destroy:function(){var e=this;e.parent();e.keyNav.destroy();a.clear(e.id)}})})(tinymce);(function(a){var c=a.DOM,b=a.each;a.create("tinymce.ui.Toolbar:tinymce.ui.Container",{renderHTML:function(){var m=this,f="",j,k,n=m.settings,e,d,g,l;l=m.controls;for(e=0;e"))}if(d&&k.ListBox){if(d.Button||d.SplitButton){f+=c.createHTML("td",{"class":"mceToolbarEnd"},c.createHTML("span",null,""))}}if(c.stdMode){f+=''+k.renderHTML()+""}else{f+=""+k.renderHTML()+""}if(g&&k.ListBox){if(g.Button||g.SplitButton){f+=c.createHTML("td",{"class":"mceToolbarStart"},c.createHTML("span",null,""))}}}j="mceToolbarEnd";if(k.Button){j+=" mceToolbarEndButton"}else{if(k.SplitButton){j+=" mceToolbarEndSplitButton"}else{if(k.ListBox){j+=" mceToolbarEndListBox"}}}f+=c.createHTML("td",{"class":j},c.createHTML("span",null,""));return c.createHTML("table",{id:m.id,"class":"mceToolbar"+(n["class"]?" "+n["class"]:""),cellpadding:"0",cellspacing:"0",align:m.settings.align||"",role:"presentation",tabindex:"-1"},""+f+"")}})})(tinymce);(function(b){var a=b.util.Dispatcher,c=b.each;b.create("tinymce.AddOnManager",{AddOnManager:function(){var d=this;d.items=[];d.urls={};d.lookup={};d.onAdd=new a(d)},get:function(d){if(this.lookup[d]){return this.lookup[d].instance}else{return undefined}},dependencies:function(e){var d;if(this.lookup[e]){d=this.lookup[e].dependencies}return d||[]},requireLangPack:function(e){var d=b.settings;if(d&&d.language&&d.language_load!==false){b.ScriptLoader.add(this.urls[e]+"/langs/"+d.language+".js")}},add:function(f,e,d){this.items.push(e);this.lookup[f]={instance:e,dependencies:d};this.onAdd.dispatch(this,f,e);return e},createUrl:function(d,e){if(typeof e==="object"){return e}else{return{prefix:d.prefix,resource:e,suffix:d.suffix}}},addComponents:function(f,d){var e=this.urls[f];b.each(d,function(g){b.ScriptLoader.add(e+"/"+g)})},load:function(j,f,d,h){var g=this,e=f;function i(){var k=g.dependencies(j);b.each(k,function(m){var l=g.createUrl(f,m);g.load(l.resource,l,undefined,undefined)});if(d){if(h){d.call(h)}else{d.call(b.ScriptLoader)}}}if(g.urls[j]){return}if(typeof f==="object"){e=f.prefix+f.resource+f.suffix}if(e.indexOf("/")!==0&&e.indexOf("://")==-1){e=b.baseURL+"/"+e}g.urls[j]=e.substring(0,e.lastIndexOf("/"));if(g.lookup[j]){i()}else{b.ScriptLoader.add(e,i,h)}}});b.PluginManager=new b.AddOnManager();b.ThemeManager=new b.AddOnManager()}(tinymce));(function(j){var g=j.each,d=j.extend,k=j.DOM,i=j.dom.Event,f=j.ThemeManager,b=j.PluginManager,e=j.explode,h=j.util.Dispatcher,a,c=0;j.documentBaseURL=window.location.href.replace(/[\?#].*$/,"").replace(/[\/\\][^\/]+$/,"");if(!/[\/\\]$/.test(j.documentBaseURL)){j.documentBaseURL+="/"}j.baseURL=new j.util.URI(j.documentBaseURL).toAbsolute(j.baseURL);j.baseURI=new j.util.URI(j.baseURL);j.onBeforeUnload=new h(j);i.add(window,"beforeunload",function(l){j.onBeforeUnload.dispatch(j,l)});j.onAddEditor=new h(j);j.onRemoveEditor=new h(j);j.EditorManager=d(j,{editors:[],i18n:{},activeEditor:null,init:function(x){var v=this,o,n=j.ScriptLoader,u,l=[],r;function q(t){var s=t.id;if(!s){s=t.name;if(s&&!k.get(s)){s=t.name}else{s=k.uniqueId()}t.setAttribute("id",s)}return s}function m(z,A,t){var y=z[A];if(!y){return}if(j.is(y,"string")){t=y.replace(/\.\w+$/,"");t=t?j.resolve(t):0;y=j.resolve(y)}return y.apply(t||this,Array.prototype.slice.call(arguments,2))}function p(t,s){return s.constructor===RegExp?s.test(t.className):k.hasClass(t,s)}v.settings=x;i.bind(window,"ready",function(){var s,t;m(x,"onpageload");switch(x.mode){case"exact":s=x.elements||"";if(s.length>0){g(e(s),function(y){if(k.get(y)){r=new j.Editor(y,x);l.push(r);r.render(1)}else{g(document.forms,function(z){g(z.elements,function(A){if(A.name===y){y="mce_editor_"+c++;k.setAttrib(A,"id",y);r=new j.Editor(y,x);l.push(r);r.render(1)}})})}})}break;case"textareas":case"specific_textareas":g(k.select("textarea"),function(y){if(x.editor_deselector&&p(y,x.editor_deselector)){return}if(!x.editor_selector||p(y,x.editor_selector)){r=new j.Editor(q(y),x);l.push(r);r.render(1)}});break;default:if(x.types){g(x.types,function(y){g(k.select(y.selector),function(A){var z=new j.Editor(q(A),j.extend({},x,y));l.push(z);z.render(1)})})}else{if(x.selector){g(k.select(x.selector),function(z){var y=new j.Editor(q(z),x);l.push(y);y.render(1)})}}}if(x.oninit){s=t=0;g(l,function(y){t++;if(!y.initialized){y.onInit.add(function(){s++;if(s==t){m(x,"oninit")}})}else{s++}if(s==t){m(x,"oninit")}})}})},get:function(l){if(l===a){return this.editors}if(!this.editors.hasOwnProperty(l)){return a}return this.editors[l]},getInstanceById:function(l){return this.get(l)},add:function(m){var l=this,n=l.editors;n[m.id]=m;n.push(m);l._setActive(m);l.onAddEditor.dispatch(l,m);return m},remove:function(n){var m=this,l,o=m.editors;if(!o[n.id]){return null}delete o[n.id];for(l=0;l':"",visual:n,font_size_style_values:"xx-small,x-small,small,medium,large,x-large,xx-large",font_size_legacy_values:"xx-small,small,medium,large,x-large,xx-large,300%",apply_source_formatting:n,directionality:"ltr",forced_root_block:"p",hidden_input:n,padd_empty_editor:n,render_ui:n,indentation:"30px",fix_table_elements:n,inline_styles:n,convert_fonts_to_spans:n,indent:"simple",indent_before:"p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist",indent_after:"p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist",validate:n,entity_encoding:"named",url_converter:m.convertURL,url_converter_scope:m,ie7_compat:n},o);m.id=m.editorId=p;m.isNotDirty=false;m.plugins={};m.documentBaseURI=new k.util.URI(o.document_base_url||k.documentBaseURL,{base_uri:tinyMCE.baseURI});m.baseURI=k.baseURI;m.contentCSS=[];m.contentStyles=[];m.setupEvents();m.execCommands={};m.queryStateCommands={};m.queryValueCommands={};m.execCallback("setup",m)},render:function(o){var p=this,q=p.settings,r=p.id,m=k.ScriptLoader;if(!j.domLoaded){j.add(window,"ready",function(){p.render()});return}tinyMCE.settings=q;if(!p.getElement()){return}if(k.isIDevice&&!k.isIOS5){return}if(!/TEXTAREA|INPUT/i.test(p.getElement().nodeName)&&q.hidden_input&&l.getParent(r,"form")){l.insertAfter(l.create("input",{type:"hidden",name:r}),r)}if(!q.content_editable){p.orgVisibility=p.getElement().style.visibility;p.getElement().style.visibility="hidden"}if(k.WindowManager){p.windowManager=new k.WindowManager(p)}if(q.encoding=="xml"){p.onGetContent.add(function(s,t){if(t.save){t.content=l.encode(t.content)}})}if(q.add_form_submit_trigger){p.onSubmit.addToTop(function(){if(p.initialized){p.save();p.isNotDirty=1}})}if(q.add_unload_trigger){p._beforeUnload=tinyMCE.onBeforeUnload.add(function(){if(p.initialized&&!p.destroyed&&!p.isHidden()){p.save({format:"raw",no_events:true})}})}k.addUnload(p.destroy,p);if(q.submit_patch){p.onBeforeRenderUI.add(function(){var s=p.getElement().form;if(!s){return}if(s._mceOldSubmit){return}if(!s.submit.nodeType&&!s.submit.length){p.formElement=s;s._mceOldSubmit=s.submit;s.submit=function(){k.triggerSave();p.isNotDirty=1;return p.formElement._mceOldSubmit(p.formElement)}}s=null})}function n(){if(q.language&&q.language_load!==false){m.add(k.baseURL+"/langs/"+q.language+".js")}if(q.theme&&typeof q.theme!="function"&&q.theme.charAt(0)!="-"&&!h.urls[q.theme]){h.load(q.theme,"themes/"+q.theme+"/editor_template"+k.suffix+".js")}i(g(q.plugins),function(t){if(t&&!c.urls[t]){if(t.charAt(0)=="-"){t=t.substr(1,t.length);var s=c.dependencies(t);i(s,function(v){var u={prefix:"plugins/",resource:v,suffix:"/editor_plugin"+k.suffix+".js"};v=c.createUrl(u,v);c.load(v.resource,v)})}else{if(t=="safari"){return}c.load(t,{prefix:"plugins/",resource:t,suffix:"/editor_plugin"+k.suffix+".js"})}}});m.loadQueue(function(){if(!p.removed){p.init()}})}n()},init:function(){var q,G=this,H=G.settings,D,y,z,C=G.getElement(),p,m,E,v,B,F,x,r=[];k.add(G);H.aria_label=H.aria_label||l.getAttrib(C,"aria-label",G.getLang("aria.rich_text_area"));if(H.theme){if(typeof H.theme!="function"){H.theme=H.theme.replace(/-/,"");p=h.get(H.theme);G.theme=new p();if(G.theme.init){G.theme.init(G,h.urls[H.theme]||k.documentBaseURL.replace(/\/$/,""))}}else{G.theme=H.theme}}function A(s){var t=c.get(s),o=c.urls[s]||k.documentBaseURL.replace(/\/$/,""),n;if(t&&k.inArray(r,s)===-1){i(c.dependencies(s),function(u){A(u)});n=new t(G,o);G.plugins[s]=n;if(n.init){n.init(G,o);r.push(s)}}}i(g(H.plugins.replace(/\-/g,"")),A);if(H.popup_css!==false){if(H.popup_css){H.popup_css=G.documentBaseURI.toAbsolute(H.popup_css)}else{H.popup_css=G.baseURI.toAbsolute("themes/"+H.theme+"/skins/"+H.skin+"/dialog.css")}}if(H.popup_css_add){H.popup_css+=","+G.documentBaseURI.toAbsolute(H.popup_css_add)}G.controlManager=new k.ControlManager(G);G.onBeforeRenderUI.dispatch(G,G.controlManager);if(H.render_ui&&G.theme){G.orgDisplay=C.style.display;if(typeof H.theme!="function"){D=H.width||C.style.width||C.offsetWidth;y=H.height||C.style.height||C.offsetHeight;z=H.min_height||100;F=/^[0-9\.]+(|px)$/i;if(F.test(""+D)){D=Math.max(parseInt(D,10)+(p.deltaWidth||0),100)}if(F.test(""+y)){y=Math.max(parseInt(y,10)+(p.deltaHeight||0),z)}p=G.theme.renderUI({targetNode:C,width:D,height:y,deltaWidth:H.delta_width,deltaHeight:H.delta_height});l.setStyles(p.sizeContainer||p.editorContainer,{width:D,height:y});y=(p.iframeHeight||y)+(typeof(y)=="number"?(p.deltaHeight||0):"");if(y';if(H.document_base_url!=k.documentBaseURL){G.iframeHTML+=''}if(H.ie7_compat){G.iframeHTML+=''}else{G.iframeHTML+=''}G.iframeHTML+='';for(x=0;x'}G.contentCSS=[];v=H.body_id||"tinymce";if(v.indexOf("=")!=-1){v=G.getParam("body_id","","hash");v=v[G.id]||v}B=H.body_class||"";if(B.indexOf("=")!=-1){B=G.getParam("body_class","","hash");B=B[G.id]||""}G.iframeHTML+='
";if(k.relaxedDomain&&(b||(k.isOpera&&parseFloat(opera.version())<11))){E='javascript:(function(){document.open();document.domain="'+document.domain+'";var ed = window.parent.tinyMCE.get("'+G.id+'");document.write(ed.iframeHTML);document.close();ed.initContentBody();})()'}q=l.add(p.iframeContainer,"iframe",{id:G.id+"_ifr",src:E||'javascript:""',frameBorder:"0",allowTransparency:"true",title:H.aria_label,style:{width:"100%",height:y,display:"block"}});G.contentAreaContainer=p.iframeContainer;if(p.editorContainer){l.get(p.editorContainer).style.display=G.orgDisplay}C.style.visibility=G.orgVisibility;l.get(G.id).style.display="none";l.setAttrib(G.id,"aria-hidden",true);if(!k.relaxedDomain||!E){G.initContentBody()}C=q=p=null},initContentBody:function(){var n=this,p=n.settings,q=l.get(n.id),r=n.getDoc(),o,m,s;if((!b||!k.relaxedDomain)&&!p.content_editable){r.open();r.write(n.iframeHTML);r.close();if(k.relaxedDomain){r.domain=k.relaxedDomain}}if(p.content_editable){l.addClass(q,"mceContentBody");n.contentDocument=r=p.content_document||document;n.contentWindow=p.content_window||window;n.bodyElement=q;p.content_document=p.content_window=null}m=n.getBody();m.disabled=true;if(!p.readonly){m.contentEditable=n.getParam("content_editable_state",true)}m.disabled=false;n.schema=new k.html.Schema(p);n.dom=new k.dom.DOMUtils(r,{keep_values:true,url_converter:n.convertURL,url_converter_scope:n,hex_colors:p.force_hex_style_colors,class_filter:p.class_filter,update_styles:true,root_element:p.content_editable?n.id:null,schema:n.schema});n.parser=new k.html.DomParser(p,n.schema);n.parser.addAttributeFilter("src,href,style",function(t,u){var v=t.length,y,A=n.dom,z,x;while(v--){y=t[v];z=y.attr(u);x="data-mce-"+u;if(!y.attributes.map[x]){if(u==="style"){y.attr(x,A.serializeStyle(A.parseStyle(z),y.name))}else{y.attr(x,n.convertURL(z,u,y.name))}}}});n.parser.addNodeFilter("script",function(t,u){var v=t.length,x;while(v--){x=t[v];x.attr("type","mce-"+(x.attr("type")||"text/javascript"))}});n.parser.addNodeFilter("#cdata",function(t,u){var v=t.length,x;while(v--){x=t[v];x.type=8;x.name="#comment";x.value="[CDATA["+x.value+"]]"}});n.parser.addNodeFilter("p,h1,h2,h3,h4,h5,h6,div",function(u,v){var x=u.length,y,t=n.schema.getNonEmptyElements();while(x--){y=u[x];if(y.isEmpty(t)){y.empty().append(new k.html.Node("br",1)).shortEnded=true}}});n.serializer=new k.dom.Serializer(p,n.dom,n.schema);n.selection=new k.dom.Selection(n.dom,n.getWin(),n.serializer,n);n.formatter=new k.Formatter(n);n.undoManager=new k.UndoManager(n);n.forceBlocks=new k.ForceBlocks(n);n.enterKey=new k.EnterKey(n);n.editorCommands=new k.EditorCommands(n);n.onExecCommand.add(function(t,u){if(!/^(FontName|FontSize)$/.test(u)){n.nodeChanged()}});n.serializer.onPreProcess.add(function(t,u){return n.onPreProcess.dispatch(n,u,t)});n.serializer.onPostProcess.add(function(t,u){return n.onPostProcess.dispatch(n,u,t)});n.onPreInit.dispatch(n);if(!p.browser_spellcheck&&!p.gecko_spellcheck){r.body.spellcheck=false}if(!p.readonly){n.bindNativeEvents()}n.controlManager.onPostRender.dispatch(n,n.controlManager);n.onPostRender.dispatch(n);n.quirks=k.util.Quirks(n);if(p.directionality){m.dir=p.directionality}if(p.nowrap){m.style.whiteSpace="nowrap"}if(p.protect){n.onBeforeSetContent.add(function(t,u){i(p.protect,function(v){u.content=u.content.replace(v,function(x){return""})})})}n.onSetContent.add(function(){n.addVisual(n.getBody())});if(p.padd_empty_editor){n.onPostProcess.add(function(t,u){u.content=u.content.replace(/^(]*>( | |\s|\u00a0|)<\/p>[\r\n]*|
[\r\n]*)$/,"")})}n.load({initial:true,format:"html"});n.startContent=n.getContent({format:"raw"});n.initialized=true;n.onInit.dispatch(n);n.execCallback("setupcontent_callback",n.id,m,r);n.execCallback("init_instance_callback",n);n.focus(true);n.nodeChanged({initial:true});if(n.contentStyles.length>0){s="";i(n.contentStyles,function(t){s+=t+"\r\n"});n.dom.addStyle(s)}i(n.contentCSS,function(t){n.dom.loadCSS(t)});if(p.auto_focus){setTimeout(function(){var t=k.get(p.auto_focus);t.selection.select(t.getBody(),1);t.selection.collapse(1);t.getBody().focus();t.getWin().focus()},100)}q=r=m=null},focus:function(p){var o,u=this,t=u.selection,q=u.settings.content_editable,n,r,s=u.getDoc(),m;if(!p){if(u.lastIERng){t.setRng(u.lastIERng)}n=t.getRng();if(n.item){r=n.item(0)}u._refreshContentEditable();if(!q){u.getWin().focus()}if(k.isGecko||q){m=u.getBody();if(m.setActive){m.setActive()}else{m.focus()}if(q){t.normalize()}}if(r&&r.ownerDocument==s){n=s.body.createControlRange();n.addElement(r);n.select()}}if(k.activeEditor!=u){if((o=k.activeEditor)!=null){o.onDeactivate.dispatch(o,u)}u.onActivate.dispatch(u,o)}k._setActive(u)},execCallback:function(q){var m=this,p=m.settings[q],o;if(!p){return}if(m.callbackLookup&&(o=m.callbackLookup[q])){p=o.func;o=o.scope}if(d(p,"string")){o=p.replace(/\.\w+$/,"");o=o?k.resolve(o):0;p=k.resolve(p);m.callbackLookup=m.callbackLookup||{};m.callbackLookup[q]={func:p,scope:o}}return p.apply(o||m,Array.prototype.slice.call(arguments,1))},translate:function(m){var o=this.settings.language||"en",n=k.i18n;if(!m){return""}return n[o+"."+m]||m.replace(/\{\#([^\}]+)\}/g,function(q,p){return n[o+"."+p]||"{#"+p+"}"})},getLang:function(o,m){return k.i18n[(this.settings.language||"en")+"."+o]||(d(m)?m:"{#"+o+"}")},getParam:function(t,q,m){var r=k.trim,p=d(this.settings[t])?this.settings[t]:q,s;if(m==="hash"){s={};if(d(p,"string")){i(p.indexOf("=")>0?p.split(/[;,](?![^=;,]*(?:[;,]|$))/):p.split(","),function(n){n=n.split("=");if(n.length>1){s[r(n[0])]=r(n[1])}else{s[r(n[0])]=r(n)}})}else{s=p}return s}return p},nodeChanged:function(q){var m=this,n=m.selection,p;if(m.initialized){q=q||{};p=n.getStart()||m.getBody();p=b&&p.ownerDocument!=m.getDoc()?m.getBody():p;q.parents=[];m.dom.getParent(p,function(o){if(o.nodeName=="BODY"){return true}q.parents.push(o)});m.onNodeChange.dispatch(m,q?q.controlManager||m.controlManager:m.controlManager,p,n.isCollapsed(),q)}},addButton:function(n,o){var m=this;m.buttons=m.buttons||{};m.buttons[n]=o},addCommand:function(m,o,n){this.execCommands[m]={func:o,scope:n||this}},addQueryStateHandler:function(m,o,n){this.queryStateCommands[m]={func:o,scope:n||this}},addQueryValueHandler:function(m,o,n){this.queryValueCommands[m]={func:o,scope:n||this}},addShortcut:function(o,q,m,p){var n=this,r;if(n.settings.custom_shortcuts===false){return false}n.shortcuts=n.shortcuts||{};if(d(m,"string")){r=m;m=function(){n.execCommand(r,false,null)}}if(d(m,"object")){r=m;m=function(){n.execCommand(r[0],r[1],r[2])}}i(g(o),function(s){var t={func:m,scope:p||this,desc:n.translate(q),alt:false,ctrl:false,shift:false};i(g(s,"+"),function(u){switch(u){case"alt":case"ctrl":case"shift":t[u]=true;break;default:t.charCode=u.charCodeAt(0);t.keyCode=u.toUpperCase().charCodeAt(0)}});n.shortcuts[(t.ctrl?"ctrl":"")+","+(t.alt?"alt":"")+","+(t.shift?"shift":"")+","+t.keyCode]=t});return true},execCommand:function(u,r,x,m){var p=this,q=0,v,n;if(!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(u)&&(!m||!m.skip_focus)){p.focus()}m=f({},m);p.onBeforeExecCommand.dispatch(p,u,r,x,m);if(m.terminate){return false}if(p.execCallback("execcommand_callback",p.id,p.selection.getNode(),u,r,x)){p.onExecCommand.dispatch(p,u,r,x,m);return true}if(v=p.execCommands[u]){n=v.func.call(v.scope,r,x);if(n!==true){p.onExecCommand.dispatch(p,u,r,x,m);return n}}i(p.plugins,function(o){if(o.execCommand&&o.execCommand(u,r,x)){p.onExecCommand.dispatch(p,u,r,x,m);q=1;return false}});if(q){return true}if(p.theme&&p.theme.execCommand&&p.theme.execCommand(u,r,x)){p.onExecCommand.dispatch(p,u,r,x,m);return true}if(p.editorCommands.execCommand(u,r,x)){p.onExecCommand.dispatch(p,u,r,x,m);return true}p.getDoc().execCommand(u,r,x);p.onExecCommand.dispatch(p,u,r,x,m)},queryCommandState:function(q){var n=this,r,p;if(n._isHidden()){return}if(r=n.queryStateCommands[q]){p=r.func.call(r.scope);if(p!==true){return p}}r=n.editorCommands.queryCommandState(q);if(r!==-1){return r}try{return this.getDoc().queryCommandState(q)}catch(m){}},queryCommandValue:function(r){var n=this,q,p;if(n._isHidden()){return}if(q=n.queryValueCommands[r]){p=q.func.call(q.scope);if(p!==true){return p}}q=n.editorCommands.queryCommandValue(r);if(d(q)){return q}try{return this.getDoc().queryCommandValue(r)}catch(m){}},show:function(){var m=this;l.show(m.getContainer());l.hide(m.id);m.load()},hide:function(){var m=this,n=m.getDoc();if(b&&n){n.execCommand("SelectAll")}m.save();setTimeout(function(){l.hide(m.getContainer())},1);l.setStyle(m.id,"display",m.orgDisplay)},isHidden:function(){return !l.isHidden(this.id)},setProgressState:function(m,n,p){this.onSetProgressState.dispatch(this,m,n,p);return m},load:function(q){var m=this,p=m.getElement(),n;if(p){q=q||{};q.load=true;n=m.setContent(d(p.value)?p.value:p.innerHTML,q);q.element=p;if(!q.no_events){m.onLoadContent.dispatch(m,q)}q.element=p=null;return n}},save:function(r){var m=this,q=m.getElement(),n,p;if(!q||!m.initialized){return}r=r||{};r.save=true;r.element=q;n=r.content=m.getContent(r);if(!r.no_events){m.onSaveContent.dispatch(m,r)}n=r.content;if(!/TEXTAREA|INPUT/i.test(q.nodeName)){q.innerHTML=n;if(p=l.getParent(m.id,"form")){i(p.elements,function(o){if(o.name==m.id){o.value=n;return false}})}}else{q.value=n}r.element=q=null;return n},setContent:function(r,p){var o=this,n,m=o.getBody(),q;p=p||{};p.format=p.format||"html";p.set=true;p.content=r;if(!p.no_events){o.onBeforeSetContent.dispatch(o,p)}r=p.content;if(!k.isIE&&(r.length===0||/^\s+$/.test(r))){q=o.settings.forced_root_block;if(q){r="<"+q+'>
"}else{r='
'}m.innerHTML=r;o.selection.select(m,true);o.selection.collapse(true);return}if(p.format!=="raw"){r=new k.html.Serializer({},o.schema).serialize(o.parser.parse(r))}p.content=k.trim(r);o.dom.setHTML(m,p.content);if(!p.no_events){o.onSetContent.dispatch(o,p)}if(!o.settings.content_editable||document.activeElement===o.getBody()){o.selection.normalize()}return p.content},getContent:function(o){var n=this,p,m=n.getBody();o=o||{};o.format=o.format||"html";o.get=true;o.getInner=true;if(!o.no_events){n.onBeforeGetContent.dispatch(n,o)}if(o.format=="raw"){p=m.innerHTML}else{if(o.format=="text"){p=m.innerText||m.textContent}else{p=n.serializer.serialize(m,o)}}if(o.format!="text"){o.content=k.trim(p)}else{o.content=p}if(!o.no_events){n.onGetContent.dispatch(n,o)}return o.content},isDirty:function(){var m=this;return k.trim(m.startContent)!=k.trim(m.getContent({format:"raw",no_events:1}))&&!m.isNotDirty},getContainer:function(){var m=this;if(!m.container){m.container=l.get(m.editorContainer||m.id+"_parent")}return m.container},getContentAreaContainer:function(){return this.contentAreaContainer},getElement:function(){return l.get(this.settings.content_element||this.id)},getWin:function(){var m=this,n;if(!m.contentWindow){n=l.get(m.id+"_ifr");if(n){m.contentWindow=n.contentWindow}}return m.contentWindow},getDoc:function(){var m=this,n;if(!m.contentDocument){n=m.getWin();if(n){m.contentDocument=n.document}}return m.contentDocument},getBody:function(){return this.bodyElement||this.getDoc().body},convertURL:function(o,n,q){var m=this,p=m.settings;if(p.urlconverter_callback){return m.execCallback("urlconverter_callback",o,q,true,n)}if(!p.convert_urls||(q&&q.nodeName=="LINK")||o.indexOf("file:")===0){return o}if(p.relative_urls){return m.documentBaseURI.toRelative(o)}o=m.documentBaseURI.toAbsolute(o,p.remove_script_host);return o},addVisual:function(q){var n=this,o=n.settings,p=n.dom,m;q=q||n.getBody();if(!d(n.hasVisual)){n.hasVisual=o.visual}i(p.select("table,a",q),function(s){var r;switch(s.nodeName){case"TABLE":m=o.visual_table_class||"mceItemTable";r=p.getAttrib(s,"border");if(!r||r=="0"){if(n.hasVisual){p.addClass(s,m)}else{p.removeClass(s,m)}}return;case"A":if(!p.getAttrib(s,"href",false)){r=p.getAttrib(s,"name")||s.id;m="mceItemAnchor";if(r){if(n.hasVisual){p.addClass(s,m)}else{p.removeClass(s,m)}}}return}});n.onVisualAid.dispatch(n,q,n.hasVisual)},remove:function(){var m=this,n=m.getContainer();if(!m.removed){m.removed=1;m.hide();if(!m.settings.content_editable){j.unbind(m.getWin());j.unbind(m.getDoc())}j.unbind(m.getBody());j.clear(n);m.execCallback("remove_instance_callback",m);m.onRemove.dispatch(m);m.onExecCommand.listeners=[];k.remove(m);l.remove(n)}},destroy:function(n){var m=this;if(m.destroyed){return}if(a){j.unbind(m.getDoc());j.unbind(m.getWin());j.unbind(m.getBody())}if(!n){k.removeUnload(m.destroy);tinyMCE.onBeforeUnload.remove(m._beforeUnload);if(m.theme&&m.theme.destroy){m.theme.destroy()}m.controlManager.destroy();m.selection.destroy();m.dom.destroy()}if(m.formElement){m.formElement.submit=m.formElement._mceOldSubmit;m.formElement._mceOldSubmit=null}m.contentAreaContainer=m.formElement=m.container=m.settings.content_element=m.bodyElement=m.contentDocument=m.contentWindow=null;if(m.selection){m.selection=m.selection.win=m.selection.dom=m.selection.dom.doc=null}m.destroyed=1},_refreshContentEditable:function(){var n=this,m,o;if(n._isHidden()){m=n.getBody();o=m.parentNode;o.removeChild(m);o.appendChild(m);m.focus()}},_isHidden:function(){var m;if(!a){return 0}m=this.selection.getSel();return(!m||!m.rangeCount||m.rangeCount===0)}})})(tinymce);(function(a){var b=a.each;a.Editor.prototype.setupEvents=function(){var c=this,d=c.settings;b(["onPreInit","onBeforeRenderUI","onPostRender","onLoad","onInit","onRemove","onActivate","onDeactivate","onClick","onEvent","onMouseUp","onMouseDown","onDblClick","onKeyDown","onKeyUp","onKeyPress","onContextMenu","onSubmit","onReset","onPaste","onPreProcess","onPostProcess","onBeforeSetContent","onBeforeGetContent","onSetContent","onGetContent","onLoadContent","onSaveContent","onNodeChange","onChange","onBeforeExecCommand","onExecCommand","onUndo","onRedo","onVisualAid","onSetProgressState","onSetAttrib"],function(e){c[e]=new a.util.Dispatcher(c)});if(d.cleanup_callback){c.onBeforeSetContent.add(function(e,f){f.content=e.execCallback("cleanup_callback","insert_to_editor",f.content,f)});c.onPreProcess.add(function(e,f){if(f.set){e.execCallback("cleanup_callback","insert_to_editor_dom",f.node,f)}if(f.get){e.execCallback("cleanup_callback","get_from_editor_dom",f.node,f)}});c.onPostProcess.add(function(e,f){if(f.set){f.content=e.execCallback("cleanup_callback","insert_to_editor",f.content,f)}if(f.get){f.content=e.execCallback("cleanup_callback","get_from_editor",f.content,f)}})}if(d.save_callback){c.onGetContent.add(function(e,f){if(f.save){f.content=e.execCallback("save_callback",e.id,f.content,e.getBody())}})}if(d.handle_event_callback){c.onEvent.add(function(f,g,h){if(c.execCallback("handle_event_callback",g,f,h)===false){g.preventDefault();g.stopPropagation()}})}if(d.handle_node_change_callback){c.onNodeChange.add(function(f,e,g){f.execCallback("handle_node_change_callback",f.id,g,-1,-1,true,f.selection.isCollapsed())})}if(d.save_callback){c.onSaveContent.add(function(e,g){var f=e.execCallback("save_callback",e.id,g.content,e.getBody());if(f){g.content=f}})}if(d.onchange_callback){c.onChange.add(function(f,e){f.execCallback("onchange_callback",f,e)})}};a.Editor.prototype.bindNativeEvents=function(){var l=this,f,d=l.settings,e=l.dom,h;h={mouseup:"onMouseUp",mousedown:"onMouseDown",click:"onClick",keyup:"onKeyUp",keydown:"onKeyDown",keypress:"onKeyPress",submit:"onSubmit",reset:"onReset",contextmenu:"onContextMenu",dblclick:"onDblClick",paste:"onPaste"};function c(i,m){var n=i.type;if(l.removed){return}if(l.onEvent.dispatch(l,i,m)!==false){l[h[i.fakeType||i.type]].dispatch(l,i,m)}}function j(i){l.focus(true)}function k(i,m){if(m.keyCode!=65||!a.VK.metaKeyPressed(m)){l.selection.normalize()}l.nodeChanged()}b(h,function(m,n){var i=d.content_editable?l.getBody():l.getDoc();switch(n){case"contextmenu":e.bind(i,n,c);break;case"paste":e.bind(l.getBody(),n,c);break;case"submit":case"reset":e.bind(l.getElement().form||a.DOM.getParent(l.id,"form"),n,c);break;default:e.bind(i,n,c)}});e.bind(d.content_editable?l.getBody():(a.isGecko?l.getDoc():l.getWin()),"focus",function(i){l.focus(true)});if(d.content_editable&&a.isOpera){e.bind(l.getBody(),"click",j);e.bind(l.getBody(),"keydown",j)}l.onMouseUp.add(k);l.onKeyUp.add(function(i,n){var m=n.keyCode;if((m>=33&&m<=36)||(m>=37&&m<=40)||m==13||m==45||m==46||m==8||(a.isMac&&(m==91||m==93))||n.ctrlKey){k(i,n)}});l.onReset.add(function(){l.setContent(l.startContent,{format:"raw"})});function g(m,i){if(m.altKey||m.ctrlKey||m.metaKey){b(l.shortcuts,function(n){var o=a.isMac?m.metaKey:m.ctrlKey;if(n.ctrl!=o||n.alt!=m.altKey||n.shift!=m.shiftKey){return}if(m.keyCode==n.keyCode||(m.charCode&&m.charCode==n.charCode)){m.preventDefault();if(i){n.func.call(n.scope)}return true}})}}l.onKeyUp.add(function(i,m){g(m)});l.onKeyPress.add(function(i,m){g(m)});l.onKeyDown.add(function(i,m){g(m,true)});if(a.isOpera){l.onClick.add(function(i,m){m.preventDefault()})}}})(tinymce);(function(d){var e=d.each,b,a=true,c=false;d.EditorCommands=function(n){var m=n.dom,p=n.selection,j={state:{},exec:{},value:{}},k=n.settings,q=n.formatter,o;function r(z,y,x){var v;z=z.toLowerCase();if(v=j.exec[z]){v(z,y,x);return a}return c}function l(x){var v;x=x.toLowerCase();if(v=j.state[x]){return v(x)}return -1}function h(x){var v;x=x.toLowerCase();if(v=j.value[x]){return v(x)}return c}function u(v,x){x=x||"exec";e(v,function(z,y){e(y.toLowerCase().split(","),function(A){j[x][A]=z})})}d.extend(this,{execCommand:r,queryCommandState:l,queryCommandValue:h,addCommands:u});function f(y,x,v){if(x===b){x=c}if(v===b){v=null}return n.getDoc().execCommand(y,x,v)}function t(v){return q.match(v)}function s(v,x){q.toggle(v,x?{value:x}:b)}function i(v){o=p.getBookmark(v)}function g(){p.moveToBookmark(o)}u({"mceResetDesignMode,mceBeginUndoLevel":function(){},"mceEndUndoLevel,mceAddUndoLevel":function(){n.undoManager.add()},"Cut,Copy,Paste":function(z){var y=n.getDoc(),v;try{f(z)}catch(x){v=a}if(v||!y.queryCommandSupported(z)){if(d.isGecko){n.windowManager.confirm(n.getLang("clipboard_msg"),function(A){if(A){open("http://www.mozilla.org/editor/midasdemo/securityprefs.html","_blank")}})}else{n.windowManager.alert(n.getLang("clipboard_no_support"))}}},unlink:function(v){if(p.isCollapsed()){p.select(p.getNode())}f(v);p.collapse(c)},"JustifyLeft,JustifyCenter,JustifyRight,JustifyFull":function(v){var x=v.substring(7);e("left,center,right,full".split(","),function(y){if(x!=y){q.remove("align"+y)}});s("align"+x);r("mceRepaint")},"InsertUnorderedList,InsertOrderedList":function(y){var v,x;f(y);v=m.getParent(p.getNode(),"ol,ul");if(v){x=v.parentNode;if(/^(H[1-6]|P|ADDRESS|PRE)$/.test(x.nodeName)){i();m.split(x,v);g()}}},"Bold,Italic,Underline,Strikethrough,Superscript,Subscript":function(v){s(v)},"ForeColor,HiliteColor,FontName":function(y,x,v){s(y,v)},FontSize:function(z,y,x){var v,A;if(x>=1&&x<=7){A=d.explode(k.font_size_style_values);v=d.explode(k.font_size_classes);if(v){x=v[x-1]||x}else{x=A[x-1]||x}}s(z,x)},RemoveFormat:function(v){q.remove(v)},mceBlockQuote:function(v){s("blockquote")},FormatBlock:function(y,x,v){return s(v||"p")},mceCleanup:function(){var v=p.getBookmark();n.setContent(n.getContent({cleanup:a}),{cleanup:a});p.moveToBookmark(v)},mceRemoveNode:function(z,y,x){var v=x||p.getNode();if(v!=n.getBody()){i();n.dom.remove(v,a);g()}},mceSelectNodeDepth:function(z,y,x){var v=0;m.getParent(p.getNode(),function(A){if(A.nodeType==1&&v++==x){p.select(A);return c}},n.getBody())},mceSelectNode:function(y,x,v){p.select(v)},mceInsertContent:function(B,I,K){var y,J,E,z,F,G,D,C,L,x,A,M,v,H;y=n.parser;J=new d.html.Serializer({},n.schema);v='\uFEFF';G={content:K,format:"html"};p.onBeforeSetContent.dispatch(p,G);K=G.content;if(K.indexOf("{$caret}")==-1){K+="{$caret}"}K=K.replace(/\{\$caret\}/,v);if(!p.isCollapsed()){n.getDoc().execCommand("Delete",false,null)}E=p.getNode();G={context:E.nodeName.toLowerCase()};F=y.parse(K,G);A=F.lastChild;if(A.attr("id")=="mce_marker"){D=A;for(A=A.prev;A;A=A.walk(true)){if(A.type==3||!m.isBlock(A.name)){A.parent.insert(D,A,A.name==="br");break}}}if(!G.invalid){K=J.serialize(F);A=E.firstChild;M=E.lastChild;if(!A||(A===M&&A.nodeName==="BR")){m.setHTML(E,K)}else{p.setContent(K)}}else{p.setContent(v);E=p.getNode();z=n.getBody();if(E.nodeType==9){E=A=z}else{A=E}while(A!==z){E=A;A=A.parentNode}K=E==z?z.innerHTML:m.getOuterHTML(E);K=J.serialize(y.parse(K.replace(//i,function(){return J.serialize(F)})));if(E==z){m.setHTML(z,K)}else{m.setOuterHTML(E,K)}}D=m.get("mce_marker");C=m.getRect(D);L=m.getViewPort(n.getWin());if((C.y+C.h>L.y+L.h||C.yL.x+L.w||C.x")},mceToggleVisualAid:function(){n.hasVisual=!n.hasVisual;n.addVisual()},mceReplaceContent:function(y,x,v){n.execCommand("mceInsertContent",false,v.replace(/\{\$selection\}/g,p.getContent({format:"text"})))},mceInsertLink:function(z,y,x){var v;if(typeof(x)=="string"){x={href:x}}v=m.getParent(p.getNode(),"a");x.href=x.href.replace(" ","%20");if(!v||!x.href){q.remove("link")}if(x.href){q.apply("link",x,v)}},selectAll:function(){var x=m.getRoot(),v=m.createRng();if(p.getRng().setStart){v.setStart(x,0);v.setEnd(x,x.childNodes.length);p.setRng(v)}else{f("SelectAll")}}});u({"JustifyLeft,JustifyCenter,JustifyRight,JustifyFull":function(z){var x="align"+z.substring(7);var v=p.isCollapsed()?[m.getParent(p.getNode(),m.isBlock)]:p.getSelectedBlocks();var y=d.map(v,function(A){return !!q.matchNode(A,x)});return d.inArray(y,a)!==-1},"Bold,Italic,Underline,Strikethrough,Superscript,Subscript":function(v){return t(v)},mceBlockQuote:function(){return t("blockquote")},Outdent:function(){var v;if(k.inline_styles){if((v=m.getParent(p.getStart(),m.isBlock))&&parseInt(v.style.paddingLeft)>0){return a}if((v=m.getParent(p.getEnd(),m.isBlock))&&parseInt(v.style.paddingLeft)>0){return a}}return l("InsertUnorderedList")||l("InsertOrderedList")||(!k.inline_styles&&!!m.getParent(p.getNode(),"BLOCKQUOTE"))},"InsertUnorderedList,InsertOrderedList":function(x){var v=m.getParent(p.getNode(),"ul,ol");return v&&(x==="insertunorderedlist"&&v.tagName==="UL"||x==="insertorderedlist"&&v.tagName==="OL")}},"state");u({"FontSize,FontName":function(y){var x=0,v;if(v=m.getParent(p.getNode(),"span")){if(y=="fontsize"){x=v.style.fontSize}else{x=v.style.fontFamily.replace(/, /g,",").replace(/[\'\"]/g,"").toLowerCase()}}return x}},"value");u({Undo:function(){n.undoManager.undo()},Redo:function(){n.undoManager.redo()}})}})(tinymce);(function(b){var a=b.util.Dispatcher;b.UndoManager=function(h){var l,i=0,e=[],g,k,j,f;function c(){return b.trim(h.getContent({format:"raw",no_events:1}).replace(/]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>/g,""))}function d(){l.typing=false;l.add()}onBeforeAdd=new a(l);k=new a(l);j=new a(l);f=new a(l);k.add(function(m,n){if(m.hasUndo()){return h.onChange.dispatch(h,n,m)}});j.add(function(m,n){return h.onUndo.dispatch(h,n,m)});f.add(function(m,n){return h.onRedo.dispatch(h,n,m)});h.onInit.add(function(){l.add()});h.onBeforeExecCommand.add(function(m,p,o,q,n){if(p!="Undo"&&p!="Redo"&&p!="mceRepaint"&&(!n||!n.skip_undo)){l.beforeChange()}});h.onExecCommand.add(function(m,p,o,q,n){if(p!="Undo"&&p!="Redo"&&p!="mceRepaint"&&(!n||!n.skip_undo)){l.add()}});h.onSaveContent.add(d);h.dom.bind(h.dom.getRoot(),"dragend",d);h.dom.bind(h.getDoc(),b.isGecko?"blur":"focusout",function(m){if(!h.removed&&l.typing){d()}});h.onKeyUp.add(function(m,o){var n=o.keyCode;if((n>=33&&n<=36)||(n>=37&&n<=40)||n==45||n==13||o.ctrlKey){d()}});h.onKeyDown.add(function(m,o){var n=o.keyCode;if((n>=33&&n<=36)||(n>=37&&n<=40)||n==45){if(l.typing){d()}return}if((n<16||n>20)&&n!=224&&n!=91&&!l.typing){l.beforeChange();l.typing=true;l.add()}});h.onMouseDown.add(function(m,n){if(l.typing){d()}});h.addShortcut("ctrl+z","undo_desc","Undo");h.addShortcut("ctrl+y","redo_desc","Redo");l={data:e,typing:false,onBeforeAdd:onBeforeAdd,onAdd:k,onUndo:j,onRedo:f,beforeChange:function(){g=h.selection.getBookmark(2,true)},add:function(p){var m,n=h.settings,o;p=p||{};p.content=c();l.onBeforeAdd.dispatch(l,p);o=e[i];if(o&&o.content==p.content){return null}if(e[i]){e[i].beforeBookmark=g}if(n.custom_undo_redo_levels){if(e.length>n.custom_undo_redo_levels){for(m=0;m0){n=e[--i];h.setContent(n.content,{format:"raw"});h.selection.moveToBookmark(n.beforeBookmark);l.onUndo.dispatch(l,n)}return n},redo:function(){var m;if(i0||this.typing},hasRedo:function(){return i0){g.moveEnd("character",q)}g.select()}catch(n){}}}c.nodeChanged()}}if(b.forced_root_block){c.onKeyUp.add(f);c.onNodeChange.add(f)}};(function(c){var b=c.DOM,a=c.dom.Event,d=c.each,e=c.extend;c.create("tinymce.ControlManager",{ControlManager:function(f,j){var h=this,g;j=j||{};h.editor=f;h.controls={};h.onAdd=new c.util.Dispatcher(h);h.onPostRender=new c.util.Dispatcher(h);h.prefix=j.prefix||f.id+"_";h._cls={};h.onPostRender.add(function(){d(h.controls,function(i){i.postRender()})})},get:function(f){return this.controls[this.prefix+f]||this.controls[f]},setActive:function(h,f){var g=null;if(g=this.get(h)){g.setActive(f)}return g},setDisabled:function(h,f){var g=null;if(g=this.get(h)){g.setDisabled(f)}return g},add:function(g){var f=this;if(g){f.controls[g.id]=g;f.onAdd.dispatch(g,f)}return g},createControl:function(j){var o,k,g,h=this,m=h.editor,n,f;if(!h.controlFactories){h.controlFactories=[];d(m.plugins,function(i){if(i.createControl){h.controlFactories.push(i)}})}n=h.controlFactories;for(k=0,g=n.length;k1||ag==ay||ag.tagName=="BR"){return ag}}}var aq=aa.selection.getRng();var av=aq.startContainer;var ap=aq.endContainer;if(av!=ap&&aq.endOffset===0){var au=ar(av,ap);var at=au.nodeType==3?au.length:au.childNodes.length;aq.setEnd(au,at)}return aq}function ad(at,ay,aw,av,aq){var ap=[],ar=-1,ax,aA=-1,au=-1,az;T(at.childNodes,function(aC,aB){if(aC.nodeName==="UL"||aC.nodeName==="OL"){ar=aB;ax=aC;return false}});T(at.childNodes,function(aC,aB){if(aC.nodeName==="SPAN"&&c.getAttrib(aC,"data-mce-type")=="bookmark"){if(aC.id==ay.id+"_start"){aA=aB}else{if(aC.id==ay.id+"_end"){au=aB}}}});if(ar<=0||(aAar)){T(a.grep(at.childNodes),aq);return 0}else{az=c.clone(aw,X);T(a.grep(at.childNodes),function(aC,aB){if((aAar&&aB>ar)){ap.push(aC);aC.parentNode.removeChild(aC)}});if(aAar){at.insertBefore(az,ax.nextSibling)}}av.push(az);T(ap,function(aB){az.appendChild(aB)});return az}}function an(aq,at,aw){var ap=[],av,ar,au=true;av=am.inline||am.block;ar=c.create(av);ab(ar);N.walk(aq,function(ax){var ay;function az(aA){var aF,aD,aB,aC,aE;aE=au;aF=aA.nodeName.toLowerCase();aD=aA.parentNode.nodeName.toLowerCase();if(aA.nodeType===1&&x(aA)){aE=au;au=x(aA)==="true";aC=true}if(g(aF,"br")){ay=0;if(am.block){c.remove(aA)}return}if(am.wrapper&&y(aA,ae,al)){ay=0;return}if(au&&!aC&&am.block&&!am.wrapper&&I(aF)){aA=c.rename(aA,av);ab(aA);ap.push(aA);ay=0;return}if(am.selector){T(ah,function(aG){if("collapsed" in aG&&aG.collapsed!==ai){return}if(c.is(aA,aG.selector)&&!b(aA)){ab(aA,aG);aB=true}});if(!am.inline||aB){ay=0;return}}if(au&&!aC&&d(av,aF)&&d(aD,av)&&!(!aw&&aA.nodeType===3&&aA.nodeValue.length===1&&aA.nodeValue.charCodeAt(0)===65279)&&!b(aA)){if(!ay){ay=c.clone(ar,X);aA.parentNode.insertBefore(ay,aA);ap.push(ay)}ay.appendChild(aA)}else{if(aF=="li"&&at){ay=ad(aA,at,ar,ap,az)}else{ay=0;T(a.grep(aA.childNodes),az);if(aC){au=aE}ay=0}}}T(ax,az)});if(am.wrap_links===false){T(ap,function(ax){function ay(aC){var aB,aA,az;if(aC.nodeName==="A"){aA=c.clone(ar,X);ap.push(aA);az=a.grep(aC.childNodes);for(aB=0;aB1||!H(az))&&ax===0){c.remove(az,1);return}if(am.inline||am.wrapper){if(!am.exact&&ax===1){az=ay(az)}T(ah,function(aB){T(c.select(aB.inline,az),function(aD){var aC;if(aB.wrap_links===false){aC=aD.parentNode;do{if(aC.nodeName==="A"){return}}while(aC=aC.parentNode)}Z(aB,al,aD,aB.exact?aD:null)})});if(y(az.parentNode,ae,al)){c.remove(az,1);az=0;return C}if(am.merge_with_parents){c.getParent(az.parentNode,function(aB){if(y(aB,ae,al)){c.remove(az,1);az=0;return C}})}if(az&&am.merge_siblings!==false){az=u(E(az),az);az=u(az,E(az,C))}}})}if(am){if(ag){if(ag.nodeType){ac=c.createRng();ac.setStartBefore(ag);ac.setEndAfter(ag);an(p(ac,ah),null,true)}else{an(ag,null,true)}}else{if(!ai||!am.inline||c.select("td.mceSelected,th.mceSelected").length){var ao=aa.selection.getNode();if(!m&&ah[0].defaultBlock&&!c.getParent(ao,c.isBlock)){Y(ah[0].defaultBlock)}aa.selection.setRng(af());ak=r.getBookmark();an(p(r.getRng(C),ah),ak);if(am.styles&&(am.styles.color||am.styles.textDecoration)){a.walk(ao,L,"childNodes");L(ao)}r.moveToBookmark(ak);R(r.getRng(C));aa.nodeChanged()}else{U("apply",ae,al)}}}}function B(ad,am,af){var ag=V(ad),ao=ag[0],ak,aj,ac,al=true;function ae(av){var au,at,ar,aq,ax,aw;if(av.nodeType===1&&x(av)){ax=al;al=x(av)==="true";aw=true}au=a.grep(av.childNodes);if(al&&!aw){for(at=0,ar=ag.length;at=0;ac--){ab=ah[ac].selector;if(!ab){return C}for(ag=ad.length-1;ag>=0;ag--){if(c.is(ad[ag],ab)){return C}}}}return X}function J(ab,ae,ac){var ad;if(!P){P={};ad={};aa.onNodeChange.addToTop(function(ag,af,ai){var ah=n(ai),aj={};T(P,function(ak,al){T(ah,function(am){if(y(am,al,{},ak.similar)){if(!ad[al]){T(ak,function(an){an(true,{node:am,format:al,parents:ah})});ad[al]=ak}aj[al]=ak;return false}})});T(ad,function(ak,al){if(!aj[al]){delete ad[al];T(ak,function(am){am(false,{node:ai,format:al,parents:ah})})}})})}T(ab.split(","),function(af){if(!P[af]){P[af]=[];P[af].similar=ac}P[af].push(ae)});return this}a.extend(this,{get:V,register:l,apply:Y,remove:B,toggle:F,match:k,matchAll:v,matchNode:y,canApply:z,formatChanged:J});j();W();function h(ab,ac){if(g(ab,ac.inline)){return C}if(g(ab,ac.block)){return C}if(ac.selector){return c.is(ab,ac.selector)}}function g(ac,ab){ac=ac||"";ab=ab||"";ac=""+(ac.nodeName||ac);ab=""+(ab.nodeName||ab);return ac.toLowerCase()==ab.toLowerCase()}function O(ac,ab){var ad=c.getStyle(ac,ab);if(ab=="color"||ab=="backgroundColor"){ad=c.toHex(ad)}if(ab=="fontWeight"&&ad==700){ad="bold"}return""+ad}function q(ab,ac){if(typeof(ab)!="string"){ab=ab(ac)}else{if(ac){ab=ab.replace(/%(\w+)/g,function(ae,ad){return ac[ad]||ae})}}return ab}function f(ab){return ab&&ab.nodeType===3&&/^([\t \r\n]+|)$/.test(ab.nodeValue)}function S(ad,ac,ab){var ae=c.create(ac,ab);ad.parentNode.insertBefore(ae,ad);ae.appendChild(ad);return ae}function p(ab,am,ae){var ap,an,ah,al,ad=ab.startContainer,ai=ab.startOffset,ar=ab.endContainer,ak=ab.endOffset;function ao(aA){var au,ax,az,aw,av,at;au=ax=aA?ad:ar;av=aA?"previousSibling":"nextSibling";at=c.getRoot();function ay(aB){return aB.nodeName=="BR"&&aB.getAttribute("data-mce-bogus")&&!aB.nextSibling}if(au.nodeType==3&&!f(au)){if(aA?ai>0:akan?an:ai];if(ad.nodeType==3){ai=0}}if(ar.nodeType==1&&ar.hasChildNodes()){an=ar.childNodes.length-1;ar=ar.childNodes[ak>an?an:ak-1];if(ar.nodeType==3){ak=ar.nodeValue.length}}function aq(au){var at=au;while(at){if(at.nodeType===1&&x(at)){return x(at)==="false"?at:au}at=at.parentNode}return au}function aj(au,ay,aA){var ax,av,az,at;function aw(aC,aE){var aF,aB,aD=aC.nodeValue;if(typeof(aE)=="undefined"){aE=aA?aD.length:0}if(aA){aF=aD.lastIndexOf(" ",aE);aB=aD.lastIndexOf("\u00a0",aE);aF=aF>aB?aF:aB;if(aF!==-1&&!ae){aF++}}else{aF=aD.indexOf(" ",aE);aB=aD.indexOf("\u00a0",aE);aF=aF!==-1&&(aB===-1||aF0&&ah.node.nodeType===3&&ah.node.nodeValue.charAt(ah.offset-1)===" "){if(ah.offset>1){ar=ah.node;ar.splitText(ah.offset-1)}}}}if(am[0].inline||am[0].block_expand){if(!am[0].inline||(ad.nodeType!=3||ai===0)){ad=ao(true)}if(!am[0].inline||(ar.nodeType!=3||ak===ar.nodeValue.length)){ar=ao()}}if(am[0].selector&&am[0].expand!==X&&!am[0].inline){ad=af(ad,"previousSibling");ar=af(ar,"nextSibling")}if(am[0].block||am[0].selector){ad=ac(ad,"previousSibling");ar=ac(ar,"nextSibling");if(am[0].block){if(!H(ad)){ad=ao(true)}if(!H(ar)){ar=ao()}}}if(ad.nodeType==1){ai=s(ad);ad=ad.parentNode}if(ar.nodeType==1){ak=s(ar)+1;ar=ar.parentNode}return{startContainer:ad,startOffset:ai,endContainer:ar,endOffset:ak}}function Z(ah,ag,ae,ab){var ad,ac,af;if(!h(ae,ah)){return X}if(ah.remove!="all"){T(ah.styles,function(aj,ai){aj=q(aj,ag);if(typeof(ai)==="number"){ai=aj;ab=0}if(!ab||g(O(ab,ai),aj)){c.setStyle(ae,ai,"")}af=1});if(af&&c.getAttrib(ae,"style")==""){ae.removeAttribute("style");ae.removeAttribute("data-mce-style")}T(ah.attributes,function(ak,ai){var aj;ak=q(ak,ag);if(typeof(ai)==="number"){ai=ak;ab=0}if(!ab||g(c.getAttrib(ab,ai),ak)){if(ai=="class"){ak=c.getAttrib(ae,ai);if(ak){aj="";T(ak.split(/\s+/),function(al){if(/mce\w+/.test(al)){aj+=(aj?" ":"")+al}});if(aj){c.setAttrib(ae,ai,aj);return}}}if(ai=="class"){ae.removeAttribute("className")}if(e.test(ai)){ae.removeAttribute("data-mce-"+ai)}ae.removeAttribute(ai)}});T(ah.classes,function(ai){ai=q(ai,ag);if(!ab||c.hasClass(ab,ai)){c.removeClass(ae,ai)}});ac=c.getAttribs(ae);for(ad=0;adad?ad:af]}if(ab.nodeType===3&&ag&&af>=ab.nodeValue.length){ab=new t(ab,aa.getBody()).next()||ab}if(ab.nodeType===3&&!ag&&af===0){ab=new t(ab,aa.getBody()).prev()||ab}return ab}function U(ak,ab,ai){var al="_mce_caret",ac=aa.settings.caret_debug;function ad(ap){var ao=c.create("span",{id:al,"data-mce-bogus":true,style:ac?"color:red":""});if(ap){ao.appendChild(aa.getDoc().createTextNode(G))}return ao}function aj(ap,ao){while(ap){if((ap.nodeType===3&&ap.nodeValue!==G)||ap.childNodes.length>1){return false}if(ao&&ap.nodeType===1){ao.push(ap)}ap=ap.firstChild}return true}function ag(ao){while(ao){if(ao.id===al){return ao}ao=ao.parentNode}}function af(ao){var ap;if(ao){ap=new t(ao,ao);for(ao=ap.current();ao;ao=ap.next()){if(ao.nodeType===3){return ao}}}}function ae(aq,ap){var ar,ao;if(!aq){aq=ag(r.getStart());if(!aq){while(aq=c.get(al)){ae(aq,false)}}}else{ao=r.getRng(true);if(aj(aq)){if(ap!==false){ao.setStartBefore(aq);ao.setEndBefore(aq)}c.remove(aq)}else{ar=af(aq);if(ar.nodeValue.charAt(0)===G){ar=ar.deleteData(0,1)}c.remove(aq,1)}r.setRng(ao)}}function ah(){var aq,ao,av,au,ar,ap,at;aq=r.getRng(true);au=aq.startOffset;ap=aq.startContainer;at=ap.nodeValue;ao=ag(r.getStart());if(ao){av=af(ao)}if(at&&au>0&&au=0;at--){aq.appendChild(c.clone(ax[at],false));aq=aq.firstChild}aq.appendChild(c.doc.createTextNode(G));aq=aq.firstChild;c.insertAfter(aw,ay);r.setCursorLocation(aq,1)}}function an(){var ap,ao,aq;ao=ag(r.getStart());if(ao&&!c.isEmpty(ao)){a.walk(ao,function(ar){if(ar.nodeType==1&&ar.id!==al&&!c.isEmpty(ar)){c.setAttrib(ar,"data-mce-bogus",null)}},"childNodes")}}if(!self._hasCaretEvents){aa.onBeforeGetContent.addToTop(function(){var ao=[],ap;if(aj(ag(r.getStart()),ao)){ap=ao.length;while(ap--){c.setAttrib(ao[ap],"data-mce-bogus","1")}}});a.each("onMouseUp onKeyUp".split(" "),function(ao){aa[ao].addToTop(function(){ae();an()})});aa.onKeyDown.addToTop(function(ao,aq){var ap=aq.keyCode;if(ap==8||ap==37||ap==39){ae(ag(r.getStart()))}an()});r.onSetContent.add(an);self._hasCaretEvents=true}if(ak=="apply"){ah()}else{am()}}function R(ac){var ab=ac.startContainer,ai=ac.startOffset,ae,ah,ag,ad,af;if(ab.nodeType==3&&ai>=ab.nodeValue.length){ai=s(ab);ab=ab.parentNode;ae=true}if(ab.nodeType==1){ad=ab.childNodes;ab=ad[Math.min(ai,ad.length-1)];ah=new t(ab,c.getParent(ab,c.isBlock));if(ai>ad.length-1||ae){ah.next()}for(ag=ah.current();ag;ag=ah.next()){if(ag.nodeType==3&&!f(ag)){af=c.create("a",null,G);ag.parentNode.insertBefore(af,ag);ac.setStart(ag,0);r.setRng(ac);c.remove(af);return}}}}}})(tinymce);tinymce.onAddEditor.add(function(e,a){var d,h,g,c=a.settings;function b(j,i){e.each(i,function(l,k){if(l){g.setStyle(j,k,l)}});g.rename(j,"span")}function f(i,j){g=i.dom;if(c.convert_fonts_to_spans){e.each(g.select("font,u,strike",j.node),function(k){d[k.nodeName.toLowerCase()](a.dom,k)})}}if(c.inline_styles){h=e.explode(c.font_size_legacy_values);d={font:function(j,i){b(i,{backgroundColor:i.style.backgroundColor,color:i.color,fontFamily:i.face,fontSize:h[parseInt(i.size,10)-1]})},u:function(j,i){b(i,{textDecoration:"underline"})},strike:function(j,i){b(i,{textDecoration:"line-through"})}};a.onPreProcess.add(f);a.onSetContent.add(f);a.onInit.add(function(){a.selection.onSetContent.add(f)})}});(function(b){var a=b.dom.TreeWalker;b.EnterKey=function(f){var i=f.dom,e=f.selection,d=f.settings,h=f.undoManager,c=f.schema.getNonEmptyElements();function g(A){var v=e.getRng(true),G,j,z,u,p,M,B,o,k,n,t,J,x,C;function E(N){return N&&i.isBlock(N)&&!/^(TD|TH|CAPTION|FORM)$/.test(N.nodeName)&&!/^(fixed|absolute)/i.test(N.style.position)&&i.getContentEditable(N)!=="true"}function F(O){var N;if(b.isIE&&i.isBlock(O)){N=e.getRng();O.appendChild(i.create("span",null,"\u00a0"));e.select(O);O.lastChild.outerHTML="";e.setRng(N)}}function y(P){var O=P,Q=[],N;while(O=O.firstChild){if(i.isBlock(O)){return}if(O.nodeType==1&&!c[O.nodeName.toLowerCase()]){Q.push(O)}}N=Q.length;while(N--){O=Q[N];if(!O.hasChildNodes()||(O.firstChild==O.lastChild&&O.firstChild.nodeValue==="")){i.remove(O)}else{if(O.nodeName=="A"&&(O.innerText||O.textContent)===" "){i.remove(O)}}}}function m(O){var T,R,N,U,S,Q=O,P;N=i.createRng();if(O.hasChildNodes()){T=new a(O,O);while(R=T.current()){if(R.nodeType==3){N.setStart(R,0);N.setEnd(R,0);break}if(c[R.nodeName.toLowerCase()]){N.setStartBefore(R);N.setEndBefore(R);break}Q=R;R=T.next()}if(!R){N.setStart(Q,0);N.setEnd(Q,0)}}else{if(O.nodeName=="BR"){if(O.nextSibling&&i.isBlock(O.nextSibling)){if(!M||M<9){P=i.create("br");O.parentNode.insertBefore(P,O)}N.setStartBefore(O);N.setEndBefore(O)}else{N.setStartAfter(O);N.setEndAfter(O)}}else{N.setStart(O,0);N.setEnd(O,0)}}e.setRng(N);i.remove(P);S=i.getViewPort(f.getWin());U=i.getPos(O).y;if(US.y+S.h){f.getWin().scrollTo(0,U'}return R}function q(Q){var P,O,N;if(z.nodeType==3&&(Q?u>0:u=z.nodeValue.length){if(!b.isIE&&!D()){O=i.create("br");v.insertNode(O);v.setStartAfter(O);v.setEndAfter(O);N=true}}O=i.create("br");v.insertNode(O);if(b.isIE&&t=="PRE"&&(!M||M<8)){O.parentNode.insertBefore(i.doc.createTextNode("\r"),O)}if(!N){v.setStartAfter(O);v.setEndAfter(O)}else{v.setStartBefore(O);v.setEndBefore(O)}e.setRng(v);h.add()}function s(N){do{if(N.nodeType===3){N.nodeValue=N.nodeValue.replace(/^[\r\n]+/,"")}N=N.firstChild}while(N)}function K(P){var N=i.getRoot(),O,Q;O=P;while(O!==N&&i.getContentEditable(O)!=="false"){if(i.getContentEditable(O)==="true"){Q=O}O=O.parentNode}return O!==N?Q:N}function I(O){var N;if(!b.isIE){O.normalize();N=O.lastChild;if(!N||(/^(left|right)$/gi.test(i.getStyle(N,"float",true)))){i.add(O,"br")}}}if(!v.collapsed){f.execCommand("Delete");return}if(A.isDefaultPrevented()){return}z=v.startContainer;u=v.startOffset;x=(d.force_p_newlines?"p":"")||d.forced_root_block;x=x?x.toUpperCase():"";M=i.doc.documentMode;B=A.shiftKey;if(z.nodeType==1&&z.hasChildNodes()){C=u>z.childNodes.length-1;z=z.childNodes[Math.min(u,z.childNodes.length-1)]||z;if(C&&z.nodeType==3){u=z.nodeValue.length}else{u=0}}j=K(z);if(!j){return}h.beforeChange();if(!i.isBlock(j)&&j!=i.getRoot()){if(!x||B){L()}return}if((x&&!B)||(!x&&B)){z=l(z,u)}p=i.getParent(z,i.isBlock);n=p?i.getParent(p.parentNode,i.isBlock):null;t=p?p.nodeName.toUpperCase():"";J=n?n.nodeName.toUpperCase():"";if(J=="LI"&&!A.ctrlKey){p=n;t=J}if(t=="LI"){if(!x&&B){L();return}if(i.isEmpty(p)){if(/^(UL|OL|LI)$/.test(n.parentNode.nodeName)){return false}H();return}}if(t=="PRE"&&d.br_in_pre!==false){if(!B){L();return}}else{if((!x&&!B&&t!="LI")||(x&&B)){L();return}}x=x||"P";if(q()){if(/^(H[1-6]|PRE)$/.test(t)&&J!="HGROUP"){o=r(x)}else{o=r()}if(d.end_container_on_empty_block&&E(n)&&i.isEmpty(p)){o=i.split(n,p)}else{i.insertAfter(o,p)}m(o)}else{if(q(true)){o=p.parentNode.insertBefore(r(),p);F(o)}else{G=v.cloneRange();G.setEndAfter(p);k=G.extractContents();s(k);o=k.firstChild;i.insertAfter(k,p);y(o);I(p);m(o)}}i.setAttrib(o,"id","");h.add()}f.onKeyDown.add(function(k,j){if(j.keyCode==13){if(g(j)!==false){j.preventDefault()}}})}})(tinymce); \ No newline at end of file +(function(e){var a=/^\s*|\s*$/g,b,d="B".replace(/A(.)|B/,"$1")==="$1";var c={majorVersion:"3",minorVersion:"5.10",releaseDate:"2013-10-24",_init:function(){var s=this,q=document,o=navigator,g=o.userAgent,m,f,l,k,j,r;s.isIE11=g.indexOf("Trident/")!=-1&&(g.indexOf("rv:")!=-1||o.appName.indexOf("Netscape")!=-1);s.isOpera=e.opera&&opera.buildNumber;s.isWebKit=/WebKit/.test(g);s.isIE=!s.isWebKit&&!s.isOpera&&(/MSIE/gi).test(g)&&(/Explorer/gi).test(o.appName)||s.isIE11;s.isIE6=s.isIE&&/MSIE [56]/.test(g);s.isIE7=s.isIE&&/MSIE [7]/.test(g);s.isIE8=s.isIE&&/MSIE [8]/.test(g);s.isIE9=s.isIE&&/MSIE [9]/.test(g);s.isGecko=!s.isWebKit&&!s.isIE11&&/Gecko/.test(g);s.isMac=g.indexOf("Mac")!=-1;s.isAir=/adobeair/i.test(g);s.isIDevice=/(iPad|iPhone)/.test(g);s.isIOS5=s.isIDevice&&g.match(/AppleWebKit\/(\d*)/)[1]>=534;if(e.tinyMCEPreInit){s.suffix=tinyMCEPreInit.suffix;s.baseURL=tinyMCEPreInit.base;s.query=tinyMCEPreInit.query;return}s.suffix="";f=q.getElementsByTagName("base");for(m=0;m0?b:[f.scope]);if(e===false){break}}a.inDispatch=false;return e}});(function(){var a=tinymce.each;tinymce.create("tinymce.util.URI",{URI:function(e,g){var f=this,i,d,c,h;e=tinymce.trim(e);g=f.settings=g||{};if(/^([\w\-]+):([^\/]{2})/i.test(e)||/^\s*#/.test(e)){f.source=e;return}if(e.indexOf("/")===0&&e.indexOf("//")!==0){e=(g.base_uri?g.base_uri.protocol||"http":"http")+"://mce_host"+e}if(!/^[\w\-]*:?\/\//.test(e)){h=g.base_uri?g.base_uri.path:new tinymce.util.URI(location.href).directory;e=((g.base_uri&&g.base_uri.protocol)||"http")+"://mce_host"+f.toAbsPath(h,e)}e=e.replace(/@@/g,"(mce_at)");e=/^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(e);a(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],function(b,j){var k=e[j];if(k){k=k.replace(/\(mce_at\)/g,"@@")}f[b]=k});c=g.base_uri;if(c){if(!f.protocol){f.protocol=c.protocol}if(!f.userInfo){f.userInfo=c.userInfo}if(!f.port&&f.host==="mce_host"){f.port=c.port}if(!f.host||f.host==="mce_host"){f.host=c.host}f.source=""}},setPath:function(c){var b=this;c=/^(.*?)\/?(\w+)?$/.exec(c);b.path=c[0];b.directory=c[1];b.file=c[2];b.source="";b.getURI()},toRelative:function(b){var d=this,f;if(b==="./"){return b}b=new tinymce.util.URI(b,{base_uri:d});if((b.host!="mce_host"&&d.host!=b.host&&b.host)||d.port!=b.port||d.protocol!=b.protocol){return b.getURI()}var c=d.getURI(),e=b.getURI();if(c==e||(c.charAt(c.length-1)=="/"&&c.substr(0,c.length-1)==e)){return c}f=d.toRelPath(d.path,b.path);if(b.query){f+="?"+b.query}if(b.anchor){f+="#"+b.anchor}return f},toAbsolute:function(b,c){b=new tinymce.util.URI(b,{base_uri:this});return b.getURI(this.host==b.host&&this.protocol==b.protocol?c:0)},toRelPath:function(g,h){var c,f=0,d="",e,b;g=g.substring(0,g.lastIndexOf("/"));g=g.split("/");c=h.split("/");if(g.length>=c.length){for(e=0,b=g.length;e=c.length||g[e]!=c[e]){f=e+1;break}}}if(g.length=g.length||g[e]!=c[e]){f=e+1;break}}}if(f===1){return h}for(e=0,b=g.length-(f-1);e=0;c--){if(f[c].length===0||f[c]==="."){continue}if(f[c]===".."){b++;continue}if(b>0){b--;continue}h.push(f[c])}c=e.length-b;if(c<=0){g=h.reverse().join("/")}else{g=e.slice(0,c).join("/")+"/"+h.reverse().join("/")}if(g.indexOf("/")!==0){g="/"+g}if(d&&g.lastIndexOf("/")!==g.length-1){g+=d}return g},getURI:function(d){var c,b=this;if(!b.source||d){c="";if(!d){if(b.protocol){c+=b.protocol+"://"}if(b.userInfo){c+=b.userInfo+"@"}if(b.host){c+=b.host}if(b.port){c+=":"+b.port}}if(b.path){c+=b.path}if(b.query){c+="?"+b.query}if(b.anchor){c+="#"+b.anchor}b.source=c}return b.source}})})();(function(){var a=tinymce.each;tinymce.create("static tinymce.util.Cookie",{getHash:function(d){var b=this.get(d),c;if(b){a(b.split("&"),function(e){e=e.split("=");c=c||{};c[unescape(e[0])]=unescape(e[1])})}return c},setHash:function(j,b,g,f,i,c){var h="";a(b,function(e,d){h+=(!h?"":"&")+escape(d)+"="+escape(e)});this.set(j,h,g,f,i,c)},get:function(i){var h=document.cookie,g,f=i+"=",d;if(!h){return}d=h.indexOf("; "+f);if(d==-1){d=h.indexOf(f);if(d!==0){return null}}else{d+=2}g=h.indexOf(";",d);if(g==-1){g=h.length}return unescape(h.substring(d+f.length,g))},set:function(i,b,g,f,h,c){document.cookie=i+"="+escape(b)+((g)?"; expires="+g.toGMTString():"")+((f)?"; path="+escape(f):"")+((h)?"; domain="+h:"")+((c)?"; secure":"")},remove:function(c,e,d){var b=new Date();b.setTime(b.getTime()-1000);this.set(c,"",b,e,d)}})})();(function(){function serialize(o,quote){var i,v,t,name;quote=quote||'"';if(o==null){return"null"}t=typeof o;if(t=="string"){v="\bb\tt\nn\ff\rr\"\"''\\\\";return quote+o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g,function(a,b){if(quote==='"'&&a==="'"){return a}i=v.indexOf(b);if(i+1){return"\\"+v.charAt(i+1)}a=b.charCodeAt().toString(16);return"\\u"+"0000".substring(a.length)+a})+quote}if(t=="object"){if(o.hasOwnProperty&&Object.prototype.toString.call(o)==="[object Array]"){for(i=0,v="[";i0?",":"")+serialize(o[i],quote)}return v+"]"}v="{";for(name in o){if(o.hasOwnProperty(name)){v+=typeof o[name]!="function"?(v.length>1?","+quote:quote)+name+quote+":"+serialize(o[name],quote):""}}return v+"}"}return""+o}tinymce.util.JSON={serialize:serialize,parse:function(s){try{return eval("("+s+")")}catch(ex){}}}})();tinymce.create("static tinymce.util.XHR",{send:function(g){var a,e,b=window,h=0;function f(){if(!g.async||a.readyState==4||h++>10000){if(g.success&&h<10000&&a.status==200){g.success.call(g.success_scope,""+a.responseText,a,g)}else{if(g.error){g.error.call(g.error_scope,h>10000?"TIMED_OUT":"GENERAL",a,g)}}a=null}else{b.setTimeout(f,10)}}g.scope=g.scope||this;g.success_scope=g.success_scope||g.scope;g.error_scope=g.error_scope||g.scope;g.async=g.async===false?false:true;g.data=g.data||"";function d(i){a=0;try{a=new ActiveXObject(i)}catch(c){}return a}a=b.XMLHttpRequest?new XMLHttpRequest():d("Microsoft.XMLHTTP")||d("Msxml2.XMLHTTP");if(a){if(a.overrideMimeType){a.overrideMimeType(g.content_type)}a.open(g.type||(g.data?"POST":"GET"),g.url,g.async);if(g.content_type){a.setRequestHeader("Content-Type",g.content_type)}a.setRequestHeader("X-Requested-With","XMLHttpRequest");a.send(g.data);if(!g.async){return f()}e=b.setTimeout(f,10)}}});(function(){var c=tinymce.extend,b=tinymce.util.JSON,a=tinymce.util.XHR;tinymce.create("tinymce.util.JSONRequest",{JSONRequest:function(d){this.settings=c({},d);this.count=0},send:function(f){var e=f.error,d=f.success;f=c(this.settings,f);f.success=function(h,g){h=b.parse(h);if(typeof(h)=="undefined"){h={error:"JSON Parse error."}}if(h.error){e.call(f.error_scope||f.scope,h.error,g)}else{d.call(f.success_scope||f.scope,h.result)}};f.error=function(h,g){if(e){e.call(f.error_scope||f.scope,h,g)}};f.data=b.serialize({id:f.id||"c"+(this.count++),method:f.method,params:f.params});f.content_type="application/json";a.send(f)},"static":{sendRPC:function(d){return new tinymce.util.JSONRequest().send(d)}}})}());(function(a){a.VK={BACKSPACE:8,DELETE:46,DOWN:40,ENTER:13,LEFT:37,RIGHT:39,SPACEBAR:32,TAB:9,UP:38,modifierPressed:function(b){return b.shiftKey||b.ctrlKey||b.altKey},metaKeyPressed:function(b){return a.isMac?b.metaKey:b.ctrlKey&&!b.altKey}}})(tinymce);tinymce.util.Quirks=function(a){var j=tinymce.VK,f=j.BACKSPACE,k=j.DELETE,e=a.dom,m=a.selection,I=a.settings,x=a.parser,p=a.serializer,F=tinymce.each;function B(O,N){try{a.getDoc().execCommand(O,false,N)}catch(M){}}function o(){var M=a.getDoc().documentMode;return M?M:6}function A(M){return M.isDefaultPrevented()}function K(){function M(S){var O,Q,N,T,P,R,U;function V(){if(P.nodeType==3){if(S&&R==P.length){return true}if(!S&&R===0){return true}}}O=m.getRng();var W=[O.startContainer,O.startOffset,O.endContainer,O.endOffset];if(!O.collapsed){S=true}P=O[(S?"start":"end")+"Container"];R=O[(S?"start":"end")+"Offset"];if(P.nodeType==3){Q=e.getParent(O.startContainer,e.isBlock);if(S){Q=e.getNext(Q,e.isBlock)}if(Q&&(V()||!O.collapsed)){N=e.create("em",{id:"__mceDel"});F(tinymce.grep(Q.childNodes),function(X){N.appendChild(X)});Q.appendChild(N)}}O=e.createRng();O.setStart(W[0],W[1]);O.setEnd(W[2],W[3]);m.setRng(O);a.getDoc().execCommand(S?"ForwardDelete":"Delete",false,null);if(N){T=m.getBookmark();while(U=e.get("__mceDel")){e.remove(U,true)}m.moveToBookmark(T)}}a.onKeyDown.add(function(N,P){var O;O=P.keyCode==k;if(!A(P)&&(O||P.keyCode==f)&&!j.modifierPressed(P)){P.preventDefault();M(O)}});a.addCommand("Delete",function(){M()})}function r(){function M(P){var O=e.create("body");var Q=P.cloneContents();O.appendChild(Q);return m.serializer.serialize(O,{format:"html"})}function N(O){var Q=M(O);var R=e.createRng();R.selectNode(a.getBody());var P=M(R);return Q===P}a.onKeyDown.add(function(P,R){var Q=R.keyCode,O;if(!A(R)&&(Q==k||Q==f)){O=P.selection.isCollapsed();if(O&&!e.isEmpty(P.getBody())){return}if(tinymce.isIE&&!O){return}if(!O&&!N(P.selection.getRng())){return}P.setContent("");P.selection.setCursorLocation(P.getBody(),0);P.nodeChanged()}})}function J(){a.onKeyDown.add(function(M,N){if(!A(N)&&N.keyCode==65&&j.metaKeyPressed(N)){N.preventDefault();M.execCommand("SelectAll")}})}function L(){if(!a.settings.content_editable){e.bind(a.getDoc(),"focusin",function(M){m.setRng(m.getRng())});e.bind(a.getDoc(),"mousedown",function(M){if(M.target==a.getDoc().documentElement){a.getWin().focus();m.setRng(m.getRng())}})}}function C(){a.onKeyDown.add(function(M,P){if(!A(P)&&P.keyCode===f){if(m.isCollapsed()&&m.getRng(true).startOffset===0){var O=m.getNode();var N=O.previousSibling;if(N&&N.nodeName&&N.nodeName.toLowerCase()==="hr"){e.remove(N);tinymce.dom.Event.cancel(P)}}}})}function z(){if(!Range.prototype.getClientRects){a.onMouseDown.add(function(N,O){if(!A(O)&&O.target.nodeName==="HTML"){var M=N.getBody();M.blur();setTimeout(function(){M.focus()},0)}})}}function h(){a.onClick.add(function(M,N){N=N.target;if(/^(IMG|HR)$/.test(N.nodeName)){m.getSel().setBaseAndExtent(N,0,N,1)}if(N.nodeName=="A"&&e.hasClass(N,"mceItemAnchor")){m.select(N)}M.nodeChanged()})}function c(){function N(){var P=e.getAttribs(m.getStart().cloneNode(false));return function(){var Q=m.getStart();if(Q!==a.getBody()){e.setAttrib(Q,"style",null);F(P,function(R){Q.setAttributeNode(R.cloneNode(true))})}}}function M(){return !m.isCollapsed()&&e.getParent(m.getStart(),e.isBlock)!=e.getParent(m.getEnd(),e.isBlock)}function O(P,Q){Q.preventDefault();return false}a.onKeyPress.add(function(P,R){var Q;if(!A(R)&&(R.keyCode==8||R.keyCode==46)&&M()){Q=N();P.getDoc().execCommand("delete",false,null);Q();R.preventDefault();return false}});e.bind(a.getDoc(),"cut",function(Q){var P;if(!A(Q)&&M()){P=N();a.onKeyUp.addToTop(O);setTimeout(function(){P();a.onKeyUp.remove(O)},0)}})}function b(){var N,M;e.bind(a.getDoc(),"selectionchange",function(){if(M){clearTimeout(M);M=0}M=window.setTimeout(function(){var O=m.getRng();if(!N||!tinymce.dom.RangeUtils.compareRanges(O,N)){a.nodeChanged();N=O}},50)})}function y(){document.body.setAttribute("role","application")}function u(){a.onKeyDown.add(function(M,O){if(!A(O)&&O.keyCode===f){if(m.isCollapsed()&&m.getRng(true).startOffset===0){var N=m.getNode().previousSibling;if(N&&N.nodeName&&N.nodeName.toLowerCase()==="table"){return tinymce.dom.Event.cancel(O)}}}})}function D(){if(o()>7){return}B("RespectVisibilityInDesign",true);a.contentStyles.push(".mceHideBrInPre pre br {display: none}");e.addClass(a.getBody(),"mceHideBrInPre");x.addNodeFilter("pre",function(M,O){var P=M.length,R,N,S,Q;while(P--){R=M[P].getAll("br");N=R.length;while(N--){S=R[N];Q=S.prev;if(Q&&Q.type===3&&Q.value.charAt(Q.value-1)!="\n"){Q.value+="\n"}else{S.parent.insert(new tinymce.html.Node("#text",3),S,true).value="\n"}}}});p.addNodeFilter("pre",function(M,O){var P=M.length,R,N,S,Q;while(P--){R=M[P].getAll("br");N=R.length;while(N--){S=R[N];Q=S.prev;if(Q&&Q.type==3){Q.value=Q.value.replace(/\r?\n$/,"")}}}})}function g(){e.bind(a.getBody(),"mouseup",function(O){var N,M=m.getNode();if(M.nodeName=="IMG"){if(N=e.getStyle(M,"width")){e.setAttrib(M,"width",N.replace(/[^0-9%]+/g,""));e.setStyle(M,"width","")}if(N=e.getStyle(M,"height")){e.setAttrib(M,"height",N.replace(/[^0-9%]+/g,""));e.setStyle(M,"height","")}}})}function d(){a.onKeyDown.add(function(S,T){var R,M,N,P,Q,U,O;R=T.keyCode==k;if(!A(T)&&(R||T.keyCode==f)&&!j.modifierPressed(T)){M=m.getRng();N=M.startContainer;P=M.startOffset;O=M.collapsed;if(N.nodeType==3&&N.nodeValue.length>0&&((P===0&&!O)||(O&&P===(R?0:1)))){U=N.previousSibling;if(U&&U.nodeName=="IMG"){return}nonEmptyElements=S.schema.getNonEmptyElements();T.preventDefault();Q=e.create("br",{id:"__tmp"});N.parentNode.insertBefore(Q,N);S.getDoc().execCommand(R?"ForwardDelete":"Delete",false,null);N=m.getRng().startContainer;U=N.previousSibling;if(U&&U.nodeType==1&&!e.isBlock(U)&&e.isEmpty(U)&&!nonEmptyElements[U.nodeName.toLowerCase()]){e.remove(U)}e.remove("__tmp")}}})}function H(){a.onKeyDown.add(function(Q,R){var O,N,S,M,P;if(A(R)||R.keyCode!=j.BACKSPACE){return}O=m.getRng();N=O.startContainer;S=O.startOffset;M=e.getRoot();P=N;if(!O.collapsed||S!==0){return}while(P&&P.parentNode&&P.parentNode.firstChild==P&&P.parentNode!=M){P=P.parentNode}if(P.tagName==="BLOCKQUOTE"){Q.formatter.toggle("blockquote",null,P);O=e.createRng();O.setStart(N,0);O.setEnd(N,0);m.setRng(O)}})}function G(){function M(){a._refreshContentEditable();B("StyleWithCSS",false);B("enableInlineTableEditing",false);if(!I.object_resizing){B("enableObjectResizing",false)}}if(!I.readonly){a.onBeforeExecCommand.add(M);a.onMouseDown.add(M)}}function t(){function M(N,O){F(e.select("a"),function(R){var P=R.parentNode,Q=e.getRoot();if(P.lastChild===R){while(P&&!e.isBlock(P)){if(P.parentNode.lastChild!==P||P===Q){return}P=P.parentNode}e.add(P,"br",{"data-mce-bogus":1})}})}a.onExecCommand.add(function(N,O){if(O==="CreateLink"){M(N)}});a.onSetContent.add(m.onSetContent.add(M))}function n(){if(I.forced_root_block){a.onInit.add(function(){B("DefaultParagraphSeparator",I.forced_root_block)})}}function q(){function M(O,N){if(!O||!N.initial){a.execCommand("mceRepaint")}}a.onUndo.add(M);a.onRedo.add(M);a.onSetContent.add(M)}function i(){a.onKeyDown.add(function(N,O){var M;if(!A(O)&&O.keyCode==f){M=N.getDoc().selection.createRange();if(M&&M.item){O.preventDefault();N.undoManager.beforeChange();e.remove(M.item(0));N.undoManager.add()}}})}function s(){var M;if(o()>=10){M="";F("p div h1 h2 h3 h4 h5 h6".split(" "),function(N,O){M+=(O>0?",":"")+N+":empty"});a.contentStyles.push(M+"{padding-right: 1px !important}")}}function v(){var O,N,ae,M,Z,ac,aa,ad,P,Q,ab,X,W,Y=document,U=a.getDoc();if(!I.object_resizing||I.webkit_fake_resize===false){return}B("enableObjectResizing",false);ab={n:[0.5,0,0,-1],e:[1,0.5,1,0],s:[0.5,1,0,1],w:[0,0.5,-1,0],nw:[0,0,-1,-1],ne:[1,0,1,-1],se:[1,1,1,1],sw:[0,1,-1,1]};function S(ai){var ah,ag;ah=ai.screenX-ac;ag=ai.screenY-aa;X=ah*Z[2]+ad;W=ag*Z[3]+P;X=X<5?5:X;W=W<5?5:W;if(j.modifierPressed(ai)||(ae.nodeName=="IMG"&&Z[2]*Z[3]!==0)){X=Math.round(W/Q);W=Math.round(X*Q)}e.setStyles(M,{width:X,height:W});if(Z[2]<0&&M.clientWidth<=X){e.setStyle(M,"left",O+(ad-X))}if(Z[3]<0&&M.clientHeight<=W){e.setStyle(M,"top",N+(P-W))}}function af(){function ag(ah,ai){if(ai){if(ae.style[ah]||!a.schema.isValid(ae.nodeName.toLowerCase(),ah)){e.setStyle(ae,ah,ai)}else{e.setAttrib(ae,ah,ai)}}}ag("width",X);ag("height",W);e.unbind(U,"mousemove",S);e.unbind(U,"mouseup",af);if(Y!=U){e.unbind(Y,"mousemove",S);e.unbind(Y,"mouseup",af)}e.remove(M);R(ae)}function R(aj){var ah,ai,ag;T();ah=e.getPos(aj);O=ah.x;N=ah.y;ai=aj.offsetWidth;ag=aj.offsetHeight;if(ae!=aj){ae=aj;X=W=0}F(ab,function(am,ak){var al;al=e.get("mceResizeHandle"+ak);if(!al){al=e.add(U.documentElement,"div",{id:"mceResizeHandle"+ak,"class":"mceResizeHandle",style:"cursor:"+ak+"-resize; margin:0; padding:0"});e.bind(al,"mousedown",function(an){an.preventDefault();af();ac=an.screenX;aa=an.screenY;ad=ae.clientWidth;P=ae.clientHeight;Q=P/ad;Z=am;M=ae.cloneNode(true);e.addClass(M,"mceClonedResizable");e.setStyles(M,{left:O,top:N,margin:0});U.documentElement.appendChild(M);e.bind(U,"mousemove",S);e.bind(U,"mouseup",af);if(Y!=U){e.bind(Y,"mousemove",S);e.bind(Y,"mouseup",af)}})}else{e.show(al)}e.setStyles(al,{left:(ai*am[0]+O)-(al.offsetWidth/2),top:(ag*am[1]+N)-(al.offsetHeight/2)})});if(!tinymce.isOpera&&ae.nodeName=="IMG"){ae.setAttribute("data-mce-selected","1")}}function T(){if(ae){ae.removeAttribute("data-mce-selected")}for(var ag in ab){e.hide("mceResizeHandle"+ag)}}a.contentStyles.push(".mceResizeHandle {position: absolute;border: 1px solid black;background: #FFF;width: 5px;height: 5px;z-index: 10000}.mceResizeHandle:hover {background: #000}img[data-mce-selected] {outline: 1px solid black}img.mceClonedResizable, table.mceClonedResizable {position: absolute;outline: 1px dashed black;opacity: .5;z-index: 10000}");function V(){var ag=e.getParent(m.getNode(),"table,img");F(e.select("img[data-mce-selected]"),function(ah){ah.removeAttribute("data-mce-selected")});if(ag){R(ag)}else{T()}}a.onNodeChange.add(V);e.bind(U,"selectionchange",V);a.serializer.addAttributeFilter("data-mce-selected",function(ag,ah){var ai=ag.length;while(ai--){ag[ai].attr(ah,null)}})}function E(){if(o()<9){x.addNodeFilter("noscript",function(M){var N=M.length,O,P;while(N--){O=M[N];P=O.firstChild;if(P){O.attr("data-mce-innertext",P.value)}}});p.addNodeFilter("noscript",function(M){var N=M.length,O,Q,P;while(N--){O=M[N];Q=M[N].firstChild;if(Q){Q.value=tinymce.html.Entities.decode(Q.value)}else{P=O.attributes.map["data-mce-innertext"];if(P){O.attr("data-mce-innertext",null);Q=new tinymce.html.Node("#text",3);Q.value=P;Q.raw=true;O.append(Q)}}}})}}function l(){a.contentStyles.push("body {min-height: 100px}");a.onClick.add(function(M,N){if(N.target.nodeName=="HTML"){a.execCommand("SelectAll");a.selection.collapse(true);a.nodeChanged()}})}u();H();r();if(tinymce.isWebKit){d();K();L();h();n();if(tinymce.isIDevice){b()}else{v();J()}}if(tinymce.isIE&&!tinymce.isIE11){C();y();D();g();i();s();E()}if(tinymce.isIE11){l()}if(tinymce.isGecko&&!tinymce.isIE11){C();z();c();G();t();q()}if(tinymce.isOpera){v()}};(function(j){var a,g,d,k=/[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,b=/[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,f=/[<>&\"\']/g,c=/&(#x|#)?([\w]+);/g,i={128:"\u20AC",130:"\u201A",131:"\u0192",132:"\u201E",133:"\u2026",134:"\u2020",135:"\u2021",136:"\u02C6",137:"\u2030",138:"\u0160",139:"\u2039",140:"\u0152",142:"\u017D",145:"\u2018",146:"\u2019",147:"\u201C",148:"\u201D",149:"\u2022",150:"\u2013",151:"\u2014",152:"\u02DC",153:"\u2122",154:"\u0161",155:"\u203A",156:"\u0153",158:"\u017E",159:"\u0178"};g={'"':""","'":"'","<":"<",">":">","&":"&"};d={"<":"<",">":">","&":"&",""":'"',"'":"'"};function h(l){var m;m=document.createElement("div");m.innerHTML=l;return m.textContent||m.innerText||l}function e(m,p){var n,o,l,q={};if(m){m=m.split(",");p=p||10;for(n=0;n1){return"&#"+(((n.charCodeAt(0)-55296)*1024)+(n.charCodeAt(1)-56320)+65536)+";"}return g[n]||"&#"+n.charCodeAt(0)+";"})},encodeNamed:function(n,l,m){m=m||a;return n.replace(l?k:b,function(o){return g[o]||m[o]||o})},getEncodeFunc:function(l,o){var p=j.html.Entities;o=e(o)||a;function m(r,q){return r.replace(q?k:b,function(s){return g[s]||o[s]||"&#"+s.charCodeAt(0)+";"||s})}function n(r,q){return p.encodeNamed(r,q,o)}l=j.makeMap(l.replace(/\+/g,","));if(l.named&&l.numeric){return m}if(l.named){if(o){return n}return p.encodeNamed}if(l.numeric){return p.encodeNumeric}return p.encodeRaw},decode:function(l){return l.replace(c,function(n,m,o){if(m){o=parseInt(o,m.length===2?16:10);if(o>65535){o-=65536;return String.fromCharCode(55296+(o>>10),56320+(o&1023))}else{return i[o]||String.fromCharCode(o)}}return d[n]||a[n]||h(n)})}}})(tinymce);tinymce.html.Styles=function(d,f){var k=/rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,h=/(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,b=/\s*([^:]+):\s*([^;]+);?/g,l=/\s+$/,m=/rgb/,e,g,a={},j;d=d||{};j="\\\" \\' \\; \\: ; : \uFEFF".split(" ");for(g=0;g1?r:"0"+r}return"#"+o(q)+o(p)+o(i)}return{toHex:function(i){return i.replace(k,c)},parse:function(s){var z={},q,n,x,r,v=d.url_converter,y=d.url_converter_scope||this;function p(D,G){var F,C,B,E;if(z["border-image"]==="none"){delete z["border-image"]}F=z[D+"-top"+G];if(!F){return}C=z[D+"-right"+G];if(F!=C){return}B=z[D+"-bottom"+G];if(C!=B){return}E=z[D+"-left"+G];if(B!=E){return}z[D+G]=E;delete z[D+"-top"+G];delete z[D+"-right"+G];delete z[D+"-bottom"+G];delete z[D+"-left"+G]}function u(C){var D=z[C],B;if(!D||D.indexOf(" ")<0){return}D=D.split(" ");B=D.length;while(B--){if(D[B]!==D[0]){return false}}z[C]=D[0];return true}function A(D,C,B,E){if(!u(C)){return}if(!u(B)){return}if(!u(E)){return}z[D]=z[C]+" "+z[B]+" "+z[E];delete z[C];delete z[B];delete z[E]}function t(B){r=true;return a[B]}function i(C,B){if(r){C=C.replace(/\uFEFF[0-9]/g,function(D){return a[D]})}if(!B){C=C.replace(/\\([\'\";:])/g,"$1")}return C}function o(C,B,F,E,G,D){G=G||D;if(G){G=i(G);return"'"+G.replace(/\'/g,"\\'")+"'"}B=i(B||F||E);if(v){B=v.call(y,B,"style")}return"url('"+B.replace(/\'/g,"\\'")+"')"}if(s){s=s.replace(/\\[\"\';:\uFEFF]/g,t).replace(/\"[^\"]+\"|\'[^\']+\'/g,function(B){return B.replace(/[;:]/g,t)});while(q=b.exec(s)){n=q[1].replace(l,"").toLowerCase();x=q[2].replace(l,"");if(n&&x.length>0){if(n==="font-weight"&&x==="700"){x="bold"}else{if(n==="color"||n==="background-color"){x=x.toLowerCase()}}x=x.replace(k,c);x=x.replace(h,o);z[n]=r?i(x,true):x}b.lastIndex=q.index+q[0].length}p("border","");p("border","-width");p("border","-color");p("border","-style");p("padding","");p("margin","");A("border","border-width","border-style","border-color");if(z.border==="medium none"){delete z.border}}return z},serialize:function(p,r){var o="",n,q;function i(t){var x,u,s,v;x=f.styles[t];if(x){for(u=0,s=x.length;u0){o+=(o.length>0?" ":"")+t+": "+v+";"}}}}if(r&&f&&f.styles){i("*");i(r)}else{for(n in p){q=p[n];if(q!==e&&q.length>0){o+=(o.length>0?" ":"")+n+": "+q+";"}}}return o}}};(function(f){var a={},e=f.makeMap,g=f.each;function d(j,i){return j.split(i||",")}function h(m,l){var j,k={};function i(n){return n.replace(/[A-Z]+/g,function(o){return i(m[o])})}for(j in m){if(m.hasOwnProperty(j)){m[j]=i(m[j])}}i(l).replace(/#/g,"#text").replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g,function(q,o,n,p){n=d(n,"|");k[o]={attributes:e(n),attributesOrder:n,children:e(p,"|",{"#comment":{}})}});return k}function b(){var i=a.html5;if(!i){i=a.html5=h({A:"id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup",B:"#|a|abbr|area|audio|b|bdo|br|button|canvas|cite|code|command|datalist|del|dfn|em|embed|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|meta|meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video|wbr",C:"#|a|abbr|area|address|article|aside|audio|b|bdo|blockquote|br|button|canvas|cite|code|command|datalist|del|details|dfn|dialog|div|dl|em|embed|fieldset|figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|menu|meta|meter|nav|noscript|ol|object|output|p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video"},"html[A|manifest][body|head]head[A][base|command|link|meta|noscript|script|style|title]title[A][#]base[A|href|target][]link[A|href|rel|media|type|sizes][]meta[A|http-equiv|name|content|charset][]style[A|type|media|scoped][#]script[A|charset|type|src|defer|async][#]noscript[A][C]body[A][C]section[A][C]nav[A][C]article[A][C]aside[A][C]h1[A][B]h2[A][B]h3[A][B]h4[A][B]h5[A][B]h6[A][B]hgroup[A][h1|h2|h3|h4|h5|h6]header[A][C]footer[A][C]address[A][C]p[A][B]br[A][]pre[A][B]dialog[A][dd|dt]blockquote[A|cite][C]ol[A|start|reversed][li]ul[A][li]li[A|value][C]dl[A][dd|dt]dt[A][B]dd[A][C]a[A|href|target|ping|rel|media|type][B]em[A][B]strong[A][B]small[A][B]cite[A][B]q[A|cite][B]dfn[A][B]abbr[A][B]code[A][B]var[A][B]samp[A][B]kbd[A][B]sub[A][B]sup[A][B]i[A][B]b[A][B]mark[A][B]progress[A|value|max][B]meter[A|value|min|max|low|high|optimum][B]time[A|datetime][B]ruby[A][B|rt|rp]rt[A][B]rp[A][B]bdo[A][B]span[A][B]ins[A|cite|datetime][B]del[A|cite|datetime][B]figure[A][C|legend|figcaption]figcaption[A][C]img[A|alt|src|height|width|usemap|ismap][]iframe[A|name|src|height|width|sandbox|seamless][]embed[A|src|height|width|type][]object[A|data|type|height|width|usemap|name|form|classid][param]param[A|name|value][]details[A|open][C|legend]command[A|type|label|icon|disabled|checked|radiogroup][]menu[A|type|label][C|li]legend[A][C|B]div[A][C]source[A|src|type|media][]audio[A|src|autobuffer|autoplay|loop|controls][source]video[A|src|autobuffer|autoplay|loop|controls|width|height|poster][source]hr[A][]form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]fieldset[A|disabled|form|name][C|legend]label[A|form|for][B]input[A|type|accept|alt|autocomplete|autofocus|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value|name][]button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]datalist[A][B|option]optgroup[A|disabled|label][option]option[A|disabled|selected|label|value][]textarea[A|autofocus|disabled|form|maxlength|name|placeholder|readonly|required|rows|cols|wrap][]keygen[A|autofocus|challenge|disabled|form|keytype|name][]output[A|for|form|name][B]canvas[A|width|height][]map[A|name][B|C]area[A|shape|coords|href|alt|target|media|rel|ping|type][]mathml[A][]svg[A][]table[A|border][caption|colgroup|thead|tfoot|tbody|tr]caption[A][C]colgroup[A|span][col]col[A|span][]thead[A][tr]tfoot[A][tr]tbody[A][tr]tr[A][th|td]th[A|headers|rowspan|colspan|scope][B]td[A|headers|rowspan|colspan][C]wbr[A][]")}return i}function c(){var i=a.html4;if(!i){i=a.html4=h({Z:"H|K|N|O|P",Y:"X|form|R|Q",ZG:"E|span|width|align|char|charoff|valign",X:"p|T|div|U|W|isindex|fieldset|table",ZF:"E|align|char|charoff|valign",W:"pre|hr|blockquote|address|center|noframes",ZE:"abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height",ZD:"[E][S]",U:"ul|ol|dl|menu|dir",ZC:"p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q",T:"h1|h2|h3|h4|h5|h6",ZB:"X|S|Q",S:"R|P",ZA:"a|G|J|M|O|P",R:"a|H|K|N|O",Q:"noscript|P",P:"ins|del|script",O:"input|select|textarea|label|button",N:"M|L",M:"em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym",L:"sub|sup",K:"J|I",J:"tt|i|b|u|s|strike",I:"big|small|font|basefont",H:"G|F",G:"br|span|bdo",F:"object|applet|img|map|iframe",E:"A|B|C",D:"accesskey|tabindex|onfocus|onblur",C:"onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup",B:"lang|xml:lang|dir",A:"id|class|style|title"},"script[id|charset|type|language|src|defer|xml:space][]style[B|id|type|media|title|xml:space][]object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]param[id|name|value|valuetype|type][]p[E|align][#|S]a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]br[A|clear][]span[E][#|S]bdo[A|C|B][#|S]applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]h1[E|align][#|S]img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]map[B|C|A|name][X|form|Q|area]h2[E|align][#|S]iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]h3[E|align][#|S]tt[E][#|S]i[E][#|S]b[E][#|S]u[E][#|S]s[E][#|S]strike[E][#|S]big[E][#|S]small[E][#|S]font[A|B|size|color|face][#|S]basefont[id|size|color|face][]em[E][#|S]strong[E][#|S]dfn[E][#|S]code[E][#|S]q[E|cite][#|S]samp[E][#|S]kbd[E][#|S]var[E][#|S]cite[E][#|S]abbr[E][#|S]acronym[E][#|S]sub[E][#|S]sup[E][#|S]input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]optgroup[E|disabled|label][option]option[E|selected|disabled|label|value][]textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]label[E|for|accesskey|onfocus|onblur][#|S]button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]h4[E|align][#|S]ins[E|cite|datetime][#|Y]h5[E|align][#|S]del[E|cite|datetime][#|Y]h6[E|align][#|S]div[E|align][#|Y]ul[E|type|compact][li]li[E|type|value][#|Y]ol[E|type|compact|start][li]dl[E|compact][dt|dd]dt[E][#|S]dd[E][#|Y]menu[E|compact][li]dir[E|compact][li]pre[E|width|xml:space][#|ZA]hr[E|align|noshade|size|width][]blockquote[E|cite][#|Y]address[E][#|S|p]center[E][#|Y]noframes[E][#|Y]isindex[A|B|prompt][]fieldset[E][#|legend|Y]legend[E|accesskey|align][#|S]table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]caption[E|align][#|S]col[ZG][]colgroup[ZG][col]thead[ZF][tr]tr[ZF|bgcolor][th|td]th[E|ZE][#|Y]form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]noscript[E][#|Y]td[E|ZE][#|Y]tfoot[ZF][tr]tbody[ZF][tr]area[E|D|shape|coords|href|nohref|alt|target][]base[id|href|target][]body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]")}return i}f.html.Schema=function(A){var u=this,s={},k={},j=[],D,y;var o,q,z,r,v,n,p={};function m(F,E,H){var G=A[F];if(!G){G=a[F];if(!G){G=e(E," ",e(E.toUpperCase()," "));G=f.extend(G,H);a[F]=G}}else{G=e(G,",",e(G.toUpperCase()," "))}return G}A=A||{};y=A.schema=="html5"?b():c();if(A.verify_html===false){A.valid_elements="*[*]"}if(A.valid_styles){D={};g(A.valid_styles,function(F,E){D[E]=f.explode(F)})}o=m("whitespace_elements","pre script noscript style textarea");q=m("self_closing_elements","colgroup dd dt li option p td tfoot th thead tr");z=m("short_ended_elements","area base basefont br col frame hr img input isindex link meta param embed source wbr");r=m("boolean_attributes","checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls");n=m("non_empty_elements","td th iframe video audio object script",z);textBlockElementsMap=m("text_block_elements","h1 h2 h3 h4 h5 h6 p div address pre form blockquote center dir fieldset header footer article section hgroup aside nav figure");v=m("block_elements","hr table tbody thead tfoot th tr td li ol ul caption dl dt dd noscript menu isindex samp option datalist select optgroup",textBlockElementsMap);function i(E){return new RegExp("^"+E.replace(/([?+*])/g,".$1")+"$")}function C(L){var K,G,Z,V,aa,F,I,U,X,Q,Y,ac,O,J,W,E,S,H,ab,ad,P,T,N=/^([#+\-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,R=/^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,M=/[*?+]/;if(L){L=d(L);if(s["@"]){S=s["@"].attributes;H=s["@"].attributesOrder}for(K=0,G=L.length;K=0){for(U=A.length-1;U>=V;U--){T=A[U];if(T.valid){n.end(T.name)}}A.length=V}}function p(U,T,Y,X,W){var Z,V;T=T.toLowerCase();Y=T in H?T:j(Y||X||W||"");if(v&&!z&&T.indexOf("data-")!==0){Z=P[T];if(!Z&&F){V=F.length;while(V--){Z=F[V];if(Z.pattern.test(T)){break}}if(V===-1){Z=null}}if(!Z){return}if(Z.validValues&&!(Y in Z.validValues)){return}}N.map[T]=Y;N.push({name:T,value:Y})}l=new RegExp("<(?:(?:!--([\\w\\W]*?)-->)|(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|(?:!DOCTYPE([\\w\\W]*?)>)|(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|(?:\\/([^>]+)>)|(?:([A-Za-z0-9\\-\\:\\.]+)((?:\\s+[^\"'>]+(?:(?:\"[^\"]*\")|(?:'[^']*')|[^>]*))*|\\/|\\s+)>))","g");D=/([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:[^\"])*)\")|(?:\'((?:[^\'])*)\')|([^>\s]+)))?/g;K={script:/<\/script[^>]*>/gi,style:/<\/style[^>]*>/gi,noscript:/<\/noscript[^>]*>/gi};M=e.getShortEndedElements();J=c.self_closing_elements||e.getSelfClosingElements();H=e.getBoolAttrs();v=c.validate;s=c.remove_internals;y=c.fix_self_closing;q=a.isIE;o=/^:/;while(g=l.exec(E)){if(G0&&A[A.length-1].name===I){u(I)}if(!v||(m=e.getElementRule(I))){k=true;if(v){P=m.attributes;F=m.attributePatterns}if(R=g[8]){z=R.indexOf("data-mce-type")!==-1;if(z&&s){k=false}N=[];N.map={};R.replace(D,p)}else{N=[];N.map={}}if(v&&!z){S=m.attributesRequired;L=m.attributesDefault;f=m.attributesForced;if(f){Q=f.length;while(Q--){t=f[Q];r=t.name;h=t.value;if(h==="{$uid}"){h="mce_"+x++}N.map[r]=h;N.push({name:r,value:h})}}if(L){Q=L.length;while(Q--){t=L[Q];r=t.name;if(!(r in N.map)){h=t.value;if(h==="{$uid}"){h="mce_"+x++}N.map[r]=h;N.push({name:r,value:h})}}}if(S){Q=S.length;while(Q--){if(S[Q] in N.map){break}}if(Q===-1){k=false}}if(N.map["data-mce-bogus"]){k=false}}if(k){n.start(I,N,O)}}else{k=false}if(B=K[I]){B.lastIndex=G=g.index+g[0].length;if(g=B.exec(E)){if(k){C=E.substr(G,g.index-G)}G=g.index+g[0].length}else{C=E.substr(G);G=E.length}if(k&&C.length>0){n.text(C,true)}if(k){n.end(I)}l.lastIndex=G;continue}if(!O){if(!R||R.indexOf("/")!=R.length-1){A.push({name:I,valid:k})}else{if(k){n.end(I)}}}}else{if(I=g[1]){n.comment(I)}else{if(I=g[2]){n.cdata(I)}else{if(I=g[3]){n.doctype(I)}else{if(I=g[4]){n.pi(I,g[5])}}}}}}G=g.index+g[0].length}if(G=0;Q--){I=A[Q];if(I.valid){n.end(I.name)}}}}})(tinymce);(function(d){var c=/^[ \t\r\n]*$/,e={"#text":3,"#comment":8,"#cdata":4,"#pi":7,"#doctype":10,"#document-fragment":11};function a(k,l,j){var i,h,f=j?"lastChild":"firstChild",g=j?"prev":"next";if(k[f]){return k[f]}if(k!==l){i=k[g];if(i){return i}for(h=k.parent;h&&h!==l;h=h.parent){i=h[g];if(i){return i}}}}function b(f,g){this.name=f;this.type=g;if(g===1){this.attributes=[];this.attributes.map={}}}d.extend(b.prototype,{replace:function(g){var f=this;if(g.parent){g.remove()}f.insert(g,f);f.remove();return f},attr:function(h,l){var f=this,g,j,k;if(typeof h!=="string"){for(j in h){f.attr(j,h[j])}return f}if(g=f.attributes){if(l!==k){if(l===null){if(h in g.map){delete g.map[h];j=g.length;while(j--){if(g[j].name===h){g=g.splice(j,1);return f}}}return f}if(h in g.map){j=g.length;while(j--){if(g[j].name===h){g[j].value=l;break}}}else{g.push({name:h,value:l})}g.map[h]=l;return f}else{return g.map[h]}}},clone:function(){var g=this,n=new b(g.name,g.type),h,f,m,j,k;if(m=g.attributes){k=[];k.map={};for(h=0,f=m.length;h1){x.reverse();A=o=f.filterNode(x[0].clone());for(u=0;u0){Q.value=l;Q=Q.prev}else{O=Q.prev;Q.remove();Q=O}}}function H(O){var P,l={};for(P in O){if(P!=="li"&&P!="p"){l[P]=O[P]}}return l}n=new b.html.SaxParser({validate:z,self_closing_elements:H(h.getSelfClosingElements()),cdata:function(l){B.append(K("#cdata",4)).value=l},text:function(P,l){var O;if(!L){P=P.replace(k," ");if(B.lastChild&&o[B.lastChild.name]){P=P.replace(E,"")}}if(P.length!==0){O=K("#text",3);O.raw=!!l;B.append(O).value=P}},comment:function(l){B.append(K("#comment",8)).value=l},pi:function(l,O){B.append(K(l,7)).value=O;I(B)},doctype:function(O){var l;l=B.append(K("#doctype",10));l.value=O;I(B)},start:function(l,W,P){var U,R,Q,O,S,X,V,T;Q=z?h.getElementRule(l):{};if(Q){U=K(Q.outputName||l,1);U.attributes=W;U.shortEnded=P;B.append(U);T=p[B.name];if(T&&p[U.name]&&!T[U.name]){M.push(U)}R=d.length;while(R--){S=d[R].name;if(S in W.map){F=c[S];if(F){F.push(U)}else{c[S]=[U]}}}if(o[l]){I(U)}if(!P){B=U}if(!L&&s[l]){L=true}}},end:function(l){var S,P,R,O,Q;P=z?h.getElementRule(l):{};if(P){if(o[l]){if(!L){S=B.firstChild;if(S&&S.type===3){R=S.value.replace(E,"");if(R.length>0){S.value=R;S=S.next}else{O=S.next;S.remove();S=O}while(S&&S.type===3){R=S.value;O=S.next;if(R.length===0||y.test(R)){S.remove();S=O}S=O}}S=B.lastChild;if(S&&S.type===3){R=S.value.replace(t,"");if(R.length>0){S.value=R;S=S.prev}else{O=S.prev;S.remove();S=O}while(S&&S.type===3){R=S.value;O=S.prev;if(R.length===0||y.test(R)){S.remove();S=O}S=O}}}}if(L&&s[l]){L=false}if(P.removeEmpty||P.paddEmpty){if(B.isEmpty(u)){if(P.paddEmpty){B.empty().append(new a("#text","3")).value="\u00a0"}else{if(!B.attributes.map.name&&!B.attributes.map.id){Q=B.parent;B.empty().remove();B=Q;return}}}}B=B.parent}}},h);J=B=new a(m.context||g.root_name,11);n.parse(v);if(z&&M.length){if(!m.context){j(M)}else{m.invalid=true}}if(q&&J.name=="body"){G()}if(!m.invalid){for(N in i){F=e[N];A=i[N];x=A.length;while(x--){if(!A[x].parent){A.splice(x,1)}}for(D=0,C=F.length;D0){o=c[c.length-1];if(o.length>0&&o!=="\n"){c.push("\n")}}c.push("<",m);if(k){for(n=0,j=k.length;n0){o=c[c.length-1];if(o.length>0&&o!=="\n"){c.push("\n")}}},end:function(h){var i;c.push("");if(a&&d[h]&&c.length>0){i=c[c.length-1];if(i.length>0&&i!=="\n"){c.push("\n")}}},text:function(i,h){if(i.length>0){c[c.length]=h?i:f(i)}},cdata:function(h){c.push("")},comment:function(h){c.push("")},pi:function(h,i){if(i){c.push("")}else{c.push("")}if(a){c.push("\n")}},doctype:function(h){c.push("",a?"\n":"")},reset:function(){c.length=0},getContent:function(){return c.join("").replace(/\n$/,"")}}};(function(a){a.html.Serializer=function(c,d){var b=this,e=new a.html.Writer(c);c=c||{};c.validate="validate" in c?c.validate:true;b.schema=d=d||new a.html.Schema();b.writer=e;b.serialize=function(h){var g,i;i=c.validate;g={3:function(k,j){e.text(k.value,k.raw)},8:function(j){e.comment(j.value)},7:function(j){e.pi(j.name,j.value)},10:function(j){e.doctype(j.value)},4:function(j){e.cdata(j.value)},11:function(j){if((j=j.firstChild)){do{f(j)}while(j=j.next)}}};e.reset();function f(k){var t=g[k.type],j,o,s,r,p,u,n,m,q;if(!t){j=k.name;o=k.shortEnded;s=k.attributes;if(i&&s&&s.length>1){u=[];u.map={};q=d.getElementRule(k.name);for(n=0,m=q.attributesOrder.length;n=8;k.boxModel=!e.isIE||o.compatMode=="CSS1Compat"||k.stdMode;k.hasOuterHTML="outerHTML" in o.createElement("a");k.settings=l=e.extend({keep_values:false,hex_colors:1},l);k.schema=l.schema;k.styles=new e.html.Styles({url_converter:l.url_converter,url_converter_scope:l.url_converter_scope},l.schema);if(e.isIE6){try{o.execCommand("BackgroundImageCache",false,true)}catch(m){k.cssFlicker=true}}k.fixDoc(o);k.events=l.ownEvents?new e.dom.EventUtils(l.proxy):e.dom.Event;e.addUnload(k.destroy,k);n=l.schema?l.schema.getBlockElements():{};k.isBlock=function(q){if(!q){return false}var p=q.nodeType;if(p){return !!(p===1&&n[q.nodeName])}return !!n[q]}},fixDoc:function(k){var j=this.settings,i;if(b&&!e.isIE11&&j.schema){("abbr article aside audio canvas details figcaption figure footer header hgroup mark menu meter nav output progress section summary time video").replace(/\w+/g,function(l){k.createElement(l)});for(i in j.schema.getCustomElements()){k.createElement(i)}}},clone:function(k,i){var j=this,m,l;if(!b||e.isIE11||k.nodeType!==1||i){return k.cloneNode(i)}l=j.doc;if(!i){m=l.createElement(k.nodeName);g(j.getAttribs(k),function(n){j.setAttrib(m,n.nodeName,j.getAttrib(k,n.nodeName))});return m}return m.firstChild},getRoot:function(){var i=this,j=i.settings;return(j&&i.get(j.root_element))||i.doc.body},getViewPort:function(j){var k,i;j=!j?this.win:j;k=j.document;i=this.boxModel?k.documentElement:k.body;return{x:j.pageXOffset||i.scrollLeft,y:j.pageYOffset||i.scrollTop,w:j.innerWidth||i.clientWidth,h:j.innerHeight||i.clientHeight}},getRect:function(l){var k,i=this,j;l=i.get(l);k=i.getPos(l);j=i.getSize(l);return{x:k.x,y:k.y,w:j.w,h:j.h}},getSize:function(l){var j=this,i,k;l=j.get(l);i=j.getStyle(l,"width");k=j.getStyle(l,"height");if(i.indexOf("px")===-1){i=0}if(k.indexOf("px")===-1){k=0}return{w:parseInt(i,10)||l.offsetWidth||l.clientWidth,h:parseInt(k,10)||l.offsetHeight||l.clientHeight}},getParent:function(k,j,i){return this.getParents(k,j,i,false)},getParents:function(s,m,k,q){var j=this,i,l=j.settings,p=[];s=j.get(s);q=q===undefined;if(l.strict_root){k=k||j.getRoot()}if(d(m,"string")){i=m;if(m==="*"){m=function(o){return o.nodeType==1}}else{m=function(o){return j.is(o,i)}}}while(s){if(s==k||!s.nodeType||s.nodeType===9){break}if(!m||m(s)){if(q){p.push(s)}else{return s}}s=s.parentNode}return q?p:null},get:function(i){var j;if(i&&this.doc&&typeof(i)=="string"){j=i;i=this.doc.getElementById(i);if(i&&i.id!==j){return this.doc.getElementsByName(j)[1]}}return i},getNext:function(j,i){return this._findSib(j,i,"nextSibling")},getPrev:function(j,i){return this._findSib(j,i,"previousSibling")},select:function(k,j){var i=this;return e.dom.Sizzle(k,i.get(j)||i.get(i.settings.root_element)||i.doc,[])},is:function(l,j){var k;if(l.length===undefined){if(j==="*"){return l.nodeType==1}if(c.test(j)){j=j.toLowerCase().split(/,/);l=l.nodeName.toLowerCase();for(k=j.length-1;k>=0;k--){if(j[k]==l){return true}}return false}}return e.dom.Sizzle.matches(j,l.nodeType?[l]:l).length>0},add:function(l,o,i,k,m){var j=this;return this.run(l,function(r){var q,n;q=d(o,"string")?j.doc.createElement(o):o;j.setAttribs(q,i);if(k){if(k.nodeType){q.appendChild(k)}else{j.setHTML(q,k)}}return !m?r.appendChild(q):q})},create:function(k,i,j){return this.add(this.doc.createElement(k),k,i,j,1)},createHTML:function(q,i,m){var p="",l=this,j;p+="<"+q;for(j in i){if(i.hasOwnProperty(j)){p+=" "+j+'="'+l.encode(i[j])+'"'}}if(typeof(m)!="undefined"){return p+">"+m+""}return p+" />"},remove:function(i,j){return this.run(i,function(l){var m,k=l.parentNode;if(!k){return null}if(j){while(m=l.firstChild){if(!e.isIE||m.nodeType!==3||m.nodeValue){k.insertBefore(m,l)}else{l.removeChild(m)}}}return k.removeChild(l)})},setStyle:function(l,i,j){var k=this;return k.run(l,function(o){var n,m;n=o.style;i=i.replace(/-(\D)/g,function(q,p){return p.toUpperCase()});if(k.pixelStyles.test(i)&&(e.is(j,"number")||/^[\-0-9\.]+$/.test(j))){j+="px"}switch(i){case"opacity":if(b&&!e.isIE11){n.filter=j===""?"":"alpha(opacity="+(j*100)+")";if(!l.currentStyle||!l.currentStyle.hasLayout){n.display="inline-block"}}n[i]=n["-moz-opacity"]=n["-khtml-opacity"]=j||"";break;case"float":(b&&!e.isIE11)?n.styleFloat=j:n.cssFloat=j;break;default:n[i]=j||""}if(k.settings.update_styles){k.setAttrib(o,"data-mce-style")}})},getStyle:function(l,i,k){l=this.get(l);if(!l){return}if(this.doc.defaultView&&k){i=i.replace(/[A-Z]/g,function(m){return"-"+m});try{return this.doc.defaultView.getComputedStyle(l,null).getPropertyValue(i)}catch(j){return null}}i=i.replace(/-(\D)/g,function(n,m){return m.toUpperCase()});if(i=="float"){i=b?"styleFloat":"cssFloat"}if(l.currentStyle&&k){return l.currentStyle[i]}return l.style?l.style[i]:undefined},setStyles:function(l,m){var j=this,k=j.settings,i;i=k.update_styles;k.update_styles=0;g(m,function(o,p){j.setStyle(l,p,o)});k.update_styles=i;if(k.update_styles){j.setAttrib(l,k.cssText)}},removeAllAttribs:function(i){return this.run(i,function(l){var k,j=l.attributes;for(k=j.length-1;k>=0;k--){l.removeAttributeNode(j.item(k))}})},setAttrib:function(k,l,i){var j=this;if(!k||!l){return}if(j.settings.strict){l=l.toLowerCase()}return this.run(k,function(p){var o=j.settings;var m=p.getAttribute(l);if(i!==null){switch(l){case"style":if(!d(i,"string")){g(i,function(q,r){j.setStyle(p,r,q)});return}if(o.keep_values){if(i&&!j._isRes(i)){p.setAttribute("data-mce-style",i,2)}else{p.removeAttribute("data-mce-style",2)}}p.style.cssText=i;break;case"class":p.className=i||"";break;case"src":case"href":if(o.keep_values){if(o.url_converter){i=o.url_converter.call(o.url_converter_scope||j,i,l,p)}j.setAttrib(p,"data-mce-"+l,i,2)}break;case"shape":p.setAttribute("data-mce-style",i);break}}if(d(i)&&i!==null&&i.length!==0){p.setAttribute(l,""+i,2)}else{p.removeAttribute(l,2)}if(tinyMCE.activeEditor&&m!=i){var n=tinyMCE.activeEditor;n.onSetAttrib.dispatch(n,p,l,i)}})},setAttribs:function(j,k){var i=this;return this.run(j,function(l){g(k,function(m,o){i.setAttrib(l,o,m)})})},getAttrib:function(m,o,k){var i,j=this,l;m=j.get(m);if(!m||m.nodeType!==1){return k===l?false:k}if(!d(k)){k=""}if(/^(src|href|style|coords|shape)$/.test(o)){i=m.getAttribute("data-mce-"+o);if(i){return i}}if(b&&j.props[o]){i=m[j.props[o]];i=i&&i.nodeValue?i.nodeValue:i}if(!i){i=m.getAttribute(o,2)}if(/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(o)){if(m[j.props[o]]===true&&i===""){return o}return i?o:""}if(m.nodeName==="FORM"&&m.getAttributeNode(o)){return m.getAttributeNode(o).nodeValue}if(o==="style"){i=i||m.style.cssText;if(i){i=j.serializeStyle(j.parseStyle(i),m.nodeName);if(j.settings.keep_values&&!j._isRes(i)){m.setAttribute("data-mce-style",i)}}}if(f&&o==="class"&&i){i=i.replace(/(apple|webkit)\-[a-z\-]+/gi,"")}if(b){switch(o){case"rowspan":case"colspan":if(i===1){i=""}break;case"size":if(i==="+0"||i===20||i===0){i=""}break;case"width":case"height":case"vspace":case"checked":case"disabled":case"readonly":if(i===0){i=""}break;case"hspace":if(i===-1){i=""}break;case"maxlength":case"tabindex":if(i===32768||i===2147483647||i==="32768"){i=""}break;case"multiple":case"compact":case"noshade":case"nowrap":if(i===65535){return o}return k;case"shape":i=i.toLowerCase();break;default:if(o.indexOf("on")===0&&i){i=e._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/,"$1",""+i)}}}return(i!==l&&i!==null&&i!=="")?""+i:k},getPos:function(q,l){var j=this,i=0,p=0,m,o=j.doc,k;q=j.get(q);l=l||o.body;if(q){if(q.getBoundingClientRect){q=q.getBoundingClientRect();m=j.boxModel?o.documentElement:o.body;i=q.left+(o.documentElement.scrollLeft||o.body.scrollLeft)-m.clientTop;p=q.top+(o.documentElement.scrollTop||o.body.scrollTop)-m.clientLeft;return{x:i,y:p}}k=q;while(k&&k!=l&&k.nodeType){i+=k.offsetLeft||0;p+=k.offsetTop||0;k=k.offsetParent}k=q.parentNode;while(k&&k!=l&&k.nodeType){i-=k.scrollLeft||0;p-=k.scrollTop||0;k=k.parentNode}}return{x:i,y:p}},parseStyle:function(i){return this.styles.parse(i)},serializeStyle:function(j,i){return this.styles.serialize(j,i)},addStyle:function(j){var k=this.doc,i;styleElm=k.getElementById("mceDefaultStyles");if(!styleElm){styleElm=k.createElement("style"),styleElm.id="mceDefaultStyles";styleElm.type="text/css";i=k.getElementsByTagName("head")[0];if(i.firstChild){i.insertBefore(styleElm,i.firstChild)}else{i.appendChild(styleElm)}}if(styleElm.styleSheet){styleElm.styleSheet.cssText+=j}else{styleElm.appendChild(k.createTextNode(j))}},loadCSS:function(i){var k=this,l=k.doc,j;if(!i){i=""}j=l.getElementsByTagName("head")[0];g(i.split(","),function(m){var n;if(k.files[m]){return}k.files[m]=true;n=k.create("link",{rel:"stylesheet",href:e._addVer(m)});if(b&&!e.isIE11&&l.documentMode&&l.recalc){n.onload=function(){if(l.recalc){l.recalc()}n.onload=null}}j.appendChild(n)})},addClass:function(i,j){return this.run(i,function(k){var l;if(!j){return 0}if(this.hasClass(k,j)){return k.className}l=this.removeClass(k,j);return k.className=(l!=""?(l+" "):"")+j})},removeClass:function(k,l){var i=this,j;return i.run(k,function(n){var m;if(i.hasClass(n,l)){if(!j){j=new RegExp("(^|\\s+)"+l+"(\\s+|$)","g")}m=n.className.replace(j," ");m=e.trim(m!=" "?m:"");n.className=m;if(!m){n.removeAttribute("class");n.removeAttribute("className")}return m}return n.className})},hasClass:function(j,i){j=this.get(j);if(!j||!i){return false}return(" "+j.className+" ").indexOf(" "+i+" ")!==-1},show:function(i){return this.setStyle(i,"display","block")},hide:function(i){return this.setStyle(i,"display","none")},isHidden:function(i){i=this.get(i);return !i||i.style.display=="none"||this.getStyle(i,"display")=="none"},uniqueId:function(i){return(!i?"mce_":i)+(this.counter++)},setHTML:function(k,j){var i=this;return i.run(k,function(m){if(b){while(m.firstChild){m.removeChild(m.firstChild)}try{m.innerHTML="
"+j;m.removeChild(m.firstChild)}catch(l){var n=i.create("div");n.innerHTML="
"+j;g(e.grep(n.childNodes),function(p,o){if(o&&m.canHaveHTML){m.appendChild(p)}})}}else{m.innerHTML=j}return j})},getOuterHTML:function(k){var j,i=this;k=i.get(k);if(!k){return null}if(k.nodeType===1&&i.hasOuterHTML){return k.outerHTML}j=(k.ownerDocument||i.doc).createElement("body");j.appendChild(k.cloneNode(true));return j.innerHTML},setOuterHTML:function(l,j,m){var i=this;function k(p,o,r){var s,q;q=r.createElement("body");q.innerHTML=o;s=q.lastChild;while(s){i.insertAfter(s.cloneNode(true),p);s=s.previousSibling}i.remove(p)}return this.run(l,function(o){o=i.get(o);if(o.nodeType==1){m=m||o.ownerDocument||i.doc;if(b){try{if(b&&o.nodeType==1){o.outerHTML=j}else{k(o,j,m)}}catch(n){k(o,j,m)}}else{k(o,j,m)}}})},decode:h.decode,encode:h.encodeAllRaw,insertAfter:function(i,j){j=this.get(j);return this.run(i,function(l){var k,m;k=j.parentNode;m=j.nextSibling;if(m){k.insertBefore(l,m)}else{k.appendChild(l)}return l})},replace:function(m,l,i){var j=this;if(d(l,"array")){m=m.cloneNode(true)}return j.run(l,function(k){if(i){g(e.grep(k.childNodes),function(n){m.appendChild(n)})}return k.parentNode.replaceChild(m,k)})},rename:function(l,i){var k=this,j;if(l.nodeName!=i.toUpperCase()){j=k.create(i);g(k.getAttribs(l),function(m){k.setAttrib(j,m.nodeName,k.getAttrib(l,m.nodeName))});k.replace(j,l,1)}return j||l},findCommonAncestor:function(k,i){var l=k,j;while(l){j=i;while(j&&l!=j){j=j.parentNode}if(l==j){break}l=l.parentNode}if(!l&&k.ownerDocument){return k.ownerDocument.documentElement}return l},toHex:function(i){var k=/^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(i);function j(l){l=parseInt(l,10).toString(16);return l.length>1?l:"0"+l}if(k){i="#"+j(k[1])+j(k[2])+j(k[3]);return i}return i},getClasses:function(){var n=this,j=[],m,o={},p=n.settings.class_filter,l;if(n.classes){return n.classes}function q(i){g(i.imports,function(s){q(s)});g(i.cssRules||i.rules,function(t){switch(t.type||1){case 1:if(t.selectorText){g(t.selectorText.split(","),function(r){r=r.replace(/^\s*|\s*$|^\s\./g,"");if(/\.mce/.test(r)||!/\.[\w\-]+$/.test(r)){return}l=r;r=e._replace(/.*\.([a-z0-9_\-]+).*/i,"$1",r);if(p&&!(r=p(r,l))){return}if(!o[r]){j.push({"class":r});o[r]=1}})}break;case 3:try{q(t.styleSheet)}catch(s){}break}})}try{g(n.doc.styleSheets,q)}catch(k){}if(j.length>0){n.classes=j}return j},run:function(l,k,j){var i=this,m;if(i.doc&&typeof(l)==="string"){l=i.get(l)}if(!l){return false}j=j||this;if(!l.nodeType&&(l.length||l.length===0)){m=[];g(l,function(o,n){if(o){if(typeof(o)=="string"){o=i.doc.getElementById(o)}m.push(k.call(j,o,n))}});return m}return k.call(j,l)},getAttribs:function(j){var i;j=this.get(j);if(!j){return[]}if(b){i=[];if(j.nodeName=="OBJECT"){return j.attributes}if(j.nodeName==="OPTION"&&this.getAttrib(j,"selected")){i.push({specified:1,nodeName:"selected"})}j.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi,"").replace(/[\w:\-]+/gi,function(k){i.push({specified:1,nodeName:k})});return i}return j.attributes},isEmpty:function(m,k){var r=this,o,n,q,j,l,p=0;m=m.firstChild;if(m){j=new e.dom.TreeWalker(m,m.parentNode);k=k||r.schema?r.schema.getNonEmptyElements():null;do{q=m.nodeType;if(q===1){if(m.getAttribute("data-mce-bogus")){continue}l=m.nodeName.toLowerCase();if(k&&k[l]){if(l==="br"){p++;continue}return false}n=r.getAttribs(m);o=m.attributes.length;while(o--){l=m.attributes[o].nodeName;if(l==="name"||l==="data-mce-bookmark"){return false}}}if(q==8){return false}if((q===3&&!a.test(m.nodeValue))){return false}}while(m=j.next())}return p<=1},destroy:function(j){var i=this;i.win=i.doc=i.root=i.events=i.frag=null;if(!j){e.removeUnload(i.destroy)}},createRng:function(){var i=this.doc;return i.createRange?i.createRange():new e.dom.Range(this)},nodeIndex:function(m,n){var i=0,k,l,j;if(m){for(k=m.nodeType,m=m.previousSibling,l=m;m;m=m.previousSibling){j=m.nodeType;if(n&&j==3){if(j==k||!m.nodeValue.length){continue}}i++;k=j}}return i},split:function(m,l,p){var q=this,i=q.createRng(),n,k,o;function j(v){var t,s=v.childNodes,u=v.nodeType;function x(A){var z=A.previousSibling&&A.previousSibling.nodeName=="SPAN";var y=A.nextSibling&&A.nextSibling.nodeName=="SPAN";return z&&y}if(u==1&&v.getAttribute("data-mce-type")=="bookmark"){return}for(t=s.length-1;t>=0;t--){j(s[t])}if(u!=9){if(u==3&&v.nodeValue.length>0){var r=e.trim(v.nodeValue).length;if(!q.isBlock(v.parentNode)||r>0||r===0&&x(v)){return}}else{if(u==1){s=v.childNodes;if(s.length==1&&s[0]&&s[0].nodeType==1&&s[0].getAttribute("data-mce-type")=="bookmark"){v.parentNode.insertBefore(s[0],v)}if(s.length||/^(br|hr|input|img)$/i.test(v.nodeName)){return}}}q.remove(v)}return v}if(m&&l){i.setStart(m.parentNode,q.nodeIndex(m));i.setEnd(l.parentNode,q.nodeIndex(l));n=i.extractContents();i=q.createRng();i.setStart(l.parentNode,q.nodeIndex(l)+1);i.setEnd(m.parentNode,q.nodeIndex(m)+1);k=i.extractContents();o=m.parentNode;o.insertBefore(j(n),m);if(p){o.replaceChild(p,l)}else{o.insertBefore(l,m)}o.insertBefore(j(k),m);q.remove(m);return p||l}},bind:function(l,i,k,j){return this.events.add(l,i,k,j||this)},unbind:function(k,i,j){return this.events.remove(k,i,j)},fire:function(k,j,i){return this.events.fire(k,j,i)},getContentEditable:function(j){var i;if(j.nodeType!=1){return null}i=j.getAttribute("data-mce-contenteditable");if(i&&i!=="inherit"){return i}return j.contentEditable!=="inherit"?j.contentEditable:null},_findSib:function(l,i,j){var k=this,m=i;if(l){if(d(m,"string")){m=function(n){return k.is(n,i)}}for(l=l[j];l;l=l[j]){if(m(l)){return l}}}return null},_isRes:function(i){return/^(top|left|bottom|right|width|height)/i.test(i)||/;\s*(top|left|bottom|right|width|height)/i.test(i)}});e.DOM=new e.dom.DOMUtils(document,{process_html:0})})(tinymce);(function(a){function b(c){var O=this,e=c.doc,U=0,F=1,j=2,E=true,S=false,W="startOffset",h="startContainer",Q="endContainer",A="endOffset",k=tinymce.extend,n=c.nodeIndex;k(O,{startContainer:e,startOffset:0,endContainer:e,endOffset:0,collapsed:E,commonAncestorContainer:e,START_TO_START:0,START_TO_END:1,END_TO_END:2,END_TO_START:3,setStart:q,setEnd:s,setStartBefore:g,setStartAfter:J,setEndBefore:K,setEndAfter:u,collapse:B,selectNode:y,selectNodeContents:G,compareBoundaryPoints:v,deleteContents:p,extractContents:I,cloneContents:d,insertNode:D,surroundContents:N,cloneRange:L,toStringIE:T});function x(){return e.createDocumentFragment()}function q(X,t){C(E,X,t)}function s(X,t){C(S,X,t)}function g(t){q(t.parentNode,n(t))}function J(t){q(t.parentNode,n(t)+1)}function K(t){s(t.parentNode,n(t))}function u(t){s(t.parentNode,n(t)+1)}function B(t){if(t){O[Q]=O[h];O[A]=O[W]}else{O[h]=O[Q];O[W]=O[A]}O.collapsed=E}function y(t){g(t);u(t)}function G(t){q(t,0);s(t,t.nodeType===1?t.childNodes.length:t.nodeValue.length)}function v(aa,t){var ad=O[h],Y=O[W],ac=O[Q],X=O[A],ab=t.startContainer,af=t.startOffset,Z=t.endContainer,ae=t.endOffset;if(aa===0){return H(ad,Y,ab,af)}if(aa===1){return H(ac,X,ab,af)}if(aa===2){return H(ac,X,Z,ae)}if(aa===3){return H(ad,Y,Z,ae)}}function p(){l(j)}function I(){return l(U)}function d(){return l(F)}function D(aa){var X=this[h],t=this[W],Z,Y;if((X.nodeType===3||X.nodeType===4)&&X.nodeValue){if(!t){X.parentNode.insertBefore(aa,X)}else{if(t>=X.nodeValue.length){c.insertAfter(aa,X)}else{Z=X.splitText(t);X.parentNode.insertBefore(aa,Z)}}}else{if(X.childNodes.length>0){Y=X.childNodes[t]}if(Y){X.insertBefore(aa,Y)}else{X.appendChild(aa)}}}function N(X){var t=O.extractContents();O.insertNode(X);X.appendChild(t);O.selectNode(X)}function L(){return k(new b(c),{startContainer:O[h],startOffset:O[W],endContainer:O[Q],endOffset:O[A],collapsed:O.collapsed,commonAncestorContainer:O.commonAncestorContainer})}function P(t,X){var Y;if(t.nodeType==3){return t}if(X<0){return t}Y=t.firstChild;while(Y&&X>0){--X;Y=Y.nextSibling}if(Y){return Y}return t}function m(){return(O[h]==O[Q]&&O[W]==O[A])}function H(Z,ab,X,aa){var ac,Y,t,ad,af,ae;if(Z==X){if(ab==aa){return 0}if(ab0){O.collapse(X)}}else{O.collapse(X)}O.collapsed=m();O.commonAncestorContainer=c.findCommonAncestor(O[h],O[Q])}function l(ad){var ac,Z=0,af=0,X,ab,Y,aa,t,ae;if(O[h]==O[Q]){return f(ad)}for(ac=O[Q],X=ac.parentNode;X;ac=X,X=X.parentNode){if(X==O[h]){return r(ac,ad)}++Z}for(ac=O[h],X=ac.parentNode;X;ac=X,X=X.parentNode){if(X==O[Q]){return V(ac,ad)}++af}ab=af-Z;Y=O[h];while(ab>0){Y=Y.parentNode;ab--}aa=O[Q];while(ab<0){aa=aa.parentNode;ab++}for(t=Y.parentNode,ae=aa.parentNode;t!=ae;t=t.parentNode,ae=ae.parentNode){Y=t;aa=ae}return o(Y,aa,ad)}function f(ac){var ae,af,t,Y,Z,ad,aa,X,ab;if(ac!=j){ae=x()}if(O[W]==O[A]){return ae}if(O[h].nodeType==3){af=O[h].nodeValue;t=af.substring(O[W],O[A]);if(ac!=F){Y=O[h];X=O[W];ab=O[A]-O[W];if(X===0&&ab>=Y.nodeValue.length-1){Y.parentNode.removeChild(Y)}else{Y.deleteData(X,ab)}O.collapse(E)}if(ac==j){return}if(t.length>0){ae.appendChild(e.createTextNode(t))}return ae}Y=P(O[h],O[W]);Z=O[A]-O[W];while(Y&&Z>0){ad=Y.nextSibling;aa=z(Y,ac);if(ae){ae.appendChild(aa)}--Z;Y=ad}if(ac!=F){O.collapse(E)}return ae}function r(ad,aa){var ac,ab,X,t,Z,Y;if(aa!=j){ac=x()}ab=i(ad,aa);if(ac){ac.appendChild(ab)}X=n(ad);t=X-O[W];if(t<=0){if(aa!=F){O.setEndBefore(ad);O.collapse(S)}return ac}ab=ad.previousSibling;while(t>0){Z=ab.previousSibling;Y=z(ab,aa);if(ac){ac.insertBefore(Y,ac.firstChild)}--t;ab=Z}if(aa!=F){O.setEndBefore(ad);O.collapse(S)}return ac}function V(ab,aa){var ad,X,ac,t,Z,Y;if(aa!=j){ad=x()}ac=R(ab,aa);if(ad){ad.appendChild(ac)}X=n(ab);++X;t=O[A]-X;ac=ab.nextSibling;while(ac&&t>0){Z=ac.nextSibling;Y=z(ac,aa);if(ad){ad.appendChild(Y)}--t;ac=Z}if(aa!=F){O.setStartAfter(ab);O.collapse(E)}return ad}function o(ab,t,ae){var Y,ag,aa,ac,ad,X,af,Z;if(ae!=j){ag=x()}Y=R(ab,ae);if(ag){ag.appendChild(Y)}aa=ab.parentNode;ac=n(ab);ad=n(t);++ac;X=ad-ac;af=ab.nextSibling;while(X>0){Z=af.nextSibling;Y=z(af,ae);if(ag){ag.appendChild(Y)}af=Z;--X}Y=i(t,ae);if(ag){ag.appendChild(Y)}if(ae!=F){O.setStartAfter(ab);O.collapse(E)}return ag}function i(ac,ad){var Y=P(O[Q],O[A]-1),ae,ab,aa,t,X,Z=Y!=O[Q];if(Y==ac){return M(Y,Z,S,ad)}ae=Y.parentNode;ab=M(ae,S,S,ad);while(ae){while(Y){aa=Y.previousSibling;t=M(Y,Z,S,ad);if(ad!=j){ab.insertBefore(t,ab.firstChild)}Z=E;Y=aa}if(ae==ac){return ab}Y=ae.previousSibling;ae=ae.parentNode;X=M(ae,S,S,ad);if(ad!=j){X.appendChild(ab)}ab=X}}function R(ac,ad){var Z=P(O[h],O[W]),aa=Z!=O[h],ae,ab,Y,t,X;if(Z==ac){return M(Z,aa,E,ad)}ae=Z.parentNode;ab=M(ae,S,E,ad);while(ae){while(Z){Y=Z.nextSibling;t=M(Z,aa,E,ad);if(ad!=j){ab.appendChild(t)}aa=E;Z=Y}if(ae==ac){return ab}Z=ae.nextSibling;ae=ae.parentNode;X=M(ae,S,E,ad);if(ad!=j){X.appendChild(ab)}ab=X}}function M(t,aa,ad,ae){var Z,Y,ab,X,ac;if(aa){return z(t,ae)}if(t.nodeType==3){Z=t.nodeValue;if(ad){X=O[W];Y=Z.substring(X);ab=Z.substring(0,X)}else{X=O[A];Y=Z.substring(0,X);ab=Z.substring(X)}if(ae!=F){t.nodeValue=ab}if(ae==j){return}ac=c.clone(t,S);ac.nodeValue=Y;return ac}if(ae==j){return}return c.clone(t,S)}function z(X,t){if(t!=j){return t==F?c.clone(X,E):X}X.parentNode.removeChild(X)}function T(){return c.create("body",null,d()).outerText}return O}a.Range=b;b.prototype.toString=function(){return this.toStringIE()}})(tinymce.dom);(function(){function a(d){var b=this,h=d.dom,c=true,f=false;function e(i,j){var k,t=0,q,n,m,l,o,r,p=-1,s;k=i.duplicate();k.collapse(j);s=k.parentElement();if(s.ownerDocument!==d.dom.doc){return}while(s.contentEditable==="false"){s=s.parentNode}if(!s.hasChildNodes()){return{node:s,inside:1}}m=s.children;q=m.length-1;while(t<=q){r=Math.floor((t+q)/2);l=m[r];k.moveToElementText(l);p=k.compareEndPoints(j?"StartToStart":"EndToEnd",i);if(p>0){q=r-1}else{if(p<0){t=r+1}else{return{node:l}}}}if(p<0){if(!l){k.moveToElementText(s);k.collapse(true);l=s;n=true}else{k.collapse(false)}o=0;while(k.compareEndPoints(j?"StartToStart":"StartToEnd",i)!==0){if(k.move("character",1)===0||s!=k.parentElement()){break}o++}}else{k.collapse(true);o=0;while(k.compareEndPoints(j?"StartToStart":"StartToEnd",i)!==0){if(k.move("character",-1)===0||s!=k.parentElement()){break}o++}}return{node:l,position:p,offset:o,inside:n}}function g(){var i=d.getRng(),r=h.createRng(),l,k,p,q,m,j;l=i.item?i.item(0):i.parentElement();if(l.ownerDocument!=h.doc){return r}k=d.isCollapsed();if(i.item){r.setStart(l.parentNode,h.nodeIndex(l));r.setEnd(r.startContainer,r.startOffset+1);return r}function o(A){var u=e(i,A),s,y,z=0,x,v,t;s=u.node;y=u.offset;if(u.inside&&!s.hasChildNodes()){r[A?"setStart":"setEnd"](s,0);return}if(y===v){r[A?"setStartBefore":"setEndAfter"](s);return}if(u.position<0){x=u.inside?s.firstChild:s.nextSibling;if(!x){r[A?"setStartAfter":"setEndAfter"](s);return}if(!y){if(x.nodeType==3){r[A?"setStart":"setEnd"](x,0)}else{r[A?"setStartBefore":"setEndBefore"](x)}return}while(x){t=x.nodeValue;z+=t.length;if(z>=y){s=x;z-=y;z=t.length-z;break}x=x.nextSibling}}else{x=s.previousSibling;if(!x){return r[A?"setStartBefore":"setEndBefore"](s)}if(!y){if(s.nodeType==3){r[A?"setStart":"setEnd"](x,s.nodeValue.length)}else{r[A?"setStartAfter":"setEndAfter"](x)}return}while(x){z+=x.nodeValue.length;if(z>=y){s=x;z-=y;break}x=x.previousSibling}}r[A?"setStart":"setEnd"](s,z)}try{o(true);if(!k){o()}}catch(n){if(n.number==-2147024809){m=b.getBookmark(2);p=i.duplicate();p.collapse(true);l=p.parentElement();if(!k){p=i.duplicate();p.collapse(false);q=p.parentElement();q.innerHTML=q.innerHTML}l.innerHTML=l.innerHTML;b.moveToBookmark(m);i=d.getRng();o(true);if(!k){o()}}else{throw n}}return r}this.getBookmark=function(m){var j=d.getRng(),o,i,l={};function n(u){var t,p,s,r,q=[];t=u.parentNode;p=h.getRoot().parentNode;while(t!=p&&t.nodeType!==9){s=t.children;r=s.length;while(r--){if(u===s[r]){q.push(r);break}}u=t;t=t.parentNode}return q}function k(q){var p;p=e(j,q);if(p){return{position:p.position,offset:p.offset,indexes:n(p.node),inside:p.inside}}}if(m===2){if(!j.item){l.start=k(true);if(!d.isCollapsed()){l.end=k()}}else{l.start={ctrl:true,indexes:n(j.item(0))}}}return l};this.moveToBookmark=function(k){var j,i=h.doc.body;function m(o){var r,q,n,p;r=h.getRoot();for(q=o.length-1;q>=0;q--){p=r.children;n=o[q];if(n<=p.length-1){r=p[n]}}return r}function l(r){var n=k[r?"start":"end"],q,p,o;if(n){q=n.position>0;p=i.createTextRange();p.moveToElementText(m(n.indexes));offset=n.offset;if(offset!==o){p.collapse(n.inside||q);p.moveStart("character",q?-offset:offset)}else{p.collapse(r)}j.setEndPoint(r?"StartToStart":"EndToStart",p);if(r){j.collapse(true)}}}if(k.start){if(k.start.ctrl){j=i.createControlRange();j.addElement(m(k.start.indexes));j.select()}else{j=i.createTextRange();l(true);l();j.select()}}};this.addRange=function(i){var n,l,k,p,v,q,t,s=d.dom.doc,m=s.body,r,u;function j(C){var y,B,x,A,z;x=h.create("a");y=C?k:v;B=C?p:q;A=n.duplicate();if(y==s||y==s.documentElement){y=m;B=0}if(y.nodeType==3){y.parentNode.insertBefore(x,y);A.moveToElementText(x);A.moveStart("character",B);h.remove(x);n.setEndPoint(C?"StartToStart":"EndToEnd",A)}else{z=y.childNodes;if(z.length){if(B>=z.length){h.insertAfter(x,z[z.length-1])}else{y.insertBefore(x,z[B])}A.moveToElementText(x)}else{if(y.canHaveHTML){y.innerHTML="\uFEFF";x=y.firstChild;A.moveToElementText(x);A.collapse(f)}}n.setEndPoint(C?"StartToStart":"EndToEnd",A);h.remove(x)}}k=i.startContainer;p=i.startOffset;v=i.endContainer;q=i.endOffset;n=m.createTextRange();if(k==v&&k.nodeType==1){if(p==q&&!k.hasChildNodes()){if(k.canHaveHTML){t=k.previousSibling;if(t&&!t.hasChildNodes()&&h.isBlock(t)){t.innerHTML="\uFEFF"}else{t=null}k.innerHTML="\uFEFF\uFEFF";n.moveToElementText(k.lastChild);n.select();h.doc.selection.clear();k.innerHTML="";if(t){t.innerHTML=""}return}else{p=h.nodeIndex(k);k=k.parentNode}}if(p==q-1){try{u=k.childNodes[p];l=m.createControlRange();l.addElement(u);l.select();r=d.getRng();if(r.item&&u===r.item(0)){return}}catch(o){}}}j(true);j();n.select()};this.getRangeAt=g}tinymce.dom.TridentSelection=a})();(function(){var n=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,i="sizcache",o=0,r=Object.prototype.toString,h=false,g=true,q=/\\/g,u=/\r\n/g,x=/\W/;[0,0].sort(function(){g=false;return 0});var d=function(C,e,F,G){F=F||[];e=e||document;var I=e;if(e.nodeType!==1&&e.nodeType!==9){return[]}if(!C||typeof C!=="string"){return F}var z,K,N,y,J,M,L,E,B=true,A=d.isXML(e),D=[],H=C;do{n.exec("");z=n.exec(H);if(z){H=z[3];D.push(z[1]);if(z[2]){y=z[3];break}}}while(z);if(D.length>1&&j.exec(C)){if(D.length===2&&k.relative[D[0]]){K=s(D[0]+D[1],e,G)}else{K=k.relative[D[0]]?[e]:d(D.shift(),e);while(D.length){C=D.shift();if(k.relative[C]){C+=D.shift()}K=s(C,K,G)}}}else{if(!G&&D.length>1&&e.nodeType===9&&!A&&k.match.ID.test(D[0])&&!k.match.ID.test(D[D.length-1])){J=d.find(D.shift(),e,A);e=J.expr?d.filter(J.expr,J.set)[0]:J.set[0]}if(e){J=G?{expr:D.pop(),set:l(G)}:d.find(D.pop(),D.length===1&&(D[0]==="~"||D[0]==="+")&&e.parentNode?e.parentNode:e,A);K=J.expr?d.filter(J.expr,J.set):J.set;if(D.length>0){N=l(K)}else{B=false}while(D.length){M=D.pop();L=M;if(!k.relative[M]){M=""}else{L=D.pop()}if(L==null){L=e}k.relative[M](N,L,A)}}else{N=D=[]}}if(!N){N=K}if(!N){d.error(M||C)}if(r.call(N)==="[object Array]"){if(!B){F.push.apply(F,N)}else{if(e&&e.nodeType===1){for(E=0;N[E]!=null;E++){if(N[E]&&(N[E]===true||N[E].nodeType===1&&d.contains(e,N[E]))){F.push(K[E])}}}else{for(E=0;N[E]!=null;E++){if(N[E]&&N[E].nodeType===1){F.push(K[E])}}}}}else{l(N,F)}if(y){d(y,I,F,G);d.uniqueSort(F)}return F};d.uniqueSort=function(y){if(p){h=g;y.sort(p);if(h){for(var e=1;e0};d.find=function(E,e,F){var D,z,B,A,C,y;if(!E){return[]}for(z=0,B=k.order.length;z":function(D,y){var C,B=typeof y==="string",z=0,e=D.length;if(B&&!x.test(y)){y=y.toLowerCase();for(;z=0)){if(!z){e.push(C)}}else{if(z){y[B]=false}}}}return false},ID:function(e){return e[1].replace(q,"")},TAG:function(y,e){return y[1].replace(q,"").toLowerCase()},CHILD:function(e){if(e[1]==="nth"){if(!e[2]){d.error(e[0])}e[2]=e[2].replace(/^\+|\s*/g,"");var y=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(e[2]==="even"&&"2n"||e[2]==="odd"&&"2n+1"||!/\D/.test(e[2])&&"0n+"+e[2]||e[2]);e[2]=(y[1]+(y[2]||1))-0;e[3]=y[3]-0}else{if(e[2]){d.error(e[0])}}e[0]=o++;return e},ATTR:function(B,y,z,e,C,D){var A=B[1]=B[1].replace(q,"");if(!D&&k.attrMap[A]){B[1]=k.attrMap[A]}B[4]=(B[4]||B[5]||"").replace(q,"");if(B[2]==="~="){B[4]=" "+B[4]+" "}return B},PSEUDO:function(B,y,z,e,C){if(B[1]==="not"){if((n.exec(B[3])||"").length>1||/^\w/.test(B[3])){B[3]=d(B[3],null,null,y)}else{var A=d.filter(B[3],y,z,true^C);if(!z){e.push.apply(e,A)}return false}}else{if(k.match.POS.test(B[0])||k.match.CHILD.test(B[0])){return true}}return B},POS:function(e){e.unshift(true);return e}},filters:{enabled:function(e){return e.disabled===false&&e.type!=="hidden"},disabled:function(e){return e.disabled===true},checked:function(e){return e.checked===true},selected:function(e){if(e.parentNode){e.parentNode.selectedIndex}return e.selected===true},parent:function(e){return !!e.firstChild},empty:function(e){return !e.firstChild},has:function(z,y,e){return !!d(e[3],z).length},header:function(e){return(/h\d/i).test(e.nodeName)},text:function(z){var e=z.getAttribute("type"),y=z.type;return z.nodeName.toLowerCase()==="input"&&"text"===y&&(e===y||e===null)},radio:function(e){return e.nodeName.toLowerCase()==="input"&&"radio"===e.type},checkbox:function(e){return e.nodeName.toLowerCase()==="input"&&"checkbox"===e.type},file:function(e){return e.nodeName.toLowerCase()==="input"&&"file"===e.type},password:function(e){return e.nodeName.toLowerCase()==="input"&&"password"===e.type},submit:function(y){var e=y.nodeName.toLowerCase();return(e==="input"||e==="button")&&"submit"===y.type},image:function(e){return e.nodeName.toLowerCase()==="input"&&"image"===e.type},reset:function(y){var e=y.nodeName.toLowerCase();return(e==="input"||e==="button")&&"reset"===y.type},button:function(y){var e=y.nodeName.toLowerCase();return e==="input"&&"button"===y.type||e==="button"},input:function(e){return(/input|select|textarea|button/i).test(e.nodeName)},focus:function(e){return e===e.ownerDocument.activeElement}},setFilters:{first:function(y,e){return e===0},last:function(z,y,e,A){return y===A.length-1},even:function(y,e){return e%2===0},odd:function(y,e){return e%2===1},lt:function(z,y,e){return ye[3]-0},nth:function(z,y,e){return e[3]-0===y},eq:function(z,y,e){return e[3]-0===y}},filter:{PSEUDO:function(z,E,D,F){var e=E[1],y=k.filters[e];if(y){return y(z,D,E,F)}else{if(e==="contains"){return(z.textContent||z.innerText||b([z])||"").indexOf(E[3])>=0}else{if(e==="not"){var A=E[3];for(var C=0,B=A.length;C=0)}}},ID:function(y,e){return y.nodeType===1&&y.getAttribute("id")===e},TAG:function(y,e){return(e==="*"&&y.nodeType===1)||!!y.nodeName&&y.nodeName.toLowerCase()===e},CLASS:function(y,e){return(" "+(y.className||y.getAttribute("class"))+" ").indexOf(e)>-1},ATTR:function(C,A){var z=A[1],e=d.attr?d.attr(C,z):k.attrHandle[z]?k.attrHandle[z](C):C[z]!=null?C[z]:C.getAttribute(z),D=e+"",B=A[2],y=A[4];return e==null?B==="!=":!B&&d.attr?e!=null:B==="="?D===y:B==="*="?D.indexOf(y)>=0:B==="~="?(" "+D+" ").indexOf(y)>=0:!y?D&&e!==false:B==="!="?D!==y:B==="^="?D.indexOf(y)===0:B==="$="?D.substr(D.length-y.length)===y:B==="|="?D===y||D.substr(0,y.length+1)===y+"-":false},POS:function(B,y,z,C){var e=y[2],A=k.setFilters[e];if(A){return A(B,z,y,C)}}}};var j=k.match.POS,c=function(y,e){return"\\"+(e-0+1)};for(var f in k.match){k.match[f]=new RegExp(k.match[f].source+(/(?![^\[]*\])(?![^\(]*\))/.source));k.leftMatch[f]=new RegExp(/(^(?:.|\r|\n)*?)/.source+k.match[f].source.replace(/\\(\d+)/g,c))}k.match.globalPOS=j;var l=function(y,e){y=Array.prototype.slice.call(y,0);if(e){e.push.apply(e,y);return e}return y};try{Array.prototype.slice.call(document.documentElement.childNodes,0)[0].nodeType}catch(v){l=function(B,A){var z=0,y=A||[];if(r.call(B)==="[object Array]"){Array.prototype.push.apply(y,B)}else{if(typeof B.length==="number"){for(var e=B.length;z";e.insertBefore(y,e.firstChild);if(document.getElementById(z)){k.find.ID=function(B,C,D){if(typeof C.getElementById!=="undefined"&&!D){var A=C.getElementById(B[1]);return A?A.id===B[1]||typeof A.getAttributeNode!=="undefined"&&A.getAttributeNode("id").nodeValue===B[1]?[A]:undefined:[]}};k.filter.ID=function(C,A){var B=typeof C.getAttributeNode!=="undefined"&&C.getAttributeNode("id");return C.nodeType===1&&B&&B.nodeValue===A}}e.removeChild(y);e=y=null})();(function(){var e=document.createElement("div");e.appendChild(document.createComment(""));if(e.getElementsByTagName("*").length>0){k.find.TAG=function(y,C){var B=C.getElementsByTagName(y[1]);if(y[1]==="*"){var A=[];for(var z=0;B[z];z++){if(B[z].nodeType===1){A.push(B[z])}}B=A}return B}}e.innerHTML="";if(e.firstChild&&typeof e.firstChild.getAttribute!=="undefined"&&e.firstChild.getAttribute("href")!=="#"){k.attrHandle.href=function(y){return y.getAttribute("href",2)}}e=null})();if(document.querySelectorAll){(function(){var e=d,A=document.createElement("div"),z="__sizzle__";A.innerHTML="

";if(A.querySelectorAll&&A.querySelectorAll(".TEST").length===0){return}d=function(L,C,G,K){C=C||document;if(!K&&!d.isXML(C)){var J=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(L);if(J&&(C.nodeType===1||C.nodeType===9)){if(J[1]){return l(C.getElementsByTagName(L),G)}else{if(J[2]&&k.find.CLASS&&C.getElementsByClassName){return l(C.getElementsByClassName(J[2]),G)}}}if(C.nodeType===9){if(L==="body"&&C.body){return l([C.body],G)}else{if(J&&J[3]){var F=C.getElementById(J[3]);if(F&&F.parentNode){if(F.id===J[3]){return l([F],G)}}else{return l([],G)}}}try{return l(C.querySelectorAll(L),G)}catch(H){}}else{if(C.nodeType===1&&C.nodeName.toLowerCase()!=="object"){var D=C,E=C.getAttribute("id"),B=E||z,N=C.parentNode,M=/^\s*[+~]/.test(L);if(!E){C.setAttribute("id",B)}else{B=B.replace(/'/g,"\\$&")}if(M&&N){C=C.parentNode}try{if(!M||N){return l(C.querySelectorAll("[id='"+B+"'] "+L),G)}}catch(I){}finally{if(!E){D.removeAttribute("id")}}}}}return e(L,C,G,K)};for(var y in e){d[y]=e[y]}A=null})()}(function(){var e=document.documentElement,z=e.matchesSelector||e.mozMatchesSelector||e.webkitMatchesSelector||e.msMatchesSelector;if(z){var B=!z.call(document.createElement("div"),"div"),y=false;try{z.call(document.documentElement,"[test!='']:sizzle")}catch(A){y=true}d.matchesSelector=function(D,F){F=F.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!d.isXML(D)){try{if(y||!k.match.PSEUDO.test(F)&&!/!=/.test(F)){var C=z.call(D,F);if(C||!B||D.document&&D.document.nodeType!==11){return C}}}catch(E){}}return d(F,null,null,[D]).length>0}}})();(function(){var e=document.createElement("div");e.innerHTML="
";if(!e.getElementsByClassName||e.getElementsByClassName("e").length===0){return}e.lastChild.className="e";if(e.getElementsByClassName("e").length===1){return}k.order.splice(1,0,"CLASS");k.find.CLASS=function(y,z,A){if(typeof z.getElementsByClassName!=="undefined"&&!A){return z.getElementsByClassName(y[1])}};e=null})();function a(y,D,C,G,E,F){for(var A=0,z=G.length;A0){B=e;break}}}e=e[y]}G[A]=B}}}if(document.documentElement.contains){d.contains=function(y,e){return y!==e&&(y.contains?y.contains(e):true)}}else{if(document.documentElement.compareDocumentPosition){d.contains=function(y,e){return !!(y.compareDocumentPosition(e)&16)}}else{d.contains=function(){return false}}}d.isXML=function(e){var y=(e?e.ownerDocument||e:0).documentElement;return y?y.nodeName!=="HTML":false};var s=function(z,e,D){var C,E=[],B="",F=e.nodeType?[e]:e;while((C=k.match.PSEUDO.exec(z))){B+=C[0];z=z.replace(k.match.PSEUDO,"")}z=k.relative[z]?z+"*":z;for(var A=0,y=F.length;A"+(i.item?i.item(0).outerHTML:i.htmlText);m.removeChild(m.firstChild)}else{m.innerHTML=i.toString()}}if(/^\s/.test(m.innerHTML)){j=" "}if(/\s+$/.test(m.innerHTML)){l=" "}h.getInner=true;h.content=g.isCollapsed()?"":j+g.serializer.serialize(m,h)+l;g.onGetContent.dispatch(g,h);return h.content},setContent:function(h,j){var o=this,g=o.getRng(),k,l=o.win.document,n,m;j=j||{format:"html"};j.set=true;h=j.content=h;if(!j.no_events){o.onBeforeSetContent.dispatch(o,j)}h=j.content;if(g.insertNode){h+='_';if(g.startContainer==l&&g.endContainer==l){l.body.innerHTML=h}else{g.deleteContents();if(l.body.childNodes.length===0){l.body.innerHTML=h}else{if(g.createContextualFragment){g.insertNode(g.createContextualFragment(h))}else{n=l.createDocumentFragment();m=l.createElement("div");n.appendChild(m);m.outerHTML=h;g.insertNode(n)}}}k=o.dom.get("__caret");g=l.createRange();g.setStartBefore(k);g.setEndBefore(k);o.setRng(g);o.dom.remove("__caret");try{o.setRng(g)}catch(i){}}else{if(g.item){l.execCommand("Delete",false,null);g=o.getRng()}if(/^\s+/.test(h)){g.pasteHTML('_'+h);o.dom.remove("__mce_tmp")}else{g.pasteHTML(h)}}if(!j.no_events){o.onSetContent.dispatch(o,j)}},getStart:function(){var i=this,h=i.getRng(),j,g,l,k;if(h.duplicate||h.item){if(h.item){return h.item(0)}l=h.duplicate();l.collapse(1);j=l.parentElement();if(j.ownerDocument!==i.dom.doc){j=i.dom.getRoot()}g=k=h.parentElement();while(k=k.parentNode){if(k==j){j=g;break}}return j}else{j=h.startContainer;if(j.nodeType==1&&j.hasChildNodes()){j=j.childNodes[Math.min(j.childNodes.length-1,h.startOffset)]}if(j&&j.nodeType==3){return j.parentNode}return j}},getEnd:function(){var h=this,g=h.getRng(),j,i;if(g.duplicate||g.item){if(g.item){return g.item(0)}g=g.duplicate();g.collapse(0);j=g.parentElement();if(j.ownerDocument!==h.dom.doc){j=h.dom.getRoot()}if(j&&j.nodeName=="BODY"){return j.lastChild||j}return j}else{j=g.endContainer;i=g.endOffset;if(j.nodeType==1&&j.hasChildNodes()){j=j.childNodes[i>0?i-1:i]}if(j&&j.nodeType==3){return j.parentNode}return j}},getBookmark:function(s,v){var y=this,n=y.dom,h,k,j,o,i,p,q,m="\uFEFF",x;function g(z,A){var t=0;e(n.select(z),function(C,B){if(C==A){t=B}});return t}function u(t){function z(E){var A,D,C,B=E?"start":"end";A=t[B+"Container"];D=t[B+"Offset"];if(A.nodeType==1&&A.nodeName=="TR"){C=A.childNodes;A=C[Math.min(E?D:D-1,C.length-1)];if(A){D=E?0:A.childNodes.length;t["set"+(E?"Start":"End")](A,D)}}}z(true);z();return t}function l(){var z=y.getRng(true),t=n.getRoot(),A={};function B(E,J){var D=E[J?"startContainer":"endContainer"],I=E[J?"startOffset":"endOffset"],C=[],F,H,G=0;if(D.nodeType==3){if(v){for(F=D.previousSibling;F&&F.nodeType==3;F=F.previousSibling){I+=F.nodeValue.length}}C.push(I)}else{H=D.childNodes;if(I>=H.length&&H.length){G=1;I=Math.max(0,H.length-1)}C.push(y.dom.nodeIndex(H[I],v)+G)}for(;D&&D!=t;D=D.parentNode){C.push(y.dom.nodeIndex(D,v))}return C}A.start=B(z,true);if(!y.isCollapsed()){A.end=B(z)}return A}if(s==2){if(y.tridentSel){return y.tridentSel.getBookmark(s)}return l()}if(s){h=y.getRng();if(h.setStart){h={startContainer:h.startContainer,startOffset:h.startOffset,endContainer:h.endContainer,endOffset:h.endOffset}}return{rng:h}}h=y.getRng();j=n.uniqueId();o=tinyMCE.activeEditor.selection.isCollapsed();x="overflow:hidden;line-height:0px";if(h.duplicate||h.item){if(!h.item){k=h.duplicate();try{h.collapse();h.pasteHTML(''+m+"");if(!o){k.collapse(false);h.moveToElementText(k.parentElement());if(h.compareEndPoints("StartToEnd",k)===0){k.move("character",-1)}k.pasteHTML(''+m+"")}}catch(r){return null}}else{p=h.item(0);i=p.nodeName;return{name:i,index:g(i,p)}}}else{p=y.getNode();i=p.nodeName;if(i=="IMG"){return{name:i,index:g(i,p)}}k=u(h.cloneRange());if(!o){k.collapse(false);k.insertNode(n.create("span",{"data-mce-type":"bookmark",id:j+"_end",style:x},m))}h=u(h);h.collapse(true);h.insertNode(n.create("span",{"data-mce-type":"bookmark",id:j+"_start",style:x},m))}y.moveToBookmark({id:j,keep:1});return{id:j}},moveToBookmark:function(q){var v=this,n=v.dom,l,j,g,i,u,k,x,r,s;function h(C){var t=q[C?"start":"end"],z,A,B,y;if(t){B=t[0];for(A=u,z=t.length-1;z>=1;z--){y=A.childNodes;if(t[z]>y.length-1){return}A=y[t[z]]}if(A.nodeType===3){B=Math.min(t[0],A.nodeValue.length)}if(A.nodeType===1){B=Math.min(t[0],A.childNodes.length)}if(C){g.setStart(A,B)}else{g.setEnd(A,B)}}return true}function m(D){var y=n.get(q.id+"_"+D),C,t,A,B,z=q.keep;if(y){C=y.parentNode;if(D=="start"){if(!z){t=n.nodeIndex(y)}else{C=y.firstChild;t=1}k=x=C;r=s=t}else{if(!z){t=n.nodeIndex(y)}else{C=y.firstChild;t=1}x=C;s=t}if(!z){B=y.previousSibling;A=y.nextSibling;e(d.grep(y.childNodes),function(E){if(E.nodeType==3){E.nodeValue=E.nodeValue.replace(/\uFEFF/g,"")}});while(y=n.get(q.id+"_"+D)){n.remove(y,1)}if(B&&A&&B.nodeType==A.nodeType&&B.nodeType==3&&!d.isOpera){t=B.nodeValue.length;B.appendData(A.nodeValue);n.remove(A);if(D=="start"){k=x=B;r=s=t}else{x=B;s=t}}}}}function o(t){if(n.isBlock(t)&&!t.innerHTML&&!b){t.innerHTML='
'}return t}if(q){if(q.start){g=n.createRng();u=n.getRoot();if(v.tridentSel){return v.tridentSel.moveToBookmark(q)}if(h(true)&&h()){v.setRng(g)}}else{if(q.id){m("start");m("end");if(k){g=n.createRng();g.setStart(o(k),r);g.setEnd(o(x),s);v.setRng(g)}}else{if(q.name){v.select(n.select(q.name)[q.index])}else{if(q.rng){g=q.rng;if(g.startContainer){i=v.dom.createRng();try{i.setStart(g.startContainer,g.startOffset);i.setEnd(g.endContainer,g.endOffset)}catch(p){}g=i}v.setRng(g)}}}}}},select:function(l,k){var j=this,m=j.dom,h=m.createRng(),g;function i(n,p){var o=new a(n,n);do{if(n.nodeType==3&&d.trim(n.nodeValue).length!==0){if(p){h.setStart(n,0)}else{h.setEnd(n,n.nodeValue.length)}return}if(n.nodeName=="BR"){if(p){h.setStartBefore(n)}else{h.setEndBefore(n)}return}}while(n=(p?o.next():o.prev()))}if(l){g=m.nodeIndex(l);h.setStart(l.parentNode,g);h.setEnd(l.parentNode,g+1);if(k){i(l,1);i(l)}j.setRng(h)}return l},isCollapsed:function(){var g=this,i=g.getRng(),h=g.getSel();if(!i||i.item){return false}if(i.compareEndPoints){return i.compareEndPoints("StartToEnd",i)===0}return !h||i.collapsed},collapse:function(g){var i=this,h=i.getRng(),j;if(h.item){j=h.item(0);h=i.win.document.body.createTextRange();h.moveToElementText(j)}h.collapse(!!g);i.setRng(h)},getSel:function(){var h=this,g=this.win;return g.getSelection?g.getSelection():g.document.selection},getRng:function(m){var h=this,j,g,l,k=h.win.document;if(m&&h.tridentSel){return h.tridentSel.getRangeAt(0)}try{if(j=h.getSel()){g=j.rangeCount>0?j.getRangeAt(0):(j.createRange?j.createRange():k.createRange())}}catch(i){}if(d.isIE&&!d.isIE11&&g&&g.setStart&&k.selection.createRange().item){l=k.selection.createRange().item(0);g=k.createRange();g.setStartBefore(l);g.setEndAfter(l)}if(!g){g=k.createRange?k.createRange():k.body.createTextRange()}if(g.setStart&&g.startContainer.nodeType===9&&g.collapsed){l=h.dom.getRoot();g.setStart(l,0);g.setEnd(l,0)}if(h.selectedRange&&h.explicitRange){if(g.compareBoundaryPoints(g.START_TO_START,h.selectedRange)===0&&g.compareBoundaryPoints(g.END_TO_END,h.selectedRange)===0){g=h.explicitRange}else{h.selectedRange=null;h.explicitRange=null}}return g},setRng:function(k,g){var j,i=this;if(!i.tridentSel){j=i.getSel();if(j){i.explicitRange=k;try{j.removeAllRanges()}catch(h){}j.addRange(k);if(g===false&&j.extend){j.collapse(k.endContainer,k.endOffset);j.extend(k.startContainer,k.startOffset)}i.selectedRange=j.rangeCount>0?j.getRangeAt(0):null}}else{if(k.cloneRange){try{i.tridentSel.addRange(k);return}catch(h){}}try{k.select()}catch(h){}}},setNode:function(h){var g=this;g.setContent(g.dom.getOuterHTML(h));return h},getNode:function(){var i=this,h=i.getRng(),j=i.getSel(),m,l=h.startContainer,g=h.endContainer;function k(q,o){var p=q;while(q&&q.nodeType===3&&q.length===0){q=o?q.nextSibling:q.previousSibling}return q||p}if(!h){return i.dom.getRoot()}if(h.setStart){m=h.commonAncestorContainer;if(!h.collapsed){if(h.startContainer==h.endContainer){if(h.endOffset-h.startOffset<2){if(h.startContainer.hasChildNodes()){m=h.startContainer.childNodes[h.startOffset]}}}if(l.nodeType===3&&g.nodeType===3){if(l.length===h.startOffset){l=k(l.nextSibling,true)}else{l=l.parentNode}if(h.endOffset===0){g=k(g.previousSibling,false)}else{g=g.parentNode}if(l&&l===g){return l}}}if(m&&m.nodeType==3){return m.parentNode}return m}return h.item?h.item(0):h.parentElement()},getSelectedBlocks:function(p,h){var o=this,k=o.dom,m,l,i,j=[];m=k.getParent(p||o.getStart(),k.isBlock);l=k.getParent(h||o.getEnd(),k.isBlock);if(m){j.push(m)}if(m&&l&&m!=l){i=m;var g=new a(m,k.getRoot());while((i=g.next())&&i!=l){if(k.isBlock(i)){j.push(i)}}}if(l&&m!=l){j.push(l)}return j},isForward:function(){var i=this.dom,g=this.getSel(),j,h;if(!g||g.anchorNode==null||g.focusNode==null){return true}j=i.createRng();j.setStart(g.anchorNode,g.anchorOffset);j.collapse(true);h=i.createRng();h.setStart(g.focusNode,g.focusOffset);h.collapse(true);return j.compareBoundaryPoints(j.START_TO_START,h)<=0},normalize:function(){var h=this,g,m,l,j,i;function k(p){var o,r,n,s=h.dom,u=s.getRoot(),q,t,v;function y(z,A){var B=new a(z,s.getParent(z.parentNode,s.isBlock)||u);while(z=B[A?"prev":"next"]()){if(z.nodeName==="BR"){return true}}}function x(B,z){var C,A;z=z||o;C=new a(z,s.getParent(z.parentNode,s.isBlock)||u);while(q=C[B?"prev":"next"]()){if(q.nodeType===3&&q.nodeValue.length>0){o=q;r=B?q.nodeValue.length:0;m=true;return}if(s.isBlock(q)||t[q.nodeName.toLowerCase()]){return}A=q}if(l&&A){o=A;m=true;r=0}}o=g[(p?"start":"end")+"Container"];r=g[(p?"start":"end")+"Offset"];t=s.schema.getNonEmptyElements();if(o.nodeType===9){o=s.getRoot();r=0}if(o===u){if(p){q=o.childNodes[r>0?r-1:0];if(q){v=q.nodeName.toLowerCase();if(t[q.nodeName]||q.nodeName=="TABLE"){return}}}if(o.hasChildNodes()){o=o.childNodes[Math.min(!p&&r>0?r-1:r,o.childNodes.length-1)];r=0;if(o.hasChildNodes()&&!/TABLE/.test(o.nodeName)){q=o;n=new a(o,u);do{if(q.nodeType===3&&q.nodeValue.length>0){r=p?0:q.nodeValue.length;o=q;m=true;break}if(t[q.nodeName.toLowerCase()]){r=s.nodeIndex(q);o=q.parentNode;if(q.nodeName=="IMG"&&!p){r++}m=true;break}}while(q=(p?n.next():n.prev()))}}}if(l){if(o.nodeType===3&&r===0){x(true)}if(o.nodeType===1){q=o.childNodes[r];if(q&&q.nodeName==="BR"&&!y(q)&&!y(q,true)){x(true,o.childNodes[r])}}}if(p&&!l&&o.nodeType===3&&r===o.nodeValue.length){x(false)}if(m){g["set"+(p?"Start":"End")](o,r)}}if(d.isIE){return}g=h.getRng();l=g.collapsed;k(true);if(!l){k()}if(m){if(l){g.collapse(true)}h.setRng(g,h.isForward())}},selectorChanged:function(g,j){var h=this,i;if(!h.selectorChangedData){h.selectorChangedData={};i={};h.editor.onNodeChange.addToTop(function(l,k,o){var p=h.dom,m=p.getParents(o,null,p.getRoot()),n={};e(h.selectorChangedData,function(r,q){e(m,function(s){if(p.is(s,q)){if(!i[q]){e(r,function(t){t(true,{node:s,selector:q,parents:m})});i[q]=r}n[q]=r;return false}})});e(i,function(r,q){if(!n[q]){delete i[q];e(r,function(s){s(false,{node:o,selector:q,parents:m})})}})})}if(!h.selectorChangedData[g]){h.selectorChangedData[g]=[]}h.selectorChangedData[g].push(j);return h},scrollIntoView:function(k){var j,h,g=this,i=g.dom;h=i.getViewPort(g.editor.getWin());j=i.getPos(k).y;if(jh.y+h.h){g.editor.getWin().scrollTo(0,j0){p.setEndPoint("StartToStart",o)}else{p.setEndPoint("EndToEnd",o)}p.select()}}else{l()}}function l(){var p=n.selection.createRange();if(o&&!p.item&&p.compareEndPoints("StartToEnd",p)===0){o.select()}h.unbind(n,"mouseup",l);h.unbind(n,"mousemove",m);o=k=0}n.documentElement.unselectable=true;h.bind(n,["mousedown","contextmenu"],function(p){if(p.target.nodeName==="HTML"){if(k){l()}g=n.documentElement;if(g.scrollHeight>g.clientHeight){return}k=1;o=j(p.x,p.y);if(o){h.bind(n,"mouseup",l);h.bind(n,"mousemove",m);h.win.focus();o.select()}}})}})})(tinymce);(function(a){a.dom.Serializer=function(e,i,f){var h,b,d=a.isIE,g=a.each,c;if(!e.apply_source_formatting){e.indent=false}i=i||a.DOM;f=f||new a.html.Schema(e);e.entity_encoding=e.entity_encoding||"named";e.remove_trailing_brs="remove_trailing_brs" in e?e.remove_trailing_brs:true;h=new a.util.Dispatcher(self);b=new a.util.Dispatcher(self);c=new a.html.DomParser(e,f);c.addAttributeFilter("src,href,style",function(k,j){var o=k.length,l,q,n="data-mce-"+j,p=e.url_converter,r=e.url_converter_scope,m;while(o--){l=k[o];q=l.attributes.map[n];if(q!==m){l.attr(j,q.length>0?q:null);l.attr(n,null)}else{q=l.attributes.map[j];if(j==="style"){q=i.serializeStyle(i.parseStyle(q),l.name)}else{if(p){q=p.call(r,q,j,l.name)}}l.attr(j,q.length>0?q:null)}}});c.addAttributeFilter("class",function(j,k){var l=j.length,m,n;while(l--){m=j[l];n=m.attr("class").replace(/(?:^|\s)mce(Item\w+|Selected)(?!\S)/g,"");m.attr("class",n.length>0?n:null)}});c.addAttributeFilter("data-mce-type",function(j,l,k){var m=j.length,n;while(m--){n=j[m];if(n.attributes.map["data-mce-type"]==="bookmark"&&!k.cleanup){n.remove()}}});c.addAttributeFilter("data-mce-expando",function(j,l,k){var m=j.length;while(m--){j[m].attr(l,null)}});c.addNodeFilter("noscript",function(j){var k=j.length,l;while(k--){l=j[k].firstChild;if(l){l.value=a.html.Entities.decode(l.value)}}});c.addNodeFilter("script,style",function(k,l){var m=k.length,n,o;function j(p){return p.replace(/()/g,"\n").replace(/^[\r\n]*|[\r\n]*$/g,"").replace(/^\s*(()?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g,"")}while(m--){n=k[m];o=n.firstChild?n.firstChild.value:"";if(l==="script"){n.attr("type",(n.attr("type")||"text/javascript").replace(/^mce\-/,""));if(o.length>0){n.firstChild.value="// "}}else{if(o.length>0){n.firstChild.value=""}}}});c.addNodeFilter("#comment",function(j,k){var l=j.length,m;while(l--){m=j[l];if(m.value.indexOf("[CDATA[")===0){m.name="#cdata";m.type=4;m.value=m.value.replace(/^\[CDATA\[|\]\]$/g,"")}else{if(m.value.indexOf("mce:protected ")===0){m.name="#text";m.type=3;m.raw=true;m.value=unescape(m.value).substr(14)}}}});c.addNodeFilter("xml:namespace,input",function(j,k){var l=j.length,m;while(l--){m=j[l];if(m.type===7){m.remove()}else{if(m.type===1){if(k==="input"&&!("type" in m.attributes.map)){m.attr("type","text")}}}}});if(e.fix_list_elements){c.addNodeFilter("ul,ol",function(k,l){var m=k.length,n,j;while(m--){n=k[m];j=n.parent;if(j.name==="ul"||j.name==="ol"){if(n.prev&&n.prev.name==="li"){n.prev.append(n)}}}})}c.addAttributeFilter("data-mce-src,data-mce-href,data-mce-style",function(j,k){var l=j.length;while(l--){j[l].attr(k,null)}});return{schema:f,addNodeFilter:c.addNodeFilter,addAttributeFilter:c.addAttributeFilter,onPreProcess:h,onPostProcess:b,serialize:function(o,m){var l,p,k,j,n;if(d&&i.select("script,style,select,map").length>0){n=o.innerHTML;o=o.cloneNode(false);i.setHTML(o,n)}else{o=o.cloneNode(true)}l=o.ownerDocument.implementation;if(l.createHTMLDocument){p=l.createHTMLDocument("");g(o.nodeName=="BODY"?o.childNodes:[o],function(q){p.body.appendChild(p.importNode(q,true))});if(o.nodeName!="BODY"){o=p.body.firstChild}else{o=p.body}k=i.doc;i.doc=p}m=m||{};m.format=m.format||"html";if(!m.no_events){m.node=o;h.dispatch(self,m)}j=new a.html.Serializer(e,f);m.content=j.serialize(c.parse(a.trim(m.getInner?o.innerHTML:i.getOuterHTML(o)),m));if(!m.cleanup){m.content=m.content.replace(/\uFEFF/g,"")}if(!m.no_events){b.dispatch(self,m)}if(k){i.doc=k}m.node=null;return m.content},addRules:function(j){f.addValidElements(j)},setRules:function(j){f.setValidElements(j)}}}})(tinymce);(function(a){a.dom.ScriptLoader=function(h){var c=0,k=1,i=2,l={},j=[],e={},d=[],g=0,f;function b(m,v){var x=this,q=a.DOM,s,o,r,n;function p(){q.remove(n);if(s){s.onreadystatechange=s.onload=s=null}v()}function u(){if(typeof(console)!=="undefined"&&console.log){console.log("Failed to load: "+m)}}n=q.uniqueId();if(a.isIE6){o=new a.util.URI(m);r=location;if(o.host==r.hostname&&o.port==r.port&&(o.protocol+":")==r.protocol&&o.protocol.toLowerCase()!="file"){a.util.XHR.send({url:a._addVer(o.getURI()),success:function(y){var t=q.create("script",{type:"text/javascript"});t.text=y;document.getElementsByTagName("head")[0].appendChild(t);q.remove(t);p()},error:u});return}}s=document.createElement("script");s.id=n;s.type="text/javascript";s.src=a._addVer(m);if(!a.isIE||a.isIE11){s.onload=p}s.onerror=u;if(!a.isOpera){s.onreadystatechange=function(){var t=s.readyState;if(t=="complete"||t=="loaded"){p()}}}(document.getElementsByTagName("head")[0]||document.body).appendChild(s)}this.isDone=function(m){return l[m]==i};this.markDone=function(m){l[m]=i};this.add=this.load=function(m,q,n){var o,p=l[m];if(p==f){j.push(m);l[m]=c}if(q){if(!e[m]){e[m]=[]}e[m].push({func:q,scope:n||this})}};this.loadQueue=function(n,m){this.loadScripts(j,n,m)};this.loadScripts=function(m,q,p){var o;function n(r){a.each(e[r],function(s){s.func.call(s.scope)});e[r]=f}d.push({func:q,scope:p||this});o=function(){var r=a.grep(m);m.length=0;a.each(r,function(s){if(l[s]==i){n(s);return}if(l[s]!=k){l[s]=k;g++;b(s,function(){l[s]=i;g--;n(s);o()})}});if(!g){a.each(d,function(s){s.func.call(s.scope)});d.length=0}};o()}};a.ScriptLoader=new a.dom.ScriptLoader()})(tinymce);(function(a){a.dom.RangeUtils=function(c){var b="\uFEFF";this.walk=function(d,s){var i=d.startContainer,l=d.startOffset,t=d.endContainer,m=d.endOffset,j,g,o,h,r,q,e;e=c.select("td.mceSelected,th.mceSelected");if(e.length>0){a.each(e,function(u){s([u])});return}function f(u){var v;v=u[0];if(v.nodeType===3&&v===i&&l>=v.nodeValue.length){u.splice(0,1)}v=u[u.length-1];if(m===0&&u.length>0&&v===t&&v.nodeType===3){u.splice(u.length-1,1)}return u}function p(x,v,u){var y=[];for(;x&&x!=u;x=x[v]){y.push(x)}return y}function n(v,u){do{if(v.parentNode==u){return v}v=v.parentNode}while(v)}function k(x,v,y){var u=y?"nextSibling":"previousSibling";for(h=x,r=h.parentNode;h&&h!=v;h=r){r=h.parentNode;q=p(h==x?h:h[u],u);if(q.length){if(!y){q.reverse()}s(f(q))}}}if(i.nodeType==1&&i.hasChildNodes()){i=i.childNodes[l]}if(t.nodeType==1&&t.hasChildNodes()){t=t.childNodes[Math.min(m-1,t.childNodes.length-1)]}if(i==t){return s(f([i]))}j=c.findCommonAncestor(i,t);for(h=i;h;h=h.parentNode){if(h===t){return k(i,j,true)}if(h===j){break}}for(h=t;h;h=h.parentNode){if(h===i){return k(t,j)}if(h===j){break}}g=n(i,j)||i;o=n(t,j)||t;k(i,g,true);q=p(g==i?g:g.nextSibling,"nextSibling",o==t?o.nextSibling:o);if(q.length){s(f(q))}k(t,o)};this.split=function(e){var h=e.startContainer,d=e.startOffset,i=e.endContainer,g=e.endOffset;function f(j,k){return j.splitText(k)}if(h==i&&h.nodeType==3){if(d>0&&dd){g=g-d;h=i=f(i,g).previousSibling;g=i.nodeValue.length;d=0}else{g=0}}}else{if(h.nodeType==3&&d>0&&d0&&g=m.length){r=0}}t=m[r];f.setAttrib(g,"tabindex","-1");f.setAttrib(t.id,"tabindex","0");f.get(t.id).focus();if(e.actOnFocus){e.onAction(t.id)}if(s){a.cancel(s)}};p=function(z){var v=37,u=39,y=38,A=40,r=27,t=14,s=13,x=32;switch(z.keyCode){case v:if(i){q.moveFocus(-1)}a.cancel(z);break;case u:if(i){q.moveFocus(1)}a.cancel(z);break;case y:if(o){q.moveFocus(-1)}a.cancel(z);break;case A:if(o){q.moveFocus(1)}a.cancel(z);break;case r:if(e.onCancel){e.onCancel();a.cancel(z)}break;case t:case s:case x:if(e.onAction){e.onAction(g);a.cancel(z)}break}};c(m,function(t,r){var s,u;if(!t.id){t.id=f.uniqueId("_mce_item_")}u=f.get(t.id);if(l){f.bind(u,"blur",h);s="-1"}else{s=(r===0?"0":"-1")}u.setAttribute("tabindex",s);f.bind(u,"focus",k)});if(m[0]){g=m[0].id}f.setAttrib(n,"tabindex","-1");var j=f.get(n);f.bind(j,"focus",d);f.bind(j,"keydown",p)}})})(tinymce);(function(c){var b=c.DOM,a=c.is;c.create("tinymce.ui.Control",{Control:function(f,e,d){this.id=f;this.settings=e=e||{};this.rendered=false;this.onRender=new c.util.Dispatcher(this);this.classPrefix="";this.scope=e.scope||this;this.disabled=0;this.active=0;this.editor=d},setAriaProperty:function(f,e){var d=b.get(this.id+"_aria")||b.get(this.id);if(d){b.setAttrib(d,"aria-"+f,!!e)}},focus:function(){b.get(this.id).focus()},setDisabled:function(d){if(d!=this.disabled){this.setAriaProperty("disabled",d);this.setState("Disabled",d);this.setState("Enabled",!d);this.disabled=d}},isDisabled:function(){return this.disabled},setActive:function(d){if(d!=this.active){this.setState("Active",d);this.active=d;this.setAriaProperty("pressed",d)}},isActive:function(){return this.active},setState:function(f,d){var e=b.get(this.id);f=this.classPrefix+f;if(d){b.addClass(e,f)}else{b.removeClass(e,f)}},isRendered:function(){return this.rendered},renderHTML:function(){},renderTo:function(d){b.setHTML(d,this.renderHTML())},postRender:function(){var e=this,d;if(a(e.disabled)){d=e.disabled;e.disabled=-1;e.setDisabled(d)}if(a(e.active)){d=e.active;e.active=-1;e.setActive(d)}},remove:function(){b.remove(this.id);this.destroy()},destroy:function(){c.dom.Event.clear(this.id)}})})(tinymce);tinymce.create("tinymce.ui.Container:tinymce.ui.Control",{Container:function(c,b,a){this.parent(c,b,a);this.controls=[];this.lookup={}},add:function(a){this.lookup[a.id]=a;this.controls.push(a);return a},get:function(a){return this.lookup[a]}});tinymce.create("tinymce.ui.Separator:tinymce.ui.Control",{Separator:function(b,a){this.parent(b,a);this.classPrefix="mceSeparator";this.setDisabled(true)},renderHTML:function(){return tinymce.DOM.createHTML("span",{"class":this.classPrefix,role:"separator","aria-orientation":"vertical",tabindex:"-1"})}});(function(d){var c=d.is,b=d.DOM,e=d.each,a=d.walk;d.create("tinymce.ui.MenuItem:tinymce.ui.Control",{MenuItem:function(g,f){this.parent(g,f);this.classPrefix="mceMenuItem"},setSelected:function(f){this.setState("Selected",f);this.setAriaProperty("checked",!!f);this.selected=f},isSelected:function(){return this.selected},postRender:function(){var f=this;f.parent();if(c(f.selected)){f.setSelected(f.selected)}}})})(tinymce);(function(d){var c=d.is,b=d.DOM,e=d.each,a=d.walk;d.create("tinymce.ui.Menu:tinymce.ui.MenuItem",{Menu:function(h,g){var f=this;f.parent(h,g);f.items={};f.collapsed=false;f.menuCount=0;f.onAddItem=new d.util.Dispatcher(this)},expand:function(g){var f=this;if(g){a(f,function(h){if(h.expand){h.expand()}},"items",f)}f.collapsed=false},collapse:function(g){var f=this;if(g){a(f,function(h){if(h.collapse){h.collapse()}},"items",f)}f.collapsed=true},isCollapsed:function(){return this.collapsed},add:function(f){if(!f.settings){f=new d.ui.MenuItem(f.id||b.uniqueId(),f)}this.onAddItem.dispatch(this,f);return this.items[f.id]=f},addSeparator:function(){return this.add({separator:true})},addMenu:function(f){if(!f.collapse){f=this.createMenu(f)}this.menuCount++;return this.add(f)},hasMenus:function(){return this.menuCount!==0},remove:function(f){delete this.items[f.id]},removeAll:function(){var f=this;a(f,function(g){if(g.removeAll){g.removeAll()}else{g.remove()}g.destroy()},"items",f);f.items={}},createMenu:function(g){var f=new d.ui.Menu(g.id||b.uniqueId(),g);f.onAddItem.add(this.onAddItem.dispatch,this.onAddItem);return f}})})(tinymce);(function(e){var d=e.is,c=e.DOM,f=e.each,a=e.dom.Event,b=e.dom.Element;e.create("tinymce.ui.DropMenu:tinymce.ui.Menu",{DropMenu:function(h,g){g=g||{};g.container=g.container||c.doc.body;g.offset_x=g.offset_x||0;g.offset_y=g.offset_y||0;g.vp_offset_x=g.vp_offset_x||0;g.vp_offset_y=g.vp_offset_y||0;if(d(g.icons)&&!g.icons){g["class"]+=" mceNoIcons"}this.parent(h,g);this.onShowMenu=new e.util.Dispatcher(this);this.onHideMenu=new e.util.Dispatcher(this);this.classPrefix="mceMenu"},createMenu:function(j){var h=this,i=h.settings,g;j.container=j.container||i.container;j.parent=h;j.constrain=j.constrain||i.constrain;j["class"]=j["class"]||i["class"];j.vp_offset_x=j.vp_offset_x||i.vp_offset_x;j.vp_offset_y=j.vp_offset_y||i.vp_offset_y;j.keyboard_focus=i.keyboard_focus;g=new e.ui.DropMenu(j.id||c.uniqueId(),j);g.onAddItem.add(h.onAddItem.dispatch,h.onAddItem);return g},focus:function(){var g=this;if(g.keyboardNav){g.keyboardNav.focus()}},update:function(){var i=this,j=i.settings,g=c.get("menu_"+i.id+"_tbl"),l=c.get("menu_"+i.id+"_co"),h,k;h=j.max_width?Math.min(g.offsetWidth,j.max_width):g.offsetWidth;k=j.max_height?Math.min(g.offsetHeight,j.max_height):g.offsetHeight;if(!c.boxModel){i.element.setStyles({width:h+2,height:k+2})}else{i.element.setStyles({width:h,height:k})}if(j.max_width){c.setStyle(l,"width",h)}if(j.max_height){c.setStyle(l,"height",k);if(g.clientHeightv){p=r?r-u:Math.max(0,(v-A.vp_offset_x)-u)}if((n+A.vp_offset_y+l)>q){n=Math.max(0,(q-A.vp_offset_y)-l)}}c.setStyles(o,{left:p,top:n});z.element.update();z.isMenuVisible=1;z.mouseClickFunc=a.add(o,"click",function(s){var h;s=s.target;if(s&&(s=c.getParent(s,"tr"))&&!c.hasClass(s,m+"ItemSub")){h=z.items[s.id];if(h.isDisabled()){return}k=z;while(k){if(k.hideMenu){k.hideMenu()}k=k.settings.parent}if(h.settings.onclick){h.settings.onclick(s)}return false}});if(z.hasMenus()){z.mouseOverFunc=a.add(o,"mouseover",function(x){var h,t,s;x=x.target;if(x&&(x=c.getParent(x,"tr"))){h=z.items[x.id];if(z.lastMenu){z.lastMenu.collapse(1)}if(h.isDisabled()){return}if(x&&c.hasClass(x,m+"ItemSub")){t=c.getRect(x);h.showMenu((t.x+t.w-i),t.y-i,t.x);z.lastMenu=h;c.addClass(c.get(h.id).firstChild,m+"ItemActive")}}})}a.add(o,"keydown",z._keyHandler,z);z.onShowMenu.dispatch(z);if(A.keyboard_focus){z._setupKeyboardNav()}},hideMenu:function(j){var g=this,i=c.get("menu_"+g.id),h;if(!g.isMenuVisible){return}if(g.keyboardNav){g.keyboardNav.destroy()}a.remove(i,"mouseover",g.mouseOverFunc);a.remove(i,"click",g.mouseClickFunc);a.remove(i,"keydown",g._keyHandler);c.hide(i);g.isMenuVisible=0;if(!j){g.collapse(1)}if(g.element){g.element.hide()}if(h=c.get(g.id)){c.removeClass(h.firstChild,g.classPrefix+"ItemActive")}g.onHideMenu.dispatch(g)},add:function(i){var g=this,h;i=g.parent(i);if(g.isRendered&&(h=c.get("menu_"+g.id))){g._add(c.select("tbody",h)[0],i)}return i},collapse:function(g){this.parent(g);this.hideMenu(1)},remove:function(g){c.remove(g.id);this.destroy();return this.parent(g)},destroy:function(){var g=this,h=c.get("menu_"+g.id);if(g.keyboardNav){g.keyboardNav.destroy()}a.remove(h,"mouseover",g.mouseOverFunc);a.remove(c.select("a",h),"focus",g.mouseOverFunc);a.remove(h,"click",g.mouseClickFunc);a.remove(h,"keydown",g._keyHandler);if(g.element){g.element.remove()}c.remove(h)},renderNode:function(){var i=this,j=i.settings,l,h,k,g;g=c.create("div",{role:"listbox",id:"menu_"+i.id,"class":j["class"],style:"position:absolute;left:0;top:0;z-index:200000;outline:0"});if(i.settings.parent){c.setAttrib(g,"aria-parent","menu_"+i.settings.parent.id)}k=c.add(g,"div",{role:"presentation",id:"menu_"+i.id+"_co","class":i.classPrefix+(j["class"]?" "+j["class"]:"")});i.element=new b("menu_"+i.id,{blocker:1,container:j.container});if(j.menu_line){c.add(k,"span",{"class":i.classPrefix+"Line"})}l=c.add(k,"table",{role:"presentation",id:"menu_"+i.id+"_tbl",border:0,cellPadding:0,cellSpacing:0});h=c.add(l,"tbody");f(i.items,function(m){i._add(h,m)});i.rendered=true;return g},_setupKeyboardNav:function(){var i,h,g=this;i=c.get("menu_"+g.id);h=c.select("a[role=option]","menu_"+g.id);h.splice(0,0,i);g.keyboardNav=new e.ui.KeyboardNavigation({root:"menu_"+g.id,items:h,onCancel:function(){g.hideMenu()},enableUpDown:true});i.focus()},_keyHandler:function(g){var h=this,i;switch(g.keyCode){case 37:if(h.settings.parent){h.hideMenu();h.settings.parent.focus();a.cancel(g)}break;case 39:if(h.mouseOverFunc){h.mouseOverFunc(g)}break}},_add:function(j,h){var i,q=h.settings,p,l,k,m=this.classPrefix,g;if(q.separator){l=c.add(j,"tr",{id:h.id,"class":m+"ItemSeparator"});c.add(l,"td",{"class":m+"ItemSeparator"});if(i=l.previousSibling){c.addClass(i,"mceLast")}return}i=l=c.add(j,"tr",{id:h.id,"class":m+"Item "+m+"ItemEnabled"});i=k=c.add(i,q.titleItem?"th":"td");i=p=c.add(i,"a",{id:h.id+"_aria",role:q.titleItem?"presentation":"option",href:"javascript:;",onclick:"return false;",onmousedown:"return false;"});if(q.parent){c.setAttrib(p,"aria-haspopup","true");c.setAttrib(p,"aria-owns","menu_"+h.id)}c.addClass(k,q["class"]);g=c.add(i,"span",{"class":"mceIcon"+(q.icon?" mce_"+q.icon:"")});if(q.icon_src){c.add(g,"img",{src:q.icon_src})}i=c.add(i,q.element||"span",{"class":"mceText",title:h.settings.title},h.settings.title);if(h.settings.style){if(typeof h.settings.style=="function"){h.settings.style=h.settings.style()}c.setAttrib(i,"style",h.settings.style)}if(j.childNodes.length==1){c.addClass(l,"mceFirst")}if((i=l.previousSibling)&&c.hasClass(i,m+"ItemSeparator")){c.addClass(l,"mceFirst")}if(h.collapse){c.addClass(l,m+"ItemSub")}if(i=l.previousSibling){c.removeClass(i,"mceLast")}c.addClass(l,"mceLast")}})})(tinymce);(function(b){var a=b.DOM;b.create("tinymce.ui.Button:tinymce.ui.Control",{Button:function(e,d,c){this.parent(e,d,c);this.classPrefix="mceButton"},renderHTML:function(){var f=this.classPrefix,e=this.settings,d,c;c=a.encode(e.label||"");d='';if(e.image&&!(this.editor&&this.editor.forcedHighContrastMode)){d+=''+a.encode(e.title)+''+(c?''+c+"":"")}else{d+=''+(c?''+c+"":"")}d+='";d+="";return d},postRender:function(){var d=this,e=d.settings,c;if(b.isIE&&d.editor){b.dom.Event.add(d.id,"mousedown",function(f){var g=d.editor.selection.getNode().nodeName;c=g==="IMG"?d.editor.selection.getBookmark():null})}b.dom.Event.add(d.id,"click",function(f){if(!d.isDisabled()){if(b.isIE&&d.editor&&c!==null){d.editor.selection.moveToBookmark(c)}return e.onclick.call(e.scope,f)}});b.dom.Event.add(d.id,"keydown",function(f){if(!d.isDisabled()&&f.keyCode==b.VK.SPACEBAR){b.dom.Event.cancel(f);return e.onclick.call(e.scope,f)}})}})})(tinymce);(function(e){var d=e.DOM,b=e.dom.Event,f=e.each,a=e.util.Dispatcher,c;e.create("tinymce.ui.ListBox:tinymce.ui.Control",{ListBox:function(j,i,g){var h=this;h.parent(j,i,g);h.items=[];h.onChange=new a(h);h.onPostRender=new a(h);h.onAdd=new a(h);h.onRenderMenu=new e.util.Dispatcher(this);h.classPrefix="mceListBox";h.marked={}},select:function(h){var g=this,j,i;g.marked={};if(h==c){return g.selectByIndex(-1)}if(h&&typeof(h)=="function"){i=h}else{i=function(k){return k==h}}if(h!=g.selectedValue){f(g.items,function(l,k){if(i(l.value)){j=1;g.selectByIndex(k);return false}});if(!j){g.selectByIndex(-1)}}},selectByIndex:function(g){var i=this,j,k,h;i.marked={};if(g!=i.selectedIndex){j=d.get(i.id+"_text");h=d.get(i.id+"_voiceDesc");k=i.items[g];if(k){i.selectedValue=k.value;i.selectedIndex=g;d.setHTML(j,d.encode(k.title));d.setHTML(h,i.settings.title+" - "+k.title);d.removeClass(j,"mceTitle");d.setAttrib(i.id,"aria-valuenow",k.title)}else{d.setHTML(j,d.encode(i.settings.title));d.setHTML(h,d.encode(i.settings.title));d.addClass(j,"mceTitle");i.selectedValue=i.selectedIndex=null;d.setAttrib(i.id,"aria-valuenow",i.settings.title)}j=0}},mark:function(g){this.marked[g]=true},add:function(j,g,i){var h=this;i=i||{};i=e.extend(i,{title:j,value:g});h.items.push(i);h.onAdd.dispatch(h,i)},getLength:function(){return this.items.length},renderHTML:function(){var j="",g=this,i=g.settings,k=g.classPrefix;j='';j+="";j+="";j+="";return j},showMenu:function(){var h=this,j,i=d.get(this.id),g;if(h.isDisabled()||h.items.length===0){return}if(h.menu&&h.menu.isMenuVisible){return h.hideMenu()}if(!h.isMenuRendered){h.renderMenu();h.isMenuRendered=true}j=d.getPos(i);g=h.menu;g.settings.offset_x=j.x;g.settings.offset_y=j.y;g.settings.keyboard_focus=!e.isOpera;f(h.items,function(k){if(g.items[k.id]){g.items[k.id].setSelected(0)}});f(h.items,function(k){if(g.items[k.id]&&h.marked[k.value]){g.items[k.id].setSelected(1)}if(k.value===h.selectedValue){g.items[k.id].setSelected(1)}});g.showMenu(0,i.clientHeight);b.add(d.doc,"mousedown",h.hideMenu,h);d.addClass(h.id,h.classPrefix+"Selected")},hideMenu:function(h){var g=this;if(g.menu&&g.menu.isMenuVisible){d.removeClass(g.id,g.classPrefix+"Selected");if(h&&h.type=="mousedown"&&(h.target.id==g.id+"_text"||h.target.id==g.id+"_open")){return}if(!h||!d.getParent(h.target,".mceMenu")){d.removeClass(g.id,g.classPrefix+"Selected");b.remove(d.doc,"mousedown",g.hideMenu,g);g.menu.hideMenu()}}},renderMenu:function(){var h=this,g;g=h.settings.control_manager.createDropMenu(h.id+"_menu",{menu_line:1,"class":h.classPrefix+"Menu mceNoIcons",max_width:250,max_height:150});g.onHideMenu.add(function(){h.hideMenu();h.focus()});g.add({title:h.settings.title,"class":"mceMenuItemTitle",onclick:function(){if(h.settings.onselect("")!==false){h.select("")}}});f(h.items,function(i){if(i.value===c){g.add({title:i.title,role:"option","class":"mceMenuItemTitle",onclick:function(){if(h.settings.onselect("")!==false){h.select("")}}})}else{i.id=d.uniqueId();i.role="option";i.onclick=function(){if(h.settings.onselect(i.value)!==false){h.select(i.value)}};g.add(i)}});h.onRenderMenu.dispatch(h,g);h.menu=g},postRender:function(){var g=this,h=g.classPrefix;b.add(g.id,"click",g.showMenu,g);b.add(g.id,"keydown",function(i){if(i.keyCode==32){g.showMenu(i);b.cancel(i)}});b.add(g.id,"focus",function(){if(!g._focused){g.keyDownHandler=b.add(g.id,"keydown",function(i){if(i.keyCode==40){g.showMenu();b.cancel(i)}});g.keyPressHandler=b.add(g.id,"keypress",function(j){var i;if(j.keyCode==13){i=g.selectedValue;g.selectedValue=null;b.cancel(j);g.settings.onselect(i)}})}g._focused=1});b.add(g.id,"blur",function(){b.remove(g.id,"keydown",g.keyDownHandler);b.remove(g.id,"keypress",g.keyPressHandler);g._focused=0});if(e.isIE6||!d.boxModel){b.add(g.id,"mouseover",function(){if(!d.hasClass(g.id,h+"Disabled")){d.addClass(g.id,h+"Hover")}});b.add(g.id,"mouseout",function(){if(!d.hasClass(g.id,h+"Disabled")){d.removeClass(g.id,h+"Hover")}})}g.onPostRender.dispatch(g,d.get(g.id))},destroy:function(){this.parent();b.clear(this.id+"_text");b.clear(this.id+"_open")}})})(tinymce);(function(e){var d=e.DOM,b=e.dom.Event,f=e.each,a=e.util.Dispatcher,c;e.create("tinymce.ui.NativeListBox:tinymce.ui.ListBox",{NativeListBox:function(h,g){this.parent(h,g);this.classPrefix="mceNativeListBox"},setDisabled:function(g){d.get(this.id).disabled=g;this.setAriaProperty("disabled",g)},isDisabled:function(){return d.get(this.id).disabled},select:function(h){var g=this,j,i;if(h==c){return g.selectByIndex(-1)}if(h&&typeof(h)=="function"){i=h}else{i=function(k){return k==h}}if(h!=g.selectedValue){f(g.items,function(l,k){if(i(l.value)){j=1;g.selectByIndex(k);return false}});if(!j){g.selectByIndex(-1)}}},selectByIndex:function(g){d.get(this.id).selectedIndex=g+1;this.selectedValue=this.items[g]?this.items[g].value:null},add:function(k,h,g){var j,i=this;g=g||{};g.value=h;if(i.isRendered()){d.add(d.get(this.id),"option",g,k)}j={title:k,value:h,attribs:g};i.items.push(j);i.onAdd.dispatch(i,j)},getLength:function(){return this.items.length},renderHTML:function(){var i,g=this;i=d.createHTML("option",{value:""},"-- "+g.settings.title+" --");f(g.items,function(h){i+=d.createHTML("option",{value:h.value},h.title)});i=d.createHTML("select",{id:g.id,"class":"mceNativeListBox","aria-labelledby":g.id+"_aria"},i);i+=d.createHTML("span",{id:g.id+"_aria",style:"display: none"},g.settings.title);return i},postRender:function(){var h=this,i,j=true;h.rendered=true;function g(l){var k=h.items[l.target.selectedIndex-1];if(k&&(k=k.value)){h.onChange.dispatch(h,k);if(h.settings.onselect){h.settings.onselect(k)}}}b.add(h.id,"change",g);b.add(h.id,"keydown",function(q){var n,p=37,m=39,l=38,r=40,k=13,o=32;b.remove(h.id,"change",i);j=false;n=b.add(h.id,"blur",function(){if(j){return}j=true;b.add(h.id,"change",g);b.remove(h.id,"blur",n)});if(q.keyCode==k||q.keyCode==o){g(q);return b.cancel(q)}else{if(q.keyCode==r||q.keyCode==l){q.stopImmediatePropagation()}}});h.onPostRender.dispatch(h,d.get(h.id))}})})(tinymce);(function(c){var b=c.DOM,a=c.dom.Event,d=c.each;c.create("tinymce.ui.MenuButton:tinymce.ui.Button",{MenuButton:function(g,f,e){this.parent(g,f,e);this.onRenderMenu=new c.util.Dispatcher(this);f.menu_container=f.menu_container||b.doc.body},showMenu:function(){var g=this,j,i,h=b.get(g.id),f;if(g.isDisabled()){return}if(!g.isMenuRendered){g.renderMenu();g.isMenuRendered=true}if(g.isMenuVisible){return g.hideMenu()}j=b.getPos(g.settings.menu_container);i=b.getPos(h);f=g.menu;f.settings.offset_x=i.x;f.settings.offset_y=i.y;f.settings.vp_offset_x=i.x;f.settings.vp_offset_y=i.y;f.settings.keyboard_focus=g._focused;f.showMenu(0,h.firstChild.clientHeight);a.add(b.doc,"mousedown",g.hideMenu,g);g.setState("Selected",1);g.isMenuVisible=1},renderMenu:function(){var f=this,e;e=f.settings.control_manager.createDropMenu(f.id+"_menu",{menu_line:1,"class":this.classPrefix+"Menu",icons:f.settings.icons});e.onHideMenu.add(function(){f.hideMenu();f.focus()});f.onRenderMenu.dispatch(f,e);f.menu=e},hideMenu:function(g){var f=this;if(g&&g.type=="mousedown"&&b.getParent(g.target,function(h){return h.id===f.id||h.id===f.id+"_open"})){return}if(!g||!b.getParent(g.target,".mceMenu")){f.setState("Selected",0);a.remove(b.doc,"mousedown",f.hideMenu,f);if(f.menu){f.menu.hideMenu()}}f.isMenuVisible=0},postRender:function(){var e=this,f=e.settings;a.add(e.id,"click",function(){if(!e.isDisabled()){if(f.onclick){f.onclick(e.value)}e.showMenu()}})}})})(tinymce);(function(c){var b=c.DOM,a=c.dom.Event,d=c.each;c.create("tinymce.ui.SplitButton:tinymce.ui.MenuButton",{SplitButton:function(g,f,e){this.parent(g,f,e);this.classPrefix="mceSplitButton"},renderHTML:function(){var i,f=this,g=f.settings,e;i="";if(g.image){e=b.createHTML("img ",{src:g.image,role:"presentation","class":"mceAction "+g["class"]})}else{e=b.createHTML("span",{"class":"mceAction "+g["class"]},"")}e+=b.createHTML("span",{"class":"mceVoiceLabel mceIconOnly",id:f.id+"_voice",style:"display:none;"},g.title);i+=""+b.createHTML("a",{role:"button",id:f.id+"_action",tabindex:"-1",href:"javascript:;","class":"mceAction "+g["class"],onclick:"return false;",onmousedown:"return false;",title:g.title},e)+"";e=b.createHTML("span",{"class":"mceOpen "+g["class"]},'');i+=""+b.createHTML("a",{role:"button",id:f.id+"_open",tabindex:"-1",href:"javascript:;","class":"mceOpen "+g["class"],onclick:"return false;",onmousedown:"return false;",title:g.title},e)+"";i+="";i=b.createHTML("table",{role:"presentation","class":"mceSplitButton mceSplitButtonEnabled "+g["class"],cellpadding:"0",cellspacing:"0",title:g.title},i);return b.createHTML("div",{id:f.id,role:"button",tabindex:"0","aria-labelledby":f.id+"_voice","aria-haspopup":"true"},i)},postRender:function(){var e=this,g=e.settings,f;if(g.onclick){f=function(h){if(!e.isDisabled()){g.onclick(e.value);a.cancel(h)}};a.add(e.id+"_action","click",f);a.add(e.id,["click","keydown"],function(h){var k=32,m=14,i=13,j=38,l=40;if((h.keyCode===32||h.keyCode===13||h.keyCode===14)&&!h.altKey&&!h.ctrlKey&&!h.metaKey){f();a.cancel(h)}else{if(h.type==="click"||h.keyCode===l){e.showMenu();a.cancel(h)}}})}a.add(e.id+"_open","click",function(h){e.showMenu();a.cancel(h)});a.add([e.id,e.id+"_open"],"focus",function(){e._focused=1});a.add([e.id,e.id+"_open"],"blur",function(){e._focused=0});if(c.isIE6||!b.boxModel){a.add(e.id,"mouseover",function(){if(!b.hasClass(e.id,"mceSplitButtonDisabled")){b.addClass(e.id,"mceSplitButtonHover")}});a.add(e.id,"mouseout",function(){if(!b.hasClass(e.id,"mceSplitButtonDisabled")){b.removeClass(e.id,"mceSplitButtonHover")}})}},destroy:function(){this.parent();a.clear(this.id+"_action");a.clear(this.id+"_open");a.clear(this.id)}})})(tinymce);(function(d){var c=d.DOM,a=d.dom.Event,b=d.is,e=d.each;d.create("tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton",{ColorSplitButton:function(i,h,f){var g=this;g.parent(i,h,f);g.settings=h=d.extend({colors:"000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF",grid_width:8,default_color:"#888888"},g.settings);g.onShowMenu=new d.util.Dispatcher(g);g.onHideMenu=new d.util.Dispatcher(g);g.value=h.default_color},showMenu:function(){var f=this,g,j,i,h;if(f.isDisabled()){return}if(!f.isMenuRendered){f.renderMenu();f.isMenuRendered=true}if(f.isMenuVisible){return f.hideMenu()}i=c.get(f.id);c.show(f.id+"_menu");c.addClass(i,"mceSplitButtonSelected");h=c.getPos(i);c.setStyles(f.id+"_menu",{left:h.x,top:h.y+i.firstChild.clientHeight,zIndex:200000});i=0;a.add(c.doc,"mousedown",f.hideMenu,f);f.onShowMenu.dispatch(f);if(f._focused){f._keyHandler=a.add(f.id+"_menu","keydown",function(k){if(k.keyCode==27){f.hideMenu()}});c.select("a",f.id+"_menu")[0].focus()}f.keyboardNav=new d.ui.KeyboardNavigation({root:f.id+"_menu",items:c.select("a",f.id+"_menu"),onCancel:function(){f.hideMenu();f.focus()}});f.keyboardNav.focus();f.isMenuVisible=1},hideMenu:function(g){var f=this;if(f.isMenuVisible){if(g&&g.type=="mousedown"&&c.getParent(g.target,function(h){return h.id===f.id+"_open"})){return}if(!g||!c.getParent(g.target,".mceSplitButtonMenu")){c.removeClass(f.id,"mceSplitButtonSelected");a.remove(c.doc,"mousedown",f.hideMenu,f);a.remove(f.id+"_menu","keydown",f._keyHandler);c.hide(f.id+"_menu")}f.isMenuVisible=0;f.onHideMenu.dispatch();f.keyboardNav.destroy()}},renderMenu:function(){var p=this,h,k=0,q=p.settings,g,j,l,o,f;o=c.add(q.menu_container,"div",{role:"listbox",id:p.id+"_menu","class":q.menu_class+" "+q["class"],style:"position:absolute;left:0;top:-1000px;"});h=c.add(o,"div",{"class":q["class"]+" mceSplitButtonMenu"});c.add(h,"span",{"class":"mceMenuLine"});g=c.add(h,"table",{role:"presentation","class":"mceColorSplitMenu"});j=c.add(g,"tbody");k=0;e(b(q.colors,"array")?q.colors:q.colors.split(","),function(m){m=m.replace(/^#/,"");if(!k--){l=c.add(j,"tr");k=q.grid_width-1}g=c.add(l,"td");var i={href:"javascript:;",style:{backgroundColor:"#"+m},title:p.editor.getLang("colors."+m,m),"data-mce-color":"#"+m};if(!d.isIE){i.role="option"}g=c.add(g,"a",i);if(p.editor.forcedHighContrastMode){g=c.add(g,"canvas",{width:16,height:16,"aria-hidden":"true"});if(g.getContext&&(f=g.getContext("2d"))){f.fillStyle="#"+m;f.fillRect(0,0,16,16)}else{c.remove(g)}}});if(q.more_colors_func){g=c.add(j,"tr");g=c.add(g,"td",{colspan:q.grid_width,"class":"mceMoreColors"});g=c.add(g,"a",{role:"option",id:p.id+"_more",href:"javascript:;",onclick:"return false;","class":"mceMoreColors"},q.more_colors_title);a.add(g,"click",function(i){q.more_colors_func.call(q.more_colors_scope||this);return a.cancel(i)})}c.addClass(h,"mceColorSplitMenu");a.add(p.id+"_menu","mousedown",function(i){return a.cancel(i)});a.add(p.id+"_menu","click",function(i){var m;i=c.getParent(i.target,"a",j);if(i&&i.nodeName.toLowerCase()=="a"&&(m=i.getAttribute("data-mce-color"))){p.setColor(m)}return false});return o},setColor:function(f){this.displayColor(f);this.hideMenu();this.settings.onselect(f)},displayColor:function(g){var f=this;c.setStyle(f.id+"_preview","backgroundColor",g);f.value=g},postRender:function(){var f=this,g=f.id;f.parent();c.add(g+"_action","div",{id:g+"_preview","class":"mceColorPreview"});c.setStyle(f.id+"_preview","backgroundColor",f.value)},destroy:function(){var f=this;f.parent();a.clear(f.id+"_menu");a.clear(f.id+"_more");c.remove(f.id+"_menu");if(f.keyboardNav){f.keyboardNav.destroy()}}})})(tinymce);(function(b){var d=b.DOM,c=b.each,a=b.dom.Event;b.create("tinymce.ui.ToolbarGroup:tinymce.ui.Container",{renderHTML:function(){var f=this,i=[],e=f.controls,j=b.each,g=f.settings;i.push('
');i.push("");i.push('");j(e,function(h){i.push(h.renderHTML())});i.push("");i.push("
");return i.join("")},focus:function(){var e=this;d.get(e.id).focus()},postRender:function(){var f=this,e=[];c(f.controls,function(g){c(g.controls,function(h){if(h.id){e.push(h)}})});f.keyNav=new b.ui.KeyboardNavigation({root:f.id,items:e,onCancel:function(){if(b.isWebKit){d.get(f.editor.id+"_ifr").focus()}f.editor.focus()},excludeFromTabOrder:!f.settings.tab_focus_toolbar})},destroy:function(){var e=this;e.parent();e.keyNav.destroy();a.clear(e.id)}})})(tinymce);(function(a){var c=a.DOM,b=a.each;a.create("tinymce.ui.Toolbar:tinymce.ui.Container",{renderHTML:function(){var m=this,f="",j,k,n=m.settings,e,d,g,l;l=m.controls;for(e=0;e"))}if(d&&k.ListBox){if(d.Button||d.SplitButton){f+=c.createHTML("td",{"class":"mceToolbarEnd"},c.createHTML("span",null,""))}}if(c.stdMode){f+=''+k.renderHTML()+""}else{f+=""+k.renderHTML()+""}if(g&&k.ListBox){if(g.Button||g.SplitButton){f+=c.createHTML("td",{"class":"mceToolbarStart"},c.createHTML("span",null,""))}}}j="mceToolbarEnd";if(k.Button){j+=" mceToolbarEndButton"}else{if(k.SplitButton){j+=" mceToolbarEndSplitButton"}else{if(k.ListBox){j+=" mceToolbarEndListBox"}}}f+=c.createHTML("td",{"class":j},c.createHTML("span",null,""));return c.createHTML("table",{id:m.id,"class":"mceToolbar"+(n["class"]?" "+n["class"]:""),cellpadding:"0",cellspacing:"0",align:m.settings.align||"",role:"presentation",tabindex:"-1"},""+f+"")}})})(tinymce);(function(b){var a=b.util.Dispatcher,c=b.each;b.create("tinymce.AddOnManager",{AddOnManager:function(){var d=this;d.items=[];d.urls={};d.lookup={};d.onAdd=new a(d)},get:function(d){if(this.lookup[d]){return this.lookup[d].instance}else{return undefined}},dependencies:function(e){var d;if(this.lookup[e]){d=this.lookup[e].dependencies}return d||[]},requireLangPack:function(e){var d=b.settings;if(d&&d.language&&d.language_load!==false){b.ScriptLoader.add(this.urls[e]+"/langs/"+d.language+".js")}},add:function(f,e,d){this.items.push(e);this.lookup[f]={instance:e,dependencies:d};this.onAdd.dispatch(this,f,e);return e},createUrl:function(d,e){if(typeof e==="object"){return e}else{return{prefix:d.prefix,resource:e,suffix:d.suffix}}},addComponents:function(f,d){var e=this.urls[f];b.each(d,function(g){b.ScriptLoader.add(e+"/"+g)})},load:function(j,f,d,h){var g=this,e=f;function i(){var k=g.dependencies(j);b.each(k,function(m){var l=g.createUrl(f,m);g.load(l.resource,l,undefined,undefined)});if(d){if(h){d.call(h)}else{d.call(b.ScriptLoader)}}}if(g.urls[j]){return}if(typeof f==="object"){e=f.prefix+f.resource+f.suffix}if(e.indexOf("/")!==0&&e.indexOf("://")==-1){e=b.baseURL+"/"+e}g.urls[j]=e.substring(0,e.lastIndexOf("/"));if(g.lookup[j]){i()}else{b.ScriptLoader.add(e,i,h)}}});b.PluginManager=new b.AddOnManager();b.ThemeManager=new b.AddOnManager()}(tinymce));(function(j){var g=j.each,d=j.extend,k=j.DOM,i=j.dom.Event,f=j.ThemeManager,b=j.PluginManager,e=j.explode,h=j.util.Dispatcher,a,c=0;j.documentBaseURL=window.location.href.replace(/[\?#].*$/,"").replace(/[\/\\][^\/]+$/,"");if(!/[\/\\]$/.test(j.documentBaseURL)){j.documentBaseURL+="/"}j.baseURL=new j.util.URI(j.documentBaseURL).toAbsolute(j.baseURL);j.baseURI=new j.util.URI(j.baseURL);j.onBeforeUnload=new h(j);i.add(window,"beforeunload",function(l){j.onBeforeUnload.dispatch(j,l)});j.onAddEditor=new h(j);j.onRemoveEditor=new h(j);j.EditorManager=d(j,{editors:[],i18n:{},activeEditor:null,init:function(x){var v=this,o,n=j.ScriptLoader,u,l=[],r;function q(t){var s=t.id;if(!s){s=t.name;if(s&&!k.get(s)){s=t.name}else{s=k.uniqueId()}t.setAttribute("id",s)}return s}function m(z,A,t){var y=z[A];if(!y){return}if(j.is(y,"string")){t=y.replace(/\.\w+$/,"");t=t?j.resolve(t):0;y=j.resolve(y)}return y.apply(t||this,Array.prototype.slice.call(arguments,2))}function p(t,s){return s.constructor===RegExp?s.test(t.className):k.hasClass(t,s)}v.settings=x;i.bind(window,"ready",function(){var s,t;m(x,"onpageload");switch(x.mode){case"exact":s=x.elements||"";if(s.length>0){g(e(s),function(y){if(k.get(y)){r=new j.Editor(y,x);l.push(r);r.render(1)}else{g(document.forms,function(z){g(z.elements,function(A){if(A.name===y){y="mce_editor_"+c++;k.setAttrib(A,"id",y);r=new j.Editor(y,x);l.push(r);r.render(1)}})})}})}break;case"textareas":case"specific_textareas":g(k.select("textarea"),function(y){if(x.editor_deselector&&p(y,x.editor_deselector)){return}if(!x.editor_selector||p(y,x.editor_selector)){r=new j.Editor(q(y),x);l.push(r);r.render(1)}});break;default:if(x.types){g(x.types,function(y){g(k.select(y.selector),function(A){var z=new j.Editor(q(A),j.extend({},x,y));l.push(z);z.render(1)})})}else{if(x.selector){g(k.select(x.selector),function(z){var y=new j.Editor(q(z),x);l.push(y);y.render(1)})}}}if(x.oninit){s=t=0;g(l,function(y){t++;if(!y.initialized){y.onInit.add(function(){s++;if(s==t){m(x,"oninit")}})}else{s++}if(s==t){m(x,"oninit")}})}})},get:function(l){if(l===a){return this.editors}if(!this.editors.hasOwnProperty(l)){return a}return this.editors[l]},getInstanceById:function(l){return this.get(l)},add:function(m){var l=this,n=l.editors;n[m.id]=m;n.push(m);l._setActive(m);l.onAddEditor.dispatch(l,m);return m},remove:function(n){var m=this,l,o=m.editors;if(!o[n.id]){return null}delete o[n.id];for(l=0;l':"",visual:n,font_size_style_values:"xx-small,x-small,small,medium,large,x-large,xx-large",font_size_legacy_values:"xx-small,small,medium,large,x-large,xx-large,300%",apply_source_formatting:n,directionality:"ltr",forced_root_block:"p",hidden_input:n,padd_empty_editor:n,render_ui:n,indentation:"30px",fix_table_elements:n,inline_styles:n,convert_fonts_to_spans:n,indent:"simple",indent_before:"p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist",indent_after:"p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist",validate:n,entity_encoding:"named",url_converter:m.convertURL,url_converter_scope:m,ie7_compat:n},o);m.id=m.editorId=p;m.isNotDirty=false;m.plugins={};m.documentBaseURI=new k.util.URI(o.document_base_url||k.documentBaseURL,{base_uri:tinyMCE.baseURI});m.baseURI=k.baseURI;m.contentCSS=[];m.contentStyles=[];m.setupEvents();m.execCommands={};m.queryStateCommands={};m.queryValueCommands={};m.execCallback("setup",m)},render:function(o){var p=this,q=p.settings,r=p.id,m=k.ScriptLoader;if(!j.domLoaded){j.add(window,"ready",function(){p.render()});return}tinyMCE.settings=q;if(!p.getElement()){return}if(k.isIDevice&&!k.isIOS5){return}if(!/TEXTAREA|INPUT/i.test(p.getElement().nodeName)&&q.hidden_input&&l.getParent(r,"form")){l.insertAfter(l.create("input",{type:"hidden",name:r}),r)}if(!q.content_editable){p.orgVisibility=p.getElement().style.visibility;p.getElement().style.visibility="hidden"}if(k.WindowManager){p.windowManager=new k.WindowManager(p)}if(q.encoding=="xml"){p.onGetContent.add(function(s,t){if(t.save){t.content=l.encode(t.content)}})}if(q.add_form_submit_trigger){p.onSubmit.addToTop(function(){if(p.initialized){p.save();p.isNotDirty=1}})}if(q.add_unload_trigger){p._beforeUnload=tinyMCE.onBeforeUnload.add(function(){if(p.initialized&&!p.destroyed&&!p.isHidden()){p.save({format:"raw",no_events:true})}})}k.addUnload(p.destroy,p);if(q.submit_patch){p.onBeforeRenderUI.add(function(){var s=p.getElement().form;if(!s){return}if(s._mceOldSubmit){return}if(!s.submit.nodeType&&!s.submit.length){p.formElement=s;s._mceOldSubmit=s.submit;s.submit=function(){k.triggerSave();p.isNotDirty=1;return p.formElement._mceOldSubmit(p.formElement)}}s=null})}function n(){if(q.language&&q.language_load!==false){m.add(k.baseURL+"/langs/"+q.language+".js")}if(q.theme&&typeof q.theme!="function"&&q.theme.charAt(0)!="-"&&!h.urls[q.theme]){h.load(q.theme,"themes/"+q.theme+"/editor_template"+k.suffix+".js")}i(g(q.plugins),function(t){if(t&&!c.urls[t]){if(t.charAt(0)=="-"){t=t.substr(1,t.length);var s=c.dependencies(t);i(s,function(v){var u={prefix:"plugins/",resource:v,suffix:"/editor_plugin"+k.suffix+".js"};v=c.createUrl(u,v);c.load(v.resource,v)})}else{if(t=="safari"){return}c.load(t,{prefix:"plugins/",resource:t,suffix:"/editor_plugin"+k.suffix+".js"})}}});m.loadQueue(function(){if(!p.removed){p.init()}})}n()},init:function(){var q,G=this,H=G.settings,D,y,z,C=G.getElement(),p,m,E,v,B,F,x,r=[];k.add(G);H.aria_label=H.aria_label||l.getAttrib(C,"aria-label",G.getLang("aria.rich_text_area"));if(H.theme){if(typeof H.theme!="function"){H.theme=H.theme.replace(/-/,"");p=h.get(H.theme);G.theme=new p();if(G.theme.init){G.theme.init(G,h.urls[H.theme]||k.documentBaseURL.replace(/\/$/,""))}}else{G.theme=H.theme}}function A(s){var t=c.get(s),o=c.urls[s]||k.documentBaseURL.replace(/\/$/,""),n;if(t&&k.inArray(r,s)===-1){i(c.dependencies(s),function(u){A(u)});n=new t(G,o);G.plugins[s]=n;if(n.init){n.init(G,o);r.push(s)}}}i(g(H.plugins.replace(/\-/g,"")),A);if(H.popup_css!==false){if(H.popup_css){H.popup_css=G.documentBaseURI.toAbsolute(H.popup_css)}else{H.popup_css=G.baseURI.toAbsolute("themes/"+H.theme+"/skins/"+H.skin+"/dialog.css")}}if(H.popup_css_add){H.popup_css+=","+G.documentBaseURI.toAbsolute(H.popup_css_add)}G.controlManager=new k.ControlManager(G);G.onBeforeRenderUI.dispatch(G,G.controlManager);if(H.render_ui&&G.theme){G.orgDisplay=C.style.display;if(typeof H.theme!="function"){D=H.width||C.style.width||C.offsetWidth;y=H.height||C.style.height||C.offsetHeight;z=H.min_height||100;F=/^[0-9\.]+(|px)$/i;if(F.test(""+D)){D=Math.max(parseInt(D,10)+(p.deltaWidth||0),100)}if(F.test(""+y)){y=Math.max(parseInt(y,10)+(p.deltaHeight||0),z)}p=G.theme.renderUI({targetNode:C,width:D,height:y,deltaWidth:H.delta_width,deltaHeight:H.delta_height});l.setStyles(p.sizeContainer||p.editorContainer,{width:D,height:y});y=(p.iframeHeight||y)+(typeof(y)=="number"?(p.deltaHeight||0):"");if(y';if(H.document_base_url!=k.documentBaseURL){G.iframeHTML+=''}if(k.isIE8){if(H.ie7_compat){G.iframeHTML+=''}else{G.iframeHTML+=''}}G.iframeHTML+='';for(x=0;x'}G.contentCSS=[];v=H.body_id||"tinymce";if(v.indexOf("=")!=-1){v=G.getParam("body_id","","hash");v=v[G.id]||v}B=H.body_class||"";if(B.indexOf("=")!=-1){B=G.getParam("body_class","","hash");B=B[G.id]||""}G.iframeHTML+='
";if(k.relaxedDomain&&(b||(k.isOpera&&parseFloat(opera.version())<11))){E='javascript:(function(){document.open();document.domain="'+document.domain+'";var ed = window.parent.tinyMCE.get("'+G.id+'");document.write(ed.iframeHTML);document.close();ed.initContentBody();})()'}q=l.add(p.iframeContainer,"iframe",{id:G.id+"_ifr",src:E||'javascript:""',frameBorder:"0",allowTransparency:"true",title:H.aria_label,style:{width:"100%",height:y,display:"block"}});G.contentAreaContainer=p.iframeContainer;if(p.editorContainer){l.get(p.editorContainer).style.display=G.orgDisplay}C.style.visibility=G.orgVisibility;l.get(G.id).style.display="none";l.setAttrib(G.id,"aria-hidden",true);if(!k.relaxedDomain||!E){G.initContentBody()}C=q=p=null},initContentBody:function(){var n=this,p=n.settings,q=l.get(n.id),r=n.getDoc(),o,m,s;if((!b||!k.relaxedDomain)&&!p.content_editable){r.open();r.write(n.iframeHTML);r.close();if(k.relaxedDomain){r.domain=k.relaxedDomain}}if(p.content_editable){l.addClass(q,"mceContentBody");n.contentDocument=r=p.content_document||document;n.contentWindow=p.content_window||window;n.bodyElement=q;p.content_document=p.content_window=null}m=n.getBody();m.disabled=true;if(!p.readonly){m.contentEditable=n.getParam("content_editable_state",true)}m.disabled=false;n.schema=new k.html.Schema(p);n.dom=new k.dom.DOMUtils(r,{keep_values:true,url_converter:n.convertURL,url_converter_scope:n,hex_colors:p.force_hex_style_colors,class_filter:p.class_filter,update_styles:true,root_element:p.content_editable?n.id:null,schema:n.schema});n.parser=new k.html.DomParser(p,n.schema);n.parser.addAttributeFilter("src,href,style",function(t,u){var v=t.length,y,A=n.dom,z,x;while(v--){y=t[v];z=y.attr(u);x="data-mce-"+u;if(!y.attributes.map[x]){if(u==="style"){y.attr(x,A.serializeStyle(A.parseStyle(z),y.name))}else{y.attr(x,n.convertURL(z,u,y.name))}}}});n.parser.addNodeFilter("script",function(t,u){var v=t.length,x;while(v--){x=t[v];x.attr("type","mce-"+(x.attr("type")||"text/javascript"))}});n.parser.addNodeFilter("#cdata",function(t,u){var v=t.length,x;while(v--){x=t[v];x.type=8;x.name="#comment";x.value="[CDATA["+x.value+"]]"}});n.parser.addNodeFilter("p,h1,h2,h3,h4,h5,h6,div",function(u,v){var x=u.length,y,t=n.schema.getNonEmptyElements();while(x--){y=u[x];if(y.isEmpty(t)){y.empty().append(new k.html.Node("br",1)).shortEnded=true}}});n.serializer=new k.dom.Serializer(p,n.dom,n.schema);n.selection=new k.dom.Selection(n.dom,n.getWin(),n.serializer,n);n.formatter=new k.Formatter(n);n.undoManager=new k.UndoManager(n);n.forceBlocks=new k.ForceBlocks(n);n.enterKey=new k.EnterKey(n);n.editorCommands=new k.EditorCommands(n);n.onExecCommand.add(function(t,u){if(!/^(FontName|FontSize)$/.test(u)){n.nodeChanged()}});n.serializer.onPreProcess.add(function(t,u){return n.onPreProcess.dispatch(n,u,t)});n.serializer.onPostProcess.add(function(t,u){return n.onPostProcess.dispatch(n,u,t)});n.onPreInit.dispatch(n);if(!p.browser_spellcheck&&!p.gecko_spellcheck){r.body.spellcheck=false}if(!p.readonly){n.bindNativeEvents()}n.controlManager.onPostRender.dispatch(n,n.controlManager);n.onPostRender.dispatch(n);n.quirks=k.util.Quirks(n);if(p.directionality){m.dir=p.directionality}if(p.nowrap){m.style.whiteSpace="nowrap"}if(p.protect){n.onBeforeSetContent.add(function(t,u){i(p.protect,function(v){u.content=u.content.replace(v,function(x){return""})})})}n.onSetContent.add(function(){n.addVisual(n.getBody())});if(p.padd_empty_editor){n.onPostProcess.add(function(t,u){u.content=u.content.replace(/^(]*>( | |\s|\u00a0|)<\/p>[\r\n]*|
[\r\n]*)$/,"")})}n.load({initial:true,format:"html"});n.startContent=n.getContent({format:"raw"});n.initialized=true;n.onInit.dispatch(n);n.execCallback("setupcontent_callback",n.id,m,r);n.execCallback("init_instance_callback",n);n.focus(true);n.nodeChanged({initial:true});if(n.contentStyles.length>0){s="";i(n.contentStyles,function(t){s+=t+"\r\n"});n.dom.addStyle(s)}i(n.contentCSS,function(t){n.dom.loadCSS(t)});if(p.auto_focus){setTimeout(function(){var t=k.get(p.auto_focus);t.selection.select(t.getBody(),1);t.selection.collapse(1);t.getBody().focus();t.getWin().focus()},100)}q=r=m=null},focus:function(p){var o,u=this,t=u.selection,q=u.settings.content_editable,n,r,s=u.getDoc(),m;if(!p){if(u.bookmark){t.moveToBookmark(u.bookmark);u.bookmark=null}n=t.getRng();if(n.item){r=n.item(0)}u._refreshContentEditable();if(!q){u.getWin().focus()}if(k.isGecko||q){m=u.getBody();if(m.setActive&&!k.isIE11){m.setActive()}else{m.focus()}if(q){t.normalize()}}if(r&&r.ownerDocument==s){n=s.body.createControlRange();n.addElement(r);n.select()}}if(k.activeEditor!=u){if((o=k.activeEditor)!=null){o.onDeactivate.dispatch(o,u)}u.onActivate.dispatch(u,o)}k._setActive(u)},execCallback:function(q){var m=this,p=m.settings[q],o;if(!p){return}if(m.callbackLookup&&(o=m.callbackLookup[q])){p=o.func;o=o.scope}if(d(p,"string")){o=p.replace(/\.\w+$/,"");o=o?k.resolve(o):0;p=k.resolve(p);m.callbackLookup=m.callbackLookup||{};m.callbackLookup[q]={func:p,scope:o}}return p.apply(o||m,Array.prototype.slice.call(arguments,1))},translate:function(m){var o=this.settings.language||"en",n=k.i18n;if(!m){return""}return n[o+"."+m]||m.replace(/\{\#([^\}]+)\}/g,function(q,p){return n[o+"."+p]||"{#"+p+"}"})},getLang:function(o,m){return k.i18n[(this.settings.language||"en")+"."+o]||(d(m)?m:"{#"+o+"}")},getParam:function(t,q,m){var r=k.trim,p=d(this.settings[t])?this.settings[t]:q,s;if(m==="hash"){s={};if(d(p,"string")){i(p.indexOf("=")>0?p.split(/[;,](?![^=;,]*(?:[;,]|$))/):p.split(","),function(n){n=n.split("=");if(n.length>1){s[r(n[0])]=r(n[1])}else{s[r(n[0])]=r(n)}})}else{s=p}return s}return p},nodeChanged:function(q){var m=this,n=m.selection,p;if(m.initialized){q=q||{};p=n.getStart()||m.getBody();p=b&&p.ownerDocument!=m.getDoc()?m.getBody():p;q.parents=[];m.dom.getParent(p,function(o){if(o.nodeName=="BODY"){return true}q.parents.push(o)});m.onNodeChange.dispatch(m,q?q.controlManager||m.controlManager:m.controlManager,p,n.isCollapsed(),q)}},addButton:function(n,o){var m=this;m.buttons=m.buttons||{};m.buttons[n]=o},addCommand:function(m,o,n){this.execCommands[m]={func:o,scope:n||this}},addQueryStateHandler:function(m,o,n){this.queryStateCommands[m]={func:o,scope:n||this}},addQueryValueHandler:function(m,o,n){this.queryValueCommands[m]={func:o,scope:n||this}},addShortcut:function(o,q,m,p){var n=this,r;if(n.settings.custom_shortcuts===false){return false}n.shortcuts=n.shortcuts||{};if(d(m,"string")){r=m;m=function(){n.execCommand(r,false,null)}}if(d(m,"object")){r=m;m=function(){n.execCommand(r[0],r[1],r[2])}}i(g(o),function(s){var t={func:m,scope:p||this,desc:n.translate(q),alt:false,ctrl:false,shift:false};i(g(s,"+"),function(u){switch(u){case"alt":case"ctrl":case"shift":t[u]=true;break;default:t.charCode=u.charCodeAt(0);t.keyCode=u.toUpperCase().charCodeAt(0)}});n.shortcuts[(t.ctrl?"ctrl":"")+","+(t.alt?"alt":"")+","+(t.shift?"shift":"")+","+t.keyCode]=t});return true},execCommand:function(u,r,x,m){var p=this,q=0,v,n;if(!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(u)&&(!m||!m.skip_focus)){p.focus()}m=f({},m);p.onBeforeExecCommand.dispatch(p,u,r,x,m);if(m.terminate){return false}if(p.execCallback("execcommand_callback",p.id,p.selection.getNode(),u,r,x)){p.onExecCommand.dispatch(p,u,r,x,m);return true}if(v=p.execCommands[u]){n=v.func.call(v.scope,r,x);if(n!==true){p.onExecCommand.dispatch(p,u,r,x,m);return n}}i(p.plugins,function(o){if(o.execCommand&&o.execCommand(u,r,x)){p.onExecCommand.dispatch(p,u,r,x,m);q=1;return false}});if(q){return true}if(p.theme&&p.theme.execCommand&&p.theme.execCommand(u,r,x)){p.onExecCommand.dispatch(p,u,r,x,m);return true}if(p.editorCommands.execCommand(u,r,x)){p.onExecCommand.dispatch(p,u,r,x,m);return true}p.getDoc().execCommand(u,r,x);p.onExecCommand.dispatch(p,u,r,x,m)},queryCommandState:function(q){var n=this,r,p;if(n._isHidden()){return}if(r=n.queryStateCommands[q]){p=r.func.call(r.scope);if(p!==true){return p}}r=n.editorCommands.queryCommandState(q);if(r!==-1){return r}try{return this.getDoc().queryCommandState(q)}catch(m){}},queryCommandValue:function(r){var n=this,q,p;if(n._isHidden()){return}if(q=n.queryValueCommands[r]){p=q.func.call(q.scope);if(p!==true){return p}}q=n.editorCommands.queryCommandValue(r);if(d(q)){return q}try{return this.getDoc().queryCommandValue(r)}catch(m){}},show:function(){var m=this;l.show(m.getContainer());l.hide(m.id);m.load()},hide:function(){var m=this,n=m.getDoc();if(b&&n){n.execCommand("SelectAll")}m.save();l.hide(m.getContainer());l.setStyle(m.id,"display",m.orgDisplay)},isHidden:function(){return !l.isHidden(this.id)},setProgressState:function(m,n,p){this.onSetProgressState.dispatch(this,m,n,p);return m},load:function(q){var m=this,p=m.getElement(),n;if(p){q=q||{};q.load=true;n=m.setContent(d(p.value)?p.value:p.innerHTML,q);q.element=p;if(!q.no_events){m.onLoadContent.dispatch(m,q)}q.element=p=null;return n}},save:function(r){var m=this,q=m.getElement(),n,p;if(!q||!m.initialized){return}r=r||{};r.save=true;r.element=q;n=r.content=m.getContent(r);if(!r.no_events){m.onSaveContent.dispatch(m,r)}n=r.content;if(!/TEXTAREA|INPUT/i.test(q.nodeName)){q.innerHTML=n;if(p=l.getParent(m.id,"form")){i(p.elements,function(o){if(o.name==m.id){o.value=n;return false}})}}else{q.value=n}r.element=q=null;return n},setContent:function(r,p){var o=this,n,m=o.getBody(),q;p=p||{};p.format=p.format||"html";p.set=true;p.content=r;if(!p.no_events){o.onBeforeSetContent.dispatch(o,p)}r=p.content;if(!k.isIE&&(r.length===0||/^\s+$/.test(r))){q=o.settings.forced_root_block;if(q){r="<"+q+'>
"}else{r='
'}m.innerHTML=r;o.selection.select(m,true);o.selection.collapse(true);return}if(p.format!=="raw"){r=new k.html.Serializer({},o.schema).serialize(o.parser.parse(r))}p.content=k.trim(r);o.dom.setHTML(m,p.content);if(!p.no_events){o.onSetContent.dispatch(o,p)}if(!o.settings.content_editable||document.activeElement===o.getBody()){o.selection.normalize()}return p.content},getContent:function(o){var n=this,p,m=n.getBody();o=o||{};o.format=o.format||"html";o.get=true;o.getInner=true;if(!o.no_events){n.onBeforeGetContent.dispatch(n,o)}if(o.format=="raw"){p=m.innerHTML}else{if(o.format=="text"){p=m.innerText||m.textContent}else{p=n.serializer.serialize(m,o)}}if(o.format!="text"){o.content=k.trim(p)}else{o.content=p}if(!o.no_events){n.onGetContent.dispatch(n,o)}return o.content},isDirty:function(){var m=this;return k.trim(m.startContent)!=k.trim(m.getContent({format:"raw",no_events:1}))&&!m.isNotDirty},getContainer:function(){var m=this;if(!m.container){m.container=l.get(m.editorContainer||m.id+"_parent")}return m.container},getContentAreaContainer:function(){return this.contentAreaContainer},getElement:function(){return l.get(this.settings.content_element||this.id)},getWin:function(){var m=this,n;if(!m.contentWindow){n=l.get(m.id+"_ifr");if(n){m.contentWindow=n.contentWindow}}return m.contentWindow},getDoc:function(){var m=this,n;if(!m.contentDocument){n=m.getWin();if(n){m.contentDocument=n.document}}return m.contentDocument},getBody:function(){return this.bodyElement||this.getDoc().body},convertURL:function(o,n,q){var m=this,p=m.settings;if(p.urlconverter_callback){return m.execCallback("urlconverter_callback",o,q,true,n)}if(!p.convert_urls||(q&&q.nodeName=="LINK")||o.indexOf("file:")===0){return o}if(p.relative_urls){return m.documentBaseURI.toRelative(o)}o=m.documentBaseURI.toAbsolute(o,p.remove_script_host);return o},addVisual:function(q){var n=this,o=n.settings,p=n.dom,m;q=q||n.getBody();if(!d(n.hasVisual)){n.hasVisual=o.visual}i(p.select("table,a",q),function(s){var r;switch(s.nodeName){case"TABLE":m=o.visual_table_class||"mceItemTable";r=p.getAttrib(s,"border");if(!r||r=="0"){if(n.hasVisual){p.addClass(s,m)}else{p.removeClass(s,m)}}return;case"A":if(!p.getAttrib(s,"href",false)){r=p.getAttrib(s,"name")||s.id;m="mceItemAnchor";if(r){if(n.hasVisual){p.addClass(s,m)}else{p.removeClass(s,m)}}}return}});n.onVisualAid.dispatch(n,q,n.hasVisual)},remove:function(){var m=this,o=m.getContainer(),n=m.getDoc();if(!m.removed){m.removed=1;if(b&&n){n.execCommand("SelectAll")}m.save();l.setStyle(m.id,"display",m.orgDisplay);if(!m.settings.content_editable){j.unbind(m.getWin());j.unbind(m.getDoc())}j.unbind(m.getBody());j.clear(o);m.execCallback("remove_instance_callback",m);m.onRemove.dispatch(m);m.onExecCommand.listeners=[];k.remove(m);l.remove(o)}},destroy:function(n){var m=this;if(m.destroyed){return}if(a){j.unbind(m.getDoc());j.unbind(m.getWin());j.unbind(m.getBody())}if(!n){k.removeUnload(m.destroy);tinyMCE.onBeforeUnload.remove(m._beforeUnload);if(m.theme&&m.theme.destroy){m.theme.destroy()}m.controlManager.destroy();m.selection.destroy();m.dom.destroy()}if(m.formElement){m.formElement.submit=m.formElement._mceOldSubmit;m.formElement._mceOldSubmit=null}m.contentAreaContainer=m.formElement=m.container=m.settings.content_element=m.bodyElement=m.contentDocument=m.contentWindow=null;if(m.selection){m.selection=m.selection.win=m.selection.dom=m.selection.dom.doc=null}m.destroyed=1},_refreshContentEditable:function(){var n=this,m,o;if(n._isHidden()){m=n.getBody();o=m.parentNode;o.removeChild(m);o.appendChild(m);m.focus()}},_isHidden:function(){var m;if(!a){return 0}m=this.selection.getSel();return(!m||!m.rangeCount||m.rangeCount===0)}})})(tinymce);(function(a){var b=a.each;a.Editor.prototype.setupEvents=function(){var c=this,d=c.settings;b(["onPreInit","onBeforeRenderUI","onPostRender","onLoad","onInit","onRemove","onActivate","onDeactivate","onClick","onEvent","onMouseUp","onMouseDown","onDblClick","onKeyDown","onKeyUp","onKeyPress","onContextMenu","onSubmit","onReset","onPaste","onPreProcess","onPostProcess","onBeforeSetContent","onBeforeGetContent","onSetContent","onGetContent","onLoadContent","onSaveContent","onNodeChange","onChange","onBeforeExecCommand","onExecCommand","onUndo","onRedo","onVisualAid","onSetProgressState","onSetAttrib"],function(e){c[e]=new a.util.Dispatcher(c)});if(d.cleanup_callback){c.onBeforeSetContent.add(function(e,f){f.content=e.execCallback("cleanup_callback","insert_to_editor",f.content,f)});c.onPreProcess.add(function(e,f){if(f.set){e.execCallback("cleanup_callback","insert_to_editor_dom",f.node,f)}if(f.get){e.execCallback("cleanup_callback","get_from_editor_dom",f.node,f)}});c.onPostProcess.add(function(e,f){if(f.set){f.content=e.execCallback("cleanup_callback","insert_to_editor",f.content,f)}if(f.get){f.content=e.execCallback("cleanup_callback","get_from_editor",f.content,f)}})}if(d.save_callback){c.onGetContent.add(function(e,f){if(f.save){f.content=e.execCallback("save_callback",e.id,f.content,e.getBody())}})}if(d.handle_event_callback){c.onEvent.add(function(f,g,h){if(c.execCallback("handle_event_callback",g,f,h)===false){g.preventDefault();g.stopPropagation()}})}if(d.handle_node_change_callback){c.onNodeChange.add(function(f,e,g){f.execCallback("handle_node_change_callback",f.id,g,-1,-1,true,f.selection.isCollapsed())})}if(d.save_callback){c.onSaveContent.add(function(e,g){var f=e.execCallback("save_callback",e.id,g.content,e.getBody());if(f){g.content=f}})}if(d.onchange_callback){c.onChange.add(function(f,e){f.execCallback("onchange_callback",f,e)})}};a.Editor.prototype.bindNativeEvents=function(){var l=this,f,d=l.settings,e=l.dom,h;h={mouseup:"onMouseUp",mousedown:"onMouseDown",click:"onClick",keyup:"onKeyUp",keydown:"onKeyDown",keypress:"onKeyPress",submit:"onSubmit",reset:"onReset",contextmenu:"onContextMenu",dblclick:"onDblClick",paste:"onPaste"};function c(i,m){var n=i.type;if(l.removed){return}if(l.onEvent.dispatch(l,i,m)!==false){l[h[i.fakeType||i.type]].dispatch(l,i,m)}}function j(i){l.focus(true)}function k(i,m){if(m.keyCode!=65||!a.VK.metaKeyPressed(m)){l.selection.normalize()}l.nodeChanged()}b(h,function(m,n){var i=d.content_editable?l.getBody():l.getDoc();switch(n){case"contextmenu":e.bind(i,n,c);break;case"paste":e.bind(l.getBody(),n,c);break;case"submit":case"reset":e.bind(l.getElement().form||a.DOM.getParent(l.id,"form"),n,c);break;default:e.bind(i,n,c)}});e.bind(d.content_editable?l.getBody():(a.isGecko?l.getDoc():l.getWin()),"focus",function(i){l.focus(true)});if(d.content_editable&&a.isOpera){e.bind(l.getBody(),"click",j);e.bind(l.getBody(),"keydown",j)}l.onMouseUp.add(k);l.onKeyUp.add(function(i,n){var m=n.keyCode;if((m>=33&&m<=36)||(m>=37&&m<=40)||m==13||m==45||m==46||m==8||(a.isMac&&(m==91||m==93))||n.ctrlKey){k(i,n)}});l.onReset.add(function(){l.setContent(l.startContent,{format:"raw"})});function g(m,i){if(m.altKey||m.ctrlKey||m.metaKey){b(l.shortcuts,function(n){var o=a.isMac?m.metaKey:m.ctrlKey;if(n.ctrl!=o||n.alt!=m.altKey||n.shift!=m.shiftKey){return}if(m.keyCode==n.keyCode||(m.charCode&&m.charCode==n.charCode)){m.preventDefault();if(i){n.func.call(n.scope)}return true}})}}l.onKeyUp.add(function(i,m){g(m)});l.onKeyPress.add(function(i,m){g(m)});l.onKeyDown.add(function(i,m){g(m,true)});if(a.isOpera){l.onClick.add(function(i,m){m.preventDefault()})}}})(tinymce);(function(d){var e=d.each,b,a=true,c=false;d.EditorCommands=function(n){var m=n.dom,p=n.selection,j={state:{},exec:{},value:{}},k=n.settings,q=n.formatter,o;function r(z,y,x){var v;z=z.toLowerCase();if(v=j.exec[z]){v(z,y,x);return a}return c}function l(x){var v;x=x.toLowerCase();if(v=j.state[x]){return v(x)}return -1}function h(x){var v;x=x.toLowerCase();if(v=j.value[x]){return v(x)}return c}function u(v,x){x=x||"exec";e(v,function(z,y){e(y.toLowerCase().split(","),function(A){j[x][A]=z})})}d.extend(this,{execCommand:r,queryCommandState:l,queryCommandValue:h,addCommands:u});function f(y,x,v){if(x===b){x=c}if(v===b){v=null}return n.getDoc().execCommand(y,x,v)}function t(v){return q.match(v)}function s(v,x){q.toggle(v,x?{value:x}:b)}function i(v){o=p.getBookmark(v)}function g(){p.moveToBookmark(o)}u({"mceResetDesignMode,mceBeginUndoLevel":function(){},"mceEndUndoLevel,mceAddUndoLevel":function(){n.undoManager.add()},"Cut,Copy,Paste":function(z){var y=n.getDoc(),v;try{f(z)}catch(x){v=a}if(v||!y.queryCommandSupported(z)){if(d.isGecko){n.windowManager.confirm(n.getLang("clipboard_msg"),function(A){if(A){open("http://www.mozilla.org/editor/midasdemo/securityprefs.html","_blank")}})}else{n.windowManager.alert(n.getLang("clipboard_no_support"))}}},unlink:function(v){if(p.isCollapsed()){p.select(p.getNode())}f(v);p.collapse(c)},"JustifyLeft,JustifyCenter,JustifyRight,JustifyFull":function(v){var x=v.substring(7);e("left,center,right,full".split(","),function(y){if(x!=y){q.remove("align"+y)}});s("align"+x);r("mceRepaint")},"InsertUnorderedList,InsertOrderedList":function(y){var v,x;f(y);v=m.getParent(p.getNode(),"ol,ul");if(v){x=v.parentNode;if(/^(H[1-6]|P|ADDRESS|PRE)$/.test(x.nodeName)){i();m.split(x,v);g()}}},"Bold,Italic,Underline,Strikethrough,Superscript,Subscript":function(v){s(v)},"ForeColor,HiliteColor,FontName":function(y,x,v){s(y,v)},FontSize:function(z,y,x){var v,A;if(x>=1&&x<=7){A=d.explode(k.font_size_style_values);v=d.explode(k.font_size_classes);if(v){x=v[x-1]||x}else{x=A[x-1]||x}}s(z,x)},RemoveFormat:function(v){q.remove(v)},mceBlockQuote:function(v){s("blockquote")},FormatBlock:function(y,x,v){return s(v||"p")},mceCleanup:function(){var v=p.getBookmark();n.setContent(n.getContent({cleanup:a}),{cleanup:a});p.moveToBookmark(v)},mceRemoveNode:function(z,y,x){var v=x||p.getNode();if(v!=n.getBody()){i();n.dom.remove(v,a);g()}},mceSelectNodeDepth:function(z,y,x){var v=0;m.getParent(p.getNode(),function(A){if(A.nodeType==1&&v++==x){p.select(A);return c}},n.getBody())},mceSelectNode:function(y,x,v){p.select(v)},mceInsertContent:function(B,I,K){var y,J,E,z,F,G,D,C,L,x,A,M,v,H;y=n.parser;J=new d.html.Serializer({},n.schema);v='\uFEFF';G={content:K,format:"html"};p.onBeforeSetContent.dispatch(p,G);K=G.content;if(K.indexOf("{$caret}")==-1){K+="{$caret}"}K=K.replace(/\{\$caret\}/,v);if(!p.isCollapsed()){n.getDoc().execCommand("Delete",false,null)}E=p.getNode();G={context:E.nodeName.toLowerCase()};F=y.parse(K,G);A=F.lastChild;if(A.attr("id")=="mce_marker"){D=A;for(A=A.prev;A;A=A.walk(true)){if(A.type==3||!m.isBlock(A.name)){A.parent.insert(D,A,A.name==="br");break}}}if(!G.invalid){K=J.serialize(F);A=E.firstChild;M=E.lastChild;if(!A||(A===M&&A.nodeName==="BR")){m.setHTML(E,K)}else{p.setContent(K)}}else{p.setContent(v);E=p.getNode();z=n.getBody();if(E.nodeType==9){E=A=z}else{A=E}while(A!==z){E=A;A=A.parentNode}K=E==z?z.innerHTML:m.getOuterHTML(E);K=J.serialize(y.parse(K.replace(//i,function(){return J.serialize(F)})));if(E==z){m.setHTML(z,K)}else{m.setOuterHTML(E,K)}}D=m.get("mce_marker");C=m.getRect(D);L=m.getViewPort(n.getWin());if((C.y+C.h>L.y+L.h||C.yL.x+L.w||C.x")},mceToggleVisualAid:function(){n.hasVisual=!n.hasVisual;n.addVisual()},mceReplaceContent:function(y,x,v){n.execCommand("mceInsertContent",false,v.replace(/\{\$selection\}/g,p.getContent({format:"text"})))},mceInsertLink:function(z,y,x){var v;if(typeof(x)=="string"){x={href:x}}v=m.getParent(p.getNode(),"a");x.href=x.href.replace(" ","%20");if(!v||!x.href){q.remove("link")}if(x.href){q.apply("link",x,v)}},selectAll:function(){var x=m.getRoot(),v=m.createRng();if(p.getRng().setStart){v.setStart(x,0);v.setEnd(x,x.childNodes.length);p.setRng(v)}else{f("SelectAll")}}});u({"JustifyLeft,JustifyCenter,JustifyRight,JustifyFull":function(z){var x="align"+z.substring(7);var v=p.isCollapsed()?[m.getParent(p.getNode(),m.isBlock)]:p.getSelectedBlocks();var y=d.map(v,function(A){return !!q.matchNode(A,x)});return d.inArray(y,a)!==-1},"Bold,Italic,Underline,Strikethrough,Superscript,Subscript":function(v){return t(v)},mceBlockQuote:function(){return t("blockquote")},Outdent:function(){var v;if(k.inline_styles){if((v=m.getParent(p.getStart(),m.isBlock))&&parseInt(v.style.paddingLeft)>0){return a}if((v=m.getParent(p.getEnd(),m.isBlock))&&parseInt(v.style.paddingLeft)>0){return a}}return l("InsertUnorderedList")||l("InsertOrderedList")||(!k.inline_styles&&!!m.getParent(p.getNode(),"BLOCKQUOTE"))},"InsertUnorderedList,InsertOrderedList":function(x){var v=m.getParent(p.getNode(),"ul,ol");return v&&(x==="insertunorderedlist"&&v.tagName==="UL"||x==="insertorderedlist"&&v.tagName==="OL")}},"state");u({"FontSize,FontName":function(y){var x=0,v;if(v=m.getParent(p.getNode(),"span")){if(y=="fontsize"){x=v.style.fontSize}else{x=v.style.fontFamily.replace(/, /g,",").replace(/[\'\"]/g,"").toLowerCase()}}return x}},"value");u({Undo:function(){n.undoManager.undo()},Redo:function(){n.undoManager.redo()}})}})(tinymce);(function(b){var a=b.util.Dispatcher;b.UndoManager=function(h){var l,i=0,e=[],g,k,j,f;function c(){return b.trim(h.getContent({format:"raw",no_events:1}).replace(/]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>/g,""))}function d(){l.typing=false;l.add()}onBeforeAdd=new a(l);k=new a(l);j=new a(l);f=new a(l);k.add(function(m,n){if(m.hasUndo()){return h.onChange.dispatch(h,n,m)}});j.add(function(m,n){return h.onUndo.dispatch(h,n,m)});f.add(function(m,n){return h.onRedo.dispatch(h,n,m)});h.onInit.add(function(){l.add()});h.onBeforeExecCommand.add(function(m,p,o,q,n){if(p!="Undo"&&p!="Redo"&&p!="mceRepaint"&&(!n||!n.skip_undo)){l.beforeChange()}});h.onExecCommand.add(function(m,p,o,q,n){if(p!="Undo"&&p!="Redo"&&p!="mceRepaint"&&(!n||!n.skip_undo)){l.add()}});h.onSaveContent.add(d);h.dom.bind(h.dom.getRoot(),"dragend",d);h.dom.bind(h.getBody(),"focusout",function(m){if(!h.removed&&l.typing){d()}});h.onKeyUp.add(function(m,o){var n=o.keyCode;if((n>=33&&n<=36)||(n>=37&&n<=40)||n==45||n==13||o.ctrlKey){d()}});h.onKeyDown.add(function(m,o){var n=o.keyCode;if((n>=33&&n<=36)||(n>=37&&n<=40)||n==45){if(l.typing){d()}return}if((n<16||n>20)&&n!=224&&n!=91&&!l.typing){l.beforeChange();l.typing=true;l.add()}});h.onMouseDown.add(function(m,n){if(l.typing){d()}});h.addShortcut("ctrl+z","undo_desc","Undo");h.addShortcut("ctrl+y","redo_desc","Redo");l={data:e,typing:false,onBeforeAdd:onBeforeAdd,onAdd:k,onUndo:j,onRedo:f,beforeChange:function(){g=h.selection.getBookmark(2,true)},add:function(p){var m,n=h.settings,o;p=p||{};p.content=c();l.onBeforeAdd.dispatch(l,p);o=e[i];if(o&&o.content==p.content){return null}if(e[i]){e[i].beforeBookmark=g}if(n.custom_undo_redo_levels){if(e.length>n.custom_undo_redo_levels){for(m=0;m0){n=e[--i];h.setContent(n.content,{format:"raw"});h.selection.moveToBookmark(n.beforeBookmark);l.onUndo.dispatch(l,n)}return n},redo:function(){var m;if(i0||this.typing},hasRedo:function(){return i0){g.moveEnd("character",q)}g.select()}catch(n){}}}c.nodeChanged()}}if(b.forced_root_block){c.onKeyUp.add(f);c.onNodeChange.add(f)}};(function(c){var b=c.DOM,a=c.dom.Event,d=c.each,e=c.extend;c.create("tinymce.ControlManager",{ControlManager:function(f,j){var h=this,g;j=j||{};h.editor=f;h.controls={};h.onAdd=new c.util.Dispatcher(h);h.onPostRender=new c.util.Dispatcher(h);h.prefix=j.prefix||f.id+"_";h._cls={};h.onPostRender.add(function(){d(h.controls,function(i){i.postRender()})})},get:function(f){return this.controls[this.prefix+f]||this.controls[f]},setActive:function(h,f){var g=null;if(g=this.get(h)){g.setActive(f)}return g},setDisabled:function(h,f){var g=null;if(g=this.get(h)){g.setDisabled(f)}return g},add:function(g){var f=this;if(g){f.controls[g.id]=g;f.onAdd.dispatch(g,f)}return g},createControl:function(j){var o,k,g,h=this,m=h.editor,n,f;if(!h.controlFactories){h.controlFactories=[];d(m.plugins,function(i){if(i.createControl){h.controlFactories.push(i)}})}n=h.controlFactories;for(k=0,g=n.length;k1||ag==ay||ag.tagName=="BR"){return ag}}}var aq=aa.selection.getRng();var av=aq.startContainer;var ap=aq.endContainer;if(av!=ap&&aq.endOffset===0){var au=ar(av,ap);var at=au.nodeType==3?au.length:au.childNodes.length;aq.setEnd(au,at)}return aq}function ad(at,ay,aw,av,aq){var ap=[],ar=-1,ax,aA=-1,au=-1,az;T(at.childNodes,function(aC,aB){if(aC.nodeName==="UL"||aC.nodeName==="OL"){ar=aB;ax=aC;return false}});T(at.childNodes,function(aC,aB){if(aC.nodeName==="SPAN"&&c.getAttrib(aC,"data-mce-type")=="bookmark"){if(aC.id==ay.id+"_start"){aA=aB}else{if(aC.id==ay.id+"_end"){au=aB}}}});if(ar<=0||(aAar)){T(a.grep(at.childNodes),aq);return 0}else{az=c.clone(aw,X);T(a.grep(at.childNodes),function(aC,aB){if((aAar&&aB>ar)){ap.push(aC);aC.parentNode.removeChild(aC)}});if(aAar){at.insertBefore(az,ax.nextSibling)}}av.push(az);T(ap,function(aB){az.appendChild(aB)});return az}}function an(aq,at,aw){var ap=[],av,ar,au=true;av=am.inline||am.block;ar=c.create(av);ab(ar);N.walk(aq,function(ax){var ay;function az(aA){var aF,aD,aB,aC,aE;aE=au;aF=aA.nodeName.toLowerCase();aD=aA.parentNode.nodeName.toLowerCase();if(aA.nodeType===1&&x(aA)){aE=au;au=x(aA)==="true";aC=true}if(g(aF,"br")){ay=0;if(am.block){c.remove(aA)}return}if(am.wrapper&&y(aA,ae,al)){ay=0;return}if(au&&!aC&&am.block&&!am.wrapper&&I(aF)){aA=c.rename(aA,av);ab(aA);ap.push(aA);ay=0;return}if(am.selector){T(ah,function(aG){if("collapsed" in aG&&aG.collapsed!==ai){return}if(c.is(aA,aG.selector)&&!b(aA)){ab(aA,aG);aB=true}});if(!am.inline||aB){ay=0;return}}if(au&&!aC&&d(av,aF)&&d(aD,av)&&!(!aw&&aA.nodeType===3&&aA.nodeValue.length===1&&aA.nodeValue.charCodeAt(0)===65279)&&!b(aA)&&(!am.inline||!H(aA))){if(!ay){ay=c.clone(ar,X);aA.parentNode.insertBefore(ay,aA);ap.push(ay)}ay.appendChild(aA)}else{if(aF=="li"&&at){ay=ad(aA,at,ar,ap,az)}else{ay=0;T(a.grep(aA.childNodes),az);if(aC){au=aE}ay=0}}}T(ax,az)});if(am.wrap_links===false){T(ap,function(ax){function ay(aC){var aB,aA,az;if(aC.nodeName==="A"){aA=c.clone(ar,X);ap.push(aA);az=a.grep(aC.childNodes);for(aB=0;aB1||!H(az))&&ax===0){c.remove(az,1);return}if(am.inline||am.wrapper){if(!am.exact&&ax===1){az=ay(az)}T(ah,function(aB){T(c.select(aB.inline,az),function(aD){var aC;if(aB.wrap_links===false){aC=aD.parentNode;do{if(aC.nodeName==="A"){return}}while(aC=aC.parentNode)}Z(aB,al,aD,aB.exact?aD:null)})});if(y(az.parentNode,ae,al)){c.remove(az,1);az=0;return C}if(am.merge_with_parents){c.getParent(az.parentNode,function(aB){if(y(aB,ae,al)){c.remove(az,1);az=0;return C}})}if(az&&am.merge_siblings!==false){az=u(E(az),az);az=u(az,E(az,C))}}})}if(am){if(ag){if(ag.nodeType){ac=c.createRng();ac.setStartBefore(ag);ac.setEndAfter(ag);an(p(ac,ah),null,true)}else{an(ag,null,true)}}else{if(!ai||!am.inline||c.select("td.mceSelected,th.mceSelected").length){var ao=aa.selection.getNode();if(!m&&ah[0].defaultBlock&&!c.getParent(ao,c.isBlock)){Y(ah[0].defaultBlock)}aa.selection.setRng(af());ak=r.getBookmark();an(p(r.getRng(C),ah),ak);if(am.styles&&(am.styles.color||am.styles.textDecoration)){a.walk(ao,L,"childNodes");L(ao)}r.moveToBookmark(ak);R(r.getRng(C));aa.nodeChanged()}else{U("apply",ae,al)}}}}function B(ad,am,af){var ag=V(ad),ao=ag[0],ak,aj,ac,al=true;function ae(av){var au,at,ar,aq,ax,aw;if(av.nodeType===3){return}if(av.nodeType===1&&x(av)){ax=al;al=x(av)==="true";aw=true}au=a.grep(av.childNodes);if(al&&!aw){for(at=0,ar=ag.length;at=0;ac--){ab=ah[ac].selector;if(!ab){return C}for(ag=ad.length-1;ag>=0;ag--){if(c.is(ad[ag],ab)){return C}}}}return X}function J(ab,ae,ac){var ad;if(!P){P={};ad={};aa.onNodeChange.addToTop(function(ag,af,ai){var ah=n(ai),aj={};T(P,function(ak,al){T(ah,function(am){if(y(am,al,{},ak.similar)){if(!ad[al]){T(ak,function(an){an(true,{node:am,format:al,parents:ah})});ad[al]=ak}aj[al]=ak;return false}})});T(ad,function(ak,al){if(!aj[al]){delete ad[al];T(ak,function(am){am(false,{node:ai,format:al,parents:ah})})}})})}T(ab.split(","),function(af){if(!P[af]){P[af]=[];P[af].similar=ac}P[af].push(ae)});return this}a.extend(this,{get:V,register:l,apply:Y,remove:B,toggle:F,match:k,matchAll:v,matchNode:y,canApply:z,formatChanged:J});j();W();function h(ab,ac){if(g(ab,ac.inline)){return C}if(g(ab,ac.block)){return C}if(ac.selector){return c.is(ab,ac.selector)}}function g(ac,ab){ac=ac||"";ab=ab||"";ac=""+(ac.nodeName||ac);ab=""+(ab.nodeName||ab);return ac.toLowerCase()==ab.toLowerCase()}function O(ac,ab){var ad=c.getStyle(ac,ab);if(ab=="color"||ab=="backgroundColor"){ad=c.toHex(ad)}if(ab=="fontWeight"&&ad==700){ad="bold"}return""+ad}function q(ab,ac){if(typeof(ab)!="string"){ab=ab(ac)}else{if(ac){ab=ab.replace(/%(\w+)/g,function(ae,ad){return ac[ad]||ae})}}return ab}function f(ab){return ab&&ab.nodeType===3&&/^([\t \r\n]+|)$/.test(ab.nodeValue)}function S(ad,ac,ab){var ae=c.create(ac,ab);ad.parentNode.insertBefore(ae,ad);ae.appendChild(ad);return ae}function p(ab,am,ae){var ap,an,ah,al,ad=ab.startContainer,ai=ab.startOffset,ar=ab.endContainer,ak=ab.endOffset;function ao(aA){var au,ax,az,aw,av,at;au=ax=aA?ad:ar;av=aA?"previousSibling":"nextSibling";at=c.getRoot();function ay(aB){return aB.nodeName=="BR"&&aB.getAttribute("data-mce-bogus")&&!aB.nextSibling}if(au.nodeType==3&&!f(au)){if(aA?ai>0:akan?an:ai];if(ad.nodeType==3){ai=0}}if(ar.nodeType==1&&ar.hasChildNodes()){an=ar.childNodes.length-1;ar=ar.childNodes[ak>an?an:ak-1];if(ar.nodeType==3){ak=ar.nodeValue.length}}function aq(au){var at=au;while(at){if(at.nodeType===1&&x(at)){return x(at)==="false"?at:au}at=at.parentNode}return au}function aj(au,ay,aA){var ax,av,az,at;function aw(aC,aE){var aF,aB,aD=aC.nodeValue;if(typeof(aE)=="undefined"){aE=aA?aD.length:0}if(aA){aF=aD.lastIndexOf(" ",aE);aB=aD.lastIndexOf("\u00a0",aE);aF=aF>aB?aF:aB;if(aF!==-1&&!ae){aF++}}else{aF=aD.indexOf(" ",aE);aB=aD.indexOf("\u00a0",aE);aF=aF!==-1&&(aB===-1||aF0&&ah.node.nodeType===3&&ah.node.nodeValue.charAt(ah.offset-1)===" "){if(ah.offset>1){ar=ah.node;ar.splitText(ah.offset-1)}}}}if(am[0].inline||am[0].block_expand){if(!am[0].inline||(ad.nodeType!=3||ai===0)){ad=ao(true)}if(!am[0].inline||(ar.nodeType!=3||ak===ar.nodeValue.length)){ar=ao()}}if(am[0].selector&&am[0].expand!==X&&!am[0].inline){ad=af(ad,"previousSibling");ar=af(ar,"nextSibling")}if(am[0].block||am[0].selector){ad=ac(ad,"previousSibling");ar=ac(ar,"nextSibling");if(am[0].block){if(!H(ad)){ad=ao(true)}if(!H(ar)){ar=ao()}}}if(ad.nodeType==1){ai=s(ad);ad=ad.parentNode}if(ar.nodeType==1){ak=s(ar)+1;ar=ar.parentNode}return{startContainer:ad,startOffset:ai,endContainer:ar,endOffset:ak}}function Z(ah,ag,ae,ab){var ad,ac,af;if(!h(ae,ah)){return X}if(ah.remove!="all"){T(ah.styles,function(aj,ai){aj=q(aj,ag);if(typeof(ai)==="number"){ai=aj;ab=0}if(!ab||g(O(ab,ai),aj)){c.setStyle(ae,ai,"")}af=1});if(af&&c.getAttrib(ae,"style")==""){ae.removeAttribute("style");ae.removeAttribute("data-mce-style")}T(ah.attributes,function(ak,ai){var aj;ak=q(ak,ag);if(typeof(ai)==="number"){ai=ak;ab=0}if(!ab||g(c.getAttrib(ab,ai),ak)){if(ai=="class"){ak=c.getAttrib(ae,ai);if(ak){aj="";T(ak.split(/\s+/),function(al){if(/mce\w+/.test(al)){aj+=(aj?" ":"")+al}});if(aj){c.setAttrib(ae,ai,aj);return}}}if(ai=="class"){ae.removeAttribute("className")}if(e.test(ai)){ae.removeAttribute("data-mce-"+ai)}ae.removeAttribute(ai)}});T(ah.classes,function(ai){ai=q(ai,ag);if(!ab||c.hasClass(ab,ai)){c.removeClass(ae,ai)}});ac=c.getAttribs(ae);for(ad=0;adad?ad:af]}if(ab.nodeType===3&&ag&&af>=ab.nodeValue.length){ab=new t(ab,aa.getBody()).next()||ab}if(ab.nodeType===3&&!ag&&af===0){ab=new t(ab,aa.getBody()).prev()||ab}return ab}function U(ak,ab,ai){var al="_mce_caret",ac=aa.settings.caret_debug;function ad(ap){var ao=c.create("span",{id:al,"data-mce-bogus":true,style:ac?"color:red":""});if(ap){ao.appendChild(aa.getDoc().createTextNode(G))}return ao}function aj(ap,ao){while(ap){if((ap.nodeType===3&&ap.nodeValue!==G)||ap.childNodes.length>1){return false}if(ao&&ap.nodeType===1){ao.push(ap)}ap=ap.firstChild}return true}function ag(ao){while(ao){if(ao.id===al){return ao}ao=ao.parentNode}}function af(ao){var ap;if(ao){ap=new t(ao,ao);for(ao=ap.current();ao;ao=ap.next()){if(ao.nodeType===3){return ao}}}}function ae(aq,ap){var ar,ao;if(!aq){aq=ag(r.getStart());if(!aq){while(aq=c.get(al)){ae(aq,false)}}}else{ao=r.getRng(true);if(aj(aq)){if(ap!==false){ao.setStartBefore(aq);ao.setEndBefore(aq)}c.remove(aq)}else{ar=af(aq);if(ar.nodeValue.charAt(0)===G){ar=ar.deleteData(0,1)}c.remove(aq,1)}r.setRng(ao)}}function ah(){var aq,ao,av,au,ar,ap,at;aq=r.getRng(true);au=aq.startOffset;ap=aq.startContainer;at=ap.nodeValue;ao=ag(r.getStart());if(ao){av=af(ao)}if(at&&au>0&&au=0;au--){aq.appendChild(c.clone(ay[au],false));aq=aq.firstChild}aq.appendChild(c.doc.createTextNode(G));aq=aq.firstChild;var ar=c.getParent(az,I);if(ar&&c.isEmpty(ar)){az.parentNode.replaceChild(ax,az)}else{c.insertAfter(ax,az)}r.setCursorLocation(aq,1);if(c.isEmpty(az)){c.remove(az)}}}function an(){var ap,ao,aq;ao=ag(r.getStart());if(ao&&!c.isEmpty(ao)){a.walk(ao,function(ar){if(ar.nodeType==1&&ar.id!==al&&!c.isEmpty(ar)){c.setAttrib(ar,"data-mce-bogus",null)}},"childNodes")}}if(!self._hasCaretEvents){aa.onBeforeGetContent.addToTop(function(){var ao=[],ap;if(aj(ag(r.getStart()),ao)){ap=ao.length;while(ap--){c.setAttrib(ao[ap],"data-mce-bogus","1")}}});a.each("onMouseUp onKeyUp".split(" "),function(ao){aa[ao].addToTop(function(){ae();an()})});aa.onKeyDown.addToTop(function(ao,aq){var ap=aq.keyCode;if(ap==8||ap==37||ap==39){ae(ag(r.getStart()))}an()});r.onSetContent.add(an);self._hasCaretEvents=true}if(ak=="apply"){ah()}else{am()}}function R(ac){var ab=ac.startContainer,ai=ac.startOffset,ae,ah,ag,ad,af;if(ab.nodeType==3&&ai>=ab.nodeValue.length){ai=s(ab);ab=ab.parentNode;ae=true}if(ab.nodeType==1){ad=ab.childNodes;ab=ad[Math.min(ai,ad.length-1)];ah=new t(ab,c.getParent(ab,c.isBlock));if(ai>ad.length-1||ae){ah.next()}for(ag=ah.current();ag;ag=ah.next()){if(ag.nodeType==3&&!f(ag)){af=c.create("a",null,G);ag.parentNode.insertBefore(af,ag);ac.setStart(ag,0);r.setRng(ac);c.remove(af);return}}}}}})(tinymce);tinymce.onAddEditor.add(function(e,a){var d,h,g,c=a.settings;function b(j,i){e.each(i,function(l,k){if(l){g.setStyle(j,k,l)}});g.rename(j,"span")}function f(i,j){g=i.dom;if(c.convert_fonts_to_spans){e.each(g.select("font,u,strike",j.node),function(k){d[k.nodeName.toLowerCase()](a.dom,k)})}}if(c.inline_styles){h=e.explode(c.font_size_legacy_values);d={font:function(j,i){b(i,{backgroundColor:i.style.backgroundColor,color:i.color,fontFamily:i.face,fontSize:h[parseInt(i.size,10)-1]})},u:function(j,i){b(i,{textDecoration:"underline"})},strike:function(j,i){b(i,{textDecoration:"line-through"})}};a.onPreProcess.add(f);a.onSetContent.add(f);a.onInit.add(function(){a.selection.onSetContent.add(f)})}});(function(b){var a=b.dom.TreeWalker;b.EnterKey=function(f){var i=f.dom,e=f.selection,d=f.settings,h=f.undoManager,c=f.schema.getNonEmptyElements();function g(B){var v=e.getRng(true),G,j,A,u,p,M,C,o,k,n,t,J,x,D;function E(N){return N&&i.isBlock(N)&&!/^(TD|TH|CAPTION|FORM)$/.test(N.nodeName)&&!/^(fixed|absolute)/i.test(N.style.position)&&i.getContentEditable(N)!=="true"}function F(O){var N;if(b.isIE&&!b.isIE11&&i.isBlock(O)){N=e.getRng();O.appendChild(i.create("span",null,"\u00a0"));e.select(O);O.lastChild.outerHTML="";e.setRng(N)}}function z(P){var O=P,Q=[],N;while(O=O.firstChild){if(i.isBlock(O)){return}if(O.nodeType==1&&!c[O.nodeName.toLowerCase()]){Q.push(O)}}N=Q.length;while(N--){O=Q[N];if(!O.hasChildNodes()||(O.firstChild==O.lastChild&&O.firstChild.nodeValue==="")){i.remove(O)}else{if(O.nodeName=="A"&&(O.innerText||O.textContent)===" "){i.remove(O)}}}}function m(O){var T,R,N,U,S,Q=O,P;N=i.createRng();if(O.hasChildNodes()){T=new a(O,O);while(R=T.current()){if(R.nodeType==3){N.setStart(R,0);N.setEnd(R,0);break}if(c[R.nodeName.toLowerCase()]){N.setStartBefore(R);N.setEndBefore(R);break}Q=R;R=T.next()}if(!R){N.setStart(Q,0);N.setEnd(Q,0)}}else{if(O.nodeName=="BR"){if(O.nextSibling&&i.isBlock(O.nextSibling)){if(!M||M<9){P=i.create("br");O.parentNode.insertBefore(P,O)}N.setStartBefore(O);N.setEndBefore(O)}else{N.setStartAfter(O);N.setEndAfter(O)}}else{N.setStart(O,0);N.setEnd(O,0)}}e.setRng(N);i.remove(P);S=i.getViewPort(f.getWin());U=i.getPos(O).y;if(US.y+S.h){f.getWin().scrollTo(0,U'}return R}function q(Q){var P,O,N;if(A.nodeType==3&&(Q?u>0:u0){return true}}}function L(){var P,O,N;if(A&&A.nodeType==3&&u>=A.nodeValue.length){if((!b.isIE||b.isIE11)&&!y()){P=i.create("br");v.insertNode(P);v.setStartAfter(P);v.setEndAfter(P);O=true}}P=i.create("br");v.insertNode(P);if((b.isIE&&!b.isIE11)&&t=="PRE"&&(!M||M<8)){P.parentNode.insertBefore(i.doc.createTextNode("\r"),P)}N=i.create("span",{}," ");P.parentNode.insertBefore(N,P);e.scrollIntoView(N);i.remove(N);if(!O){v.setStartAfter(P);v.setEndAfter(P)}else{v.setStartBefore(P);v.setEndBefore(P)}e.setRng(v);h.add()}function s(N){do{if(N.nodeType===3){N.nodeValue=N.nodeValue.replace(/^[\r\n]+/,"")}N=N.firstChild}while(N)}function K(P){var N=i.getRoot(),O,Q;O=P;while(O!==N&&i.getContentEditable(O)!=="false"){if(i.getContentEditable(O)==="true"){Q=O}O=O.parentNode}return O!==N?Q:N}function I(O){var N;if(!b.isIE||b.isIE11){O.normalize();N=O.lastChild;if(!N||(/^(left|right)$/gi.test(i.getStyle(N,"float",true)))){i.add(O,"br")}}}if(!v.collapsed){f.execCommand("Delete");return}if(B.isDefaultPrevented()){return}A=v.startContainer;u=v.startOffset;x=(d.force_p_newlines?"p":"")||d.forced_root_block;x=x?x.toUpperCase():"";M=i.doc.documentMode;C=B.shiftKey;if(A.nodeType==1&&A.hasChildNodes()){D=u>A.childNodes.length-1;A=A.childNodes[Math.min(u,A.childNodes.length-1)]||A;if(D&&A.nodeType==3){u=A.nodeValue.length}else{u=0}}j=K(A);if(!j){return}h.beforeChange();if(!i.isBlock(j)&&j!=i.getRoot()){if(!x||C){L()}return}if((x&&!C)||(!x&&C)){A=l(A,u)}p=i.getParent(A,i.isBlock);n=p?i.getParent(p.parentNode,i.isBlock):null;t=p?p.nodeName.toUpperCase():"";J=n?n.nodeName.toUpperCase():"";if(J=="LI"&&!B.ctrlKey){p=n;t=J}if(t=="LI"){if(!x&&C){L();return}if(i.isEmpty(p)){if(/^(UL|OL|LI)$/.test(n.parentNode.nodeName)){return false}H();return}}if(t=="PRE"&&d.br_in_pre!==false){if(!C){L();return}}else{if((!x&&!C&&t!="LI")||(x&&C)){L();return}}x=x||"P";if(q()){if(/^(H[1-6]|PRE)$/.test(t)&&J!="HGROUP"){o=r(x)}else{o=r()}if(d.end_container_on_empty_block&&E(n)&&i.isEmpty(p)){o=i.split(n,p)}else{i.insertAfter(o,p)}m(o)}else{if(q(true)){o=p.parentNode.insertBefore(r(),p);F(o)}else{G=v.cloneRange();G.setEndAfter(p);k=G.extractContents();s(k);o=k.firstChild;i.insertAfter(k,p);z(o);I(p);m(o)}}i.setAttrib(o,"id","");h.add()}f.onKeyDown.add(function(k,j){if(j.keyCode==13){if(g(j)!==false){j.preventDefault()}}})}})(tinymce); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tinymce3/tiny_mce_popup.js b/src/Umbraco.Web.UI/umbraco_client/tinymce3/tiny_mce_popup.js index bb8e58c88a..0808f8f9b2 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tinymce3/tiny_mce_popup.js +++ b/src/Umbraco.Web.UI/umbraco_client/tinymce3/tiny_mce_popup.js @@ -2,4 +2,4 @@ // Uncomment and change this document.domain value if you are loading the script cross subdomains // document.domain = 'moxiecode.com'; -var tinymce=null,tinyMCEPopup,tinyMCE;tinyMCEPopup={init:function(){var b=this,a,c;a=b.getWin();tinymce=a.tinymce;tinyMCE=a.tinyMCE;b.editor=tinymce.EditorManager.activeEditor;b.params=b.editor.windowManager.params;b.features=b.editor.windowManager.features;b.dom=b.editor.windowManager.createInstance("tinymce.dom.DOMUtils",document,{ownEvents:true,proxy:tinyMCEPopup._eventProxy});b.dom.bind(window,"ready",b._onDOMLoaded,b);if(b.features.popup_css!==false){b.dom.loadCSS(b.features.popup_css||b.editor.settings.popup_css)}b.listeners=[];b.onInit={add:function(e,d){b.listeners.push({func:e,scope:d})}};b.isWindow=!b.getWindowArg("mce_inline");b.id=b.getWindowArg("mce_window_id");b.editor.windowManager.onOpen.dispatch(b.editor.windowManager,window)},getWin:function(){return(!window.frameElement&&window.dialogArguments)||opener||parent||top},getWindowArg:function(c,b){var a=this.params[c];return tinymce.is(a)?a:b},getParam:function(b,a){return this.editor.getParam(b,a)},getLang:function(b,a){return this.editor.getLang(b,a)},execCommand:function(d,c,e,b){b=b||{};b.skip_focus=1;this.restoreSelection();return this.editor.execCommand(d,c,e,b)},resizeToInnerSize:function(){var a=this;setTimeout(function(){var b=a.dom.getViewPort(window);a.editor.windowManager.resizeBy(a.getWindowArg("mce_width")-b.w,a.getWindowArg("mce_height")-b.h,a.id||window)},10)},executeOnLoad:function(s){this.onInit.add(function(){eval(s)})},storeSelection:function(){this.editor.windowManager.bookmark=tinyMCEPopup.editor.selection.getBookmark(1)},restoreSelection:function(){var a=tinyMCEPopup;if(!a.isWindow&&tinymce.isIE){a.editor.selection.moveToBookmark(a.editor.windowManager.bookmark)}},requireLangPack:function(){var b=this,a=b.getWindowArg("plugin_url")||b.getWindowArg("theme_url");if(a&&b.editor.settings.language&&b.features.translate_i18n!==false&&b.editor.settings.language_load!==false){a+="/langs/"+b.editor.settings.language+"_dlg.js";if(!tinymce.ScriptLoader.isDone(a)){document.write(' diff --git a/src/Umbraco.Web/umbraco.presentation/install/utills/LegacyClasses.cs b/src/Umbraco.Web/umbraco.presentation/install/utills/LegacyClasses.cs index 52fd5d3b9a..c068ac2c01 100644 --- a/src/Umbraco.Web/umbraco.presentation/install/utills/LegacyClasses.cs +++ b/src/Umbraco.Web/umbraco.presentation/install/utills/LegacyClasses.cs @@ -66,7 +66,11 @@ namespace umbraco.presentation.install.utills { LogHelper.Info

("Running 'installOrUpgrade' service"); - var result = ApplicationContext.Current.DatabaseContext.CreateDatabaseSchemaAndDataOrUpgrade(); + var result = ApplicationContext.Current.DatabaseContext.CreateDatabaseSchemaAndData(); + if (result.RequiresUpgrade) + { + result = ApplicationContext.Current.DatabaseContext.UpgradeSchemaAndData(); + } // Remove legacy umbracoDbDsn configuration setting if it exists and connectionstring also exists if (ConfigurationManager.ConnectionStrings[Umbraco.Core.Configuration.GlobalSettings.UmbracoConnectionName] != null) From 51da5343eae89c71ecda9504fa6255580ceb2151 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 13 Dec 2013 17:07:29 +1100 Subject: [PATCH 0064/1046] Fixes installation issue with rebuilding media cache Conflicts: src/Umbraco.Core/Umbraco.Core.csproj src/Umbraco.Web.UI/config/trees.config src/Umbraco.Web/Umbraco.Web.csproj --- .../RemoveCachedRecycleMediaXml.cs | 31 --------------- src/Umbraco.Core/Umbraco.Core.csproj | 1 - .../RebuildMediaXmlCacheAfterUpgrade.cs | 38 +++++++++++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 1 + 4 files changed, 39 insertions(+), 32 deletions(-) delete mode 100644 src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/RemoveCachedRecycleMediaXml.cs create mode 100644 src/Umbraco.Web/Strategies/Migrations/RebuildMediaXmlCacheAfterUpgrade.cs diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/RemoveCachedRecycleMediaXml.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/RemoveCachedRecycleMediaXml.cs deleted file mode 100644 index d0945db957..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/RemoveCachedRecycleMediaXml.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Umbraco.Core.Configuration; -using Umbraco.Core.Services; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixTwoZero -{ - ///

- /// Due to this bug: http://issues.umbraco.org/issue/U4-3820 we need to remove the cached media - /// xml found in the cmsContentXml table for any media that has been recycled. - /// - [Migration("6.2.0", 1, GlobalSettings.UmbracoMigrationName)] - public class RemoveCachedRecycleMediaXml : MigrationBase - { - public override void Up() - { - //now that the controlId column is renamed and now a string we need to convert - if (Context == null || Context.Database == null) return; - - Execute.Code(database => - { - var mediasvc = (MediaService)ApplicationContext.Current.Services.MediaService; - mediasvc.RebuildXmlStructures(); - - return string.Empty; - }); - } - - public override void Down() - { - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index d9ff8081c2..021ae04633 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -189,7 +189,6 @@ - diff --git a/src/Umbraco.Web/Strategies/Migrations/RebuildMediaXmlCacheAfterUpgrade.cs b/src/Umbraco.Web/Strategies/Migrations/RebuildMediaXmlCacheAfterUpgrade.cs new file mode 100644 index 0000000000..72cf6c24ea --- /dev/null +++ b/src/Umbraco.Web/Strategies/Migrations/RebuildMediaXmlCacheAfterUpgrade.cs @@ -0,0 +1,38 @@ +using System; +using Umbraco.Core; +using Umbraco.Core.Persistence.Migrations; +using Umbraco.Core.Services; +using umbraco.interfaces; + +namespace Umbraco.Web.Strategies.Migrations +{ + /// + /// This will execute after upgrading to rebuild the xml cache + /// + /// + /// This cannot execute as part of a db migration since we need access to the services/repos. + /// + /// This will execute for specific versions - + /// + /// * If current is less than or equal to 7.0.0 + /// + public class RebuildMediaXmlCacheAfterUpgrade : IApplicationStartupHandler + { + public RebuildMediaXmlCacheAfterUpgrade() + { + MigrationRunner.Migrated += MigrationRunner_Migrated; + } + + void MigrationRunner_Migrated(MigrationRunner sender, Core.Events.MigrationEventArgs e) + { + var target70 = new Version(7, 0, 0); + + if (e.ConfiguredVersion <= target70) + { + var mediasvc = (MediaService)ApplicationContext.Current.Services.MediaService; + mediasvc.RebuildXmlStructures(); + } + + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index c9a8e14740..42379fb990 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -389,6 +389,7 @@ + From eeaa9166ac87ba46b7dbec38c0ce0989b1d7b9e5 Mon Sep 17 00:00:00 2001 From: james-melville Date: Fri, 13 Dec 2013 10:25:32 +0000 Subject: [PATCH 0065/1046] Fixes: U4-3814 - 6.1.6 XSS vulnerability in backoffice --- src/umbraco.controls/TabView.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/umbraco.controls/TabView.cs b/src/umbraco.controls/TabView.cs index dfd9b7f9a7..55923cee15 100644 --- a/src/umbraco.controls/TabView.cs +++ b/src/umbraco.controls/TabView.cs @@ -98,7 +98,7 @@ namespace umbraco.uicontrols { string TabId = this.ClientID + "_tab0" + (i + 1); writer.WriteLine("
  • "); writer.WriteLine(" "); - writer.WriteLine(" " + TabPageCaption + ""); + writer.WriteLine(" " + HttpUtility.HtmlEncode(TabPageCaption) + ""); writer.WriteLine(" "); writer.WriteLine("
  • "); } @@ -107,9 +107,9 @@ namespace umbraco.uicontrols { writer.WriteLine("
    "); this.RenderChildren(writer); writer.WriteLine("\t
    "); - writer.WriteLine("\t"); + writer.WriteLine("\t"); writer.WriteLine(""); - writer.WriteLine(""); + writer.WriteLine(""); } } } \ No newline at end of file From 5aec75385947b7a1e85f3d705dfc3781b52781be Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 13 Dec 2013 12:06:56 +0100 Subject: [PATCH 0066/1046] U4-3710, -3732 - Fix default ShortStringHelper --- src/Umbraco.Core/CoreBootManager.cs | 13 +- src/Umbraco.Core/StringExtensions.cs | 8 +- src/Umbraco.Core/Strings/CleanStringType.cs | 27 +- .../Strings/DefaultShortStringHelper.cs | 700 ++++++++---------- .../Strings/Utf8ToAsciiConverter.cs | 7 +- .../DefaultShortStringHelperTests.cs | 583 ++++++++++----- 6 files changed, 734 insertions(+), 604 deletions(-) diff --git a/src/Umbraco.Core/CoreBootManager.cs b/src/Umbraco.Core/CoreBootManager.cs index 91ee5db40f..7b8b0b5b9d 100644 --- a/src/Umbraco.Core/CoreBootManager.cs +++ b/src/Umbraco.Core/CoreBootManager.cs @@ -266,17 +266,10 @@ namespace Umbraco.Core PropertyValueConvertersResolver.Current = new PropertyValueConvertersResolver( PluginManager.Current.ResolveTypes()); - // use the new DefaultShortStringHelper but sort-of remain compatible - // - use UmbracoSettings UrlReplaceCharacters - // - allow underscores in terms, allow leading digits + // use the new DefaultShortStringHelper ShortStringHelperResolver.Current = new ShortStringHelperResolver( - new DefaultShortStringHelper() - .WithConfig(CleanStringType.Url, DefaultShortStringHelper.ApplyUrlReplaceCharacters, - allowUnderscoreInTerm: true, allowLeadingDigits: true)); - - // that was the old one - //ShortStringHelperResolver.Current = new ShortStringHelperResolver( - // new LegacyShortStringHelper()); + //new LegacyShortStringHelper()); + new DefaultShortStringHelper().WithDefaultConfig()); UrlSegmentProviderResolver.Current = new UrlSegmentProviderResolver( typeof (DefaultUrlSegmentProvider)); diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index 4c3cfbeba5..8f4c7a57b1 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -801,9 +801,11 @@ namespace Umbraco.Core if (_helper != null) return _helper; - // there *has* to be a short string helper, even if the resolver has not - // been initialized - used the default one with default configuration. - _helper = new DefaultShortStringHelper().WithConfig(allowLeadingDigits: true); + // we don't want Umbraco to die because the resolver hasn't been initialized + // as the ShortStringHelper is too important, so as long as it's not there + // already, we use a default one. That should never happen, but... + Logging.LogHelper.Warn("ShortStringHelperResolver.HasCurrent == false, fallback to default."); + _helper = new DefaultShortStringHelper().WithDefaultConfig(); _helper.Freeze(); return _helper; } diff --git a/src/Umbraco.Core/Strings/CleanStringType.cs b/src/Umbraco.Core/Strings/CleanStringType.cs index 28a801aa54..f681c42d4a 100644 --- a/src/Umbraco.Core/Strings/CleanStringType.cs +++ b/src/Umbraco.Core/Strings/CleanStringType.cs @@ -14,6 +14,9 @@ namespace Umbraco.Core.Strings // note: you have 32 bits at your disposal // 0xffffffff + + // masks + /// /// Flag mask for casing. /// @@ -27,13 +30,19 @@ namespace Umbraco.Core.Strings /// /// Flag mask for role. /// - RoleMask = 0x030000, // 0xff0000 - 8 possible values + RoleMask = 0x070000, // 0xff0000 - 8 possible values + + + // no value /// /// No value. /// None = 0x00, + + // casing values + /// /// Pascal casing eg "PascalCase". /// @@ -66,9 +75,13 @@ namespace Umbraco.Core.Strings /// and is pascal otherwise. UmbracoCase = 0x20, + + // encoding values + /// /// Unicode encoding. /// + [Obsolete("Use .Utf8 instead.")] Unicode = 0x0100, /// @@ -81,14 +94,22 @@ namespace Umbraco.Core.Strings /// Ascii = 0x0400, + + // role values + /// /// Url role. /// - Url = 0x010000, + UrlSegment = 0x010000, /// /// Alias role. /// - Alias = 0x020000 + Alias = 0x020000, + + /// + /// FileName role. + /// + FileName = 0x040000 } } diff --git a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs index c3845e7318..bb85984d0d 100644 --- a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs +++ b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Globalization; @@ -13,14 +14,13 @@ namespace Umbraco.Core.Strings /// /// Not optimized to work on large bodies of text. /// Meant to replace LegacyShortStringHelper where/when backward compatibility is not an issue. - /// Full-unicode support is probably not so good. /// NOTE: pre-filters run _before_ the string is re-encoded. /// public class DefaultShortStringHelper : IShortStringHelper { #region Ctor and vars - static DefaultShortStringHelper() + public DefaultShortStringHelper() { InitializeLegacyUrlReplaceCharacters(); } @@ -41,7 +41,7 @@ namespace Umbraco.Core.Strings private CultureInfo _defaultCulture = CultureInfo.InvariantCulture; private bool _frozen; - private readonly Dictionary> _configs = new Dictionary>(); + private readonly Dictionary> _configs = new Dictionary>(); // see notes for CleanAsciiString //static DefaultShortStringHelper() @@ -51,11 +51,11 @@ namespace Umbraco.Core.Strings #endregion - #region Legacy UrlReplaceCharacters + #region Filters - static readonly Dictionary UrlReplaceCharacters = new Dictionary(); + private readonly Dictionary _urlReplaceCharacters = new Dictionary(); - static void InitializeLegacyUrlReplaceCharacters() + private void InitializeLegacyUrlReplaceCharacters() { var replaceChars = UmbracoSettings.UrlReplaceCharacters; if (replaceChars == null) return; @@ -67,7 +67,7 @@ namespace Umbraco.Core.Strings if (attributes == null) continue; var org = attributes.GetNamedItem("org"); if (org != null && org.Value != "") - UrlReplaceCharacters[org.Value] = XmlHelper.GetNodeValue(node); + _urlReplaceCharacters[org.Value] = XmlHelper.GetNodeValue(node); } } @@ -76,9 +76,21 @@ namespace Umbraco.Core.Strings /// /// The string to filter. /// The filtered string. - public static string ApplyUrlReplaceCharacters(string s) + public string ApplyUrlReplaceCharacters(string s) { - return s.ReplaceMany(UrlReplaceCharacters); + return s.ReplaceMany(_urlReplaceCharacters); + } + + // ok to be static here because it's not configureable in any way + private static readonly char[] InvalidFileNameChars = + Path.GetInvalidFileNameChars() + .Union("!*'();:@&=+$,/?%#[]-~{}\"<>\\^`| ".ToCharArray()) + .Distinct() + .ToArray(); + + public static bool IsValidFileNameChar(char c) + { + return InvalidFileNameChars.Contains(c) == false; } #endregion @@ -91,6 +103,11 @@ namespace Umbraco.Core.Strings throw new InvalidOperationException("Cannot configure the helper once it is frozen."); } + /// + /// Sets a default culture. + /// + /// The default culture. + /// The short string helper. public DefaultShortStringHelper WithDefaultCulture(CultureInfo culture) { EnsureNotFrozen(); @@ -98,75 +115,131 @@ namespace Umbraco.Core.Strings return this; } - public DefaultShortStringHelper WithConfig( - Func preFilter = null, - bool breakTermsOnUpper = true, bool allowLeadingDigits = false, bool allowUnderscoreInTerm = false) + public DefaultShortStringHelper WithConfig(Config config) { - return WithConfig(_defaultCulture, CleanStringType.RoleMask, - preFilter, breakTermsOnUpper, allowLeadingDigits, allowUnderscoreInTerm); + return WithConfig(_defaultCulture, CleanStringType.RoleMask, config); } - public DefaultShortStringHelper WithConfig(CleanStringType stringRole, - Func preFilter = null, - bool breakTermsOnUpper = true, bool allowLeadingDigits = false, bool allowUnderscoreInTerm = false) + public DefaultShortStringHelper WithConfig(CleanStringType stringRole, Config config) { - return WithConfig(_defaultCulture, stringRole, - preFilter, breakTermsOnUpper, allowLeadingDigits, allowUnderscoreInTerm); + return WithConfig(_defaultCulture, stringRole, config); } - public DefaultShortStringHelper WithConfig(CultureInfo culture, CleanStringType stringRole, - Func preFilter = null, - bool breakTermsOnUpper = true, bool allowLeadingDigits = false, bool allowUnderscoreInTerm = false) + public DefaultShortStringHelper WithConfig(CultureInfo culture, CleanStringType stringRole, Config config) { + if (config == null) + throw new ArgumentNullException("config"); + EnsureNotFrozen(); if (_configs.ContainsKey(culture) == false) - _configs[culture] = new Dictionary(); - _configs[culture][stringRole] = new HelperConfig(preFilter, breakTermsOnUpper, allowLeadingDigits, allowUnderscoreInTerm); + _configs[culture] = new Dictionary(); + _configs[culture][stringRole] = config.Clone(); // clone so it can't be changed return this; } - internal sealed class HelperConfig + /// + /// Sets the default configuration. + /// + /// The short string helper. + public DefaultShortStringHelper WithDefaultConfig() { - private HelperConfig() + return WithConfig(CleanStringType.UrlSegment, new Config { + PreFilter = ApplyUrlReplaceCharacters, + IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore + StringType = CleanStringType.Utf8 | CleanStringType.LowerCase, + BreakTermsOnUpper = false, + Separator = '-' + }).WithConfig(CleanStringType.FileName, new Config + { + PreFilter = ApplyUrlReplaceCharacters, + IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore + StringType = CleanStringType.Utf8 | CleanStringType.LowerCase, + BreakTermsOnUpper = false, + Separator = '-' + }).WithConfig(CleanStringType.Alias, new Config + { + PreFilter = ApplyUrlReplaceCharacters, + IsTerm = (c, leading) => leading + ? char.IsLetter(c) // only letters + : (char.IsLetterOrDigit(c) || c == '_'), // letter, digit or underscore + StringType = CleanStringType.Ascii | CleanStringType.UmbracoCase, + BreakTermsOnUpper = false + }); + } + + public sealed class Config + { + public Config() + { + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged; PreFilter = null; - BreakTermsOnUpper = true; - AllowLeadingDigits = false; + IsTerm = (c, leading) => leading ? char.IsLetter(c) : char.IsLetterOrDigit(c); + BreakTermsOnUpper = false; + CutAcronymOnNonUpper = false; + GreedyAcronyms = false; + Separator = Char.MinValue; } - public HelperConfig(Func preFilter, bool breakTermsOnUpper, bool allowLeadingDigits, bool allowUnderscoreInTerm) - : this() + public Config Clone() { - PreFilter = preFilter; - BreakTermsOnUpper = breakTermsOnUpper; - AllowLeadingDigits = allowLeadingDigits; - AllowUnderscoreInTerm = allowUnderscoreInTerm; + return new Config + { + PreFilter = PreFilter, + IsTerm = IsTerm, + StringType = StringType, + BreakTermsOnUpper = BreakTermsOnUpper, + CutAcronymOnNonUpper = CutAcronymOnNonUpper, + GreedyAcronyms = GreedyAcronyms, + Separator = Separator + }; } - public Func PreFilter { get; private set; } + public Func PreFilter { get; set; } + public Func IsTerm { get; set; } + + public CleanStringType StringType { get; set; } // indicate whether an uppercase within a term eg "fooBar" is to break // into a new term, or to be considered as part of the current term - public bool BreakTermsOnUpper { get; private set; } + public bool BreakTermsOnUpper { get; set; } - // indicates whether it is legal to have leading digits, or whether they - // should be stripped as any other illegal character - public bool AllowLeadingDigits { get; private set; } - - // indicates whether underscore is a valid character in a term or is - // to be considered as a separator - public bool AllowUnderscoreInTerm { get; private set; } + // indicate whether a non-uppercase within an acronym eg "FOOBar" is to cut + // the acronym (at "B" or "a" depending on GreedyAcronyms) or to give + // up the acronym and treat the term as a word + public bool CutAcronymOnNonUpper { get; set; } // indicates whether acronyms parsing is greedy ie whether "FOObar" is // "FOO" + "bar" (greedy) or "FO" + "Obar" (non-greedy) - public bool GreedyAcronyms { get { return false; } } + public bool GreedyAcronyms { get; set; } - public static readonly HelperConfig Empty = new HelperConfig(); + // the separator char + // but then how can we tell we dont want any? + public char Separator { get; set; } + + // extends the config + public CleanStringType StringTypeExtend(CleanStringType stringType) + { + var st = StringType; + foreach (var mask in new[] { CleanStringType.CaseMask, CleanStringType.CodeMask }) + { + var a = stringType & mask; + if (a == 0) continue; + + st = st & ~mask; // clear what we have + st = st | a; // set the new value + } + return st; + } + + internal static readonly Config NotConfigured = new Config(); } - private HelperConfig GetConfig(CleanStringType stringType, CultureInfo culture) + private Config GetConfig(CleanStringType stringType, CultureInfo culture) { - Dictionary config; + stringType = stringType & CleanStringType.RoleMask; + + Dictionary config; if (_configs.ContainsKey(culture)) { config = _configs[culture]; @@ -184,7 +257,7 @@ namespace Umbraco.Core.Strings return config[CleanStringType.RoleMask]; } - return HelperConfig.Empty; + return Config.NotConfigured; } #endregion @@ -252,7 +325,7 @@ function validateSafeAlias(id, value, immediate, callback) {{ /// public virtual string CleanStringForSafeAlias(string text) { - return CleanString(text, CleanStringType.Ascii | CleanStringType.UmbracoCase | CleanStringType.Alias); + return CleanStringForSafeAlias(text, _defaultCulture); } /// @@ -266,7 +339,7 @@ function validateSafeAlias(id, value, immediate, callback) {{ /// public virtual string CleanStringForSafeAlias(string text, CultureInfo culture) { - return CleanString(text, CleanStringType.Ascii | CleanStringType.UmbracoCase | CleanStringType.Alias, culture); + return CleanString(text, CleanStringType.Alias, culture); } /// @@ -280,7 +353,7 @@ function validateSafeAlias(id, value, immediate, callback) {{ /// public virtual string CleanStringForUrlSegment(string text) { - return CleanString(text, CleanStringType.Ascii | CleanStringType.LowerCase | CleanStringType.Url, '-'); + return CleanStringForUrlSegment(text, _defaultCulture); } /// @@ -294,11 +367,11 @@ function validateSafeAlias(id, value, immediate, callback) {{ /// public virtual string CleanStringForUrlSegment(string text, CultureInfo culture) { - return CleanString(text, CleanStringType.Ascii | CleanStringType.LowerCase | CleanStringType.Url, '-', culture); + return CleanString(text, CleanStringType.UrlSegment, culture); } /// - /// Cleans a string, in the context of the invariant culture, to produce a string that can safely be used as a filename, + /// Cleans a string, in the context of the default culture, to produce a string that can safely be used as a filename, /// both internally (on disk) and externally (as a url). /// /// The text to filter. @@ -306,23 +379,11 @@ function validateSafeAlias(id, value, immediate, callback) {{ /// Legacy says this was used to "overcome an issue when Umbraco is used in IE in an intranet environment" but that issue is not documented. public virtual string CleanStringForSafeFileName(string text) { - if (string.IsNullOrWhiteSpace(text)) - return string.Empty; - - text = text.ReplaceMany(Path.GetInvalidFileNameChars(), '-'); - - var pos = text.LastIndexOf('.'); - var name = pos < 0 ? text : text.Substring(0, pos); - var ext = pos < 0 ? string.Empty : text.Substring(pos + 1); - - name = CleanString(name, CleanStringType.Ascii | CleanStringType.Alias | CleanStringType.LowerCase, '-'); - ext = CleanString(ext, CleanStringType.Ascii | CleanStringType.Alias | CleanStringType.LowerCase, '-'); - - return pos < 0 ? name : (name + "." + ext); + return CleanStringForSafeFileName(text, _defaultCulture); } /// - /// Cleans a string, in the context of the invariant culture, to produce a string that can safely be used as a filename, + /// Cleans a string to produce a string that can safely be used as a filename, /// both internally (on disk) and externally (as a url). /// /// The text to filter. @@ -335,14 +396,17 @@ function validateSafeAlias(id, value, immediate, callback) {{ text = text.ReplaceMany(Path.GetInvalidFileNameChars(), '-'); - var pos = text.LastIndexOf('.'); - var name = pos < 0 ? text : text.Substring(0, pos); - var ext = pos < 0 ? string.Empty : text.Substring(pos + 1); + var name = Path.GetFileNameWithoutExtension(text); + var ext = Path.GetExtension(text); // includes the dot, empty if no extension - name = CleanString(name, CleanStringType.Ascii | CleanStringType.Alias | CleanStringType.LowerCase, '-', culture); - ext = CleanString(ext, CleanStringType.Ascii | CleanStringType.Alias | CleanStringType.LowerCase, '-', culture); + Debug.Assert(name != null, "name != null"); + if (name.Length > 0) + name = CleanString(name, CleanStringType.FileName, culture); + Debug.Assert(ext != null, "ext != null"); + if (ext.Length > 0) + ext = CleanString(ext.Substring(1), CleanStringType.FileName, culture); - return pos < 0 ? name : (name + "." + ext); + return ext.Length > 0 ? (name + "." + ext) : name; } #endregion @@ -351,7 +415,7 @@ function validateSafeAlias(id, value, immediate, callback) {{ // MS rules & guidelines: // - Do capitalize both characters of two-character acronyms, except the first word of a camel-cased identifier. - // eg "DBRate" (pascal) or "ioHelper" (camel) - "specialDBRate" (pascal) or "specialIOHelper" (camel) + // eg "DBRate" (pascal) or "ioHelper" (camel) - "SpecialDBRate" (pascal) or "specialIOHelper" (camel) // - Do capitalize only the first character of acronyms with three or more characters, except the first word of a camel-cased identifier. // eg "XmlWriter (pascal) or "htmlReader" (camel) - "SpecialXmlWriter" (pascal) or "specialHtmlReader" (camel) // - Do not capitalize any of the characters of any acronyms, whatever their length, at the beginning of a camel-cased identifier. @@ -376,7 +440,7 @@ function validateSafeAlias(id, value, immediate, callback) {{ /// The string is cleaned in the context of the default culture. public string CleanString(string text, CleanStringType stringType) { - return CleanString(text, stringType, char.MinValue, _defaultCulture); + return CleanString(text, stringType, _defaultCulture, null); } /// @@ -390,7 +454,7 @@ function validateSafeAlias(id, value, immediate, callback) {{ /// The string is cleaned in the context of the default culture. public string CleanString(string text, CleanStringType stringType, char separator) { - return CleanString(text, stringType, separator, _defaultCulture); + return CleanString(text, stringType, _defaultCulture, separator); } /// @@ -403,7 +467,7 @@ function validateSafeAlias(id, value, immediate, callback) {{ /// The clean string. public string CleanString(string text, CleanStringType stringType, CultureInfo culture) { - return CleanString(text, stringType, char.MinValue, culture); + return CleanString(text, stringType, culture, null); } /// @@ -415,23 +479,12 @@ function validateSafeAlias(id, value, immediate, callback) {{ /// The separator. /// The culture. /// The clean string. - public virtual string CleanString(string text, CleanStringType stringType, char separator, CultureInfo culture) + public string CleanString(string text, CleanStringType stringType, char separator, CultureInfo culture) { - var config = GetConfig(stringType & CleanStringType.RoleMask, culture); - return CleanString(text, stringType, separator, culture, config); + return CleanString(text, stringType, culture, separator); } - /// - /// Cleans a string in the context of a specified culture, using a specified separator and configuration. - /// - /// The text to clean. - /// A flag indicating the target casing and encoding of the string. By default, - /// strings are cleaned up to camelCase and Ascii. - /// The separator. - /// The culture. - /// The configuration. - /// The clean string. - private string CleanString(string text, CleanStringType stringType, char separator, CultureInfo culture, HelperConfig config) + protected virtual string CleanString(string text, CleanStringType stringType, CultureInfo culture, char? separator) { // be safe if (text == null) @@ -439,13 +492,18 @@ function validateSafeAlias(id, value, immediate, callback) {{ if (culture == null) throw new ArgumentNullException("culture"); + // get config + var config = GetConfig(stringType, culture); + stringType = config.StringTypeExtend(stringType); + // apply defaults if ((stringType & CleanStringType.CaseMask) == CleanStringType.None) stringType |= CleanStringType.CamelCase; if ((stringType & CleanStringType.CodeMask) == CleanStringType.None) stringType |= CleanStringType.Ascii; - var codeType = stringType & CleanStringType.CodeMask; + // use configured unless specified + separator = separator ?? config.Separator; // apply pre-filter if (config.PreFilter != null) @@ -456,231 +514,46 @@ function validateSafeAlias(id, value, immediate, callback) {{ // text = ReplaceMany(text, config.Replacements); // recode - text = Recode(text, stringType); + var codeType = stringType & CleanStringType.CodeMask; + text = codeType == CleanStringType.Ascii + ? Utf8ToAsciiConverter.ToAsciiString(text) + : RemoveSurrogatePairs(text); // clean - switch (codeType) - { - case CleanStringType.Ascii: - // see note below - don't use CleanAsciiString - //text = CleanAsciiString(text, stringType, separator); - //break; - case CleanStringType.Utf8: - text = CleanUtf8String(text, stringType, separator, culture, config); - break; - case CleanStringType.Unicode: - throw new NotImplementedException("DefaultShortStringHelper does not handle unicode yet."); - default: - throw new ArgumentOutOfRangeException("stringType"); - } + text = CleanCodeString(text, stringType, separator.Value, culture, config); return text; } - // however proud I can be of that subtle, ascii-optimized code, - // benchmarking shows it is an order of magnitude slower that the utf8 version - // don't use it - keep it here should anyone be tempted to micro-optimize again... - // - // beware, it has bugs that are fixed in CleanUtf8String but I'm not going to - // bugfix commented code.... - - /* - internal string CleanAsciiString(string text) + private static string RemoveSurrogatePairs(string text) { - return CleanAsciiString(text, CleanStringType.CamelCase, char.MinValue); - } + var input = text.ToCharArray(); + var output = new char[input.Length]; + var opos = 0; - internal string CleanAsciiString(string text, CleanStringType caseType, char separator) - { - int opos = 0, ipos = 0; - var state = StateBreak; - - caseType &= CleanStringType.CaseMask; - - //switch (caseType) - //{ - // case CleanStringType.LowerCase: - // input = text.ToLowerInvariant().ToCharArray(); - // break; - // case CleanStringType.UpperCase: - // input = text.ToUpperInvariant().ToCharArray(); - // break; - // default: - // input = text.ToCharArray(); - // break; - //} - // if we apply global ToUpper or ToLower to text here - // then we cannot break words on uppercase chars - var input = text; - - // because we shouldn't be adding any extra char - // it's faster to use an array than a StringBuilder - var ilen = input.Length; - var output = new char[ilen]; - - Func termFilter = null; - - for (var i = 0; i < ilen; i++) + for (var ipos = 0; ipos < input.Length; ipos++) { - var idx = ValidStringCharacters.IndexOf(input[i]); - - switch (state) + var c = input[ipos]; + if (char.IsSurrogate(c)) // ignore high surrogate { - case StateBreak: - if (idx >= 0 && (opos > 0 || idx < 26 || idx >= 36)) - { - ipos = i; - if (opos > 0 && separator != char.MinValue) - output[opos++] = separator; - state = idx < 36 ? StateWord : StateUp; - } - break; - - case StateWord: - if (idx < 0 || (_breakTermsOnUpper && idx >= 36)) - { - CopyAsciiTerm(input, ipos, output, ref opos, i - ipos, caseType, termFilter, false); - ipos = i; - state = idx < 0 ? StateBreak : StateUp; - if (state != StateBreak && separator != char.MinValue) - output[opos++] = separator; - } - break; - - case StateAcronym: - if (idx < 36) - { - CopyAsciiTerm(input, ipos, output, ref opos, i - ipos, caseType, termFilter, true); - ipos = i; - state = idx < 0 ? StateBreak : StateWord; - if (state != StateBreak && separator != char.MinValue) - output[opos++] = separator; - } - break; - - case StateUp: - if (idx >= 0) - { - state = idx < 36 ? StateWord : StateAcronym; - } - else - { - CopyAsciiTerm(input, ipos, output, ref opos, 1, caseType, termFilter, false); - state = StateBreak; - } - break; - - default: - throw new Exception("Invalid state."); + ipos++; // and skip low surrogate + output[opos++] = '?'; + } + else + { + output[opos++] = c; } - } - - //Console.WriteLine("xx: ({0}) {1}, {2}, {3}", state, input.Length, ipos, opos); - switch (state) - { - case StateBreak: - break; - - case StateWord: - CopyAsciiTerm(input, ipos, output, ref opos, input.Length - ipos, caseType, termFilter, false); - break; - - case StateAcronym: - case StateUp: - CopyAsciiTerm(input, ipos, output, ref opos, input.Length - ipos, caseType, termFilter, true); - break; - - default: - throw new Exception("Invalid state."); } return new string(output, 0, opos); } - internal void CopyAsciiTerm(string input, int ipos, char[] output, ref int opos, int len, - CleanStringType caseType, Func termFilter, bool isAcronym) - { - var term = input.Substring(ipos, len); - ipos = 0; + // here was a subtle, ascii-optimized version of the cleaning code, and I was + // very proud of it until benchmarking showed it was an order of magnitude slower + // that the utf8 version. Micro-optimizing sometimes isn't such a good idea. - if (termFilter != null) - { - term = termFilter(term); - len = term.Length; - } - - if (isAcronym) - { - if (caseType == CleanStringType.CamelCase && len <= 2 && opos > 0) - caseType = CleanStringType.Unchanged; - else if (caseType == CleanStringType.PascalCase && len <= 2) - caseType = CleanStringType.Unchanged; - } - - int idx; - switch (caseType) - { - //case CleanStringType.LowerCase: - //case CleanStringType.UpperCase: - case CleanStringType.Unchanged: - term.CopyTo(ipos, output, opos, len); - opos += len; - break; - - case CleanStringType.LowerCase: - for (var i = ipos; i < ipos + len; i++) - { - idx = ValidStringCharacters.IndexOf(term[i]); - output[opos++] = ValidStringCharacters[idx >= 36 ? idx - 36 : idx]; - } - break; - - case CleanStringType.UpperCase: - for (var i = ipos; i < ipos + len; i++) - { - idx = ValidStringCharacters.IndexOf(term[i]); - output[opos++] = ValidStringCharacters[idx < 26 ? idx + 36 : idx]; - } - break; - - case CleanStringType.CamelCase: - idx = ValidStringCharacters.IndexOf(term[ipos]); - if (opos == 0) - output[opos++] = ValidStringCharacters[idx >= 36 ? idx - 36 : idx]; - else - output[opos++] = ValidStringCharacters[idx < 26 ? idx + 36 : idx]; - for (var i = ipos + 1; i < ipos + len; i++) - { - idx = ValidStringCharacters.IndexOf(term[i]); - output[opos++] = ValidStringCharacters[idx >= 36 ? idx - 36 : idx]; - } - break; - - case CleanStringType.PascalCase: - idx = ValidStringCharacters.IndexOf(term[ipos]); - output[opos++] = ValidStringCharacters[idx < 26 ? idx + 36 : idx]; - for (var i = ipos + 1; i < ipos + len; i++) - { - idx = ValidStringCharacters.IndexOf(term[i]); - output[opos++] = ValidStringCharacters[idx >= 36 ? idx - 36 : idx]; - } - break; - - default: - throw new ArgumentOutOfRangeException("caseType"); - } - } - */ - - // that's the default code that will work for utf8 strings - // will not handle unicode, though - - internal string CleanUtf8String(string text) - { - return CleanUtf8String(text, CleanStringType.CamelCase, char.MinValue, _defaultCulture, HelperConfig.Empty); - } - - internal string CleanUtf8String(string text, CleanStringType caseType, char separator, CultureInfo culture, HelperConfig config) + // note: does NOT support surrogate pairs in text + internal string CleanCodeString(string text, CleanStringType caseType, char separator, CultureInfo culture, Config config) { int opos = 0, ipos = 0; var state = StateBreak; @@ -695,21 +568,28 @@ function validateSafeAlias(id, value, immediate, callback) {{ var ilen = input.Length; var output = new char[ilen * 2]; // twice the length should be OK in all cases - //var termFilter = config.TermFilter; - for (var i = 0; i < ilen; i++) { var c = input[i]; - var isDigit = char.IsDigit(c); + var isTerm = config.IsTerm(c, opos == 0); + + //var isDigit = char.IsDigit(c); var isUpper = char.IsUpper(c); // false for digits, symbols... - var isLower = char.IsLower(c); // false for digits, symbols... - var isUnder = config.AllowUnderscoreInTerm && c == '_'; - var isTerm = char.IsLetterOrDigit(c) || isUnder; + //var isLower = char.IsLower(c); // false for digits, symbols... + + // what should I do with surrogates? + // no idea, really, so they are not supported at the moment + var isPair = char.IsSurrogate(c); + if (isPair) + throw new NotSupportedException("Surrogate pairs are not supported."); switch (state) { + // within a break case StateBreak: - if (isTerm && (opos > 0 || (isUnder == false && (config.AllowLeadingDigits || isDigit == false)))) + // begin a new term if char is a term char, + // and ( pos > 0 or it's also a valid leading char ) + if (isTerm) { ipos = i; if (opos > 0 && separator != char.MinValue) @@ -718,10 +598,13 @@ function validateSafeAlias(id, value, immediate, callback) {{ } break; + // within a term / word case StateWord: + // end a term if char is not a term char, + // or ( it's uppercase and we break terms on uppercase) if (isTerm == false || (config.BreakTermsOnUpper && isUpper)) { - CopyUtf8Term(input, ipos, output, ref opos, i - ipos, caseType, culture, /*termFilter,*/ false); + CopyTerm(input, ipos, output, ref opos, i - ipos, caseType, culture, false); ipos = i; state = isTerm ? StateUp : StateBreak; if (state != StateBreak && separator != char.MinValue) @@ -729,27 +612,48 @@ function validateSafeAlias(id, value, immediate, callback) {{ } break; + // within a term / acronym case StateAcronym: - if (isTerm == false || isLower || isDigit) + // end an acronym if char is not a term char, + // or if it's not uppercase / config + //Console.WriteLine("acro {0} {1}", c, (config.CutAcronymOnNonUpper && isUpper == false)); + if (isTerm == false || (config.CutAcronymOnNonUpper && isUpper == false)) { - if (isLower && config.GreedyAcronyms == false) - i -= 1; - CopyUtf8Term(input, ipos, output, ref opos, i - ipos, caseType, culture, /*termFilter,*/ true); - ipos = i; - state = isTerm ? StateWord : StateBreak; - if (state != StateBreak && separator != char.MinValue) - output[opos++] = separator; + // whether it's part of the acronym depends on whether we're greedy + if (isTerm && config.GreedyAcronyms == false) + i -= 1; // handle that char again, in another state - not part of the acronym + if (i - ipos > 1) // single-char can't be an acronym + { + CopyTerm(input, ipos, output, ref opos, i - ipos, caseType, culture, true); + ipos = i; + state = isTerm ? StateWord : StateBreak; + if (state != StateBreak && separator != char.MinValue) + output[opos++] = separator; + } + else if (isTerm) + { + state = StateWord; + } + } + else if (isUpper == false) // isTerm == true + { + // it's a term char and we don't cut... + // keep moving forward as a word + state = StateWord; } break; + // within a term / uppercase = could be a word or an acronym case StateUp: if (isTerm) { + // add that char to the term and pick word or acronym state = isUpper ? StateAcronym : StateWord; } else { - CopyUtf8Term(input, ipos, output, ref opos, 1, caseType, culture, /*termFilter,*/ false); + // single char, copy then break + CopyTerm(input, ipos, output, ref opos, 1, caseType, culture, false); state = StateBreak; } break; @@ -765,12 +669,12 @@ function validateSafeAlias(id, value, immediate, callback) {{ break; case StateWord: - CopyUtf8Term(input, ipos, output, ref opos, input.Length - ipos, caseType, culture, /*termFilter,*/ false); + CopyTerm(input, ipos, output, ref opos, input.Length - ipos, caseType, culture, false); break; case StateAcronym: case StateUp: - CopyUtf8Term(input, ipos, output, ref opos, input.Length - ipos, caseType, culture, /*termFilter,*/ true); + CopyTerm(input, ipos, output, ref opos, input.Length - ipos, caseType, culture, true); break; default: @@ -780,17 +684,15 @@ function validateSafeAlias(id, value, immediate, callback) {{ return new string(output, 0, opos); } - internal void CopyUtf8Term(string input, int ipos, char[] output, ref int opos, int len, - CleanStringType caseType, CultureInfo culture, /*Func termFilter,*/ bool isAcronym) + // note: supports surrogate pairs in input string + internal void CopyTerm(string input, int ipos, char[] output, ref int opos, int len, + CleanStringType caseType, CultureInfo culture, bool isAcronym) { var term = input.Substring(ipos, len); - ipos = 0; - - //if (termFilter != null) - //{ - // term = termFilter(term); - // len = term.Length; - //} + //Console.WriteLine("TERM \"{0}\" {1} {2}", + // term, + // isAcronym ? "acronym" : "word", + // caseType); if (isAcronym) { @@ -800,48 +702,100 @@ function validateSafeAlias(id, value, immediate, callback) {{ caseType = CleanStringType.Unchanged; } + // note: MSDN seems to imply that ToUpper or ToLower preserve the length + // of the string, but that this behavior is not guaranteed and could change. + char c; + int i; + string s; switch (caseType) { //case CleanStringType.LowerCase: //case CleanStringType.UpperCase: case CleanStringType.Unchanged: - term.CopyTo(ipos, output, opos, len); + term.CopyTo(0, output, opos, len); opos += len; break; case CleanStringType.LowerCase: - term.ToLower(culture).CopyTo(ipos, output, opos, len); - opos += len; + term = term.ToLower(culture); + term.CopyTo(0, output, opos, term.Length); + opos += term.Length; break; case CleanStringType.UpperCase: - term.ToUpper(culture).CopyTo(ipos, output, opos, len); - opos += len; + term = term.ToUpper(culture); + term.CopyTo(0, output, opos, term.Length); + opos += term.Length; break; case CleanStringType.CamelCase: - c = term[ipos++]; - output[opos] = opos++ == 0 ? char.ToLower(c, culture) : char.ToUpper(c, culture); - if (len > 1) - term.ToLower(culture).CopyTo(ipos, output, opos, len - 1); - opos += len - 1; + c = term[0]; + i = 1; + if (char.IsSurrogate(c)) + { + s = term.Substring(ipos, 2); + s = opos == 0 ? s.ToLower(culture) : s.ToUpper(culture); + s.CopyTo(0, output, opos, s.Length); + opos += s.Length; + i++; // surrogate pair len is 2 + } + else + { + output[opos] = opos++ == 0 ? char.ToLower(c, culture) : char.ToUpper(c, culture); + } + if (len > i) + { + term = term.Substring(i).ToLower(culture); + term.CopyTo(0, output, opos, term.Length); + opos += term.Length; + } break; case CleanStringType.PascalCase: - c = term[ipos++]; - output[opos++] = char.ToUpper(c, culture); - if (len > 1) - term.ToLower(culture).CopyTo(ipos, output, opos, len - 1); - opos += len - 1; + c = term[0]; + i = 1; + if (char.IsSurrogate(c)) + { + s = term.Substring(ipos, 2); + s = s.ToUpper(culture); + s.CopyTo(0, output, opos, s.Length); + opos += s.Length; + i++; // surrogate pair len is 2 + } + else + { + output[opos++] = char.ToUpper(c, culture); + } + if (len > i) + { + term = term.Substring(i).ToLower(culture); + term.CopyTo(0, output, opos, term.Length); + opos += term.Length; + } break; case CleanStringType.UmbracoCase: - c = term[ipos++]; - output[opos] = opos++ == 0 ? c : char.ToUpper(c, culture); - if (len > 1) - term.CopyTo(ipos, output, opos, len - 1); - opos += len - 1; + c = term[0]; + i = 1; + if (char.IsSurrogate(c)) + { + s = term.Substring(ipos, 2); + s = opos == 0 ? s : s.ToUpper(culture); + s.CopyTo(0, output, opos, s.Length); + opos += s.Length; + i++; // surrogate pair len is 2 + } + else + { + output[opos] = opos++ == 0 ? c : char.ToUpper(c, culture); + } + if (len > i) + { + term = term.Substring(i); + term.CopyTo(0, output, opos, term.Length); + opos += term.Length; + } break; default: @@ -860,6 +814,7 @@ function validateSafeAlias(id, value, immediate, callback) {{ /// The separator, which defaults to a whitespace. /// The splitted text. /// Supports Utf8 and Ascii strings, not Unicode strings. + // NOTE does not support surrogates pairs at the moment public virtual string SplitPascalCasing(string text, char separator) { // be safe @@ -904,55 +859,6 @@ function validateSafeAlias(id, value, immediate, callback) {{ #endregion - #region Recode - - /// - /// Returns a new string containing only characters within the specified code type. - /// - /// The string to filter. - /// The string type. - /// The filtered string. - /// If is not Unicode then non-utf8 characters are - /// removed. If it is Ascii we try to do some intelligent replacement of accents, etc. - public virtual string Recode(string text, CleanStringType stringType) - { - // be safe - if (text == null) - throw new ArgumentNullException("text"); - - var codeType = stringType & CleanStringType.CodeMask; - - // unicode to utf8 or ascii: just remove the unicode chars - // utf8 to ascii: try to be clever and replace some chars - - // what's the point? - if (codeType == CleanStringType.Unicode) - return text; - - return codeType == CleanStringType.Utf8 - ? RemoveNonUtf8(text) - : Utf8ToAsciiConverter.ToAsciiString(text); - } - - private string RemoveNonUtf8(string text) - { - var len = text.Length; - var output = new char[len]; // we won't be adding chars - int opos = 0; - - for (var ipos = 0; ipos < len; ipos++) - { - var c = text[ipos]; - if (char.IsSurrogate(c)) - ipos++; - else - output[opos++] = c; - } - return new string(output, 0, opos); - } - - #endregion - #region ReplaceMany /// diff --git a/src/Umbraco.Core/Strings/Utf8ToAsciiConverter.cs b/src/Umbraco.Core/Strings/Utf8ToAsciiConverter.cs index f93c461fa3..23ac4e3931 100644 --- a/src/Umbraco.Core/Strings/Utf8ToAsciiConverter.cs +++ b/src/Umbraco.Core/Strings/Utf8ToAsciiConverter.cs @@ -72,8 +72,11 @@ namespace Umbraco.Core.Strings var opos = 0; for (var ipos = 0; ipos < input.Length; ipos++) - if (char.IsSurrogate(input[ipos])) - ipos++; + if (char.IsSurrogate(input[ipos])) // ignore high surrogate + { + ipos++; // and skip low surrogate + output[opos++] = '?'; + } else ToAscii(input, ipos, output, ref opos); diff --git a/src/Umbraco.Tests/CoreStrings/DefaultShortStringHelperTests.cs b/src/Umbraco.Tests/CoreStrings/DefaultShortStringHelperTests.cs index b9188730d7..09df7d0abf 100644 --- a/src/Umbraco.Tests/CoreStrings/DefaultShortStringHelperTests.cs +++ b/src/Umbraco.Tests/CoreStrings/DefaultShortStringHelperTests.cs @@ -1,6 +1,10 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; +using System.IO; using System.Linq; +using System.Text; using System.Text.RegularExpressions; using NUnit.Framework; using Umbraco.Core; @@ -26,10 +30,39 @@ namespace Umbraco.Tests.CoreStrings // so there still may be utf8 chars even though you want ascii _helper = new DefaultShortStringHelper() - .WithConfig(CleanStringType.Url, StripQuotes, allowLeadingDigits: true) - .WithConfig(new CultureInfo("fr-FR"), CleanStringType.Url, FilterFrenchElisions, allowLeadingDigits: true) - .WithConfig(CleanStringType.Alias, StripQuotes) - .WithConfig(new CultureInfo("fr-FR"), CleanStringType.Alias, WhiteQuotes); + .WithConfig(CleanStringType.FileName, new DefaultShortStringHelper.Config + { + //PreFilter = ClearFileChars, // done in IsTerm + IsTerm = (c, leading) => (char.IsLetterOrDigit(c) || c == '_') && DefaultShortStringHelper.IsValidFileNameChar(c), + StringType = CleanStringType.LowerCase | CleanStringType.Ascii, + Separator = '-' + }) + .WithConfig(CleanStringType.UrlSegment, new DefaultShortStringHelper.Config + { + PreFilter = StripQuotes, + IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', + StringType = CleanStringType.LowerCase | CleanStringType.Ascii, + Separator = '-' + }) + .WithConfig(new CultureInfo("fr-FR"), CleanStringType.UrlSegment, new DefaultShortStringHelper.Config + { + PreFilter = FilterFrenchElisions, + IsTerm = (c, leading) => leading ? char.IsLetter(c) : (char.IsLetterOrDigit(c) || c == '_'), + StringType = CleanStringType.LowerCase | CleanStringType.Ascii, + Separator = '-' + }) + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + PreFilter = StripQuotes, + IsTerm = (c, leading) => leading ? char.IsLetter(c) : char.IsLetterOrDigit(c), + StringType = CleanStringType.UmbracoCase | CleanStringType.Ascii + }) + .WithConfig(new CultureInfo("fr-FR"), CleanStringType.Alias, new DefaultShortStringHelper.Config + { + PreFilter = WhiteQuotes, + IsTerm = (c, leading) => leading ? char.IsLetter(c) : char.IsLetterOrDigit(c), + StringType = CleanStringType.UmbracoCase | CleanStringType.Ascii + }); ShortStringHelperResolver.Reset(); ShortStringHelperResolver.Current = new ShortStringHelperResolver(_helper); @@ -61,6 +94,333 @@ namespace Umbraco.Tests.CoreStrings return s; } + [Test] + public void CleanStringUnderscoreInTerm() + { + var helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + // underscore is accepted within terms + IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + Separator = '*' + }); + Assert.AreEqual("foo_bar*nil", helper.CleanString("foo_bar nil", CleanStringType.Alias)); + + helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + // underscore is not accepted within terms + IsTerm = (c, leading) => char.IsLetterOrDigit(c), + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + Separator = '*' + }); + Assert.AreEqual("foo*bar*nil", helper.CleanString("foo_bar nil", CleanStringType.Alias)); + } + + [Test] + public void CleanStringLeadingChars() + { + var helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + // letters and digits are valid leading chars + IsTerm = (c, leading) => char.IsLetterOrDigit(c), + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + Separator = '*' + }); + Assert.AreEqual("0123foo*bar*nil", helper.CleanString("0123foo_bar nil", CleanStringType.Alias)); + + helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + // only letters are valid leading chars + IsTerm = (c, leading) => leading ? char.IsLetter(c) : char.IsLetterOrDigit(c), + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + Separator = '*' + }); + Assert.AreEqual("foo*bar*nil", helper.CleanString("0123foo_bar nil", CleanStringType.Alias)); + Assert.AreEqual("foo*bar*nil", helper.CleanString("0123 foo_bar nil", CleanStringType.Alias)); + } + + [Test] + public void CleanStringTermOnUpper() + { + var helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + // uppercase letter means new term + BreakTermsOnUpper = true, + Separator = '*' + }); + Assert.AreEqual("foo*Bar", helper.CleanString("fooBar", CleanStringType.Alias)); + + helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + // uppercase letter is part of term + BreakTermsOnUpper = false, + Separator = '*' + }); + Assert.AreEqual("fooBar", helper.CleanString("fooBar", CleanStringType.Alias)); + } + + [Test] + public void CleanStringAcronymOnNonUpper() + { + var helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + // non-uppercase letter means cut acronym + CutAcronymOnNonUpper = true, + Separator = '*' + }); + Assert.AreEqual("foo*BAR*Rnil", helper.CleanString("foo BARRnil", CleanStringType.Alias)); + Assert.AreEqual("foo*BA*Rnil", helper.CleanString("foo BARnil", CleanStringType.Alias)); + Assert.AreEqual("foo*BAnil", helper.CleanString("foo BAnil", CleanStringType.Alias)); + Assert.AreEqual("foo*Bnil", helper.CleanString("foo Bnil", CleanStringType.Alias)); + + helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + // non-uppercase letter means word + CutAcronymOnNonUpper = false, + Separator = '*' + }); + Assert.AreEqual("foo*BARRnil", helper.CleanString("foo BARRnil", CleanStringType.Alias)); + Assert.AreEqual("foo*BARnil", helper.CleanString("foo BARnil", CleanStringType.Alias)); + Assert.AreEqual("foo*BAnil", helper.CleanString("foo BAnil", CleanStringType.Alias)); + Assert.AreEqual("foo*Bnil", helper.CleanString("foo Bnil", CleanStringType.Alias)); + } + + [Test] + public void CleanStringGreedyAcronyms() + { + var helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + CutAcronymOnNonUpper = true, + GreedyAcronyms = true, + Separator = '*' + }); + Assert.AreEqual("foo*BARR*nil", helper.CleanString("foo BARRnil", CleanStringType.Alias)); + Assert.AreEqual("foo*BAR*nil", helper.CleanString("foo BARnil", CleanStringType.Alias)); + Assert.AreEqual("foo*BA*nil", helper.CleanString("foo BAnil", CleanStringType.Alias)); + Assert.AreEqual("foo*Bnil", helper.CleanString("foo Bnil", CleanStringType.Alias)); + + helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + CutAcronymOnNonUpper = true, + GreedyAcronyms = false, + Separator = '*' + }); + Assert.AreEqual("foo*BAR*Rnil", helper.CleanString("foo BARRnil", CleanStringType.Alias)); + Assert.AreEqual("foo*BA*Rnil", helper.CleanString("foo BARnil", CleanStringType.Alias)); + Assert.AreEqual("foo*BAnil", helper.CleanString("foo BAnil", CleanStringType.Alias)); + Assert.AreEqual("foo*Bnil", helper.CleanString("foo Bnil", CleanStringType.Alias)); + } + + [Test] + public void CleanStringWhiteSpace() + { + var helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + Separator = '*' + }); + Assert.AreEqual("foo", helper.CleanString(" foo ", CleanStringType.Alias)); + Assert.AreEqual("foo*bar", helper.CleanString(" foo bar ", CleanStringType.Alias)); + } + + [Test] + public void CleanStringSeparator() + { + var helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + Separator = '*' + }); + Assert.AreEqual("foo*bar", helper.CleanString("foo bar", CleanStringType.Alias)); + + helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + Separator = ' ' + }); + Assert.AreEqual("foo bar", helper.CleanString("foo bar", CleanStringType.Alias)); + + helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged + }); + Assert.AreEqual("foobar", helper.CleanString("foo bar", CleanStringType.Alias)); + + helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + Separator = '文' + }); + Assert.AreEqual("foo文bar", helper.CleanString("foo bar", CleanStringType.Alias)); + } + + [Test] + public void CleanStringSymbols() + { + var helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + Separator = '*' + }); + Assert.AreEqual("house*2", helper.CleanString("house (2)", CleanStringType.Alias)); + + // FIXME but for a filename we want to keep them! + // FIXME and what about a url? + } + + [Test] + public void Utf8Surrogates() + { + // Unicode values between 0x10000 and 0x10FFF are represented by two 16-bit "surrogate" characters + const string str = "a\U00010F00z\uA74Ft"; + Assert.AreEqual(6, str.Length); + Assert.IsTrue(char.IsSurrogate(str[1])); + Assert.IsTrue(char.IsHighSurrogate(str[1])); + Assert.IsTrue(char.IsSurrogate(str[2])); + Assert.IsTrue(char.IsLowSurrogate(str[2])); + Assert.AreEqual('z', str[3]); + Assert.IsFalse(char.IsSurrogate(str[4])); + Assert.AreEqual('\uA74F', str[4]); + Assert.AreEqual('t', str[5]); + + Assert.AreEqual("z", str.Substring(3, 1)); + Assert.AreEqual("\U00010F00", str.Substring(1, 2)); + + var bytes = Encoding.UTF8.GetBytes(str); + Assert.AreEqual(10, bytes.Length); + Assert.AreEqual('a', bytes[0]); + // then next string element is two chars (surrogate pair) or 4 bytes, 21 bits of code point + Assert.AreEqual('z', bytes[5]); + // then next string element is one char and 3 bytes, 16 bits of code point + Assert.AreEqual('t', bytes[9]); + //foreach (var b in bytes) + // Console.WriteLine("{0:X}", b); + + Console.WriteLine("\U00010B70"); + } + + [Test] + public void Utf8ToAsciiConverter() + { + const string str = "a\U00010F00z\uA74Ftéô"; + var output = Core.Strings.Utf8ToAsciiConverter.ToAsciiString(str); + Assert.AreEqual("a?zooteo", output); + } + + [Test] + public void CleanStringEncoding() + { + var helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + Separator = '*' + }); + Assert.AreEqual("中文测试", helper.CleanString("中文测试", CleanStringType.Alias)); + Assert.AreEqual("léger*中文测试*ZÔRG", helper.CleanString("léger 中文测试 ZÔRG", CleanStringType.Alias)); + + helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + StringType = CleanStringType.Ascii | CleanStringType.Unchanged, + Separator = '*' + }); + Assert.AreEqual("", helper.CleanString("中文测试", CleanStringType.Alias)); + Assert.AreEqual("leger*ZORG", helper.CleanString("léger 中文测试 ZÔRG", CleanStringType.Alias)); + } + + [Test] + public void CleanStringDefaultConfig() + { + var helper = new DefaultShortStringHelper().WithDefaultConfig(); + + const string input = "0123 中文测试 中文测试 léger ZÔRG (2) a?? *x"; + + var alias = helper.CleanStringForSafeAlias(input); + var filename = helper.CleanStringForSafeFileName(input); + var segment = helper.CleanStringForUrlSegment(input); + + // umbraco-cased ascii alias, must begin with a proper letter + Assert.AreEqual("legerZORG2AX", alias, "alias"); + + // lower-cased, utf8 filename, removing illegal filename chars, using dash-separator + Assert.AreEqual("0123-中文测试-中文测试-léger-zôrg-2-a-x", filename, "filename"); + + // lower-cased, utf8 url segment, only letters and digits, using dash-separator + Assert.AreEqual("0123-中文测试-中文测试-léger-zôrg-2-a-x", segment, "segment"); + } + + [Test] + public void CleanStringCasing() + { + var helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + Separator = ' ' + }); + + // BBB is an acronym + // E is a word (too short to be an acronym) + // FF is an acronym + + // FIXME "C" can't be an acronym + // FIXME "DBXreview" = acronym?! + + Assert.AreEqual("aaa BBB CCc Ddd E FF", helper.CleanString("aaa BBB CCc Ddd E FF", CleanStringType.Alias)); // unchanged + Assert.AreEqual("aaa Bbb Ccc Ddd E FF", helper.CleanString("aaa BBB CCc Ddd E FF", CleanStringType.Alias | CleanStringType.CamelCase)); + Assert.AreEqual("Aaa Bbb Ccc Ddd E FF", helper.CleanString("aaa BBB CCc Ddd E FF", CleanStringType.Alias | CleanStringType.PascalCase)); + Assert.AreEqual("aaa bbb ccc ddd e ff", helper.CleanString("aaa BBB CCc Ddd E FF", CleanStringType.Alias | CleanStringType.LowerCase)); + Assert.AreEqual("AAA BBB CCC DDD E FF", helper.CleanString("aaa BBB CCc Ddd E FF", CleanStringType.Alias | CleanStringType.UpperCase)); + Assert.AreEqual("aaa BBB CCc Ddd E FF", helper.CleanString("aaa BBB CCc Ddd E FF", CleanStringType.Alias | CleanStringType.UmbracoCase)); + + // MS rules & guidelines: + // - Do capitalize both characters of two-character acronyms, except the first word of a camel-cased identifier. + // eg "DBRate" (pascal) or "ioHelper" (camel) - "SpecialDBRate" (pascal) or "specialIOHelper" (camel) + // - Do capitalize only the first character of acronyms with three or more characters, except the first word of a camel-cased identifier. + // eg "XmlWriter (pascal) or "htmlReader" (camel) - "SpecialXmlWriter" (pascal) or "specialHtmlReader" (camel) + // - Do not capitalize any of the characters of any acronyms, whatever their length, at the beginning of a camel-cased identifier. + // eg "xmlWriter" or "dbWriter" (camel) + + Assert.AreEqual("aaa BB Ccc", helper.CleanString("aaa BB ccc", CleanStringType.Alias | CleanStringType.CamelCase)); + Assert.AreEqual("aa Bb Ccc", helper.CleanString("AA bb ccc", CleanStringType.Alias | CleanStringType.CamelCase)); + Assert.AreEqual("aaa Bb Ccc", helper.CleanString("AAA bb ccc", CleanStringType.Alias | CleanStringType.CamelCase)); + Assert.AreEqual("db Rate", helper.CleanString("DB rate", CleanStringType.Alias | CleanStringType.CamelCase)); + Assert.AreEqual("special DB Rate", helper.CleanString("special DB rate", CleanStringType.Alias | CleanStringType.CamelCase)); + Assert.AreEqual("xml Writer", helper.CleanString("XML writer", CleanStringType.Alias | CleanStringType.CamelCase)); + Assert.AreEqual("special Xml Writer", helper.CleanString("special XML writer", CleanStringType.Alias | CleanStringType.CamelCase)); + + Assert.AreEqual("Aaa BB Ccc", helper.CleanString("aaa BB ccc", CleanStringType.Alias | CleanStringType.PascalCase)); + Assert.AreEqual("AA Bb Ccc", helper.CleanString("AA bb ccc", CleanStringType.Alias | CleanStringType.PascalCase)); + Assert.AreEqual("Aaa Bb Ccc", helper.CleanString("AAA bb ccc", CleanStringType.Alias | CleanStringType.PascalCase)); + Assert.AreEqual("DB Rate", helper.CleanString("DB rate", CleanStringType.Alias | CleanStringType.PascalCase)); + Assert.AreEqual("Special DB Rate", helper.CleanString("special DB rate", CleanStringType.Alias | CleanStringType.PascalCase)); + Assert.AreEqual("Xml Writer", helper.CleanString("XML writer", CleanStringType.Alias | CleanStringType.PascalCase)); + Assert.AreEqual("Special Xml Writer", helper.CleanString("special XML writer", CleanStringType.Alias | CleanStringType.PascalCase)); + } + #region Cases [TestCase("foo", "foo")] [TestCase(" foo ", "foo")] @@ -100,29 +460,29 @@ namespace Umbraco.Tests.CoreStrings Assert.AreEqual(expected, output); } - #region Cases - [TestCase("This is my_little_house so cute.", "thisIsMyLittleHouseSoCute", false)] - [TestCase("This is my_little_house so cute.", "thisIsMy_little_houseSoCute", true)] - [TestCase("This is my_Little_House so cute.", "thisIsMyLittleHouseSoCute", false)] - [TestCase("This is my_Little_House so cute.", "thisIsMy_Little_HouseSoCute", true)] - [TestCase("An UPPER_CASE_TEST to check", "anUpperCaseTestToCheck", false)] - [TestCase("An UPPER_CASE_TEST to check", "anUpper_case_testToCheck", true)] - [TestCase("Trailing_", "trailing", false)] - [TestCase("Trailing_", "trailing_", true)] - [TestCase("_Leading", "leading", false)] - [TestCase("_Leading", "leading", true)] - [TestCase("Repeat___Repeat", "repeatRepeat", false)] - [TestCase("Repeat___Repeat", "repeat___Repeat", true)] - [TestCase("Repeat___repeat", "repeatRepeat", false)] - [TestCase("Repeat___repeat", "repeat___repeat", true)] - #endregion - public void CleanStringWithUnderscore(string input, string expected, bool allowUnderscoreInTerm) - { - var helper = new DefaultShortStringHelper() - .WithConfig(allowUnderscoreInTerm: allowUnderscoreInTerm); - var output = helper.CleanString(input, CleanStringType.Alias | CleanStringType.Ascii | CleanStringType.CamelCase); - Assert.AreEqual(expected, output); - } + //#region Cases + //[TestCase("This is my_little_house so cute.", "thisIsMyLittleHouseSoCute", false)] + //[TestCase("This is my_little_house so cute.", "thisIsMy_little_houseSoCute", true)] + //[TestCase("This is my_Little_House so cute.", "thisIsMyLittleHouseSoCute", false)] + //[TestCase("This is my_Little_House so cute.", "thisIsMy_Little_HouseSoCute", true)] + //[TestCase("An UPPER_CASE_TEST to check", "anUpperCaseTestToCheck", false)] + //[TestCase("An UPPER_CASE_TEST to check", "anUpper_case_testToCheck", true)] + //[TestCase("Trailing_", "trailing", false)] + //[TestCase("Trailing_", "trailing_", true)] + //[TestCase("_Leading", "leading", false)] + //[TestCase("_Leading", "leading", true)] + //[TestCase("Repeat___Repeat", "repeatRepeat", false)] + //[TestCase("Repeat___Repeat", "repeat___Repeat", true)] + //[TestCase("Repeat___repeat", "repeatRepeat", false)] + //[TestCase("Repeat___repeat", "repeat___repeat", true)] + //#endregion + //public void CleanStringWithUnderscore(string input, string expected, bool allowUnderscoreInTerm) + //{ + // var helper = new DefaultShortStringHelper() + // .WithConfig(allowUnderscoreInTerm: allowUnderscoreInTerm); + // var output = helper.CleanString(input, CleanStringType.Alias | CleanStringType.Ascii | CleanStringType.CamelCase); + // Assert.AreEqual(expected, output); + //} #region Cases [TestCase("Home Page", "home-page")] @@ -133,7 +493,6 @@ namespace Umbraco.Tests.CoreStrings [TestCase("汉#字*/漢?字", "")] [TestCase("Réalösk fix bran#lo'sk", "realosk-fix-bran-losk")] [TestCase("200 ways to be happy", "200-ways-to-be-happy")] - [TestCase("aBCdEfGhIJK", "a-b-cd-ef-gh-ijk")] #endregion public void CleanStringForUrlSegment(string input, string expected) { @@ -162,173 +521,19 @@ namespace Umbraco.Tests.CoreStrings } #region Cases - [TestCase("foo", "foo")] - [TestCase(" foo ", "foo")] - [TestCase("Foo", "foo")] - [TestCase("FoO", "foO")] - [TestCase("FoO bar", "foOBar")] - [TestCase("FoO bar NIL", "foOBarNil")] - [TestCase("FoO 33bar 22NIL", "foO33bar22Nil")] - [TestCase("FoO 33bar 22NI", "foO33bar22NI")] - [TestCase("0foo", "foo")] - [TestCase("2foo bar", "fooBar")] - [TestCase("9FOO", "foo")] - [TestCase("foo-BAR", "fooBar")] - [TestCase("foo-BA-dang", "fooBADang")] - [TestCase("foo_BAR", "fooBar")] - [TestCase("foo'BAR", "fooBar")] - [TestCase("sauté dans l'espace", "sautéDansLEspace")] - [TestCase("foo\"\"bar", "fooBar")] - [TestCase("-foo-", "foo")] - [TestCase("_foo_", "foo")] - [TestCase("spécial", "spécial")] - [TestCase("brô dëk ", "brôDëk")] - [TestCase("1235brô dëk ", "brôDëk")] - [TestCase("汉#字*/漢?字", "汉字漢字")] - [TestCase("aa DB cd EFG X KLMN OP qrst", "aaDBCdEfgXKlmnOPQrst")] - [TestCase("AA db cd EFG X KLMN OP qrst", "aaDbCdEfgXKlmnOPQrst")] - [TestCase("AAA db cd EFG X KLMN OP qrst", "aaaDbCdEfgXKlmnOPQrst")] - [TestCase("quelle élévation à partir", "quelleÉlévationÀPartir")] - #endregion - public void CleanUtf8String(string input, string expected) - { - input = _helper.Recode(input, CleanStringType.Utf8); - var output = _helper.CleanUtf8String(input); - Assert.AreEqual(expected, output); - } - - #region Cases - [TestCase("sauté dans l'espace", "saute-dans-espace", "fr-FR", CleanStringType.Url | CleanStringType.Ascii | CleanStringType.LowerCase)] - [TestCase("sauté dans l'espace", "sauté-dans-espace", "fr-FR", CleanStringType.Url | CleanStringType.Utf8 | CleanStringType.LowerCase)] + [TestCase("sauté dans l'espace", "saute-dans-espace", "fr-FR", CleanStringType.UrlSegment | CleanStringType.Ascii | CleanStringType.LowerCase)] + [TestCase("sauté dans l'espace", "sauté-dans-espace", "fr-FR", CleanStringType.UrlSegment | CleanStringType.Utf8 | CleanStringType.LowerCase)] [TestCase("sauté dans l'espace", "SauteDansLEspace", "fr-FR", CleanStringType.Alias | CleanStringType.Ascii | CleanStringType.PascalCase)] - [TestCase("he doesn't want", "he-doesnt-want", null, CleanStringType.Url | CleanStringType.Ascii | CleanStringType.LowerCase)] + [TestCase("he doesn't want", "he-doesnt-want", null, CleanStringType.UrlSegment | CleanStringType.Ascii | CleanStringType.LowerCase)] [TestCase("he doesn't want", "heDoesntWant", null, CleanStringType.Alias | CleanStringType.Ascii | CleanStringType.CamelCase)] #endregion public void CleanStringWithTypeAndCulture(string input, string expected, string culture, CleanStringType stringType) { var cinfo = culture == null ? CultureInfo.InvariantCulture : new CultureInfo(culture); - var separator = (stringType & CleanStringType.Url) == CleanStringType.Url ? '-' : char.MinValue; - var output = _helper.CleanString(input, stringType, separator, cinfo); - Assert.AreEqual(expected, output); - } - #region Cases - [TestCase("foo", "foo")] - [TestCase(" foo ", "foo")] - [TestCase("Foo", "foo")] - [TestCase("FoO", "foO")] - [TestCase("FoO bar", "foOBar")] - [TestCase("FoO bar NIL", "foOBarNil")] - [TestCase("FoO 33bar 22NIL", "foO33bar22Nil")] - [TestCase("FoO 33bar 22NI", "foO33bar22NI")] - [TestCase("0foo", "foo")] - [TestCase("2foo bar", "fooBar")] - [TestCase("9FOO", "foo")] - [TestCase("foo-BAR", "fooBar")] - [TestCase("foo-BA-dang", "fooBADang")] - [TestCase("foo_BAR", "fooBar")] - [TestCase("foo'BAR", "fooBar")] - [TestCase("sauté dans l'espace", "sauteDansLEspace")] - [TestCase("foo\"\"bar", "fooBar")] - [TestCase("-foo-", "foo")] - [TestCase("_foo_", "foo")] - [TestCase("spécial", "special")] - [TestCase("brô dëk ", "broDek")] - [TestCase("1235brô dëk ", "broDek")] - [TestCase("汉#字*/漢?字", "")] - [TestCase("aa DB cd EFG X KLMN OP qrst", "aaDBCdEfgXKlmnOPQrst")] - [TestCase("AA db cd EFG X KLMN OP qrst", "aaDbCdEfgXKlmnOPQrst")] - [TestCase("AAA db cd EFG X KLMN OP qrst", "aaaDbCdEfgXKlmnOPQrst")] - #endregion - public void CleanStringToAscii(string input, string expected) - { - var output = _helper.CleanString(input, CleanStringType.Ascii | CleanStringType.CamelCase); - Assert.AreEqual(expected, output); - } - - #region Cases - [TestCase("1235brô dëK tzARlan ban123!pOo", "brodeKtzARlanban123pOo", CleanStringType.Unchanged)] - [TestCase(" 1235brô dëK tzARlan ban123!pOo ", "brodeKtzARlanban123pOo", CleanStringType.Unchanged)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "BroDeKTzARlanBan123POo", CleanStringType.PascalCase)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "broDeKTzARlanBan123POo", CleanStringType.CamelCase)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "BRODEKTZARLANBAN123POO", CleanStringType.UpperCase)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "brodektzarlanban123poo", CleanStringType.LowerCase)] - [TestCase("aa DB cd EFG X KLMN OP qrst", "aaDBCdEfgXKlmnOPQrst", CleanStringType.CamelCase)] - [TestCase("aaa DB cd EFG X KLMN OP qrst", "aaaDBCdEfgXKlmnOPQrst", CleanStringType.CamelCase)] - [TestCase("aa DB cd EFG X KLMN OP qrst", "AaDBCdEfgXKlmnOPQrst", CleanStringType.PascalCase)] - [TestCase("aaa DB cd EFG X KLMN OP qrst", "AaaDBCdEfgXKlmnOPQrst", CleanStringType.PascalCase)] - [TestCase("AA db cd EFG X KLMN OP qrst", "aaDbCdEfgXKlmnOPQrst", CleanStringType.CamelCase)] - [TestCase("AAA db cd EFG X KLMN OP qrst", "aaaDbCdEfgXKlmnOPQrst", CleanStringType.CamelCase)] - [TestCase("AA db cd EFG X KLMN OP qrst", "AADbCdEfgXKlmnOPQrst", CleanStringType.PascalCase)] - [TestCase("AAA db cd EFG X KLMN OP qrst", "AaaDbCdEfgXKlmnOPQrst", CleanStringType.PascalCase)] - [TestCase("We store some HTML in the DB for performance", "WeStoreSomeHtmlInTheDBForPerformance", CleanStringType.PascalCase)] - [TestCase("We store some HTML in the DB for performance", "weStoreSomeHtmlInTheDBForPerformance", CleanStringType.CamelCase)] - [TestCase("X is true", "XIsTrue", CleanStringType.PascalCase)] - [TestCase("X is true", "xIsTrue", CleanStringType.CamelCase)] - [TestCase("IO are slow", "IOAreSlow", CleanStringType.PascalCase)] - [TestCase("IO are slow", "ioAreSlow", CleanStringType.CamelCase)] - [TestCase("RAM is fast", "RamIsFast", CleanStringType.PascalCase)] - [TestCase("RAM is fast", "ramIsFast", CleanStringType.CamelCase)] - [TestCase("Tab 1", "tab1", CleanStringType.CamelCase)] - [TestCase("Home - Page", "homePage", CleanStringType.CamelCase)] - [TestCase("Shannon's Document Type", "shannonSDocumentType", CleanStringType.CamelCase)] - [TestCase("Shannon's Document Type", "shannonsDocumentType", CleanStringType.CamelCase | CleanStringType.Alias)] - [TestCase("!BADDLY nam-ed Document Type", "baddlyNamEdDocumentType", CleanStringType.CamelCase)] - [TestCase(" !BADDLY nam-ed Document Type", "BADDLYnamedDocumentType", CleanStringType.Unchanged)] - [TestCase("!BADDLY nam-ed Document Type", "BaddlyNamEdDocumentType", CleanStringType.PascalCase)] - [TestCase("i %Want!thisTo end up In Proper@case", "IWantThisToEndUpInProperCase", CleanStringType.PascalCase)] - [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "raksmorgasKeKe", CleanStringType.CamelCase)] - [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "RaksmorgasKeKe", CleanStringType.PascalCase)] - [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "RaksmorgaskeKe", CleanStringType.Unchanged)] - [TestCase("TRii", "TRii", CleanStringType.Unchanged)] - [TestCase("**TRii", "TRii", CleanStringType.Unchanged)] - [TestCase("TRii", "tRii", CleanStringType.CamelCase)] - [TestCase("TRXii", "trXii", CleanStringType.CamelCase)] - [TestCase("**TRii", "tRii", CleanStringType.CamelCase)] - [TestCase("TRii", "TRii", CleanStringType.PascalCase)] - [TestCase("TRXii", "TRXii", CleanStringType.PascalCase)] - [TestCase("**TRii", "TRii", CleanStringType.PascalCase)] - [TestCase("trII", "trII", CleanStringType.Unchanged)] - [TestCase("**trII", "trII", CleanStringType.Unchanged)] - [TestCase("trII", "trII", CleanStringType.CamelCase)] - [TestCase("**trII", "trII", CleanStringType.CamelCase)] - [TestCase("trII", "TrII", CleanStringType.PascalCase)] - [TestCase("**trII", "TrII", CleanStringType.PascalCase)] - [TestCase("trIIX", "trIix", CleanStringType.CamelCase)] - [TestCase("**trIIX", "trIix", CleanStringType.CamelCase)] - [TestCase("trIIX", "TrIix", CleanStringType.PascalCase)] - [TestCase("**trIIX", "TrIix", CleanStringType.PascalCase)] - #endregion - public void CleanStringToAsciiWithType(string input, string expected, CleanStringType caseType) - { - var output = _helper.CleanString(input, caseType | CleanStringType.Ascii); - Assert.AreEqual(expected, output); - } - - #region Cases - [TestCase("1235brô dëK tzARlan ban123!pOo", "bro de K tz A Rlan ban123 p Oo", ' ', CleanStringType.Unchanged)] - [TestCase(" 1235brô dëK tzARlan ban123!pOo ", "bro de K tz A Rlan ban123 p Oo", ' ', CleanStringType.Unchanged)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "Bro De K Tz A Rlan Ban123 P Oo", ' ', CleanStringType.PascalCase)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "Bro De K Tz A Rlan Ban123 P Oo", ' ', CleanStringType.PascalCase)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "bro De K Tz A Rlan Ban123 P Oo", ' ', CleanStringType.CamelCase)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "bro-De-K-Tz-A-Rlan-Ban123-P-Oo", '-', CleanStringType.CamelCase)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "BRO-DE-K-TZ-A-RLAN-BAN123-P-OO", '-', CleanStringType.UpperCase)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "bro-de-k-tz-a-rlan-ban123-p-oo", '-', CleanStringType.LowerCase)] - [TestCase("Tab 1", "tab 1", ' ', CleanStringType.CamelCase)] - [TestCase("Home - Page", "home Page", ' ', CleanStringType.CamelCase)] - [TestCase("Shannon's Document Type", "shannon S Document Type", ' ', CleanStringType.CamelCase)] - [TestCase("Shannon's Document Type", "shannons Document Type", ' ', CleanStringType.CamelCase | CleanStringType.Alias)] - [TestCase("!BADDLY nam-ed Document Type", "baddly Nam Ed Document Type", ' ', CleanStringType.CamelCase)] - [TestCase(" !BADDLY nam-ed Document Type", "BADDLY nam ed Document Type", ' ', CleanStringType.Unchanged)] - [TestCase("!BADDLY nam-ed Document Type", "Baddly Nam Ed Document Type", ' ', CleanStringType.PascalCase)] - [TestCase("i %Want!thisTo end up In Proper@case", "I Want This To End Up In Proper Case", ' ', CleanStringType.PascalCase)] - [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "raksmorgas Ke Ke", ' ', CleanStringType.CamelCase)] - [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "Raksmorgas Ke Ke", ' ', CleanStringType.PascalCase)] - [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "Raksmorgas ke Ke", ' ', CleanStringType.Unchanged)] - #endregion - public void CleanStringToAsciiWithTypeAndSeparator(string input, string expected, char separator, CleanStringType caseType) - { - var output = _helper.CleanString(input, caseType | CleanStringType.Ascii, separator); + // picks the proper config per culture + // and overrides some stringType params (ascii...) + var output = _helper.CleanString(input, stringType, cinfo); Assert.AreEqual(expected, output); } From 31e5ed7b1bf2ec675c1616ef0c1615d96d0b4b76 Mon Sep 17 00:00:00 2001 From: Niels Hartvig Date: Sat, 14 Dec 2013 11:48:08 +0100 Subject: [PATCH 0067/1046] Fixes links in help --- .../src/views/common/dialogs/help.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/help.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/help.html index ca8a1e133f..e112413822 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/help.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/help.html @@ -11,7 +11,7 @@
  • - + {{topic.title}} {{topic.description}} @@ -21,7 +21,7 @@
    • - + go to our.umbraco.org The friendliest community @@ -35,7 +35,7 @@
      • @@ -45,7 +45,7 @@
        • - + go to umbraco.tv The best umbraco video tutorials From 9105c08a4cc3dbb5aa1612020f26b7141b4b5cd2 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 16 Dec 2013 12:20:38 +1100 Subject: [PATCH 0068/1046] getting membership provider to the same state as v7 --- .../businesslogic/member/Member.cs | 29 +- .../members/MembersMembershipProvider.cs | 590 +++++++++--------- .../members/MembershipEventHandler.cs | 35 ++ .../umbraco.providers.csproj | 1 + 4 files changed, 342 insertions(+), 313 deletions(-) create mode 100644 src/umbraco.providers/members/MembershipEventHandler.cs diff --git a/src/umbraco.cms/businesslogic/member/Member.cs b/src/umbraco.cms/businesslogic/member/Member.cs index aa6b71f46c..9b051aea7b 100644 --- a/src/umbraco.cms/businesslogic/member/Member.cs +++ b/src/umbraco.cms/businesslogic/member/Member.cs @@ -466,7 +466,7 @@ namespace umbraco.cms.businesslogic.member /// /// The members password, used when logging in on the public website /// - [Obsolete("Do not use this property, use GetPassword and ChangePassword instead, if using ChangePassword ensure that the password is encrypted or hashed based on the active membership provider")] + [Obsolete("Do not use this property, use GetPassword and ChangePassword instead, if using ChangePassword ensure that the password is encrypted or hashed based on the active membership provider")] public string Password { get @@ -479,7 +479,7 @@ namespace umbraco.cms.businesslogic.member } return _password; - } + } set { // We need to use the provider for this in order for hashing, etc. support @@ -745,10 +745,10 @@ namespace umbraco.cms.businesslogic.member } } - /// - /// Sets the password for the user - ensure it is encrypted or hashed based on the active membership provider. - /// - /// + /// + /// Sets the password for the user - ensure it is encrypted or hashed based on the active membership provider. + /// + /// public void ChangePassword(string newPassword) { SqlHelper.ExecuteNonQuery( @@ -774,23 +774,8 @@ namespace umbraco.cms.businesslogic.member } return _password; } - + /// - /// Returns the currently stored password - this may be encrypted or hashed string depending on the active membership provider - /// - /// - public string GetPassword() - { - if (string.IsNullOrEmpty(m_Password)) - { - m_Password = SqlHelper.ExecuteScalar( - "select Password from cmsMember where nodeId = @id", - SqlHelper.CreateParameter("@id", Id)); - } - return m_Password; - } - - /// /// Adds the member to group with the specified id /// /// The id of the group which the member is being added to diff --git a/src/umbraco.providers/members/MembersMembershipProvider.cs b/src/umbraco.providers/members/MembersMembershipProvider.cs index b6b90b47b8..a14b47d84d 100644 --- a/src/umbraco.providers/members/MembersMembershipProvider.cs +++ b/src/umbraco.providers/members/MembersMembershipProvider.cs @@ -7,13 +7,12 @@ using System.Web.Security; using System.Configuration; using Umbraco.Core; using Umbraco.Core.Models; -using Umbraco.Core.Security; +using Umbraco.Core.Security; using umbraco.BusinessLogic; using System.Security.Cryptography; using System.Web.Util; using System.Collections.Specialized; using System.Configuration.Provider; -using umbraco.cms.businesslogic; using System.Security; using System.Security.Permissions; using System.Runtime.CompilerServices; @@ -28,7 +27,7 @@ namespace umbraco.providers.members /// Custom Membership Provider for Umbraco Members (User authentication for Frontend applications NOT umbraco CMS) ///
  • - public class UmbracoMembershipProvider : MembershipProviderBase + public class UmbracoMembershipProvider : MembershipProviderBase { #region Fields @@ -37,15 +36,34 @@ namespace umbraco.providers.members private string _lockPropertyTypeAlias = Constants.Conventions.Member.IsLockedOut; private string _lastLockedOutPropertyTypeAlias = Constants.Conventions.Member.LastLockoutDate; private string _failedPasswordAttemptsPropertyTypeAlias = Constants.Conventions.Member.FailedPasswordAttempts; - private string _approvedPropertyTypeAlias = Constants.Conventions.Member.IsApproved; + private string _approvedPropertyTypeAlias = Constants.Conventions.Member.IsApproved; private string _commentPropertyTypeAlias = Constants.Conventions.Member.Comments; private string _lastLoginPropertyTypeAlias = Constants.Conventions.Member.LastLoginDate; private string _lastPasswordChangedPropertyTypeAlias = Constants.Conventions.Member.LastPasswordChangeDate; - private string _passwordRetrievalQuestionPropertyTypeAlias = Constants.Conventions.Member.PasswordQuestion; - private string _passwordRetrievalAnswerPropertyTypeAlias = Constants.Conventions.Member.PasswordAnswer; - - private string _providerName = Member.UmbracoMemberProviderName; + private string _passwordRetrievalQuestionPropertyTypeAlias = Constants.Conventions.Member.PasswordQuestion; + private string _passwordRetrievalAnswerPropertyTypeAlias = Constants.Conventions.Member.PasswordAnswer; + private string _providerName = Member.UmbracoMemberProviderName; + + //Need to expose these publicly so we know what field aliases to use in the editor, we only care about these 3 fields + // because they are the only 'settable' provider properties that are not stored against the IMember directly (i.e. they are + // property type properties). + + public string LockPropertyTypeAlias + { + get { return _lockPropertyTypeAlias; } + } + + public string ApprovedPropertyTypeAlias + { + get { return _approvedPropertyTypeAlias; } + } + + public string CommentPropertyTypeAlias + { + get { return _commentPropertyTypeAlias; } + } + #endregion /// @@ -97,28 +115,28 @@ namespace umbraco.providers.members if (config == null) throw new ArgumentNullException("config"); if (string.IsNullOrEmpty(name)) name = "UmbracoMembershipProvider"; - + base.Initialize(name, config); - - _providerName = name; - + + _providerName = name; + // test for membertype (if not specified, choose the first member type available) if (config["defaultMemberTypeAlias"] != null) - _defaultMemberTypeAlias = config["defaultMemberTypeAlias"]; + _defaultMemberTypeAlias = config["defaultMemberTypeAlias"]; else if (MemberType.GetAll.Length == 1) - _defaultMemberTypeAlias = MemberType.GetAll[0].Alias; + _defaultMemberTypeAlias = MemberType.GetAll[0].Alias; else throw new ProviderException("No default MemberType alias is specified in the web.config string. Please add a 'defaultMemberTypeAlias' to the add element in the provider declaration in web.config"); // test for approve status if (config["umbracoApprovePropertyTypeAlias"] != null) { - _approvedPropertyTypeAlias = config["umbracoApprovePropertyTypeAlias"]; + _approvedPropertyTypeAlias = config["umbracoApprovePropertyTypeAlias"]; } // test for lock attempts if (config["umbracoLockPropertyTypeAlias"] != null) { - _lockPropertyTypeAlias = config["umbracoLockPropertyTypeAlias"]; + _lockPropertyTypeAlias = config["umbracoLockPropertyTypeAlias"]; } if (config["umbracoLastLockedPropertyTypeAlias"] != null) { @@ -130,26 +148,26 @@ namespace umbraco.providers.members } if (config["umbracoFailedPasswordAttemptsPropertyTypeAlias"] != null) { - _failedPasswordAttemptsPropertyTypeAlias = config["umbracoFailedPasswordAttemptsPropertyTypeAlias"]; + _failedPasswordAttemptsPropertyTypeAlias = config["umbracoFailedPasswordAttemptsPropertyTypeAlias"]; } // comment property if (config["umbracoCommentPropertyTypeAlias"] != null) { - _commentPropertyTypeAlias = config["umbracoCommentPropertyTypeAlias"]; + _commentPropertyTypeAlias = config["umbracoCommentPropertyTypeAlias"]; } // last login date if (config["umbracoLastLoginPropertyTypeAlias"] != null) { - _lastLoginPropertyTypeAlias = config["umbracoLastLoginPropertyTypeAlias"]; + _lastLoginPropertyTypeAlias = config["umbracoLastLoginPropertyTypeAlias"]; } // password retrieval if (config["umbracoPasswordRetrievalQuestionPropertyTypeAlias"] != null) { - _passwordRetrievalQuestionPropertyTypeAlias = config["umbracoPasswordRetrievalQuestionPropertyTypeAlias"]; + _passwordRetrievalQuestionPropertyTypeAlias = config["umbracoPasswordRetrievalQuestionPropertyTypeAlias"]; } if (config["umbracoPasswordRetrievalAnswerPropertyTypeAlias"] != null) { - _passwordRetrievalAnswerPropertyTypeAlias = config["umbracoPasswordRetrievalAnswerPropertyTypeAlias"]; + _passwordRetrievalAnswerPropertyTypeAlias = config["umbracoPasswordRetrievalAnswerPropertyTypeAlias"]; } } @@ -173,29 +191,29 @@ namespace umbraco.providers.members // This is allowed based on the overridden AllowManuallyChangingPassword option. // in order to support updating passwords from the umbraco core, we can't validate the old password - var m = Member.GetMemberFromLoginName(username); + var m = Member.GetMemberFromLoginName(username); if (m == null) return false; - var args = new ValidatePasswordEventArgs(username, newPassword, false); + var args = new ValidatePasswordEventArgs(username, newPassword, false); OnValidatingPassword(args); if (args.Cancel) { if (args.FailureInformation != null) throw args.FailureInformation; - throw new MembershipPasswordException("Change password canceled due to password validation failure."); + throw new MembershipPasswordException("Change password canceled due to password validation failure."); } - string salt; - var encodedPassword = EncryptOrHashNewPassword(newPassword, out salt); - m.ChangePassword( + string salt; + var encodedPassword = EncryptOrHashNewPassword(newPassword, out salt); + m.ChangePassword( FormatPasswordForStorage(encodedPassword, salt)); UpdateMemberProperty(m, _lastPasswordChangedPropertyTypeAlias, DateTime.Now); - m.Save(); - - return true; + m.Save(); + + return true; } /// @@ -210,15 +228,15 @@ namespace umbraco.providers.members /// public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) { - if (!String.IsNullOrEmpty(_passwordRetrievalQuestionPropertyTypeAlias) && !String.IsNullOrEmpty(_passwordRetrievalAnswerPropertyTypeAlias)) + if (!String.IsNullOrEmpty(_passwordRetrievalQuestionPropertyTypeAlias) && !String.IsNullOrEmpty(_passwordRetrievalAnswerPropertyTypeAlias)) { if (ValidateUser(username, password)) { Member m = Member.GetMemberFromLoginName(username); if (m != null) { - UpdateMemberProperty(m, _passwordRetrievalQuestionPropertyTypeAlias, newPasswordQuestion); - UpdateMemberProperty(m, _passwordRetrievalAnswerPropertyTypeAlias, newPasswordAnswer); + UpdateMemberProperty(m, _passwordRetrievalQuestionPropertyTypeAlias, newPasswordQuestion); + UpdateMemberProperty(m, _passwordRetrievalAnswerPropertyTypeAlias, newPasswordAnswer); m.Save(); return true; } @@ -256,23 +274,23 @@ namespace umbraco.providers.members public MembershipUser CreateUser(string memberTypeAlias, string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) { - - var args = new ValidatePasswordEventArgs(username, password, true); - OnValidatingPassword(args); - if (args.Cancel) - { - status = MembershipCreateStatus.InvalidPassword; - return null; - } - + + var args = new ValidatePasswordEventArgs(username, password, true); + OnValidatingPassword(args); + if (args.Cancel) + { + status = MembershipCreateStatus.InvalidPassword; + return null; + } + if (Member.GetMemberFromLoginName(username) != null) - { + { status = MembershipCreateStatus.DuplicateUserName; - } + } else if (Member.GetMemberFromEmail(email) != null && RequiresUniqueEmail) - { + { status = MembershipCreateStatus.DuplicateEmail; - } + } else { var memberType = MemberType.GetByAlias(memberTypeAlias); @@ -281,30 +299,33 @@ namespace umbraco.providers.members throw new InvalidOperationException("Could not find a member type with alias " + memberTypeAlias + ". Ensure your membership provider configuration is up to date and that the default member type exists."); } - var m = Member.MakeNew(username, email, memberType, User.GetUser(0)); - - string salt; - var encodedPassword = EncryptOrHashNewPassword(password, out salt); - //set the password on the member - m.ChangePassword( - FormatPasswordForStorage(encodedPassword, salt)); - - var mUser = ConvertToMembershipUser(m); + var m = Member.MakeNew(username, email, memberType, User.GetUser(0)); + string salt; + var encodedPassword = EncryptOrHashNewPassword(password, out salt); + //set the password on the member + m.ChangePassword( + FormatPasswordForStorage(encodedPassword, salt)); + // custom fields - if (string.IsNullOrEmpty(_passwordRetrievalQuestionPropertyTypeAlias) == false) - UpdateMemberProperty(m, _passwordRetrievalQuestionPropertyTypeAlias, passwordQuestion); - - if (string.IsNullOrEmpty(_passwordRetrievalAnswerPropertyTypeAlias) == false) - UpdateMemberProperty(m, _passwordRetrievalAnswerPropertyTypeAlias, passwordAnswer); - - if (string.IsNullOrEmpty(_approvedPropertyTypeAlias) == false) - UpdateMemberProperty(m, _approvedPropertyTypeAlias, isApproved ? 1 : 0); - - if (string.IsNullOrEmpty(_lastLoginPropertyTypeAlias) == false) + if (string.IsNullOrEmpty(_passwordRetrievalQuestionPropertyTypeAlias) == false) { - mUser.LastActivityDate = DateTime.Now; - UpdateMemberProperty(m, _lastLoginPropertyTypeAlias, mUser.LastActivityDate); + UpdateMemberProperty(m, _passwordRetrievalQuestionPropertyTypeAlias, passwordQuestion); + } + + if (string.IsNullOrEmpty(_passwordRetrievalAnswerPropertyTypeAlias) == false) + { + UpdateMemberProperty(m, _passwordRetrievalAnswerPropertyTypeAlias, passwordAnswer); + } + + if (string.IsNullOrEmpty(ApprovedPropertyTypeAlias) == false) + { + UpdateMemberProperty(m, ApprovedPropertyTypeAlias, isApproved ? 1 : 0); + } + + if (string.IsNullOrEmpty(_lastLoginPropertyTypeAlias) == false) + { + UpdateMemberProperty(m, _lastLoginPropertyTypeAlias, DateTime.Now); } if (string.IsNullOrEmpty(_lastPasswordChangedPropertyTypeAlias) == false) @@ -312,6 +333,8 @@ namespace umbraco.providers.members UpdateMemberProperty(m, _lastPasswordChangedPropertyTypeAlias, DateTime.Now); } + var mUser = ConvertToMembershipUser(m); + // save m.Save(); @@ -339,7 +362,7 @@ namespace umbraco.providers.members public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) { - return CreateUser(_defaultMemberTypeAlias, username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); + return CreateUser(_defaultMemberTypeAlias, username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); } /// @@ -377,7 +400,7 @@ namespace umbraco.providers.members var collection = new MembershipUserCollection(); foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) { - collection.Add(ConvertToMembershipUser(m)); + collection.Add(ConvertToMembershipUser(m)); } return collection; } @@ -473,13 +496,13 @@ namespace umbraco.providers.members if (RequiresQuestionAndAnswer) { // check if password answer property alias is set - if (string.IsNullOrEmpty(_passwordRetrievalAnswerPropertyTypeAlias) == false) + if (string.IsNullOrEmpty(_passwordRetrievalAnswerPropertyTypeAlias) == false) { // check if user is locked out - if (string.IsNullOrEmpty(_lockPropertyTypeAlias) == false) + if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) { var isLockedOut = false; - bool.TryParse(GetMemberProperty(m, _lockPropertyTypeAlias, true), out isLockedOut); + bool.TryParse(GetMemberProperty(m, LockPropertyTypeAlias, true), out isLockedOut); if (isLockedOut) { throw new MembershipPasswordException("The supplied user is locked out"); @@ -487,7 +510,7 @@ namespace umbraco.providers.members } // match password answer - if (GetMemberProperty(m, _passwordRetrievalAnswerPropertyTypeAlias, false) != answer) + if (GetMemberProperty(m, _passwordRetrievalAnswerPropertyTypeAlias, false) != answer) { throw new MembershipPasswordException("Incorrect password answer"); } @@ -532,10 +555,20 @@ namespace umbraco.providers.members /// public override MembershipUser GetUser(object providerUserKey, bool userIsOnline) { - if (String.IsNullOrEmpty(providerUserKey.ToString())) - return null; - var m = new Member(Convert.ToInt32(providerUserKey)); - return ConvertToMembershipUser(m); + var asGuid = providerUserKey.TryConvertTo(); + if (asGuid.Success) + { + var m = new Member(asGuid.Result); + return ConvertToMembershipUser(m); + } + var asInt = providerUserKey.TryConvertTo(); + if (asInt.Success) + { + var m = new Member(asInt.Result); + return ConvertToMembershipUser(m); + } + throw new InvalidOperationException("The " + GetType() + " provider only supports GUID or Int as a providerUserKey"); + } @@ -549,7 +582,7 @@ namespace umbraco.providers.members public override string GetUserNameByEmail(string email) { Member m = Member.GetMemberFromEmail(email); - return m == null ? null : m.LoginName; + return m == null ? null : m.LoginName; } /// @@ -560,66 +593,66 @@ namespace umbraco.providers.members /// The new password for the specified user. public override string ResetPassword(string username, string answer) { - if (EnablePasswordReset == false) + if (EnablePasswordReset == false) { throw new NotSupportedException("Password reset is not supported"); } - //TODO: This should be here - but how do we update failure count in this provider?? - //if (answer == null && RequiresQuestionAndAnswer) - //{ - // UpdateFailureCount(username, "passwordAnswer"); - - // throw new ProviderException("Password answer required for password reset."); - //} - - var newPassword = Membership.GeneratePassword(MinRequiredPasswordLength, MinRequiredNonAlphanumericCharacters); - - var args = new ValidatePasswordEventArgs(username, newPassword, true); - OnValidatingPassword(args); - if (args.Cancel) - { - if (args.FailureInformation != null) - throw args.FailureInformation; - throw new MembershipPasswordException("Reset password canceled due to password validation failure."); - } - - var m = Member.GetMemberFromLoginName(username); + //TODO: This should be here - but how do we update failure count in this provider?? + //if (answer == null && RequiresQuestionAndAnswer) + //{ + // UpdateFailureCount(username, "passwordAnswer"); + + // throw new ProviderException("Password answer required for password reset."); + //} + + var newPassword = Membership.GeneratePassword(MinRequiredPasswordLength, MinRequiredNonAlphanumericCharacters); + + var args = new ValidatePasswordEventArgs(username, newPassword, true); + OnValidatingPassword(args); + if (args.Cancel) + { + if (args.FailureInformation != null) + throw args.FailureInformation; + throw new MembershipPasswordException("Reset password canceled due to password validation failure."); + } + + var m = Member.GetMemberFromLoginName(username); if (m == null) throw new MembershipPasswordException("The supplied user is not found"); - - if (RequiresQuestionAndAnswer) + + if (RequiresQuestionAndAnswer) { - // check if password answer property alias is set - if (string.IsNullOrEmpty(_passwordRetrievalAnswerPropertyTypeAlias) == false) + // check if password answer property alias is set + if (string.IsNullOrEmpty(_passwordRetrievalAnswerPropertyTypeAlias) == false) { - // check if user is locked out - if (string.IsNullOrEmpty(_lockPropertyTypeAlias) == false) + // check if user is locked out + if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) { - var isLockedOut = false; - bool.TryParse(GetMemberProperty(m, _lockPropertyTypeAlias, true), out isLockedOut); - if (isLockedOut) + var isLockedOut = false; + bool.TryParse(GetMemberProperty(m, LockPropertyTypeAlias, true), out isLockedOut); + if (isLockedOut) { - throw new MembershipPasswordException("The supplied user is locked out"); + throw new MembershipPasswordException("The supplied user is locked out"); } } - - // match password answer - if (GetMemberProperty(m, _passwordRetrievalAnswerPropertyTypeAlias, false) != answer) + + // match password answer + if (GetMemberProperty(m, _passwordRetrievalAnswerPropertyTypeAlias, false) != answer) { - throw new MembershipPasswordException("Incorrect password answer"); + throw new MembershipPasswordException("Incorrect password answer"); } } - else - { - throw new ProviderException("Password retrieval answer property alias is not set! To automatically support password question/answers you'll need to add references to the membertype properties in the 'Member' element in web.config by adding their aliases to the 'umbracoPasswordRetrievalQuestionPropertyTypeAlias' and 'umbracoPasswordRetrievalAnswerPropertyTypeAlias' attributes"); - } + else + { + throw new ProviderException("Password retrieval answer property alias is not set! To automatically support password question/answers you'll need to add references to the membertype properties in the 'Member' element in web.config by adding their aliases to the 'umbracoPasswordRetrievalQuestionPropertyTypeAlias' and 'umbracoPasswordRetrievalAnswerPropertyTypeAlias' attributes"); + } } - - string salt; - var encodedPassword = EncryptOrHashNewPassword(newPassword, out salt); - //set the password on the member - m.ChangePassword( + + string salt; + var encodedPassword = EncryptOrHashNewPassword(newPassword, out salt); + //set the password on the member + m.ChangePassword( FormatPasswordForStorage(encodedPassword, salt)); if (string.IsNullOrEmpty(_lastPasswordChangedPropertyTypeAlias) == false) @@ -627,9 +660,9 @@ namespace umbraco.providers.members UpdateMemberProperty(m, _lastPasswordChangedPropertyTypeAlias, DateTime.Now); } - m.Save(); - - return newPassword; + m.Save(); + + return newPassword; } /// @@ -641,18 +674,18 @@ namespace umbraco.providers.members /// public override bool UnlockUser(string userName) { - if (string.IsNullOrEmpty(_lockPropertyTypeAlias) == false) + if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) { - var m = Member.GetMemberFromLoginName(userName); + var m = Member.GetMemberFromLoginName(userName); if (m != null) { - UpdateMemberProperty(m, _lockPropertyTypeAlias, 0); - m.Save(); + UpdateMemberProperty(m, LockPropertyTypeAlias, 0); + m.Save(); return true; } - throw new Exception(String.Format("No member with the username '{0}' found", userName)); + throw new Exception(String.Format("No member with the username '{0}' found", userName)); } - throw new ProviderException("To enable lock/unlocking, you need to add a 'bool' property on your membertype and add the alias of the property in the 'umbracoLockPropertyTypeAlias' attribute of the membership element in the web.config."); + throw new ProviderException("To enable lock/unlocking, you need to add a 'bool' property on your membertype and add the alias of the property in the 'umbracoLockPropertyTypeAlias' attribute of the membership element in the web.config."); } /// @@ -661,28 +694,28 @@ namespace umbraco.providers.members /// A object that represents the user to update and the updated information for the user. public override void UpdateUser(MembershipUser user) { - var m = Member.GetMemberFromLoginName(user.UserName); + var m = Member.GetMemberFromLoginName(user.UserName); m.Email = user.Email; // if supported, update approve status - UpdateMemberProperty(m, _approvedPropertyTypeAlias, user.IsApproved ? 1 : 0); + UpdateMemberProperty(m, ApprovedPropertyTypeAlias, user.IsApproved ? 1 : 0); // if supported, update lock status - UpdateMemberProperty(m, _lockPropertyTypeAlias, user.IsLockedOut ? 1 : 0); + UpdateMemberProperty(m, LockPropertyTypeAlias, user.IsLockedOut ? 1 : 0); if (user.IsLockedOut) { UpdateMemberProperty(m, _lastLockedOutPropertyTypeAlias, DateTime.Now); - } + } // if supported, update comment - UpdateMemberProperty(m, _commentPropertyTypeAlias, user.Comment); + UpdateMemberProperty(m, CommentPropertyTypeAlias, user.Comment); m.Save(); } private static void UpdateMemberProperty(Member m, string propertyAlias, object propertyValue) { - if (string.IsNullOrEmpty(propertyAlias) == false) + if (string.IsNullOrEmpty(propertyAlias) == false) { if (m.getProperty(propertyAlias) != null) { @@ -693,7 +726,7 @@ namespace umbraco.providers.members private static string GetMemberProperty(Member m, string propertyAlias, bool isBool) { - if (string.IsNullOrEmpty(propertyAlias) == false) + if (string.IsNullOrEmpty(propertyAlias) == false) { if (m.getProperty(propertyAlias) != null && m.getProperty(propertyAlias).Value != null) @@ -709,26 +742,26 @@ namespace umbraco.providers.members return null; } - - private static string GetMemberProperty(IMember m, string propertyAlias, bool isBool) - { - if (string.IsNullOrEmpty(propertyAlias) == false) - { - if (m.Properties[propertyAlias] != null && - m.Properties[propertyAlias].Value != null) - { - if (isBool) - { - // Umbraco stored true as 1, which means it can be bool.tryParse'd - return m.Properties[propertyAlias].Value.ToString().Replace("1", "true").Replace("0", "false"); - } - return m.Properties[propertyAlias].Value.ToString(); - } - } - - return null; - } - + + private static string GetMemberProperty(IMember m, string propertyAlias, bool isBool) + { + if (string.IsNullOrEmpty(propertyAlias) == false) + { + if (m.Properties[propertyAlias] != null && + m.Properties[propertyAlias].Value != null) + { + if (isBool) + { + // Umbraco stored true as 1, which means it can be bool.tryParse'd + return m.Properties[propertyAlias].Value.ToString().Replace("1", "true").Replace("0", "false"); + } + return m.Properties[propertyAlias].Value.ToString(); + } + } + + return null; + } + /// /// Verifies that the specified user name and password exist in the data source. /// @@ -739,16 +772,16 @@ namespace umbraco.providers.members /// public override bool ValidateUser(string username, string password) { - var m = Member.GetMemberFromLoginAndEncodedPassword(username, EncryptOrHashExistingPassword(password)); + var m = Member.GetMemberFromLoginAndEncodedPassword(username, EncryptOrHashExistingPassword(password)); if (m != null) { // check for lock status. If locked, then set the member property to null - if (string.IsNullOrEmpty(_lockPropertyTypeAlias) == false) + if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) { - string lockedStatus = GetMemberProperty(m, _lockPropertyTypeAlias, true); - if (string.IsNullOrEmpty(lockedStatus) == false) + string lockedStatus = GetMemberProperty(m, LockPropertyTypeAlias, true); + if (string.IsNullOrEmpty(lockedStatus) == false) { - var isLocked = false; + var isLocked = false; if (bool.TryParse(lockedStatus, out isLocked)) { if (isLocked) @@ -759,44 +792,44 @@ namespace umbraco.providers.members } } - // check for approve status. If not approved, then set the member property to null - if (!CheckApproveStatus(m)) { + //check for approve status. If not approved, then set the member property to null + if (m != null && !CheckApproveStatus(m)) { m = null; } // maybe update login date - if (m != null && string.IsNullOrEmpty(_lastLoginPropertyTypeAlias) == false) + if (m != null && string.IsNullOrEmpty(_lastLoginPropertyTypeAlias) == false) { - UpdateMemberProperty(m, _lastLoginPropertyTypeAlias, DateTime.Now); + UpdateMemberProperty(m, _lastLoginPropertyTypeAlias, DateTime.Now); } // maybe reset password attempts - if (m != null && string.IsNullOrEmpty(_failedPasswordAttemptsPropertyTypeAlias) == false) + if (m != null && string.IsNullOrEmpty(_failedPasswordAttemptsPropertyTypeAlias) == false) { - UpdateMemberProperty(m, _failedPasswordAttemptsPropertyTypeAlias, 0); + UpdateMemberProperty(m, _failedPasswordAttemptsPropertyTypeAlias, 0); } // persist data if (m != null) m.Save(); } - else if (string.IsNullOrEmpty(_lockPropertyTypeAlias) == false - && string.IsNullOrEmpty(_failedPasswordAttemptsPropertyTypeAlias) == false) + else if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false + && string.IsNullOrEmpty(_failedPasswordAttemptsPropertyTypeAlias) == false) { - var updateMemberDataObject = Member.GetMemberFromLoginName(username); + var updateMemberDataObject = Member.GetMemberFromLoginName(username); // update fail rate if it's approved if (updateMemberDataObject != null && CheckApproveStatus(updateMemberDataObject)) { int failedAttempts = 0; - int.TryParse(GetMemberProperty(updateMemberDataObject, _failedPasswordAttemptsPropertyTypeAlias, false), out failedAttempts); + int.TryParse(GetMemberProperty(updateMemberDataObject, _failedPasswordAttemptsPropertyTypeAlias, false), out failedAttempts); failedAttempts = failedAttempts+1; - UpdateMemberProperty(updateMemberDataObject, _failedPasswordAttemptsPropertyTypeAlias, failedAttempts); + UpdateMemberProperty(updateMemberDataObject, _failedPasswordAttemptsPropertyTypeAlias, failedAttempts); // lock user? if (failedAttempts >= MaxInvalidPasswordAttempts) { - UpdateMemberProperty(updateMemberDataObject, _lockPropertyTypeAlias, 1); - UpdateMemberProperty(updateMemberDataObject, _lastLockedOutPropertyTypeAlias, DateTime.Now); + UpdateMemberProperty(updateMemberDataObject, LockPropertyTypeAlias, 1); + UpdateMemberProperty(updateMemberDataObject, _lastLockedOutPropertyTypeAlias, DateTime.Now); } updateMemberDataObject.Save(); } @@ -807,13 +840,13 @@ namespace umbraco.providers.members private bool CheckApproveStatus(Member m) { - var isApproved = false; - if (string.IsNullOrEmpty(_approvedPropertyTypeAlias) == false) + var isApproved = false; + if (string.IsNullOrEmpty(ApprovedPropertyTypeAlias) == false) { if (m != null) { - var approveStatus = GetMemberProperty(m, _approvedPropertyTypeAlias, true); - if (string.IsNullOrEmpty(approveStatus) == false) + var approveStatus = GetMemberProperty(m, ApprovedPropertyTypeAlias, true); + if (string.IsNullOrEmpty(approveStatus) == false) { //try parsing as bool first (just in case) if (bool.TryParse(approveStatus, out isApproved) == false) @@ -826,6 +859,11 @@ namespace umbraco.providers.members } } } + else + { + //There is no property so we shouldn't use the approve status + isApproved = true; + } } } else { @@ -837,7 +875,7 @@ namespace umbraco.providers.members #endregion #region Helper Methods - + /// /// Checks the password. /// @@ -852,10 +890,10 @@ namespace umbraco.providers.members switch (PasswordFormat) { case MembershipPasswordFormat.Encrypted: - pass2 = DecodePassword(dbPassword); + pass2 = DecodePassword(dbPassword); break; case MembershipPasswordFormat.Hashed: - pass1 = EncryptOrHashExistingPassword(password); + pass1 = EncryptOrHashExistingPassword(password); break; default: break; @@ -869,10 +907,10 @@ namespace umbraco.providers.members /// /// The password. /// The encoded password. - [Obsolete("Do not use this, it is the legacy way to encode a password")] + [Obsolete("Do not use this, it is the legacy way to encode a password")] public string EncodePassword(string password) { - return LegacyEncodePassword(password); + return LegacyEncodePassword(password); } /// @@ -880,10 +918,10 @@ namespace umbraco.providers.members /// /// The encoded password. /// The unencoded password. - [Obsolete("Do not use this, it is the legacy way to decode a password")] + [Obsolete("Do not use this, it is the legacy way to decode a password")] public string UnEncodePassword(string encodedPassword) { - return LegacyUnEncodePassword(encodedPassword); + return LegacyUnEncodePassword(encodedPassword); } /// @@ -894,130 +932,100 @@ namespace umbraco.providers.members private MembershipUser ConvertToMembershipUser(Member m) { if (m == null) return null; - + var lastLogin = DateTime.Now; - var lastLocked = DateTime.MinValue; - var isApproved = true; - var isLocked = false; - var comment = ""; - var passwordQuestion = ""; - - // last login - if (string.IsNullOrEmpty(_lastLoginPropertyTypeAlias) == false) - { - DateTime.TryParse(GetMemberProperty(m, _lastLoginPropertyTypeAlias, false), out lastLogin); - } - // approved - if (string.IsNullOrEmpty(_approvedPropertyTypeAlias) == false) - { - bool.TryParse(GetMemberProperty(m, _approvedPropertyTypeAlias, true), out isApproved); - } - // locked - if (string.IsNullOrEmpty(_lockPropertyTypeAlias) == false) - { - bool.TryParse(GetMemberProperty(m, _lockPropertyTypeAlias, true), out isLocked); + var lastLocked = DateTime.MinValue; + var isApproved = true; + var isLocked = false; + var comment = ""; + var passwordQuestion = ""; + + // last login + if (string.IsNullOrEmpty(_lastLoginPropertyTypeAlias) == false) + { + DateTime.TryParse(GetMemberProperty(m, _lastLoginPropertyTypeAlias, false), out lastLogin); + } + // approved + if (string.IsNullOrEmpty(ApprovedPropertyTypeAlias) == false) + { + bool.TryParse(GetMemberProperty(m, ApprovedPropertyTypeAlias, true), out isApproved); + } + // locked + if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) + { + bool.TryParse(GetMemberProperty(m, LockPropertyTypeAlias, true), out isLocked); } // last locked if (string.IsNullOrEmpty(_lastLockedOutPropertyTypeAlias) == false) { DateTime.TryParse(GetMemberProperty(m, _lastLockedOutPropertyTypeAlias, false), out lastLocked); - } - // comment - if (string.IsNullOrEmpty(_commentPropertyTypeAlias) == false) - { - comment = GetMemberProperty(m, _commentPropertyTypeAlias, false); - } - // password question - if (string.IsNullOrEmpty(_passwordRetrievalQuestionPropertyTypeAlias) == false) + } + // comment + if (string.IsNullOrEmpty(CommentPropertyTypeAlias) == false) { - passwordQuestion = GetMemberProperty(m, _passwordRetrievalQuestionPropertyTypeAlias, false); - } + comment = GetMemberProperty(m, CommentPropertyTypeAlias, false); + } + // password question + if (string.IsNullOrEmpty(_passwordRetrievalQuestionPropertyTypeAlias) == false) + { + passwordQuestion = GetMemberProperty(m, _passwordRetrievalQuestionPropertyTypeAlias, false); + } return new MembershipUser(_providerName, m.LoginName, m.Id, m.Email, passwordQuestion, comment, isApproved, isLocked, m.CreateDateTime, lastLogin, - DateTime.Now, DateTime.Now, lastLocked); - } + DateTime.Now, DateTime.Now, lastLocked); + } + + /// + /// Converts to membership user. + /// + /// The m. + /// + private MembershipUser ConvertToMembershipUser(IMember m) + { + if (m == null) return null; - /// - /// Converts to membership user. - /// - /// The m. - /// - private MembershipUser ConvertToMembershipUser(IMember m) - { - if (m == null) return null; - var lastLogin = DateTime.Now; - var lastLocked = DateTime.MinValue; - var isApproved = true; - var isLocked = false; - var comment = ""; - var passwordQuestion = ""; - - // last login - if (string.IsNullOrEmpty(_lastLoginPropertyTypeAlias) == false) - { - DateTime.TryParse(GetMemberProperty(m, _lastLoginPropertyTypeAlias, false), out lastLogin); - } - // approved - if (string.IsNullOrEmpty(_approvedPropertyTypeAlias) == false) - { - bool.TryParse(GetMemberProperty(m, _approvedPropertyTypeAlias, true), out isApproved); - } - // locked - if (string.IsNullOrEmpty(_lockPropertyTypeAlias) == false) - { - bool.TryParse(GetMemberProperty(m, _lockPropertyTypeAlias, true), out isLocked); + var lastLocked = DateTime.MinValue; + var isApproved = true; + var isLocked = false; + var comment = ""; + var passwordQuestion = ""; + + // last login + if (string.IsNullOrEmpty(_lastLoginPropertyTypeAlias) == false) + { + DateTime.TryParse(GetMemberProperty(m, _lastLoginPropertyTypeAlias, false), out lastLogin); + } + // approved + if (string.IsNullOrEmpty(ApprovedPropertyTypeAlias) == false) + { + bool.TryParse(GetMemberProperty(m, ApprovedPropertyTypeAlias, true), out isApproved); + } + // locked + if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) + { + bool.TryParse(GetMemberProperty(m, LockPropertyTypeAlias, true), out isLocked); } // last locked if (string.IsNullOrEmpty(_lastLockedOutPropertyTypeAlias) == false) { DateTime.TryParse(GetMemberProperty(m, _lastLockedOutPropertyTypeAlias, false), out lastLocked); - } - // comment - if (string.IsNullOrEmpty(_commentPropertyTypeAlias) == false) - { - comment = GetMemberProperty(m, _commentPropertyTypeAlias, false); } - // password question - if (string.IsNullOrEmpty(_passwordRetrievalQuestionPropertyTypeAlias) == false) - { - passwordQuestion = GetMemberProperty(m, _passwordRetrievalQuestionPropertyTypeAlias, false); - } - + // comment + if (string.IsNullOrEmpty(CommentPropertyTypeAlias) == false) + { + comment = GetMemberProperty(m, CommentPropertyTypeAlias, false); + } + // password question + if (string.IsNullOrEmpty(_passwordRetrievalQuestionPropertyTypeAlias) == false) + { + passwordQuestion = GetMemberProperty(m, _passwordRetrievalQuestionPropertyTypeAlias, false); + } + return new MembershipUser(_providerName, m.Username, m.Id, m.Email, passwordQuestion, comment, isApproved, isLocked, m.CreateDate, lastLogin, - DateTime.Now, DateTime.Now, lastLocked); + DateTime.Now, DateTime.Now, lastLocked); } #endregion } - - //TODO: We need to re-enable this in 6.2, but need to back port most of the membership provider changes (still in a custom branch atm) - - ///// - ///// Adds some event handling - ///// - //public class MembershipEventHandler : ApplicationEventHandler - //{ - // protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - // { - // Member.New += Member_New; - // } - - // void Member_New(Member sender, NewEventArgs e) - // { - // //This is a bit of a hack to ensure that the member is approved when created since many people will be using - // // this old api to create members on the front-end and they need to be approved - which is based on whether or not - // // the Umbraco membership provider is configured. - // var provider = Membership.Provider as UmbracoMembershipProvider; - // if (provider != null) - // { - // var approvedField = provider.ApprovedPropertyTypeAlias; - // var property = sender.getProperty(approvedField); - // if (property != null) - // { - // property.Value = 1; - // } - // } - // } - //} } diff --git a/src/umbraco.providers/members/MembershipEventHandler.cs b/src/umbraco.providers/members/MembershipEventHandler.cs new file mode 100644 index 0000000000..71330dd8e0 --- /dev/null +++ b/src/umbraco.providers/members/MembershipEventHandler.cs @@ -0,0 +1,35 @@ +using System.Web.Security; +using Umbraco.Core; +using umbraco.cms.businesslogic; +using umbraco.cms.businesslogic.member; + +namespace umbraco.providers.members +{ + /// + /// Adds some event handling + /// + public class MembershipEventHandler : ApplicationEventHandler + { + protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) + { + Member.New += Member_New; + } + + void Member_New(Member sender, NewEventArgs e) + { + //This is a bit of a hack to ensure that the member is approved when created since many people will be using + // this old api to create members on the front-end and they need to be approved - which is based on whether or not + // the Umbraco membership provider is configured. + var provider = Membership.Provider as UmbracoMembershipProvider; + if (provider != null) + { + var approvedField = provider.ApprovedPropertyTypeAlias; + var property = sender.getProperty(approvedField); + if (property != null) + { + property.Value = 1; + } + } + } + } +} \ No newline at end of file diff --git a/src/umbraco.providers/umbraco.providers.csproj b/src/umbraco.providers/umbraco.providers.csproj index 2a8c4503be..de33276993 100644 --- a/src/umbraco.providers/umbraco.providers.csproj +++ b/src/umbraco.providers/umbraco.providers.csproj @@ -79,6 +79,7 @@ Properties\SolutionInfo.cs + From f11d4fbeddfd58ac51c080d7e23c2c3eb5a0038a Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 16 Dec 2013 12:51:02 +1100 Subject: [PATCH 0069/1046] Worked on the application cache, added request cache, changed some stuff over to be interfaces, added unit tests suite (need mroe) to test all caching providers. Conflicts: src/Umbraco.Core/Cache/CacheProviderBase.cs src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs src/Umbraco.Core/Cache/NullCacheProvider.cs src/Umbraco.Core/Cache/StaticCacheProvider.cs --- src/Umbraco.Core/Cache/CacheProviderBase.cs | 25 --- .../Cache/DictionaryCacheProdiverBase.cs | 178 ++++++++++++++++ .../Cache/DictionaryCacheWrapper.cs | 45 ++++ .../Cache/DictionaryItemWrapper.cs | 14 ++ .../Cache/HttpRequestCacheProvider.cs | 50 +++++ .../Cache/HttpRuntimeCacheProvider.cs | 177 +++------------- src/Umbraco.Core/Cache/ICacheProvider.cs | 24 +++ .../Cache/IRuntimeCacheProvider.cs | 24 +++ src/Umbraco.Core/Cache/NullCacheProvider.cs | 34 +-- .../Cache/ObjectCacheRuntimeCacheProvider.cs | 198 ++++++++++++++++++ .../Cache/RuntimeCacheProviderBase.cs | 24 --- src/Umbraco.Core/Cache/StaticCacheProvider.cs | 75 ++++--- src/Umbraco.Core/CacheHelper.cs | 172 ++++++++++++++- src/Umbraco.Core/Umbraco.Core.csproj | 9 +- src/Umbraco.Tests/Cache/CacheHelperTests.cs | 86 -------- src/Umbraco.Tests/Cache/CacheProviderTests.cs | 63 ++++++ .../Cache/HttpRequestCacheProviderTests.cs | 52 +++++ .../Cache/HttpRuntimeCacheProviderTests.cs | 33 +++ .../Cache/ObjectCacheProviderTests.cs | 36 ++++ .../Cache/RuntimeCacheProviderTests.cs | 30 +++ src/Umbraco.Tests/Umbraco.Tests.csproj | 7 +- 21 files changed, 1012 insertions(+), 344 deletions(-) delete mode 100644 src/Umbraco.Core/Cache/CacheProviderBase.cs create mode 100644 src/Umbraco.Core/Cache/DictionaryCacheProdiverBase.cs create mode 100644 src/Umbraco.Core/Cache/DictionaryCacheWrapper.cs create mode 100644 src/Umbraco.Core/Cache/DictionaryItemWrapper.cs create mode 100644 src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs create mode 100644 src/Umbraco.Core/Cache/ICacheProvider.cs create mode 100644 src/Umbraco.Core/Cache/IRuntimeCacheProvider.cs create mode 100644 src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs delete mode 100644 src/Umbraco.Core/Cache/RuntimeCacheProviderBase.cs delete mode 100644 src/Umbraco.Tests/Cache/CacheHelperTests.cs create mode 100644 src/Umbraco.Tests/Cache/CacheProviderTests.cs create mode 100644 src/Umbraco.Tests/Cache/HttpRequestCacheProviderTests.cs create mode 100644 src/Umbraco.Tests/Cache/HttpRuntimeCacheProviderTests.cs create mode 100644 src/Umbraco.Tests/Cache/ObjectCacheProviderTests.cs create mode 100644 src/Umbraco.Tests/Cache/RuntimeCacheProviderTests.cs diff --git a/src/Umbraco.Core/Cache/CacheProviderBase.cs b/src/Umbraco.Core/Cache/CacheProviderBase.cs deleted file mode 100644 index 012d8b23b2..0000000000 --- a/src/Umbraco.Core/Cache/CacheProviderBase.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Umbraco.Core.Cache -{ - /// - /// An abstract class for implementing a basic cache provider - /// - /// - /// THIS MUST REMAIN INTERNAL UNTIL WE STREAMLINE HOW ALL CACHE IS HANDLED, WE NEED TO SUPPORT HTTP RUNTIME CACHE, IN MEMORY CACHE, ETC... - /// - internal abstract class CacheProviderBase - { - public abstract void ClearAllCache(); - public abstract void ClearCacheItem(string key); - public abstract void ClearCacheObjectTypes(string typeName); - public abstract void ClearCacheObjectTypes(); - public abstract void ClearCacheObjectTypes(Func predicate); - public abstract void ClearCacheByKeySearch(string keyStartsWith); - public abstract void ClearCacheByKeyExpression(string regexString); - public abstract IEnumerable GetCacheItemsByKeySearch(string keyStartsWith); - public abstract T GetCacheItem(string cacheKey); - public abstract T GetCacheItem(string cacheKey, Func getCacheItem); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/DictionaryCacheProdiverBase.cs b/src/Umbraco.Core/Cache/DictionaryCacheProdiverBase.cs new file mode 100644 index 0000000000..adc02f9104 --- /dev/null +++ b/src/Umbraco.Core/Cache/DictionaryCacheProdiverBase.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Umbraco.Core.Cache +{ + internal abstract class DictionaryCacheProdiverBase : ICacheProvider + { + private static readonly object Locker = new object(); + protected abstract DictionaryCacheWrapper DictionaryCache { get; } + + /// + /// Clears everything in umbraco's runtime cache + /// + /// + /// Does not clear other stuff the user has put in httpruntime.cache! + /// + public virtual void ClearAllCache() + { + lock (Locker) + { + var keysToRemove = DictionaryCache.Cast() + .Select(item => new DictionaryItemWrapper(item)) + .Where(c => c.Key is string && ((string)c.Key).StartsWith(CacheItemPrefix) && DictionaryCache[c.Key.ToString()] != null) + .Select(c => c.Key) + .ToList(); + + foreach (var k in keysToRemove) + { + DictionaryCache.Remove(k); + } + } + } + + /// + /// Clears the item in umbraco's runtime cache with the given key + /// + /// Key + public virtual void ClearCacheItem(string key) + { + lock (Locker) + { + if (DictionaryCache[GetCacheKey(key)] == null) return; + DictionaryCache.Remove(GetCacheKey(key)); ; + } + } + + /// + /// Clears all objects in the System.Web.Cache with the System.Type name as the + /// input parameter. (using [object].GetType()) + /// + /// The name of the System.Type which should be cleared from cache ex "System.Xml.XmlDocument" + public virtual void ClearCacheObjectTypes(string typeName) + { + lock (Locker) + { + var keysToRemove = DictionaryCache.Cast() + .Select(item => new DictionaryItemWrapper(item)) + .Where(c => DictionaryCache[c.Key.ToString()] != null && DictionaryCache[c.Key.ToString()].GetType().ToString().InvariantEquals(typeName)) + .Select(c => c.Key) + .ToList(); + + foreach (var k in keysToRemove) + { + DictionaryCache.Remove(k); + } + } + } + + /// + /// Clears all objects in the System.Web.Cache with the System.Type specified + /// + public virtual void ClearCacheObjectTypes() + { + lock (Locker) + { + var keysToRemove = DictionaryCache.Cast() + .Select(item => new DictionaryItemWrapper(item)) + .Where(c => DictionaryCache[c.Key.ToString()] != null && DictionaryCache[c.Key.ToString()].GetType() == typeof (T)) + .Select(c => c.Key) + .ToList(); + + foreach (var k in keysToRemove) + { + DictionaryCache.Remove(k); + } + + } + } + + /// + /// Clears all cache items that starts with the key passed. + /// + /// The start of the key + public virtual void ClearCacheByKeySearch(string keyStartsWith) + { + var keysToRemove = DictionaryCache.Cast() + .Select(item => new DictionaryItemWrapper(item)) + .Where(c => c.Key is string && ((string)c.Key).InvariantStartsWith(string.Format("{0}-{1}", CacheItemPrefix, keyStartsWith))) + .Select(c => c.Key) + .ToList(); + + foreach (var k in keysToRemove) + { + DictionaryCache.Remove(k); + } + } + + /// + /// Clears all cache items that have a key that matches the regular expression + /// + /// + public virtual void ClearCacheByKeyExpression(string regexString) + { + var keysToRemove = new List(); + foreach (var item in DictionaryCache) + { + var c = new DictionaryItemWrapper(item); + var s = c.Key as string; + if (s != null) + { + var withoutPrefix = s.TrimStart(string.Format("{0}-", CacheItemPrefix)); + if (Regex.IsMatch(withoutPrefix, regexString)) + { + keysToRemove.Add(c.Key); + } + } + } + + foreach (var k in keysToRemove) + { + DictionaryCache.Remove(k); + } + } + + public virtual IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) + { + return (from object item in DictionaryCache + select new DictionaryItemWrapper(item) + into c + where c.Key is string && ((string) c.Key).InvariantStartsWith(string.Format("{0}-{1}", CacheItemPrefix, keyStartsWith)) + select c.Value.TryConvertTo() + into converted + where converted.Success + select converted.Result).ToList(); + } + + /// + /// Returns a cache item by key, does not update the cache if it isn't there. + /// + /// + /// + /// + public virtual TT GetCacheItem(string cacheKey) + { + var result = DictionaryCache.Get(GetCacheKey(cacheKey)); + if (result == null) + { + return default(TT); + } + return result.TryConvertTo().Result; + } + + public abstract T GetCacheItem(string cacheKey, Func getCacheItem); + + /// + /// We prefix all cache keys with this so that we know which ones this class has created when + /// using the HttpRuntime cache so that when we clear it we don't clear other entries we didn't create. + /// + protected const string CacheItemPrefix = "umbrtmche"; + + protected string GetCacheKey(string key) + { + return string.Format("{0}-{1}", CacheItemPrefix, key); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/DictionaryCacheWrapper.cs b/src/Umbraco.Core/Cache/DictionaryCacheWrapper.cs new file mode 100644 index 0000000000..8203b6e481 --- /dev/null +++ b/src/Umbraco.Core/Cache/DictionaryCacheWrapper.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections; + +namespace Umbraco.Core.Cache +{ + internal class DictionaryCacheWrapper : IEnumerable + { + private readonly IEnumerable _inner; + private readonly Func _get; + private readonly Action _remove; + + public DictionaryCacheWrapper( + IEnumerable inner, + Func get, + Action remove) + { + _inner = inner; + _get = get; + _remove = remove; + } + + public object this[object key] + { + get + { + return Get(key); + } + } + + public object Get(object key) + { + return _get(key); + } + + public void Remove(object key) + { + _remove(key); + } + + public IEnumerator GetEnumerator() + { + return _inner.GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/DictionaryItemWrapper.cs b/src/Umbraco.Core/Cache/DictionaryItemWrapper.cs new file mode 100644 index 0000000000..4a1ecaf9ec --- /dev/null +++ b/src/Umbraco.Core/Cache/DictionaryItemWrapper.cs @@ -0,0 +1,14 @@ +namespace Umbraco.Core.Cache +{ + internal class DictionaryItemWrapper + { + public DictionaryItemWrapper(dynamic item) + { + Key = item.Key; + Value = item.Value; + } + + public object Key { get; private set; } + public object Value { get; private set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs b/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs new file mode 100644 index 0000000000..134b780f2b --- /dev/null +++ b/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Web; + +namespace Umbraco.Core.Cache +{ + /// + /// A cache provider that caches items in the HttpContext.Items + /// + internal class HttpRequestCacheProvider : DictionaryCacheProdiverBase + { + private readonly Func _context; + + public HttpRequestCacheProvider(HttpContext context) + { + _context = () => new HttpContextWrapper(context); + } + + public HttpRequestCacheProvider(Func context) + { + _context = context; + } + + protected override DictionaryCacheWrapper DictionaryCache + { + get + { + var ctx = _context(); + return new DictionaryCacheWrapper( + ctx.Items, + o => ctx.Items[o], + o => ctx.Items.Remove(o)); + } + } + + public override T GetCacheItem(string cacheKey, Func getCacheItem) + { + var ctx = _context(); + var ck = GetCacheKey(cacheKey); + if (ctx.Items[ck] == null) + { + ctx.Items[ck] = getCacheItem(); + } + return (T)ctx.Items[ck]; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs index f5fc0e237f..02b4aa46d2 100644 --- a/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs @@ -1,109 +1,27 @@ using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; +using System.Web; using System.Web.Caching; using Umbraco.Core.Logging; +using CacheItemPriority = System.Web.Caching.CacheItemPriority; namespace Umbraco.Core.Cache { /// /// A CacheProvider that wraps the logic of the HttpRuntime.Cache /// - internal class HttpRuntimeCacheProvider : RuntimeCacheProviderBase + internal class HttpRuntimeCacheProvider : DictionaryCacheProdiverBase, IRuntimeCacheProvider { private readonly System.Web.Caching.Cache _cache; + private readonly DictionaryCacheWrapper _wrapper; private static readonly object Locker = new object(); - + public HttpRuntimeCacheProvider(System.Web.Caching.Cache cache) { _cache = cache; + _wrapper = new DictionaryCacheWrapper(_cache, s => _cache.Get(s.ToString()), o => _cache.Remove(o.ToString())); } - /// - /// Clears everything in umbraco's runtime cache, which means that not only - /// umbraco content is removed, but also other cache items from pages running in - /// the same application / website. Use with care :-) - /// - public override void ClearAllCache() - { - var cacheEnumerator = _cache.GetEnumerator(); - while (cacheEnumerator.MoveNext()) - { - _cache.Remove(cacheEnumerator.Key.ToString()); - } - } - - /// - /// Clears the item in umbraco's runtime cache with the given key - /// - /// Key - public override void ClearCacheItem(string key) - { - // NH 10 jan 2012 - // Patch by the always wonderful Stéphane Gay to avoid cache null refs - lock (Locker) - { - if (_cache[key] == null) return; - _cache.Remove(key); ; - } - } - - - /// - /// Clears all objects in the System.Web.Cache with the System.Type name as the - /// input parameter. (using [object].GetType()) - /// - /// The name of the System.Type which should be cleared from cache ex "System.Xml.XmlDocument" - public override void ClearCacheObjectTypes(string typeName) - { - try - { - lock (Locker) - { - foreach (DictionaryEntry c in _cache) - { - if (_cache[c.Key.ToString()] != null - && _cache[c.Key.ToString()].GetType().ToString().InvariantEquals(typeName)) - { - _cache.Remove(c.Key.ToString()); - } - } - } - } - catch (Exception e) - { - LogHelper.Error("Cache clearing error", e); - } - } - - /// - /// Clears all objects in the System.Web.Cache with the System.Type specified - /// - public override void ClearCacheObjectTypes() - { - try - { - lock (Locker) - { - foreach (DictionaryEntry c in _cache) - { - if (_cache[c.Key.ToString()] != null - && _cache[c.Key.ToString()].GetType() == typeof(T)) - { - _cache.Remove(c.Key.ToString()); - } - } - } - } - catch (Exception e) - { - LogHelper.Error("Cache clearing error", e); - } - } - - /// + protected override DictionaryCacheWrapper DictionaryCache /// Clears all objects in the System.Web.Cache with the System.Type specified that satisfy the predicate /// public override void ClearCacheObjectTypes(Func predicate) @@ -131,69 +49,18 @@ namespace Umbraco.Core.Cache } /// - /// Clears all cache items that starts with the key passed. - /// - /// The start of the key - public override void ClearCacheByKeySearch(string keyStartsWith) { - foreach (DictionaryEntry c in _cache) - { - if (c.Key is string && ((string)c.Key).InvariantStartsWith(keyStartsWith)) - { - ClearCacheItem((string)c.Key); - } - } - } - - /// - /// Clears all cache items that have a key that matches the regular expression - /// - /// - public override void ClearCacheByKeyExpression(string regexString) - { - foreach (DictionaryEntry c in _cache) - { - if (c.Key is string && Regex.IsMatch(((string)c.Key), regexString)) - { - ClearCacheItem((string)c.Key); - } - } - } - - public override IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) - { - return (from DictionaryEntry c in _cache - where c.Key is string && ((string)c.Key).InvariantStartsWith(keyStartsWith) - select c.Value.TryConvertTo() - into attempt - where attempt.Success - select attempt.Result).ToList(); - } - - /// - /// Returns a cache item by key, does not update the cache if it isn't there. - /// - /// - /// - /// - public override TT GetCacheItem(string cacheKey) - { - var result = _cache.Get(cacheKey); - if (result == null) - { - return default(TT); - } - return result.TryConvertTo().Result; + get { return _wrapper; } } /// /// Gets (and adds if necessary) an item from the cache with all of the default parameters /// - /// + /// /// /// /// - public override TT GetCacheItem(string cacheKey, Func getCacheItem) + public override T GetCacheItem(string cacheKey, Func getCacheItem) { return GetCacheItem(cacheKey, CacheItemPriority.Normal, null, null, null, getCacheItem, Locker); } @@ -206,7 +73,7 @@ namespace Umbraco.Core.Cache /// This will set an absolute expiration from now until the timeout /// /// - public override TT GetCacheItem(string cacheKey, + public virtual TT GetCacheItem(string cacheKey, TimeSpan? timeout, Func getCacheItem) { return GetCacheItem(cacheKey, null, timeout, getCacheItem); @@ -221,7 +88,7 @@ namespace Umbraco.Core.Cache /// This will set an absolute expiration from now until the timeout /// /// - public override TT GetCacheItem(string cacheKey, + public virtual TT GetCacheItem(string cacheKey, CacheItemRemovedCallback refreshAction, TimeSpan? timeout, Func getCacheItem) { @@ -238,7 +105,7 @@ namespace Umbraco.Core.Cache /// This will set an absolute expiration from now until the timeout /// /// - public override TT GetCacheItem(string cacheKey, + public virtual TT GetCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, TimeSpan? timeout, Func getCacheItem) { @@ -256,7 +123,7 @@ namespace Umbraco.Core.Cache /// This will set an absolute expiration from now until the timeout /// /// - public override TT GetCacheItem(string cacheKey, + public virtual TT GetCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, CacheDependency cacheDependency, @@ -282,12 +149,14 @@ namespace Umbraco.Core.Cache CacheItemPriority priority, CacheItemRemovedCallback refreshAction, CacheDependency cacheDependency, TimeSpan? timeout, Func getCacheItem, object syncLock) { - var result = _cache.Get(cacheKey); + cacheKey = GetCacheKey(cacheKey); + + var result = DictionaryCache.Get(cacheKey); if (result == null) { lock (syncLock) { - result = _cache.Get(cacheKey); + result = DictionaryCache.Get(cacheKey); if (result == null) { result = getCacheItem(); @@ -311,7 +180,7 @@ namespace Umbraco.Core.Cache /// /// /// - public override void InsertCacheItem(string cacheKey, + public virtual void InsertCacheItem(string cacheKey, CacheItemPriority priority, Func getCacheItem) { @@ -326,7 +195,7 @@ namespace Umbraco.Core.Cache /// /// This will set an absolute expiration from now until the timeout /// - public override void InsertCacheItem(string cacheKey, + public virtual void InsertCacheItem(string cacheKey, CacheItemPriority priority, TimeSpan? timeout, Func getCacheItem) @@ -343,7 +212,7 @@ namespace Umbraco.Core.Cache /// /// This will set an absolute expiration from now until the timeout /// - public override void InsertCacheItem(string cacheKey, + public virtual void InsertCacheItem(string cacheKey, CacheItemPriority priority, CacheDependency cacheDependency, TimeSpan? timeout, @@ -362,7 +231,7 @@ namespace Umbraco.Core.Cache /// /// This will set an absolute expiration from now until the timeout /// - public override void InsertCacheItem(string cacheKey, + public virtual void InsertCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, CacheDependency cacheDependency, @@ -372,6 +241,8 @@ namespace Umbraco.Core.Cache object result = getCacheItem(); if (result != null) { + cacheKey = GetCacheKey(cacheKey); + //we use Insert instead of add if for some crazy reason there is now a cache with the cache key in there, it will just overwrite it. _cache.Insert(cacheKey, result, cacheDependency, timeout == null ? System.Web.Caching.Cache.NoAbsoluteExpiration : DateTime.Now.Add(timeout.Value), diff --git a/src/Umbraco.Core/Cache/ICacheProvider.cs b/src/Umbraco.Core/Cache/ICacheProvider.cs new file mode 100644 index 0000000000..ab70cf63c0 --- /dev/null +++ b/src/Umbraco.Core/Cache/ICacheProvider.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Cache +{ + /// + /// An abstract class for implementing a basic cache provider + /// + /// + /// THIS MUST REMAIN INTERNAL UNTIL WE STREAMLINE HOW ALL CACHE IS HANDLED, WE NEED TO SUPPORT HTTP RUNTIME CACHE, IN MEMORY CACHE, ETC... + /// + internal interface ICacheProvider + { + void ClearAllCache(); + void ClearCacheItem(string key); + void ClearCacheObjectTypes(string typeName); + void ClearCacheObjectTypes(); + void ClearCacheByKeySearch(string keyStartsWith); + void ClearCacheByKeyExpression(string regexString); + IEnumerable GetCacheItemsByKeySearch(string keyStartsWith); + T GetCacheItem(string cacheKey); + T GetCacheItem(string cacheKey, Func getCacheItem); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/IRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/IRuntimeCacheProvider.cs new file mode 100644 index 0000000000..8dd1d31af0 --- /dev/null +++ b/src/Umbraco.Core/Cache/IRuntimeCacheProvider.cs @@ -0,0 +1,24 @@ +using System; +using System.Text; +using System.Web.Caching; + +namespace Umbraco.Core.Cache +{ + /// + /// An abstract class for implementing a runtime cache provider + /// + /// + /// THIS MUST REMAIN INTERNAL UNTIL WE STREAMLINE HOW ALL CACHE IS HANDLED, WE NEED TO SUPPORT HTTP RUNTIME CACHE, IN MEMORY CACHE, REQUEST CACHE, ETC... + /// + internal interface IRuntimeCacheProvider : ICacheProvider + { + T GetCacheItem(string cacheKey, TimeSpan? timeout, Func getCacheItem); + T GetCacheItem(string cacheKey, CacheItemRemovedCallback refreshAction, TimeSpan? timeout, Func getCacheItem); + T GetCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, TimeSpan? timeout, Func getCacheItem); + T GetCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, CacheDependency cacheDependency, TimeSpan? timeout, Func getCacheItem); + void InsertCacheItem(string cacheKey, CacheItemPriority priority, Func getCacheItem); + void InsertCacheItem(string cacheKey, CacheItemPriority priority, TimeSpan? timeout, Func getCacheItem); + void InsertCacheItem(string cacheKey, CacheItemPriority priority, CacheDependency cacheDependency, TimeSpan? timeout, Func getCacheItem); + void InsertCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, CacheDependency cacheDependency, TimeSpan? timeout, Func getCacheItem); + } +} diff --git a/src/Umbraco.Core/Cache/NullCacheProvider.cs b/src/Umbraco.Core/Cache/NullCacheProvider.cs index 0ffecf59e3..ff993bdbc0 100644 --- a/src/Umbraco.Core/Cache/NullCacheProvider.cs +++ b/src/Umbraco.Core/Cache/NullCacheProvider.cs @@ -5,21 +5,21 @@ using System.Web.Caching; namespace Umbraco.Core.Cache { - internal class NullCacheProvider : RuntimeCacheProviderBase + internal class NullCacheProvider : IRuntimeCacheProvider { - public override void ClearAllCache() + public virtual void ClearAllCache() { } - public override void ClearCacheItem(string key) + public virtual void ClearCacheItem(string key) { } - public override void ClearCacheObjectTypes(string typeName) + public virtual void ClearCacheObjectTypes(string typeName) { } - public override void ClearCacheObjectTypes() + public virtual void ClearCacheObjectTypes() { } @@ -31,58 +31,58 @@ namespace Umbraco.Core.Cache { } - public override void ClearCacheByKeyExpression(string regexString) + public virtual void ClearCacheByKeyExpression(string regexString) { } - public override IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) + public virtual IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) { return Enumerable.Empty(); } - public override T GetCacheItem(string cacheKey) + public virtual T GetCacheItem(string cacheKey) { return default(T); } - public override T GetCacheItem(string cacheKey, Func getCacheItem) + public virtual T GetCacheItem(string cacheKey, Func getCacheItem) { return getCacheItem(); } - public override T GetCacheItem(string cacheKey, TimeSpan? timeout, Func getCacheItem) + public virtual T GetCacheItem(string cacheKey, TimeSpan? timeout, Func getCacheItem) { return getCacheItem(); } - public override T GetCacheItem(string cacheKey, CacheItemRemovedCallback refreshAction, TimeSpan? timeout, Func getCacheItem) + public virtual T GetCacheItem(string cacheKey, CacheItemRemovedCallback refreshAction, TimeSpan? timeout, Func getCacheItem) { return getCacheItem(); } - public override T GetCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, TimeSpan? timeout, Func getCacheItem) + public virtual T GetCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, TimeSpan? timeout, Func getCacheItem) { return getCacheItem(); } - public override T GetCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, CacheDependency cacheDependency, TimeSpan? timeout, Func getCacheItem) + public virtual T GetCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, CacheDependency cacheDependency, TimeSpan? timeout, Func getCacheItem) { return getCacheItem(); } - public override void InsertCacheItem(string cacheKey, CacheItemPriority priority, Func getCacheItem) + public virtual void InsertCacheItem(string cacheKey, CacheItemPriority priority, Func getCacheItem) { } - public override void InsertCacheItem(string cacheKey, CacheItemPriority priority, TimeSpan? timeout, Func getCacheItem) + public virtual void InsertCacheItem(string cacheKey, CacheItemPriority priority, TimeSpan? timeout, Func getCacheItem) { } - public override void InsertCacheItem(string cacheKey, CacheItemPriority priority, CacheDependency cacheDependency, TimeSpan? timeout, Func getCacheItem) + public virtual void InsertCacheItem(string cacheKey, CacheItemPriority priority, CacheDependency cacheDependency, TimeSpan? timeout, Func getCacheItem) { } - public override void InsertCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, CacheDependency cacheDependency, TimeSpan? timeout, Func getCacheItem) + public virtual void InsertCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, CacheDependency cacheDependency, TimeSpan? timeout, Func getCacheItem) { } } diff --git a/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs new file mode 100644 index 0000000000..c9a37d830d --- /dev/null +++ b/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs @@ -0,0 +1,198 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.Caching; +using System.Text.RegularExpressions; +using System.Threading; +using System.Web.Caching; +using Umbraco.Core.Logging; +using CacheItemPriority = System.Web.Caching.CacheItemPriority; + +namespace Umbraco.Core.Cache +{ + /// + /// A cache provider that wraps the logic of a System.Runtime.Caching.ObjectCache + /// + internal class ObjectCacheRuntimeCacheProvider : IRuntimeCacheProvider + { + private static readonly ReaderWriterLockSlim ClearLock = new ReaderWriterLockSlim(); + internal ObjectCache MemoryCache; + + public ObjectCacheRuntimeCacheProvider() + { + MemoryCache = new MemoryCache("in-memory"); + } + + public virtual void ClearAllCache() + { + using (new WriteLock(ClearLock)) + { + MemoryCache.DisposeIfDisposable(); + MemoryCache = new MemoryCache("in-memory"); + } + } + + public virtual void ClearCacheItem(string key) + { + using (new WriteLock(ClearLock)) + { + if (MemoryCache[key] == null) return; + MemoryCache.Remove(key); + } + } + + public virtual void ClearCacheObjectTypes(string typeName) + { + using (new WriteLock(ClearLock)) + { + var keysToRemove = (from c in MemoryCache where c.Value.GetType().ToString().InvariantEquals(typeName) select c.Key).ToList(); + foreach (var k in keysToRemove) + { + MemoryCache.Remove(k); + } + } + } + + public virtual void ClearCacheObjectTypes() + { + using (new WriteLock(ClearLock)) + { + var keysToRemove = (from c in MemoryCache where c.Value.GetType() == typeof (T) select c.Key).ToList(); + foreach (var k in keysToRemove) + { + MemoryCache.Remove(k); + } + } + } + + public virtual void ClearCacheByKeySearch(string keyStartsWith) + { + using (new WriteLock(ClearLock)) + { + var keysToRemove = (from c in MemoryCache where c.Key.InvariantStartsWith(keyStartsWith) select c.Key).ToList(); + foreach (var k in keysToRemove) + { + MemoryCache.Remove(k); + } + } + } + + public virtual void ClearCacheByKeyExpression(string regexString) + { + using (new WriteLock(ClearLock)) + { + var keysToRemove = (from c in MemoryCache where Regex.IsMatch(c.Key, regexString) select c.Key).ToList(); + foreach (var k in keysToRemove) + { + MemoryCache.Remove(k); + } + } + } + + public virtual IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) + { + return (from c in MemoryCache + where c.Key.InvariantStartsWith(keyStartsWith) + select c.Value.TryConvertTo() + into attempt + where attempt.Success + select attempt.Result).ToList(); + } + + public virtual T GetCacheItem(string cacheKey) + { + var result = MemoryCache.Get(cacheKey); + if (result == null) + { + return default(T); + } + return result.TryConvertTo().Result; + } + + public virtual T GetCacheItem(string cacheKey, Func getCacheItem) + { + return GetCacheItem(cacheKey, CacheItemPriority.Normal, null, null, null, getCacheItem); + } + + public virtual T GetCacheItem(string cacheKey, TimeSpan? timeout, Func getCacheItem) + { + return GetCacheItem(cacheKey, null, timeout, getCacheItem); + } + + public virtual T GetCacheItem(string cacheKey, CacheItemRemovedCallback refreshAction, TimeSpan? timeout, Func getCacheItem) + { + return GetCacheItem(cacheKey, CacheItemPriority.Normal, refreshAction, timeout, getCacheItem); + } + + public virtual T GetCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, TimeSpan? timeout, Func getCacheItem) + { + return GetCacheItem(cacheKey, priority, refreshAction, null, timeout, getCacheItem); + } + + public virtual T GetCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, CacheDependency cacheDependency, TimeSpan? timeout, Func getCacheItem) + { + using (var lck = new UpgradeableReadLock(ClearLock)) + { + var result = MemoryCache.Get(cacheKey); + if (result == null) + { + lck.UpgradeToWriteLock(); + + result = getCacheItem(); + if (result != null) + { + var policy = new CacheItemPolicy + { + AbsoluteExpiration = timeout == null ? ObjectCache.InfiniteAbsoluteExpiration : DateTime.Now.Add(timeout.Value), + SlidingExpiration = TimeSpan.Zero + }; + + //TODO: CUrrently we cannot implement this in this provider, we'll have to change the underlying interface + // to accept an array of files instead of CacheDependency. + //policy.ChangeMonitors.Add(new HostFileChangeMonitor(cacheDependency.)); + + MemoryCache.Set(cacheKey, result, policy); + } + } + return result.TryConvertTo().Result; + } + } + + public virtual void InsertCacheItem(string cacheKey, CacheItemPriority priority, Func getCacheItem) + { + InsertCacheItem(cacheKey, priority, null, null, null, getCacheItem); + } + + public virtual void InsertCacheItem(string cacheKey, CacheItemPriority priority, TimeSpan? timeout, Func getCacheItem) + { + InsertCacheItem(cacheKey, priority, null, null, timeout, getCacheItem); + } + + public virtual void InsertCacheItem(string cacheKey, CacheItemPriority priority, CacheDependency cacheDependency, TimeSpan? timeout, Func getCacheItem) + { + InsertCacheItem(cacheKey, priority, null, cacheDependency, timeout, getCacheItem); + } + + public virtual void InsertCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, CacheDependency cacheDependency, TimeSpan? timeout, Func getCacheItem) + { + object result = getCacheItem(); + if (result != null) + { + + var policy = new CacheItemPolicy + { + AbsoluteExpiration = timeout == null ? ObjectCache.InfiniteAbsoluteExpiration : DateTime.Now.Add(timeout.Value), + SlidingExpiration = TimeSpan.Zero + }; + + //TODO: CUrrently we cannot implement this in this provider, we'll have to change the underlying interface + // to accept an array of files instead of CacheDependency. + //policy.ChangeMonitors.Add(new HostFileChangeMonitor(cacheDependency.)); + + MemoryCache.Set(cacheKey, result, policy); + } + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/RuntimeCacheProviderBase.cs b/src/Umbraco.Core/Cache/RuntimeCacheProviderBase.cs deleted file mode 100644 index d88a3922bb..0000000000 --- a/src/Umbraco.Core/Cache/RuntimeCacheProviderBase.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Text; -using System.Web.Caching; - -namespace Umbraco.Core.Cache -{ - /// - /// An abstract class for implementing a runtime cache provider - /// - /// - /// THIS MUST REMAIN INTERNAL UNTIL WE STREAMLINE HOW ALL CACHE IS HANDLED, WE NEED TO SUPPORT HTTP RUNTIME CACHE, IN MEMORY CACHE, REQUEST CACHE, ETC... - /// - internal abstract class RuntimeCacheProviderBase : CacheProviderBase - { - public abstract T GetCacheItem(string cacheKey, TimeSpan? timeout, Func getCacheItem); - public abstract T GetCacheItem(string cacheKey, CacheItemRemovedCallback refreshAction, TimeSpan? timeout, Func getCacheItem); - public abstract T GetCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, TimeSpan? timeout, Func getCacheItem); - public abstract T GetCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, CacheDependency cacheDependency, TimeSpan? timeout, Func getCacheItem); - public abstract void InsertCacheItem(string cacheKey, CacheItemPriority priority, Func getCacheItem); - public abstract void InsertCacheItem(string cacheKey, CacheItemPriority priority, TimeSpan? timeout, Func getCacheItem); - public abstract void InsertCacheItem(string cacheKey, CacheItemPriority priority, CacheDependency cacheDependency, TimeSpan? timeout, Func getCacheItem); - public abstract void InsertCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, CacheDependency cacheDependency, TimeSpan? timeout, Func getCacheItem); - } -} diff --git a/src/Umbraco.Core/Cache/StaticCacheProvider.cs b/src/Umbraco.Core/Cache/StaticCacheProvider.cs index 765e84fd7c..67c0527393 100644 --- a/src/Umbraco.Core/Cache/StaticCacheProvider.cs +++ b/src/Umbraco.Core/Cache/StaticCacheProvider.cs @@ -10,51 +10,72 @@ namespace Umbraco.Core.Cache /// /// A cache provider that statically caches everything in an in memory dictionary /// - internal class StaticCacheProvider : CacheProviderBase + internal class StaticCacheProvider : ICacheProvider { - private readonly ConcurrentDictionary _staticCache = new ConcurrentDictionary(); + internal readonly ConcurrentDictionary StaticCache = new ConcurrentDictionary(); - public override void ClearAllCache() + public virtual void ClearAllCache() { - _staticCache.Clear(); + StaticCache.Clear(); } - public override void ClearCacheItem(string key) + public virtual void ClearCacheItem(string key) { object val; - _staticCache.TryRemove(key, out val); + StaticCache.TryRemove(key, out val); } - public override void ClearCacheObjectTypes(string typeName) + public virtual void ClearCacheObjectTypes(string typeName) { - _staticCache.RemoveAll(kvp => kvp.Value != null && kvp.Value.GetType().ToString().InvariantEquals(typeName)); + foreach (var key in StaticCache.Keys) + { + if (StaticCache[key] != null + && StaticCache[key].GetType().ToString().InvariantEquals(typeName)) + { + object val; + StaticCache.TryRemove(key, out val); + } + } } - public override void ClearCacheObjectTypes() + public virtual void ClearCacheObjectTypes() { - var typeOfT = typeof (T); - _staticCache.RemoveAll(kvp => kvp.Value != null && kvp.Value.GetType() == typeOfT); + foreach (var key in StaticCache.Keys) + { + if (StaticCache[key] != null + && StaticCache[key].GetType() == typeof(T)) + { + object val; + StaticCache.TryRemove(key, out val); + } + } } - public override void ClearCacheObjectTypes(Func predicate) + public virtual void ClearCacheByKeySearch(string keyStartsWith) { - var typeOfT = typeof(T); - _staticCache.RemoveAll(kvp => kvp.Value != null && kvp.Value.GetType() == typeOfT && predicate(kvp.Key, (T)kvp.Value)); + foreach (var key in StaticCache.Keys) + { + if (key.InvariantStartsWith(keyStartsWith)) + { + ClearCacheItem(key); + } + } } - public override void ClearCacheByKeySearch(string keyStartsWith) + public virtual void ClearCacheByKeyExpression(string regexString) { - _staticCache.RemoveAll(kvp => kvp.Key.InvariantStartsWith(keyStartsWith)); + foreach (var key in StaticCache.Keys) + { + if (Regex.IsMatch(key, regexString)) + { + ClearCacheItem(key); + } + } } - public override void ClearCacheByKeyExpression(string regexString) + public virtual IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) { - _staticCache.RemoveAll(kvp => Regex.IsMatch(kvp.Key, regexString)); - } - - public override IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) - { - return (from KeyValuePair c in _staticCache + return (from KeyValuePair c in StaticCache where c.Key.InvariantStartsWith(keyStartsWith) select c.Value.TryConvertTo() into attempt @@ -62,9 +83,9 @@ namespace Umbraco.Core.Cache select attempt.Result).ToList(); } - public override T GetCacheItem(string cacheKey) + public virtual T GetCacheItem(string cacheKey) { - var result = _staticCache[cacheKey]; + var result = StaticCache[cacheKey]; if (result == null) { return default(T); @@ -72,9 +93,9 @@ namespace Umbraco.Core.Cache return result.TryConvertTo().Result; } - public override T GetCacheItem(string cacheKey, Func getCacheItem) + public virtual T GetCacheItem(string cacheKey, Func getCacheItem) { - return (T)_staticCache.GetOrAdd(cacheKey, key => getCacheItem()); + return (T)StaticCache.GetOrAdd(cacheKey, getCacheItem()); } } diff --git a/src/Umbraco.Core/CacheHelper.cs b/src/Umbraco.Core/CacheHelper.cs index 8f7edeb777..7ec76f165c 100644 --- a/src/Umbraco.Core/CacheHelper.cs +++ b/src/Umbraco.Core/CacheHelper.cs @@ -18,10 +18,12 @@ namespace Umbraco.Core public class CacheHelper { private readonly bool _enableCache; - private readonly CacheProviderBase _staticCache; - private readonly CacheProviderBase _nullStaticCache = new NullCacheProvider(); - private readonly RuntimeCacheProviderBase _httpCache; - private readonly RuntimeCacheProviderBase _nullHttpCache = new NullCacheProvider(); + private readonly ICacheProvider _requestCache; + private readonly ICacheProvider _nullRequestCache = new NullCacheProvider(); + private readonly ICacheProvider _staticCache; + private readonly ICacheProvider _nullStaticCache = new NullCacheProvider(); + private readonly IRuntimeCacheProvider _httpCache; + private readonly IRuntimeCacheProvider _nullHttpCache = new NullCacheProvider(); public CacheHelper(System.Web.Caching.Cache cache) : this(cache, true) @@ -33,18 +35,171 @@ namespace Umbraco.Core { } - internal CacheHelper(RuntimeCacheProviderBase httpCacheProvider, bool enableCache) - : this(httpCacheProvider, new StaticCacheProvider(), enableCache) + internal CacheHelper(IRuntimeCacheProvider httpCacheProvider, bool enableCache) + : this(httpCacheProvider, new StaticCacheProvider(), new HttpRequestCacheProvider(HttpContext.Current), enableCache) { } - internal CacheHelper(RuntimeCacheProviderBase httpCacheProvider, CacheProviderBase staticCacheProvider, bool enableCache) + internal CacheHelper( + IRuntimeCacheProvider httpCacheProvider, + ICacheProvider staticCacheProvider, + ICacheProvider requestCacheProvider, + bool enableCache) { _httpCache = httpCacheProvider; _staticCache = staticCacheProvider; _enableCache = enableCache; + _requestCache = requestCacheProvider; } + #region Request cache + + /// + /// Clears the item in umbraco's request cache + /// + internal void ClearAllRequestCache() + { + if (!_enableCache) + { + _nullRequestCache.ClearAllCache(); + } + else + { + _requestCache.ClearAllCache(); + } + } + + /// + /// Clears the item in umbraco's request cache with the given key + /// + /// Key + internal void ClearRequestCacheItem(string key) + { + if (!_enableCache) + { + _nullRequestCache.ClearCacheItem(key); + } + else + { + _requestCache.ClearCacheItem(key); + } + } + + /// + /// Clears all objects in the request cache with the System.Type name as the + /// input parameter. (using [object].GetType()) + /// + /// The name of the System.Type which should be cleared from cache ex "System.Xml.XmlDocument" + internal void ClearRequestCacheObjectTypes(string typeName) + { + if (!_enableCache) + { + _nullRequestCache.ClearCacheObjectTypes(typeName); + } + else + { + _requestCache.ClearCacheObjectTypes(typeName); + } + } + + /// + /// Clears all objects in the request cache with the System.Type specified + /// + internal void ClearRequestCacheObjectTypes() + { + if (!_enableCache) + { + _nullRequestCache.ClearCacheObjectTypes(); + } + else + { + _requestCache.ClearCacheObjectTypes(); + } + } + + /// + /// Clears all request cache items that starts with the key passed. + /// + /// The start of the key + internal void ClearRequestCacheByKeySearch(string keyStartsWith) + { + if (!_enableCache) + { + _nullRequestCache.ClearCacheByKeySearch(keyStartsWith); + } + else + { + _requestCache.ClearCacheByKeySearch(keyStartsWith); + } + } + + /// + /// Clears all cache items that have a key that matches the regular expression + /// + /// + internal void ClearRequestCacheByKeyExpression(string regexString) + { + if (!_enableCache) + { + _nullRequestCache.ClearCacheByKeyExpression(regexString); + } + else + { + _requestCache.ClearCacheByKeyExpression(regexString); + } + } + + internal IEnumerable GetRequestCacheItemsByKeySearch(string keyStartsWith) + { + if (!_enableCache) + { + return _nullRequestCache.GetCacheItemsByKeySearch(keyStartsWith); + } + else + { + return _requestCache.GetCacheItemsByKeySearch(keyStartsWith); + } + } + + /// + /// Returns a request cache item by key, does not update the cache if it isn't there. + /// + /// + /// + /// + internal TT GetRequestCacheItem(string cacheKey) + { + if (!_enableCache) + { + return _nullRequestCache.GetCacheItem(cacheKey); + } + else + { + return _requestCache.GetCacheItem(cacheKey); + } + } + + /// + /// Gets (and adds if necessary) an item from the request cache with all of the default parameters + /// + /// + /// + /// + /// + internal TT GetRequestCacheItem(string cacheKey, Func getCacheItem) + { + if (!_enableCache) + { + return _nullRequestCache.GetCacheItem(cacheKey, getCacheItem); + } + else + { + return _requestCache.GetCacheItem(cacheKey, getCacheItem); + } + } + + #endregion + #region Static cache /// @@ -200,8 +355,7 @@ namespace Umbraco.Core } #endregion - - + #region Runtime/Http Cache /// /// Clears the item in umbraco's runtime cache diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 98e9c17650..84a52c2947 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -113,10 +113,15 @@ - + - + + + + + + diff --git a/src/Umbraco.Tests/Cache/CacheHelperTests.cs b/src/Umbraco.Tests/Cache/CacheHelperTests.cs deleted file mode 100644 index afe296dd8b..0000000000 --- a/src/Umbraco.Tests/Cache/CacheHelperTests.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Web; -using System.Web.Caching; -using System.Web.UI; -using NUnit.Framework; -using Umbraco.Core; -using umbraco; - -namespace Umbraco.Tests.Cache -{ - [TestFixture] - public class CacheHelperTests - { - - private CacheHelper _helper; - - [SetUp] - public void Setup() - { - _helper = new CacheHelper(HttpRuntime.Cache); - } - - [TearDown] - public void TearDown() - { - _helper.ClearAllCache(); - } - - [Test] - public void Can_Remove_By_Type_Name() - { - var cacheContent1 = new MacroCacheContent(new LiteralControl(), "Test1"); - var cacheContent2 = new MacroCacheContent(new LiteralControl(), "Test2"); - var cacheContent3 = new MacroCacheContent(new LiteralControl(), "Test3"); - var cacheContent4 = new MacroCacheContent(new LiteralControl(), "Test4"); - _helper.InsertCacheItem("Test1", CacheItemPriority.Default, new TimeSpan(0, 0, 60), () => cacheContent1); - _helper.InsertCacheItem("Test2", CacheItemPriority.Default, new TimeSpan(0, 0, 60), () => cacheContent2); - _helper.InsertCacheItem("Test3", CacheItemPriority.Default, new TimeSpan(0, 0, 60), () => cacheContent3); - _helper.InsertCacheItem("Test4", CacheItemPriority.Default, new TimeSpan(0, 0, 60), () => cacheContent4); - - Assert.AreEqual(4, HttpRuntime.Cache.Count); - - _helper.ClearCacheObjectTypes("umbraco.MacroCacheContent"); - - Assert.AreEqual(0, HttpRuntime.Cache.Count); - } - - [Test] - public void Can_Remove_By_Strong_Type() - { - var cacheContent1 = new MacroCacheContent(new LiteralControl(), "Test1"); - var cacheContent2 = new MacroCacheContent(new LiteralControl(), "Test2"); - var cacheContent3 = new MacroCacheContent(new LiteralControl(), "Test3"); - var cacheContent4 = new MacroCacheContent(new LiteralControl(), "Test4"); - _helper.InsertCacheItem("Test1", CacheItemPriority.Default, new TimeSpan(0, 0, 60), () => cacheContent1); - _helper.InsertCacheItem("Test2", CacheItemPriority.Default, new TimeSpan(0, 0, 60), () => cacheContent2); - _helper.InsertCacheItem("Test3", CacheItemPriority.Default, new TimeSpan(0, 0, 60), () => cacheContent3); - _helper.InsertCacheItem("Test4", CacheItemPriority.Default, new TimeSpan(0, 0, 60), () => cacheContent4); - - Assert.AreEqual(4, HttpRuntime.Cache.Count); - - _helper.ClearCacheObjectTypes(); - - Assert.AreEqual(0, HttpRuntime.Cache.Count); - } - - [Test] - public void Can_Add_Remove_Struct_Strongly_Typed_With_Null() - { - var now = DateTime.Now; - _helper.InsertCacheItem("DateTimeTest", CacheItemPriority.Default, new TimeSpan(0, 0, 0, 0, 200), () => now); - Assert.AreEqual(now, _helper.GetCacheItem("DateTimeTest")); - Assert.AreEqual(now, _helper.GetCacheItem("DateTimeTest")); - - Thread.Sleep(300); //sleep longer than the cache expiration - - Assert.AreEqual(default(DateTime), _helper.GetCacheItem("DateTimeTest")); - Assert.AreEqual(null, _helper.GetCacheItem("DateTimeTest")); - } - - } -} diff --git a/src/Umbraco.Tests/Cache/CacheProviderTests.cs b/src/Umbraco.Tests/Cache/CacheProviderTests.cs new file mode 100644 index 0000000000..b7ed588bb8 --- /dev/null +++ b/src/Umbraco.Tests/Cache/CacheProviderTests.cs @@ -0,0 +1,63 @@ +using System.Web.UI; +using NUnit.Framework; +using Umbraco.Core.Cache; +using umbraco; + +namespace Umbraco.Tests.Cache +{ + public abstract class CacheProviderTests + { + internal abstract ICacheProvider Provider { get; } + protected abstract int GetTotalItemCount { get; } + + [SetUp] + public virtual void Setup() + { + + } + + [TearDown] + public virtual void TearDown() + { + Provider.ClearAllCache(); + } + + [Test] + public void Can_Remove_By_Type_Name() + { + var cacheContent1 = new MacroCacheContent(new LiteralControl(), "Test1"); + var cacheContent2 = new MacroCacheContent(new LiteralControl(), "Test2"); + var cacheContent3 = new MacroCacheContent(new LiteralControl(), "Test3"); + var cacheContent4 = new MacroCacheContent(new LiteralControl(), "Test4"); + Provider.GetCacheItem("Test1", () => cacheContent1); + Provider.GetCacheItem("Test2", () => cacheContent2); + Provider.GetCacheItem("Test3", () => cacheContent3); + Provider.GetCacheItem("Test4", () => cacheContent4); + + Assert.AreEqual(4, GetTotalItemCount); + + Provider.ClearCacheObjectTypes("umbraco.MacroCacheContent"); + + Assert.AreEqual(0, GetTotalItemCount); + } + + [Test] + public void Can_Remove_By_Strong_Type() + { + var cacheContent1 = new MacroCacheContent(new LiteralControl(), "Test1"); + var cacheContent2 = new MacroCacheContent(new LiteralControl(), "Test2"); + var cacheContent3 = new MacroCacheContent(new LiteralControl(), "Test3"); + var cacheContent4 = new MacroCacheContent(new LiteralControl(), "Test4"); + Provider.GetCacheItem("Test1", () => cacheContent1); + Provider.GetCacheItem("Test2", () => cacheContent2); + Provider.GetCacheItem("Test3", () => cacheContent3); + Provider.GetCacheItem("Test4", () => cacheContent4); + + Assert.AreEqual(4, GetTotalItemCount); + + Provider.ClearCacheObjectTypes(); + + Assert.AreEqual(0, GetTotalItemCount); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Cache/HttpRequestCacheProviderTests.cs b/src/Umbraco.Tests/Cache/HttpRequestCacheProviderTests.cs new file mode 100644 index 0000000000..dcbddbb44e --- /dev/null +++ b/src/Umbraco.Tests/Cache/HttpRequestCacheProviderTests.cs @@ -0,0 +1,52 @@ +using NUnit.Framework; +using Umbraco.Core.Cache; +using Umbraco.Tests.TestHelpers; + +namespace Umbraco.Tests.Cache +{ + [TestFixture] + public class HttpRequestCacheProviderTests : CacheProviderTests + { + private HttpRequestCacheProvider _provider; + private FakeHttpContextFactory _ctx; + + public override void Setup() + { + base.Setup(); + _ctx = new FakeHttpContextFactory("http://localhost/test"); + _provider = new HttpRequestCacheProvider(() => _ctx.HttpContext); + } + + internal override ICacheProvider Provider + { + get { return _provider; } + } + + protected override int GetTotalItemCount + { + get { return _ctx.HttpContext.Items.Count; } + } + } + + [TestFixture] + public class StaticCacheProviderTests : CacheProviderTests + { + private StaticCacheProvider _provider; + + public override void Setup() + { + base.Setup(); + _provider = new StaticCacheProvider(); + } + + internal override ICacheProvider Provider + { + get { return _provider; } + } + + protected override int GetTotalItemCount + { + get { return _provider.StaticCache.Count; } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Cache/HttpRuntimeCacheProviderTests.cs b/src/Umbraco.Tests/Cache/HttpRuntimeCacheProviderTests.cs new file mode 100644 index 0000000000..9cbbc926f8 --- /dev/null +++ b/src/Umbraco.Tests/Cache/HttpRuntimeCacheProviderTests.cs @@ -0,0 +1,33 @@ +using System.Web; +using NUnit.Framework; +using Umbraco.Core.Cache; + +namespace Umbraco.Tests.Cache +{ + [TestFixture] + public class HttpRuntimeCacheProviderTests : RuntimeCacheProviderTests + { + private HttpRuntimeCacheProvider _provider; + + protected override int GetTotalItemCount + { + get { return HttpRuntime.Cache.Count; } + } + + public override void Setup() + { + base.Setup(); + _provider = new HttpRuntimeCacheProvider(HttpRuntime.Cache); + } + + internal override ICacheProvider Provider + { + get { return _provider; } + } + + internal override IRuntimeCacheProvider RuntimeProvider + { + get { return _provider; } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Cache/ObjectCacheProviderTests.cs b/src/Umbraco.Tests/Cache/ObjectCacheProviderTests.cs new file mode 100644 index 0000000000..e373fdda4d --- /dev/null +++ b/src/Umbraco.Tests/Cache/ObjectCacheProviderTests.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Cache; + +namespace Umbraco.Tests.Cache +{ + [TestFixture] + public class ObjectCacheProviderTests : RuntimeCacheProviderTests + { + private ObjectCacheRuntimeCacheProvider _provider; + + protected override int GetTotalItemCount + { + get { return _provider.MemoryCache.Count(); } + } + + public override void Setup() + { + base.Setup(); + _provider = new ObjectCacheRuntimeCacheProvider(); + } + + internal override ICacheProvider Provider + { + get { return _provider; } + } + + internal override IRuntimeCacheProvider RuntimeProvider + { + get { return _provider; } + } + } +} diff --git a/src/Umbraco.Tests/Cache/RuntimeCacheProviderTests.cs b/src/Umbraco.Tests/Cache/RuntimeCacheProviderTests.cs new file mode 100644 index 0000000000..03ad355f42 --- /dev/null +++ b/src/Umbraco.Tests/Cache/RuntimeCacheProviderTests.cs @@ -0,0 +1,30 @@ +using System; +using System.Threading; +using System.Web.Caching; +using NUnit.Framework; +using Umbraco.Core.Cache; + +namespace Umbraco.Tests.Cache +{ + public abstract class RuntimeCacheProviderTests : CacheProviderTests + { + + internal abstract IRuntimeCacheProvider RuntimeProvider { get; } + + + [Test] + public void Can_Add_And_Expire_Struct_Strongly_Typed_With_Null() + { + var now = DateTime.Now; + RuntimeProvider.InsertCacheItem("DateTimeTest", CacheItemPriority.Default, new TimeSpan(0, 0, 0, 0, 200), () => now); + Assert.AreEqual(now, Provider.GetCacheItem("DateTimeTest")); + Assert.AreEqual(now, Provider.GetCacheItem("DateTimeTest")); + + Thread.Sleep(300); //sleep longer than the cache expiration + + Assert.AreEqual(default(DateTime), Provider.GetCacheItem("DateTimeTest")); + Assert.AreEqual(null, Provider.GetCacheItem("DateTimeTest")); + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index f36ac5008a..c04eb1a868 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -101,6 +101,7 @@ ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll + @@ -151,7 +152,11 @@ - + + + + + From a42b660648da4b8f1066ae972e43f39f544d953b Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 16 Dec 2013 13:19:10 +1100 Subject: [PATCH 0070/1046] Backporting the cachehelper and associated cache classes. --- src/Umbraco.Core/ApplicationContext.cs | 292 +++++----- .../Cache/CacheProviderExtensions.cs | 64 +++ .../Cache/DictionaryCacheProdiverBase.cs | 96 ++-- .../Cache/HttpRequestCacheProvider.cs | 9 +- .../Cache/HttpRuntimeCacheProvider.cs | 240 +++------ src/Umbraco.Core/Cache/ICacheProvider.cs | 12 +- .../Cache/IRuntimeCacheProvider.cs | 31 +- src/Umbraco.Core/Cache/NullCacheProvider.cs | 49 +- .../Cache/ObjectCacheRuntimeCacheProvider.cs | 177 +++--- src/Umbraco.Core/Cache/StaticCacheProvider.cs | 62 +-- src/Umbraco.Core/CacheHelper.cs | 508 ++++++------------ src/Umbraco.Core/CoreBootManager.cs | 29 +- .../PublishedContent/PublishedContentType.cs | 14 +- src/Umbraco.Core/Services/ServiceContext.cs | 93 +++- .../Standalone/ServiceContextManager.cs | 10 +- src/Umbraco.Core/Umbraco.Core.csproj | 9 +- src/Umbraco.Tests/BusinessLogic/BaseTest.cs | 2 +- src/Umbraco.Tests/Cache/CacheProviderTests.cs | 128 ++++- .../Cache/RuntimeCacheProviderTests.cs | 2 +- src/Umbraco.Tests/Macros/MacroTests.cs | 13 +- src/Umbraco.Tests/MockTests.cs | 28 +- src/Umbraco.Tests/Mvc/UmbracoViewPageTests.cs | 2 +- .../Persistence/BaseTableByTableTest.cs | 16 +- .../Persistence/DatabaseContextTests.cs | 22 +- .../PublishedContentMoreTests.cs | 2 +- .../PublishedContent/PublishedContentTests.cs | 2 +- .../Services/ThreadSafetyServiceTest.cs | 41 +- .../TestHelpers/BaseDatabaseFactoryTest.cs | 38 +- .../TestHelpers/BaseDatabaseTest.cs | 47 +- .../TestHelpers/BaseUmbracoApplicationTest.cs | 18 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 14 +- .../Standalone/ServiceContextManager.cs | 13 +- 32 files changed, 1034 insertions(+), 1049 deletions(-) create mode 100644 src/Umbraco.Core/Cache/CacheProviderExtensions.cs diff --git a/src/Umbraco.Core/ApplicationContext.cs b/src/Umbraco.Core/ApplicationContext.cs index 33748ae46b..a3f069ac7a 100644 --- a/src/Umbraco.Core/ApplicationContext.cs +++ b/src/Umbraco.Core/ApplicationContext.cs @@ -12,7 +12,7 @@ using Umbraco.Core.Services; namespace Umbraco.Core { - /// + /// /// the Umbraco Application context /// /// @@ -20,61 +20,45 @@ namespace Umbraco.Core /// public class ApplicationContext : IDisposable { - /// - /// Constructor - /// - internal ApplicationContext(DatabaseContext dbContext, ServiceContext serviceContext) - : this(dbContext, serviceContext, true) - { - - } /// /// Constructor /// /// /// - /// - public ApplicationContext(DatabaseContext dbContext, ServiceContext serviceContext, bool enableCache) - : this(enableCache) + /// + public ApplicationContext(DatabaseContext dbContext, ServiceContext serviceContext, CacheHelper cache) { + if (dbContext == null) throw new ArgumentNullException("dbContext"); + if (serviceContext == null) throw new ArgumentNullException("serviceContext"); + if (cache == null) throw new ArgumentNullException("cache"); _databaseContext = dbContext; - _services = serviceContext; + _services = serviceContext; + ApplicationCache = cache; } - /// - /// Empty constructor normally reserved for unit tests when a DatabaseContext or a ServiceContext is not - /// necessarily required or needs to be set after construction. - /// - internal ApplicationContext() : this(true) - { - } - /// - /// Constructor used to specify if we will enable application cache or not + /// Creates a basic app context /// - /// - public ApplicationContext(bool enableCache) + /// + public ApplicationContext(CacheHelper cache) { - //create a new application cache from the HttpRuntime.Cache - ApplicationCache = HttpRuntime.Cache == null - ? new CacheHelper(new System.Web.Caching.Cache(), enableCache) - : new CacheHelper(HttpRuntime.Cache, enableCache); + ApplicationCache = cache; } - /// - /// A method used to set and/or ensure that a global ApplicationContext singleton is created. - /// - /// - /// The instance to set on the global application singleton - /// - /// If set to true and the singleton is already set, it will be replaced - /// - /// - /// This is NOT thread safe - /// - public static ApplicationContext EnsureContext(ApplicationContext appContext, bool replaceContext) - { + /// + /// A method used to set and/or ensure that a global ApplicationContext singleton is created. + /// + /// + /// The instance to set on the global application singleton + /// + /// If set to true and the singleton is already set, it will be replaced + /// + /// + /// This is NOT thread safe + /// + public static ApplicationContext EnsureContext(ApplicationContext appContext, bool replaceContext) + { if (ApplicationContext.Current != null) { if (!replaceContext) @@ -82,56 +66,56 @@ namespace Umbraco.Core } ApplicationContext.Current = appContext; return ApplicationContext.Current; - } + } - /// - /// A method used to create and ensure that a global ApplicationContext singleton is created. - /// - /// - /// - /// If set to true will replace the current singleton instance - This should only be used for unit tests or on app - /// startup if for some reason the boot manager is not the umbraco boot manager. - /// - /// - /// - /// - /// - /// This is NOT thread safe - /// - public static ApplicationContext EnsureContext(DatabaseContext dbContext, ServiceContext serviceContext, bool enableCache, bool replaceContext) + /// + /// A method used to create and ensure that a global ApplicationContext singleton is created. + /// + /// + /// + /// If set to true will replace the current singleton instance - This should only be used for unit tests or on app + /// startup if for some reason the boot manager is not the umbraco boot manager. + /// + /// + /// + /// + /// + /// This is NOT thread safe + /// + public static ApplicationContext EnsureContext(DatabaseContext dbContext, ServiceContext serviceContext, CacheHelper cache, bool replaceContext) { if (ApplicationContext.Current != null) { if (!replaceContext) return ApplicationContext.Current; } - var ctx = new ApplicationContext(dbContext, serviceContext, enableCache); + var ctx = new ApplicationContext(dbContext, serviceContext, cache); ApplicationContext.Current = ctx; return ApplicationContext.Current; } - /// - /// Singleton accessor - /// - public static ApplicationContext Current { get; internal set; } + /// + /// Singleton accessor + /// + public static ApplicationContext Current { get; internal set; } - /// - /// Returns the application wide cache accessor - /// - /// - /// Any caching that is done in the application (app wide) should be done through this property - /// - public CacheHelper ApplicationCache { get; private set; } + /// + /// Returns the application wide cache accessor + /// + /// + /// Any caching that is done in the application (app wide) should be done through this property + /// + public CacheHelper ApplicationCache { get; private set; } - // IsReady is set to true by the boot manager once it has successfully booted + // IsReady is set to true by the boot manager once it has successfully booted // note - the original umbraco module checks on content.Instance in umbraco.dll // now, the boot task that setup the content store ensures that it is ready bool _isReady = false; - readonly System.Threading.ManualResetEventSlim _isReadyEvent = new System.Threading.ManualResetEventSlim(false); - private DatabaseContext _databaseContext; - private ServiceContext _services; + readonly ManualResetEventSlim _isReadyEvent = new ManualResetEventSlim(false); + private DatabaseContext _databaseContext; + private ServiceContext _services; - public bool IsReady + public bool IsReady { get { @@ -141,14 +125,14 @@ namespace Umbraco.Core { AssertIsNotReady(); _isReady = value; - _isReadyEvent.Set(); + _isReadyEvent.Set(); } } - public bool WaitForReady(int timeout) - { - return _isReadyEvent.WaitHandle.WaitOne(timeout); - } + public bool WaitForReady(int timeout) + { + return _isReadyEvent.WaitHandle.WaitOne(timeout); + } // notes @@ -157,14 +141,14 @@ namespace Umbraco.Core // the system is configured if they match // if they don't, install runs, updates web.config (presumably) and updates GlobalSettings.ConfiguredStatus // - // then there is Application["umbracoNeedConfiguration"] which makes no sense... getting rid of it... + // then there is Application["umbracoNeedConfiguration"] which makes no sense... getting rid of it... SD: I have actually remove that now! // public bool IsConfigured { // todo - we should not do this - ok for now get { - return Configured; + return Configured; } } @@ -180,45 +164,45 @@ namespace Umbraco.Core /// internal string OriginalRequestUrl { get; set; } - private bool Configured - { - get - { - try - { - string configStatus = ConfigurationStatus; - string currentVersion = UmbracoVersion.Current.ToString(3); + private bool Configured + { + get + { + try + { + string configStatus = ConfigurationStatus; + string currentVersion = UmbracoVersion.Current.ToString(3); - if (currentVersion != configStatus) - { - LogHelper.Info("CurrentVersion different from configStatus: '" + currentVersion + "','" + configStatus + "'"); - } - + if (currentVersion != configStatus) + { + LogHelper.Info("CurrentVersion different from configStatus: '" + currentVersion + "','" + configStatus + "'"); + } - return (configStatus == currentVersion); - } - catch - { - return false; - } - } - } - private string ConfigurationStatus - { - get - { - try - { - return ConfigurationManager.AppSettings["umbracoConfigurationStatus"]; - } - catch - { - return String.Empty; - } - } - } + return (configStatus == currentVersion); + } + catch + { + return false; + } + } + } + + private string ConfigurationStatus + { + get + { + try + { + return ConfigurationManager.AppSettings["umbracoConfigurationStatus"]; + } + catch + { + return String.Empty; + } + } + } private void AssertIsReady() { @@ -232,39 +216,39 @@ namespace Umbraco.Core throw new Exception("ApplicationContext has already been initialized."); } - /// - /// Gets the current DatabaseContext - /// - /// - /// Internal set is generally only used for unit tests - /// - public DatabaseContext DatabaseContext - { - get - { - if (_databaseContext == null) - throw new InvalidOperationException("The DatabaseContext has not been set on the ApplicationContext"); - return _databaseContext; - } - internal set { _databaseContext = value; } - } - - /// - /// Gets the current ServiceContext - /// - /// - /// Internal set is generally only used for unit tests - /// - public ServiceContext Services - { - get - { - if (_services == null) - throw new InvalidOperationException("The ServiceContext has not been set on the ApplicationContext"); - return _services; - } - internal set { _services = value; } - } + /// + /// Gets the current DatabaseContext + /// + /// + /// Internal set is generally only used for unit tests + /// + public DatabaseContext DatabaseContext + { + get + { + if (_databaseContext == null) + throw new InvalidOperationException("The DatabaseContext has not been set on the ApplicationContext"); + return _databaseContext; + } + internal set { _databaseContext = value; } + } + + /// + /// Gets the current ServiceContext + /// + /// + /// Internal set is generally only used for unit tests + /// + public ServiceContext Services + { + get + { + if (_services == null) + throw new InvalidOperationException("The ServiceContext has not been set on the ApplicationContext"); + return _services; + } + internal set { _services = value; } + } private volatile bool _disposed; @@ -290,26 +274,26 @@ namespace Umbraco.Core //clear the cache if (ApplicationCache != null) { - ApplicationCache.ClearAllCache(); + ApplicationCache.ClearAllCache(); } //reset all resolvers ResolverCollection.ResetAll(); //reset resolution itself (though this should be taken care of by resetting any of the resolvers above) Resolution.Reset(); - + //reset the instance objects this.ApplicationCache = null; if (_databaseContext != null) //need to check the internal field here { if (DatabaseContext.IsDatabaseConfigured) { - DatabaseContext.Database.Dispose(); - } + DatabaseContext.Database.Dispose(); + } } this.DatabaseContext = null; this.Services = null; this._isReady = false; //set the internal field - + // Indicate that the instance has been disposed. _disposed = true; } diff --git a/src/Umbraco.Core/Cache/CacheProviderExtensions.cs b/src/Umbraco.Core/Cache/CacheProviderExtensions.cs new file mode 100644 index 0000000000..7b621d013f --- /dev/null +++ b/src/Umbraco.Core/Cache/CacheProviderExtensions.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web.Caching; + +namespace Umbraco.Core.Cache +{ + /// + /// Extensions for strongly typed access + /// + internal static class CacheProviderExtensions + { + public static T GetCacheItem(this IRuntimeCacheProvider provider, + string cacheKey, + Func getCacheItem, + TimeSpan? timeout, + bool isSliding = false, + CacheItemPriority priority = CacheItemPriority.Normal, + CacheItemRemovedCallback removedCallback = null, + string[] dependentFiles = null) + { + var result = provider.GetCacheItem(cacheKey, () => getCacheItem(), timeout, isSliding, priority, removedCallback, dependentFiles); + return result == null ? default(T) : result.TryConvertTo().Result; + } + + public static void InsertCacheItem(this IRuntimeCacheProvider provider, + string cacheKey, + Func getCacheItem, + TimeSpan? timeout = null, + bool isSliding = false, + CacheItemPriority priority = CacheItemPriority.Normal, + CacheItemRemovedCallback removedCallback = null, + string[] dependentFiles = null) + { + provider.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, isSliding, priority, removedCallback, dependentFiles); + } + + public static IEnumerable GetCacheItemsByKeySearch(this ICacheProvider provider, string keyStartsWith) + { + var result = provider.GetCacheItemsByKeySearch(keyStartsWith); + return result.Select(x => x.TryConvertTo().Result); + } + + public static T GetCacheItem(this ICacheProvider provider, string cacheKey) + { + var result = provider.GetCacheItem(cacheKey); + if (result == null) + { + return default(T); + } + return result.TryConvertTo().Result; + } + + public static T GetCacheItem(this ICacheProvider provider, string cacheKey, Func getCacheItem) + { + var result = provider.GetCacheItem(cacheKey, () => getCacheItem()); + if (result == null) + { + return default(T); + } + return result.TryConvertTo().Result; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/DictionaryCacheProdiverBase.cs b/src/Umbraco.Core/Cache/DictionaryCacheProdiverBase.cs index adc02f9104..c1a4d17592 100644 --- a/src/Umbraco.Core/Cache/DictionaryCacheProdiverBase.cs +++ b/src/Umbraco.Core/Cache/DictionaryCacheProdiverBase.cs @@ -2,12 +2,13 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; +using System.Threading; namespace Umbraco.Core.Cache { internal abstract class DictionaryCacheProdiverBase : ICacheProvider { - private static readonly object Locker = new object(); + protected static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); protected abstract DictionaryCacheWrapper DictionaryCache { get; } /// @@ -18,7 +19,7 @@ namespace Umbraco.Core.Cache /// public virtual void ClearAllCache() { - lock (Locker) + using (new WriteLock(Locker)) { var keysToRemove = DictionaryCache.Cast() .Select(item => new DictionaryItemWrapper(item)) @@ -39,7 +40,7 @@ namespace Umbraco.Core.Cache /// Key public virtual void ClearCacheItem(string key) { - lock (Locker) + using (new WriteLock(Locker)) { if (DictionaryCache[GetCacheKey(key)] == null) return; DictionaryCache.Remove(GetCacheKey(key)); ; @@ -53,39 +54,66 @@ namespace Umbraco.Core.Cache /// The name of the System.Type which should be cleared from cache ex "System.Xml.XmlDocument" public virtual void ClearCacheObjectTypes(string typeName) { - lock (Locker) + using (new WriteLock(Locker)) { - var keysToRemove = DictionaryCache.Cast() - .Select(item => new DictionaryItemWrapper(item)) - .Where(c => DictionaryCache[c.Key.ToString()] != null && DictionaryCache[c.Key.ToString()].GetType().ToString().InvariantEquals(typeName)) - .Select(c => c.Key) - .ToList(); + var keysToRemove = DictionaryCache + .Cast() + .Select(item => new DictionaryItemWrapper(item)) + .Where(c => + { + var k = c.Key.ToString(); + var v = DictionaryCache[k]; + return v != null && v.GetType().ToString().InvariantEquals(typeName); + }) + .Select(c => c.Key) + .ToList(); foreach (var k in keysToRemove) - { DictionaryCache.Remove(k); - } } } - /// - /// Clears all objects in the System.Web.Cache with the System.Type specified - /// public virtual void ClearCacheObjectTypes() - { - lock (Locker) + { + using (new WriteLock(Locker)) { - var keysToRemove = DictionaryCache.Cast() - .Select(item => new DictionaryItemWrapper(item)) - .Where(c => DictionaryCache[c.Key.ToString()] != null && DictionaryCache[c.Key.ToString()].GetType() == typeof (T)) - .Select(c => c.Key) - .ToList(); + var typeOfT = typeof(T); + var keysToRemove = DictionaryCache + .Cast() + .Select(item => new DictionaryItemWrapper(item)) + .Where(c => + { + var k = c.Key.ToString(); + var v = DictionaryCache[k]; + return v != null && v.GetType() == typeOfT; + }) + .Select(c => c.Key) + .ToList(); foreach (var k in keysToRemove) - { - DictionaryCache.Remove(k); - } - + DictionaryCache.Remove(k); + } + } + + public virtual void ClearCacheObjectTypes(Func predicate) + { + using (new WriteLock(Locker)) + { + var typeOfT = typeof(T); + var keysToRemove = DictionaryCache + .Cast() + .Select(item => new DictionaryItemWrapper(item)) + .Where(c => + { + var k = c.Key.ToString(); + var v = DictionaryCache[k]; + return v != null && v.GetType() == typeOfT && predicate(k, (T)v); + }) + .Select(c => c.Key) + .ToList(); + + foreach (var k in keysToRemove) + DictionaryCache.Remove(k); } } @@ -134,35 +162,27 @@ namespace Umbraco.Core.Cache } } - public virtual IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) + public virtual IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) { return (from object item in DictionaryCache select new DictionaryItemWrapper(item) into c where c.Key is string && ((string) c.Key).InvariantStartsWith(string.Format("{0}-{1}", CacheItemPrefix, keyStartsWith)) - select c.Value.TryConvertTo() - into converted - where converted.Success - select converted.Result).ToList(); + select c.Value).ToList(); } /// /// Returns a cache item by key, does not update the cache if it isn't there. /// - /// /// /// - public virtual TT GetCacheItem(string cacheKey) + public virtual object GetCacheItem(string cacheKey) { var result = DictionaryCache.Get(GetCacheKey(cacheKey)); - if (result == null) - { - return default(TT); - } - return result.TryConvertTo().Result; + return result; } - public abstract T GetCacheItem(string cacheKey, Func getCacheItem); + public abstract object GetCacheItem(string cacheKey, Func getCacheItem); /// /// We prefix all cache keys with this so that we know which ones this class has created when diff --git a/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs b/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs index 134b780f2b..26c6dc0af8 100644 --- a/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs +++ b/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs @@ -36,15 +36,12 @@ namespace Umbraco.Core.Cache } } - public override T GetCacheItem(string cacheKey, Func getCacheItem) + public override object GetCacheItem(string cacheKey, Func getCacheItem) { var ctx = _context(); var ck = GetCacheKey(cacheKey); - if (ctx.Items[ck] == null) - { - ctx.Items[ck] = getCacheItem(); - } - return (T)ctx.Items[ck]; + return ctx.Items[ck] ?? (ctx.Items[ck] = getCacheItem()); } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs index 02b4aa46d2..9838f8d1e6 100644 --- a/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs @@ -1,4 +1,7 @@ using System; +using System.Collections; +using System.Linq; +using System.Threading; using System.Web; using System.Web.Caching; using Umbraco.Core.Logging; @@ -13,7 +16,6 @@ namespace Umbraco.Core.Cache { private readonly System.Web.Caching.Cache _cache; private readonly DictionaryCacheWrapper _wrapper; - private static readonly object Locker = new object(); public HttpRuntimeCacheProvider(System.Web.Caching.Cache cache) { @@ -22,6 +24,11 @@ namespace Umbraco.Core.Cache } protected override DictionaryCacheWrapper DictionaryCache + { + get { return _wrapper; } + } + + /// /// Clears all objects in the System.Web.Cache with the System.Type specified that satisfy the predicate /// public override void ClearCacheObjectTypes(Func predicate) @@ -48,207 +55,94 @@ namespace Umbraco.Core.Cache } } - /// - { - get { return _wrapper; } - } - /// /// Gets (and adds if necessary) an item from the cache with all of the default parameters /// - /// /// /// /// - public override T GetCacheItem(string cacheKey, Func getCacheItem) + public override object GetCacheItem(string cacheKey, Func getCacheItem) { - return GetCacheItem(cacheKey, CacheItemPriority.Normal, null, null, null, getCacheItem, Locker); + return GetCacheItem(cacheKey, getCacheItem, null, dependentFiles: null); } /// - /// Gets (and adds if necessary) an item from the cache with the specified absolute expiration date (from NOW) + /// This overload is here for legacy purposes /// - /// /// - /// This will set an absolute expiration from now until the timeout /// - /// - public virtual TT GetCacheItem(string cacheKey, - TimeSpan? timeout, Func getCacheItem) - { - return GetCacheItem(cacheKey, null, timeout, getCacheItem); - } - - /// - /// Gets (and adds if necessary) an item from the cache with the specified absolute expiration date (from NOW) - /// - /// - /// - /// - /// This will set an absolute expiration from now until the timeout - /// - /// - public virtual TT GetCacheItem(string cacheKey, - CacheItemRemovedCallback refreshAction, TimeSpan? timeout, - Func getCacheItem) - { - return GetCacheItem(cacheKey, CacheItemPriority.Normal, refreshAction, timeout, getCacheItem); - } - - /// - /// Gets (and adds if necessary) an item from the cache with the specified absolute expiration date (from NOW) - /// - /// - /// + /// + /// /// - /// - /// This will set an absolute expiration from now until the timeout - /// + /// + /// /// - public virtual TT GetCacheItem(string cacheKey, - CacheItemPriority priority, CacheItemRemovedCallback refreshAction, TimeSpan? timeout, - Func getCacheItem) - { - return GetCacheItem(cacheKey, priority, refreshAction, null, timeout, getCacheItem); - } - - /// - /// Gets (and adds if necessary) an item from the cache with the specified absolute expiration date (from NOW) - /// - /// - /// - /// - /// - /// - /// This will set an absolute expiration from now until the timeout - /// - /// - public virtual TT GetCacheItem(string cacheKey, - CacheItemPriority priority, - CacheItemRemovedCallback refreshAction, - CacheDependency cacheDependency, - TimeSpan? timeout, - Func getCacheItem) - { - return GetCacheItem(cacheKey, priority, refreshAction, cacheDependency, timeout, getCacheItem, Locker); - } - - /// - /// Gets (and adds if necessary) an item from the cache with the specified absolute expiration date (from NOW) - /// - /// - /// - /// - /// - /// - /// This will set an absolute expiration from now until the timeout - /// - /// - /// - private TT GetCacheItem(string cacheKey, - CacheItemPriority priority, CacheItemRemovedCallback refreshAction, - CacheDependency cacheDependency, TimeSpan? timeout, Func getCacheItem, object syncLock) + internal object GetCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, CacheDependency dependency = null) { cacheKey = GetCacheKey(cacheKey); - var result = DictionaryCache.Get(cacheKey); - if (result == null) + using (var lck = new UpgradeableReadLock(Locker)) { - lock (syncLock) + var result = DictionaryCache.Get(cacheKey); + if (result == null) { - result = DictionaryCache.Get(cacheKey); - if (result == null) - { - result = getCacheItem(); - if (result != null) - { - //we use Insert instead of add if for some crazy reason there is now a cache with the cache key in there, it will just overwrite it. - _cache.Insert(cacheKey, result, cacheDependency, - timeout == null ? System.Web.Caching.Cache.NoAbsoluteExpiration : DateTime.Now.Add(timeout.Value), - TimeSpan.Zero, priority, refreshAction); - } + lck.UpgradeToWriteLock(); + + result = getCacheItem(); + if (result != null) + { + var absolute = isSliding ? System.Web.Caching.Cache.NoAbsoluteExpiration : (timeout == null ? System.Web.Caching.Cache.NoAbsoluteExpiration : DateTime.Now.Add(timeout.Value)); + var sliding = isSliding == false ? System.Web.Caching.Cache.NoSlidingExpiration : (timeout ?? System.Web.Caching.Cache.NoSlidingExpiration); + + _cache.Insert(cacheKey, result, dependency, absolute, sliding, priority, removedCallback); } + } + return result; } - return result.TryConvertTo().Result; } - /// - /// Inserts an item into the cache, if it already exists in the cache it will be replaced - /// - /// - /// - /// - /// - public virtual void InsertCacheItem(string cacheKey, - CacheItemPriority priority, - Func getCacheItem) + public object GetCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) { - InsertCacheItem(cacheKey, priority, null, null, null, getCacheItem); - } - - /// - /// Inserts an item into the cache, if it already exists in the cache it will be replaced - /// - /// - /// - /// - /// This will set an absolute expiration from now until the timeout - /// - public virtual void InsertCacheItem(string cacheKey, - CacheItemPriority priority, - TimeSpan? timeout, - Func getCacheItem) - { - InsertCacheItem(cacheKey, priority, null, null, timeout, getCacheItem); - } - - /// - /// Inserts an item into the cache, if it already exists in the cache it will be replaced - /// - /// - /// - /// - /// - /// This will set an absolute expiration from now until the timeout - /// - public virtual void InsertCacheItem(string cacheKey, - CacheItemPriority priority, - CacheDependency cacheDependency, - TimeSpan? timeout, - Func getCacheItem) - { - InsertCacheItem(cacheKey, priority, null, cacheDependency, timeout, getCacheItem); - } - - /// - /// Inserts an item into the cache, if it already exists in the cache it will be replaced - /// - /// - /// - /// - /// - /// - /// This will set an absolute expiration from now until the timeout - /// - public virtual void InsertCacheItem(string cacheKey, - CacheItemPriority priority, - CacheItemRemovedCallback refreshAction, - CacheDependency cacheDependency, - TimeSpan? timeout, - Func getCacheItem) - { - object result = getCacheItem(); - if (result != null) + CacheDependency dependency = null; + if (dependentFiles != null && dependentFiles.Any()) { - cacheKey = GetCacheKey(cacheKey); - - //we use Insert instead of add if for some crazy reason there is now a cache with the cache key in there, it will just overwrite it. - _cache.Insert(cacheKey, result, cacheDependency, - timeout == null ? System.Web.Caching.Cache.NoAbsoluteExpiration : DateTime.Now.Add(timeout.Value), - TimeSpan.Zero, priority, refreshAction); + dependency = new CacheDependency(dependentFiles); } + return GetCacheItem(cacheKey, getCacheItem, timeout, isSliding, priority, removedCallback, dependency); } + /// + /// This overload is here for legacy purposes + /// + /// + /// + /// + /// + /// + /// + /// + internal void InsertCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, CacheDependency dependency = null) + { + var result = getCacheItem(); + if (result == null) return; + + cacheKey = GetCacheKey(cacheKey); + + var absolute = isSliding ? System.Web.Caching.Cache.NoAbsoluteExpiration : (timeout == null ? System.Web.Caching.Cache.NoAbsoluteExpiration : DateTime.Now.Add(timeout.Value)); + var sliding = isSliding == false ? System.Web.Caching.Cache.NoSlidingExpiration : (timeout ?? System.Web.Caching.Cache.NoSlidingExpiration); + + _cache.Insert(cacheKey, result, dependency, absolute, sliding, priority, removedCallback); + } + + public void InsertCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) + { + CacheDependency dependency = null; + if (dependentFiles != null && dependentFiles.Any()) + { + dependency = new CacheDependency(dependentFiles); + } + InsertCacheItem(cacheKey, getCacheItem, timeout, isSliding, priority, removedCallback, dependency); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/ICacheProvider.cs b/src/Umbraco.Core/Cache/ICacheProvider.cs index ab70cf63c0..e9bdde3ff2 100644 --- a/src/Umbraco.Core/Cache/ICacheProvider.cs +++ b/src/Umbraco.Core/Cache/ICacheProvider.cs @@ -6,19 +6,17 @@ namespace Umbraco.Core.Cache /// /// An abstract class for implementing a basic cache provider /// - /// - /// THIS MUST REMAIN INTERNAL UNTIL WE STREAMLINE HOW ALL CACHE IS HANDLED, WE NEED TO SUPPORT HTTP RUNTIME CACHE, IN MEMORY CACHE, ETC... - /// - internal interface ICacheProvider + public interface ICacheProvider { void ClearAllCache(); void ClearCacheItem(string key); void ClearCacheObjectTypes(string typeName); void ClearCacheObjectTypes(); + void ClearCacheObjectTypes(Func predicate); void ClearCacheByKeySearch(string keyStartsWith); void ClearCacheByKeyExpression(string regexString); - IEnumerable GetCacheItemsByKeySearch(string keyStartsWith); - T GetCacheItem(string cacheKey); - T GetCacheItem(string cacheKey, Func getCacheItem); + IEnumerable GetCacheItemsByKeySearch(string keyStartsWith); + object GetCacheItem(string cacheKey); + object GetCacheItem(string cacheKey, Func getCacheItem); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/IRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/IRuntimeCacheProvider.cs index 8dd1d31af0..b0227ca114 100644 --- a/src/Umbraco.Core/Cache/IRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Cache/IRuntimeCacheProvider.cs @@ -1,6 +1,8 @@ using System; +using System.Runtime.Caching; using System.Text; using System.Web.Caching; +using CacheItemPriority = System.Web.Caching.CacheItemPriority; namespace Umbraco.Core.Cache { @@ -8,17 +10,26 @@ namespace Umbraco.Core.Cache /// An abstract class for implementing a runtime cache provider /// /// - /// THIS MUST REMAIN INTERNAL UNTIL WE STREAMLINE HOW ALL CACHE IS HANDLED, WE NEED TO SUPPORT HTTP RUNTIME CACHE, IN MEMORY CACHE, REQUEST CACHE, ETC... /// - internal interface IRuntimeCacheProvider : ICacheProvider + public interface IRuntimeCacheProvider : ICacheProvider { - T GetCacheItem(string cacheKey, TimeSpan? timeout, Func getCacheItem); - T GetCacheItem(string cacheKey, CacheItemRemovedCallback refreshAction, TimeSpan? timeout, Func getCacheItem); - T GetCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, TimeSpan? timeout, Func getCacheItem); - T GetCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, CacheDependency cacheDependency, TimeSpan? timeout, Func getCacheItem); - void InsertCacheItem(string cacheKey, CacheItemPriority priority, Func getCacheItem); - void InsertCacheItem(string cacheKey, CacheItemPriority priority, TimeSpan? timeout, Func getCacheItem); - void InsertCacheItem(string cacheKey, CacheItemPriority priority, CacheDependency cacheDependency, TimeSpan? timeout, Func getCacheItem); - void InsertCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, CacheDependency cacheDependency, TimeSpan? timeout, Func getCacheItem); + object GetCacheItem( + string cacheKey, + Func getCacheItem, + TimeSpan? timeout, + bool isSliding = false, + CacheItemPriority priority = CacheItemPriority.Normal, + CacheItemRemovedCallback removedCallback = null, + string[] dependentFiles = null); + + void InsertCacheItem( + string cacheKey, + Func getCacheItem, + TimeSpan? timeout = null, + bool isSliding = false, + CacheItemPriority priority = CacheItemPriority.Normal, + CacheItemRemovedCallback removedCallback = null, + string[] dependentFiles = null); + } } diff --git a/src/Umbraco.Core/Cache/NullCacheProvider.cs b/src/Umbraco.Core/Cache/NullCacheProvider.cs index ff993bdbc0..ba88637661 100644 --- a/src/Umbraco.Core/Cache/NullCacheProvider.cs +++ b/src/Umbraco.Core/Cache/NullCacheProvider.cs @@ -23,11 +23,14 @@ namespace Umbraco.Core.Cache { } - public override void ClearCacheObjectTypes(Func predicate) + public virtual void ClearCacheObjectTypes(Func predicate) { } - public override void ClearCacheByKeySearch(string keyStartsWith) + + + + public virtual void ClearCacheByKeySearch(string keyStartsWith) { } @@ -35,55 +38,29 @@ namespace Umbraco.Core.Cache { } - public virtual IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) + public virtual IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) { - return Enumerable.Empty(); + return Enumerable.Empty(); } - public virtual T GetCacheItem(string cacheKey) + public virtual object GetCacheItem(string cacheKey) { - return default(T); + return default(object); } - public virtual T GetCacheItem(string cacheKey, Func getCacheItem) + public virtual object GetCacheItem(string cacheKey, Func getCacheItem) { return getCacheItem(); } - public virtual T GetCacheItem(string cacheKey, TimeSpan? timeout, Func getCacheItem) + public object GetCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) { return getCacheItem(); } - public virtual T GetCacheItem(string cacheKey, CacheItemRemovedCallback refreshAction, TimeSpan? timeout, Func getCacheItem) - { - return getCacheItem(); - } - - public virtual T GetCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, TimeSpan? timeout, Func getCacheItem) - { - return getCacheItem(); - } - - public virtual T GetCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, CacheDependency cacheDependency, TimeSpan? timeout, Func getCacheItem) - { - return getCacheItem(); - } - - public virtual void InsertCacheItem(string cacheKey, CacheItemPriority priority, Func getCacheItem) - { - } - - public virtual void InsertCacheItem(string cacheKey, CacheItemPriority priority, TimeSpan? timeout, Func getCacheItem) - { - } - - public virtual void InsertCacheItem(string cacheKey, CacheItemPriority priority, CacheDependency cacheDependency, TimeSpan? timeout, Func getCacheItem) - { - } - - public virtual void InsertCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, CacheDependency cacheDependency, TimeSpan? timeout, Func getCacheItem) + public void InsertCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) { + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs index c9a37d830d..aa6a5bbb22 100644 --- a/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs @@ -16,7 +16,7 @@ namespace Umbraco.Core.Cache /// internal class ObjectCacheRuntimeCacheProvider : IRuntimeCacheProvider { - private static readonly ReaderWriterLockSlim ClearLock = new ReaderWriterLockSlim(); + private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); internal ObjectCache MemoryCache; public ObjectCacheRuntimeCacheProvider() @@ -26,7 +26,7 @@ namespace Umbraco.Core.Cache public virtual void ClearAllCache() { - using (new WriteLock(ClearLock)) + using (new WriteLock(Locker)) { MemoryCache.DisposeIfDisposable(); MemoryCache = new MemoryCache("in-memory"); @@ -35,7 +35,7 @@ namespace Umbraco.Core.Cache public virtual void ClearCacheItem(string key) { - using (new WriteLock(ClearLock)) + using (new WriteLock(Locker)) { if (MemoryCache[key] == null) return; MemoryCache.Remove(key); @@ -44,31 +44,48 @@ namespace Umbraco.Core.Cache public virtual void ClearCacheObjectTypes(string typeName) { - using (new WriteLock(ClearLock)) + using (new WriteLock(Locker)) { - var keysToRemove = (from c in MemoryCache where c.Value.GetType().ToString().InvariantEquals(typeName) select c.Key).ToList(); + var keysToRemove = MemoryCache + .Where(c => c.Value != null && c.Value.GetType().ToString().InvariantEquals(typeName)) + .Select(c => c.Key) + .ToArray(); foreach (var k in keysToRemove) - { MemoryCache.Remove(k); - } } } public virtual void ClearCacheObjectTypes() { - using (new WriteLock(ClearLock)) + using (new WriteLock(Locker)) { - var keysToRemove = (from c in MemoryCache where c.Value.GetType() == typeof (T) select c.Key).ToList(); + var typeOfT = typeof (T); + var keysToRemove = MemoryCache + .Where(c => c.Value != null && c.Value.GetType() == typeOfT) + .Select(c => c.Key) + .ToArray(); + foreach (var k in keysToRemove) + MemoryCache.Remove(k); + } + } + + public virtual void ClearCacheObjectTypes(Func predicate) + { + using (new WriteLock(Locker)) + { + var typeOfT = typeof(T); + var keysToRemove = MemoryCache + .Where(c => c.Value != null && c.Value.GetType() == typeOfT && predicate(c.Key, (T)c.Value)) + .Select(c => c.Key) + .ToArray(); foreach (var k in keysToRemove) - { MemoryCache.Remove(k); - } } } public virtual void ClearCacheByKeySearch(string keyStartsWith) { - using (new WriteLock(ClearLock)) + using (new WriteLock(Locker)) { var keysToRemove = (from c in MemoryCache where c.Key.InvariantStartsWith(keyStartsWith) select c.Key).ToList(); foreach (var k in keysToRemove) @@ -80,7 +97,7 @@ namespace Umbraco.Core.Cache public virtual void ClearCacheByKeyExpression(string regexString) { - using (new WriteLock(ClearLock)) + using (new WriteLock(Locker)) { var keysToRemove = (from c in MemoryCache where Regex.IsMatch(c.Key, regexString) select c.Key).ToList(); foreach (var k in keysToRemove) @@ -90,49 +107,34 @@ namespace Umbraco.Core.Cache } } - public virtual IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) + public virtual IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) { return (from c in MemoryCache where c.Key.InvariantStartsWith(keyStartsWith) - select c.Value.TryConvertTo() - into attempt - where attempt.Success - select attempt.Result).ToList(); + select c.Value).ToList(); } - public virtual T GetCacheItem(string cacheKey) + public virtual object GetCacheItem(string cacheKey) { var result = MemoryCache.Get(cacheKey); - if (result == null) - { - return default(T); - } - return result.TryConvertTo().Result; + return result; } - public virtual T GetCacheItem(string cacheKey, Func getCacheItem) + public virtual object GetCacheItem(string cacheKey, Func getCacheItem) { - return GetCacheItem(cacheKey, CacheItemPriority.Normal, null, null, null, getCacheItem); + return GetCacheItem(cacheKey, getCacheItem, null); } - public virtual T GetCacheItem(string cacheKey, TimeSpan? timeout, Func getCacheItem) + public object GetCacheItem( + string cacheKey, + Func getCacheItem, + TimeSpan? timeout, + bool isSliding = false, + CacheItemPriority priority = CacheItemPriority.Normal, + CacheItemRemovedCallback removedCallback = null, + string[] dependentFiles = null) { - return GetCacheItem(cacheKey, null, timeout, getCacheItem); - } - - public virtual T GetCacheItem(string cacheKey, CacheItemRemovedCallback refreshAction, TimeSpan? timeout, Func getCacheItem) - { - return GetCacheItem(cacheKey, CacheItemPriority.Normal, refreshAction, timeout, getCacheItem); - } - - public virtual T GetCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, TimeSpan? timeout, Func getCacheItem) - { - return GetCacheItem(cacheKey, priority, refreshAction, null, timeout, getCacheItem); - } - - public virtual T GetCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, CacheDependency cacheDependency, TimeSpan? timeout, Func getCacheItem) - { - using (var lck = new UpgradeableReadLock(ClearLock)) + using (var lck = new UpgradeableReadLock(Locker)) { var result = MemoryCache.Get(cacheKey); if (result == null) @@ -142,57 +144,70 @@ namespace Umbraco.Core.Cache result = getCacheItem(); if (result != null) { - var policy = new CacheItemPolicy - { - AbsoluteExpiration = timeout == null ? ObjectCache.InfiniteAbsoluteExpiration : DateTime.Now.Add(timeout.Value), - SlidingExpiration = TimeSpan.Zero - }; - - //TODO: CUrrently we cannot implement this in this provider, we'll have to change the underlying interface - // to accept an array of files instead of CacheDependency. - //policy.ChangeMonitors.Add(new HostFileChangeMonitor(cacheDependency.)); - + var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles); MemoryCache.Set(cacheKey, result, policy); } } - return result.TryConvertTo().Result; + return result; } } - public virtual void InsertCacheItem(string cacheKey, CacheItemPriority priority, Func getCacheItem) - { - InsertCacheItem(cacheKey, priority, null, null, null, getCacheItem); - } - - public virtual void InsertCacheItem(string cacheKey, CacheItemPriority priority, TimeSpan? timeout, Func getCacheItem) - { - InsertCacheItem(cacheKey, priority, null, null, timeout, getCacheItem); - } - - public virtual void InsertCacheItem(string cacheKey, CacheItemPriority priority, CacheDependency cacheDependency, TimeSpan? timeout, Func getCacheItem) - { - InsertCacheItem(cacheKey, priority, null, cacheDependency, timeout, getCacheItem); - } - - public virtual void InsertCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, CacheDependency cacheDependency, TimeSpan? timeout, Func getCacheItem) + public void InsertCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) { object result = getCacheItem(); if (result != null) { - - var policy = new CacheItemPolicy - { - AbsoluteExpiration = timeout == null ? ObjectCache.InfiniteAbsoluteExpiration : DateTime.Now.Add(timeout.Value), - SlidingExpiration = TimeSpan.Zero - }; - - //TODO: CUrrently we cannot implement this in this provider, we'll have to change the underlying interface - // to accept an array of files instead of CacheDependency. - //policy.ChangeMonitors.Add(new HostFileChangeMonitor(cacheDependency.)); - + var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles); MemoryCache.Set(cacheKey, result, policy); } } + + private static CacheItemPolicy GetPolicy(TimeSpan? timeout = null, bool isSliding = false, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) + { + var absolute = isSliding ? ObjectCache.InfiniteAbsoluteExpiration : (timeout == null ? ObjectCache.InfiniteAbsoluteExpiration : DateTime.Now.Add(timeout.Value)); + var sliding = isSliding == false ? ObjectCache.NoSlidingExpiration : (timeout ?? ObjectCache.NoSlidingExpiration); + + var policy = new CacheItemPolicy + { + AbsoluteExpiration = absolute, + SlidingExpiration = sliding + }; + + if (dependentFiles != null && dependentFiles.Any()) + { + policy.ChangeMonitors.Add(new HostFileChangeMonitor(dependentFiles.ToList())); + } + + if (removedCallback != null) + { + policy.RemovedCallback = arguments => + { + //convert the reason + var reason = CacheItemRemovedReason.Removed; + switch (arguments.RemovedReason) + { + case CacheEntryRemovedReason.Removed: + reason = CacheItemRemovedReason.Removed; + break; + case CacheEntryRemovedReason.Expired: + reason = CacheItemRemovedReason.Expired; + break; + case CacheEntryRemovedReason.Evicted: + reason = CacheItemRemovedReason.Underused; + break; + case CacheEntryRemovedReason.ChangeMonitorChanged: + reason = CacheItemRemovedReason.Expired; + break; + case CacheEntryRemovedReason.CacheSpecificEviction: + reason = CacheItemRemovedReason.Underused; + break; + } + //call the callback + removedCallback(arguments.CacheItem.Key, arguments.CacheItem.Value, reason); + }; + } + return policy; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/StaticCacheProvider.cs b/src/Umbraco.Core/Cache/StaticCacheProvider.cs index 67c0527393..cd58fc3dde 100644 --- a/src/Umbraco.Core/Cache/StaticCacheProvider.cs +++ b/src/Umbraco.Core/Cache/StaticCacheProvider.cs @@ -27,75 +27,47 @@ namespace Umbraco.Core.Cache public virtual void ClearCacheObjectTypes(string typeName) { - foreach (var key in StaticCache.Keys) - { - if (StaticCache[key] != null - && StaticCache[key].GetType().ToString().InvariantEquals(typeName)) - { - object val; - StaticCache.TryRemove(key, out val); - } - } + StaticCache.RemoveAll(kvp => kvp.Value != null && kvp.Value.GetType().ToString().InvariantEquals(typeName)); } public virtual void ClearCacheObjectTypes() { - foreach (var key in StaticCache.Keys) - { - if (StaticCache[key] != null - && StaticCache[key].GetType() == typeof(T)) - { - object val; - StaticCache.TryRemove(key, out val); - } - } + var typeOfT = typeof(T); + StaticCache.RemoveAll(kvp => kvp.Value != null && kvp.Value.GetType() == typeOfT); + } + + public virtual void ClearCacheObjectTypes(Func predicate) + { + var typeOfT = typeof(T); + StaticCache.RemoveAll(kvp => kvp.Value != null && kvp.Value.GetType() == typeOfT && predicate(kvp.Key, (T)kvp.Value)); } public virtual void ClearCacheByKeySearch(string keyStartsWith) { - foreach (var key in StaticCache.Keys) - { - if (key.InvariantStartsWith(keyStartsWith)) - { - ClearCacheItem(key); - } - } + StaticCache.RemoveAll(kvp => kvp.Key.InvariantStartsWith(keyStartsWith)); } public virtual void ClearCacheByKeyExpression(string regexString) { - foreach (var key in StaticCache.Keys) - { - if (Regex.IsMatch(key, regexString)) - { - ClearCacheItem(key); - } - } + StaticCache.RemoveAll(kvp => Regex.IsMatch(kvp.Key, regexString)); } - public virtual IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) + public virtual IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) { return (from KeyValuePair c in StaticCache where c.Key.InvariantStartsWith(keyStartsWith) - select c.Value.TryConvertTo() - into attempt - where attempt.Success - select attempt.Result).ToList(); + select c.Value).ToList(); } - public virtual T GetCacheItem(string cacheKey) + public virtual object GetCacheItem(string cacheKey) { var result = StaticCache[cacheKey]; - if (result == null) - { - return default(T); - } - return result.TryConvertTo().Result; + return result; } - public virtual T GetCacheItem(string cacheKey, Func getCacheItem) + public virtual object GetCacheItem(string cacheKey, Func getCacheItem) { - return (T)StaticCache.GetOrAdd(cacheKey, getCacheItem()); + return StaticCache.GetOrAdd(cacheKey, key => getCacheItem()); } } diff --git a/src/Umbraco.Core/CacheHelper.cs b/src/Umbraco.Core/CacheHelper.cs index 7ec76f165c..539ac796b7 100644 --- a/src/Umbraco.Core/CacheHelper.cs +++ b/src/Umbraco.Core/CacheHelper.cs @@ -12,357 +12,130 @@ using Umbraco.Core.Logging; namespace Umbraco.Core { - /// - /// Class that is exposed by the ApplicationContext for application wide caching purposes - /// + /// + /// Class that is exposed by the ApplicationContext for application wide caching purposes + /// public class CacheHelper - { - private readonly bool _enableCache; - private readonly ICacheProvider _requestCache; + { + private readonly bool _enableCache; + private readonly ICacheProvider _requestCache; private readonly ICacheProvider _nullRequestCache = new NullCacheProvider(); private readonly ICacheProvider _staticCache; private readonly ICacheProvider _nullStaticCache = new NullCacheProvider(); private readonly IRuntimeCacheProvider _httpCache; private readonly IRuntimeCacheProvider _nullHttpCache = new NullCacheProvider(); - public CacheHelper(System.Web.Caching.Cache cache) - : this(cache, true) - { - } + /// + /// Creates a cache helper with disabled caches + /// + /// + /// + /// Good for unit testing + /// + public static CacheHelper CreateDisabledCacheHelper() + { + return new CacheHelper(null, null, null, false); + } - internal CacheHelper(System.Web.Caching.Cache cache, bool enableCache) - : this(new HttpRuntimeCacheProvider(cache), enableCache) - { - } - - internal CacheHelper(IRuntimeCacheProvider httpCacheProvider, bool enableCache) - : this(httpCacheProvider, new StaticCacheProvider(), new HttpRequestCacheProvider(HttpContext.Current), enableCache) + /// + /// Initializes a new instance for use in the web + /// + public CacheHelper() + : this( + new HttpRuntimeCacheProvider(HttpRuntime.Cache), + new StaticCacheProvider(), + new HttpRequestCacheProvider(() => new HttpContextWrapper(HttpContext.Current))) { } - internal CacheHelper( - IRuntimeCacheProvider httpCacheProvider, - ICacheProvider staticCacheProvider, - ICacheProvider requestCacheProvider, + /// + /// Initializes a new instance for use in the web + /// + /// + public CacheHelper(System.Web.Caching.Cache cache) + : this( + new HttpRuntimeCacheProvider(cache), + new StaticCacheProvider(), + new HttpRequestCacheProvider(() => new HttpContextWrapper(HttpContext.Current))) + { + } + + /// + /// Initializes a new instance based on the provided providers + /// + /// + /// + /// + public CacheHelper( + IRuntimeCacheProvider httpCacheProvider, + ICacheProvider staticCacheProvider, + ICacheProvider requestCacheProvider) + : this(httpCacheProvider, staticCacheProvider, requestCacheProvider, true) + { + } + + /// + /// Private ctor used for creating a disabled cache helper + /// + /// + /// + /// + /// + private CacheHelper( + IRuntimeCacheProvider httpCacheProvider, + ICacheProvider staticCacheProvider, + ICacheProvider requestCacheProvider, bool enableCache) { - _httpCache = httpCacheProvider; - _staticCache = staticCacheProvider; + if (enableCache) + { + _httpCache = httpCacheProvider; + _staticCache = staticCacheProvider; + _requestCache = requestCacheProvider; + } + else + { + _httpCache = null; + _staticCache = null; + _requestCache = null; + } + _enableCache = enableCache; - _requestCache = requestCacheProvider; - } - - #region Request cache - - /// - /// Clears the item in umbraco's request cache - /// - internal void ClearAllRequestCache() - { - if (!_enableCache) - { - _nullRequestCache.ClearAllCache(); - } - else - { - _requestCache.ClearAllCache(); - } } /// - /// Clears the item in umbraco's request cache with the given key + /// Returns the current Request cache /// - /// Key - internal void ClearRequestCacheItem(string key) + public ICacheProvider RequestCache { - if (!_enableCache) - { - _nullRequestCache.ClearCacheItem(key); - } - else - { - _requestCache.ClearCacheItem(key); - } + get { return _enableCache ? _requestCache : _nullRequestCache; } } /// - /// Clears all objects in the request cache with the System.Type name as the - /// input parameter. (using [object].GetType()) + /// Returns the current Runtime cache /// - /// The name of the System.Type which should be cleared from cache ex "System.Xml.XmlDocument" - internal void ClearRequestCacheObjectTypes(string typeName) + public ICacheProvider StaticCache { - if (!_enableCache) - { - _nullRequestCache.ClearCacheObjectTypes(typeName); - } - else - { - _requestCache.ClearCacheObjectTypes(typeName); - } + get { return _enableCache ? _staticCache : _nullStaticCache; } } /// - /// Clears all objects in the request cache with the System.Type specified + /// Returns the current Runtime cache /// - internal void ClearRequestCacheObjectTypes() + public IRuntimeCacheProvider RuntimeCache { - if (!_enableCache) - { - _nullRequestCache.ClearCacheObjectTypes(); - } - else - { - _requestCache.ClearCacheObjectTypes(); - } + get { return _enableCache ? _httpCache : _nullHttpCache; } } - /// - /// Clears all request cache items that starts with the key passed. - /// - /// The start of the key - internal void ClearRequestCacheByKeySearch(string keyStartsWith) - { - if (!_enableCache) - { - _nullRequestCache.ClearCacheByKeySearch(keyStartsWith); - } - else - { - _requestCache.ClearCacheByKeySearch(keyStartsWith); - } - } + #region Legacy Runtime/Http Cache accessors - /// - /// Clears all cache items that have a key that matches the regular expression - /// - /// - internal void ClearRequestCacheByKeyExpression(string regexString) - { - if (!_enableCache) - { - _nullRequestCache.ClearCacheByKeyExpression(regexString); - } - else - { - _requestCache.ClearCacheByKeyExpression(regexString); - } - } - - internal IEnumerable GetRequestCacheItemsByKeySearch(string keyStartsWith) - { - if (!_enableCache) - { - return _nullRequestCache.GetCacheItemsByKeySearch(keyStartsWith); - } - else - { - return _requestCache.GetCacheItemsByKeySearch(keyStartsWith); - } - } - - /// - /// Returns a request cache item by key, does not update the cache if it isn't there. - /// - /// - /// - /// - internal TT GetRequestCacheItem(string cacheKey) - { - if (!_enableCache) - { - return _nullRequestCache.GetCacheItem(cacheKey); - } - else - { - return _requestCache.GetCacheItem(cacheKey); - } - } - - /// - /// Gets (and adds if necessary) an item from the request cache with all of the default parameters - /// - /// - /// - /// - /// - internal TT GetRequestCacheItem(string cacheKey, Func getCacheItem) - { - if (!_enableCache) - { - return _nullRequestCache.GetCacheItem(cacheKey, getCacheItem); - } - else - { - return _requestCache.GetCacheItem(cacheKey, getCacheItem); - } - } - - #endregion - - #region Static cache - - /// - /// Clears the item in umbraco's static cache - /// - internal void ClearAllStaticCache() - { - if (!_enableCache) - { - _nullStaticCache.ClearAllCache(); - } - else - { - _staticCache.ClearAllCache(); - } - } - - /// - /// Clears the item in umbraco's static cache with the given key - /// - /// Key - internal void ClearStaticCacheItem(string key) - { - if (!_enableCache) - { - _nullStaticCache.ClearCacheItem(key); - } - else - { - _staticCache.ClearCacheItem(key); - } - } - - /// - /// Clears all objects in the static cache with the System.Type name as the - /// input parameter. (using [object].GetType()) - /// - /// The name of the System.Type which should be cleared from cache ex "System.Xml.XmlDocument" - internal void ClearStaticCacheObjectTypes(string typeName) - { - if (!_enableCache) - { - _nullStaticCache.ClearCacheObjectTypes(typeName); - } - else - { - _staticCache.ClearCacheObjectTypes(typeName); - } - } - - /// - /// Clears all objects in the static cache with the System.Type specified - /// - internal void ClearStaticCacheObjectTypes() - { - if (!_enableCache) - { - _nullStaticCache.ClearCacheObjectTypes(); - } - else - { - _staticCache.ClearCacheObjectTypes(); - } - } - - internal void ClearStaticCacheObjectTypes(Func predicate) - { - if (_enableCache) - _staticCache.ClearCacheObjectTypes(predicate); - else - _nullStaticCache.ClearCacheObjectTypes(predicate); - } - - /// - /// Clears all static cache items that starts with the key passed. - /// - /// The start of the key - internal void ClearStaticCacheByKeySearch(string keyStartsWith) - { - if (!_enableCache) - { - _nullStaticCache.ClearCacheByKeySearch(keyStartsWith); - } - else - { - _staticCache.ClearCacheByKeySearch(keyStartsWith); - } - } - - /// - /// Clears all cache items that have a key that matches the regular expression - /// - /// - internal void ClearStaticCacheByKeyExpression(string regexString) - { - if (!_enableCache) - { - _nullStaticCache.ClearCacheByKeyExpression(regexString); - } - else - { - _staticCache.ClearCacheByKeyExpression(regexString); - } - } - - internal IEnumerable GetStaticCacheItemsByKeySearch(string keyStartsWith) - { - if (!_enableCache) - { - return _nullStaticCache.GetCacheItemsByKeySearch(keyStartsWith); - } - else - { - return _staticCache.GetCacheItemsByKeySearch(keyStartsWith); - } - } - - /// - /// Returns a static cache item by key, does not update the cache if it isn't there. - /// - /// - /// - /// - internal TT GetStaticCacheItem(string cacheKey) - { - if (!_enableCache) - { - return _nullStaticCache.GetCacheItem(cacheKey); - } - else - { - return _staticCache.GetCacheItem(cacheKey); - } - } - - /// - /// Gets (and adds if necessary) an item from the static cache with all of the default parameters - /// - /// - /// - /// - /// - internal TT GetStaticCacheItem(string cacheKey, Func getCacheItem) - { - if (!_enableCache) - { - return _nullStaticCache.GetCacheItem(cacheKey, getCacheItem); - } - else - { - return _staticCache.GetCacheItem(cacheKey, getCacheItem); - } - } - - #endregion - - #region Runtime/Http Cache /// /// Clears the item in umbraco's runtime cache /// + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] public void ClearAllCache() { - if (!_enableCache) + if (_enableCache == false) { _nullHttpCache.ClearAllCache(); } @@ -376,9 +149,10 @@ namespace Umbraco.Core /// Clears the item in umbraco's runtime cache with the given key /// /// Key + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] public void ClearCacheItem(string key) { - if (!_enableCache) + if (_enableCache == false) { _nullHttpCache.ClearCacheItem(key); } @@ -394,9 +168,10 @@ namespace Umbraco.Core /// input parameter. (using [object].GetType()) /// /// The name of the System.Type which should be cleared from cache ex "System.Xml.XmlDocument" + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] public void ClearCacheObjectTypes(string typeName) { - if (!_enableCache) + if (_enableCache == false) { _nullHttpCache.ClearCacheObjectTypes(typeName); } @@ -409,9 +184,10 @@ namespace Umbraco.Core /// /// Clears all objects in the System.Web.Cache with the System.Type specified /// + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] public void ClearCacheObjectTypes() { - if (!_enableCache) + if (_enableCache == false) { _nullHttpCache.ClearCacheObjectTypes(); } @@ -425,9 +201,10 @@ namespace Umbraco.Core /// Clears all cache items that starts with the key passed. /// /// The start of the key + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] public void ClearCacheByKeySearch(string keyStartsWith) { - if (!_enableCache) + if (_enableCache == false) { _nullHttpCache.ClearCacheByKeySearch(keyStartsWith); } @@ -441,9 +218,10 @@ namespace Umbraco.Core /// Clears all cache items that have a key that matches the regular expression /// /// + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] public void ClearCacheByKeyExpression(string regexString) { - if (!_enableCache) + if (_enableCache == false) { _nullHttpCache.ClearCacheByKeyExpression(regexString); } @@ -453,9 +231,10 @@ namespace Umbraco.Core } } + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] public IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) { - if (!_enableCache) + if (_enableCache == false) { return _nullHttpCache.GetCacheItemsByKeySearch(keyStartsWith); } @@ -471,9 +250,10 @@ namespace Umbraco.Core /// /// /// + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] public TT GetCacheItem(string cacheKey) { - if (!_enableCache) + if (_enableCache == false) { return _nullHttpCache.GetCacheItem(cacheKey); } @@ -490,9 +270,10 @@ namespace Umbraco.Core /// /// /// + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] public TT GetCacheItem(string cacheKey, Func getCacheItem) { - if (!_enableCache) + if (_enableCache == false) { return _nullHttpCache.GetCacheItem(cacheKey, getCacheItem); } @@ -510,16 +291,17 @@ namespace Umbraco.Core /// This will set an absolute expiration from now until the timeout /// /// + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] public TT GetCacheItem(string cacheKey, TimeSpan timeout, Func getCacheItem) { - if (!_enableCache) + if (_enableCache == false) { - return _nullHttpCache.GetCacheItem(cacheKey, timeout, getCacheItem); + return _nullHttpCache.GetCacheItem(cacheKey, getCacheItem, timeout); } else { - return _httpCache.GetCacheItem(cacheKey, timeout, getCacheItem); + return _httpCache.GetCacheItem(cacheKey, getCacheItem, timeout); } } @@ -532,17 +314,18 @@ namespace Umbraco.Core /// This will set an absolute expiration from now until the timeout /// /// + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] public TT GetCacheItem(string cacheKey, CacheItemRemovedCallback refreshAction, TimeSpan timeout, Func getCacheItem) { if (!_enableCache) { - return _nullHttpCache.GetCacheItem(cacheKey, refreshAction, timeout, getCacheItem); + return _nullHttpCache.GetCacheItem(cacheKey, getCacheItem, timeout, removedCallback: refreshAction); } else { - return _httpCache.GetCacheItem(cacheKey, refreshAction, timeout, getCacheItem); + return _httpCache.GetCacheItem(cacheKey, getCacheItem, timeout, removedCallback: refreshAction); } } @@ -556,17 +339,18 @@ namespace Umbraco.Core /// This will set an absolute expiration from now until the timeout /// /// + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] public TT GetCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, TimeSpan timeout, Func getCacheItem) { - if (!_enableCache) + if (_enableCache == false) { - return _nullHttpCache.GetCacheItem(cacheKey, priority, refreshAction, timeout, getCacheItem); + return _nullHttpCache.GetCacheItem(cacheKey, getCacheItem, timeout, false, priority, refreshAction); } else { - return _httpCache.GetCacheItem(cacheKey, priority, refreshAction, timeout, getCacheItem); + return _httpCache.GetCacheItem(cacheKey, getCacheItem, timeout, false, priority, refreshAction); } } @@ -581,6 +365,7 @@ namespace Umbraco.Core /// This will set an absolute expiration from now until the timeout /// /// + [Obsolete("Do not use this method, we no longer support the caching overloads with references to CacheDependency, use the overloads specifying a file collection instead")] public TT GetCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, @@ -588,13 +373,19 @@ namespace Umbraco.Core TimeSpan timeout, Func getCacheItem) { - if (!_enableCache) + if (_enableCache == false) { - return _nullHttpCache.GetCacheItem(cacheKey, priority, refreshAction, cacheDependency, timeout, getCacheItem); + return _nullHttpCache.GetCacheItem(cacheKey, getCacheItem, timeout, false, priority, refreshAction, null); } else { - return _httpCache.GetCacheItem(cacheKey, priority, refreshAction, cacheDependency, timeout, getCacheItem); + var cache = _httpCache as HttpRuntimeCacheProvider; + if (cache != null) + { + var result = cache.GetCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, refreshAction, cacheDependency); + return result == null ? default(TT) : result.TryConvertTo().Result; + } + throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider)); } } @@ -607,6 +398,7 @@ namespace Umbraco.Core /// /// /// + [Obsolete("Do not use this method, we no longer support the caching overloads with references to CacheDependency, use the overloads specifying a file collection instead")] public TT GetCacheItem(string cacheKey, CacheItemPriority priority, CacheDependency cacheDependency, @@ -614,11 +406,17 @@ namespace Umbraco.Core { if (!_enableCache) { - return _nullHttpCache.GetCacheItem(cacheKey, priority, null, cacheDependency, null, getCacheItem); + return _nullHttpCache.GetCacheItem(cacheKey, getCacheItem, null, false, priority, null, null); } else { - return _httpCache.GetCacheItem(cacheKey, priority, null, cacheDependency, null, getCacheItem); + var cache = _httpCache as HttpRuntimeCacheProvider; + if (cache != null) + { + var result = cache.GetCacheItem(cacheKey, () => getCacheItem(), null, false, priority, null, cacheDependency); + return result == null ? default(TT) : result.TryConvertTo().Result; + } + throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider)); } } @@ -633,13 +431,13 @@ namespace Umbraco.Core CacheItemPriority priority, Func getCacheItem) { - if (!_enableCache) + if (_enableCache == false) { - _nullHttpCache.InsertCacheItem(cacheKey, priority, getCacheItem); + _nullHttpCache.InsertCacheItem(cacheKey, getCacheItem, priority: priority); } else { - _httpCache.InsertCacheItem(cacheKey, priority, getCacheItem); + _httpCache.InsertCacheItem(cacheKey, getCacheItem, priority: priority); } } @@ -656,13 +454,13 @@ namespace Umbraco.Core TimeSpan timeout, Func getCacheItem) { - if (!_enableCache) + if (_enableCache == false) { - _nullHttpCache.InsertCacheItem(cacheKey, priority, timeout, getCacheItem); + _nullHttpCache.InsertCacheItem(cacheKey, getCacheItem, timeout, priority: priority); } else { - _httpCache.InsertCacheItem(cacheKey, priority, timeout, getCacheItem); + _httpCache.InsertCacheItem(cacheKey, getCacheItem, timeout, priority: priority); } } @@ -675,19 +473,25 @@ namespace Umbraco.Core /// /// This will set an absolute expiration from now until the timeout /// + [Obsolete("Do not use this method, we no longer support the caching overloads with references to CacheDependency, use the overloads specifying a file collection instead")] public void InsertCacheItem(string cacheKey, CacheItemPriority priority, CacheDependency cacheDependency, TimeSpan timeout, Func getCacheItem) { - if (!_enableCache) + if (_enableCache == false) { - _nullHttpCache.InsertCacheItem(cacheKey, priority, cacheDependency, timeout, getCacheItem); + _nullHttpCache.InsertCacheItem(cacheKey, getCacheItem, timeout, priority: priority, dependentFiles: null); } else { - _httpCache.InsertCacheItem(cacheKey, priority, cacheDependency, timeout, getCacheItem); + var cache = _httpCache as HttpRuntimeCacheProvider; + if (cache != null) + { + cache.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, null, cacheDependency); + } + throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider)); } } @@ -701,6 +505,7 @@ namespace Umbraco.Core /// /// This will set an absolute expiration from now until the timeout /// + [Obsolete("Do not use this method, we no longer support the caching overloads with references to CacheDependency, use the overloads specifying a file collection instead")] public void InsertCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, @@ -708,17 +513,22 @@ namespace Umbraco.Core TimeSpan? timeout, Func getCacheItem) { - if (!_enableCache) + if (_enableCache == false) { - _nullHttpCache.InsertCacheItem(cacheKey, priority, refreshAction, cacheDependency, timeout, getCacheItem); + _nullHttpCache.InsertCacheItem(cacheKey, getCacheItem, timeout, false, priority, refreshAction, null); } else { - _httpCache.InsertCacheItem(cacheKey, priority, refreshAction, cacheDependency, timeout, getCacheItem); + var cache = _httpCache as HttpRuntimeCacheProvider; + if (cache != null) + { + cache.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, refreshAction, cacheDependency); + } + throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider)); } - } + } #endregion - } + } } diff --git a/src/Umbraco.Core/CoreBootManager.cs b/src/Umbraco.Core/CoreBootManager.cs index 91ee5db40f..0ed62f720a 100644 --- a/src/Umbraco.Core/CoreBootManager.cs +++ b/src/Umbraco.Core/CoreBootManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; @@ -38,6 +39,7 @@ namespace Umbraco.Core private bool _isComplete = false; private readonly UmbracoApplicationBase _umbracoApplication; protected ApplicationContext ApplicationContext { get; private set; } + protected CacheHelper ApplicationCache { get; set; } protected UmbracoApplicationBase UmbracoApplication { @@ -57,16 +59,19 @@ namespace Umbraco.Core InitializeProfilerResolver(); + CreateApplicationCache(); + _timer = DisposableTimer.DebugDuration("Umbraco application starting", "Umbraco application startup complete"); //create database and service contexts for the app context var dbFactory = new DefaultDatabaseFactory(GlobalSettings.UmbracoConnectionName); Database.Mapper = new PetaPocoMapper(); var dbContext = new DatabaseContext(dbFactory); - var serviceContext = new ServiceContext( - new PetaPocoUnitOfWorkProvider(dbFactory), - new FileUnitOfWorkProvider(), - new PublishingStrategy()); + var serviceContext = new ServiceContext( + new PetaPocoUnitOfWorkProvider(dbFactory), + new FileUnitOfWorkProvider(), + new PublishingStrategy(), + ApplicationCache); CreateApplicationContext(dbContext, serviceContext); @@ -94,7 +99,21 @@ namespace Umbraco.Core protected virtual void CreateApplicationContext(DatabaseContext dbContext, ServiceContext serviceContext) { //create the ApplicationContext - ApplicationContext = ApplicationContext.Current = new ApplicationContext(dbContext, serviceContext); + ApplicationContext = ApplicationContext.Current = new ApplicationContext(dbContext, serviceContext, ApplicationCache); + } + + /// + /// Creates and assigns the ApplicationCache based on a new instance of System.Web.Caching.Cache + /// + protected virtual void CreateApplicationCache() + { + var cacheHelper = new CacheHelper( + new ObjectCacheRuntimeCacheProvider(), + new StaticCacheProvider(), + //we have no request based cache when not running in web-based context + new NullCacheProvider()); + + ApplicationCache = cacheHelper; } /// diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs index 91532fdaad..eb53215896 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs @@ -102,7 +102,7 @@ namespace Umbraco.Core.Models.PublishedContent Logging.LogHelper.Debug("Clear all."); // ok and faster to do it by types, assuming noone else caches PublishedContentType instances //ApplicationContext.Current.ApplicationCache.ClearStaticCacheByKeySearch("PublishedContentType_"); - ApplicationContext.Current.ApplicationCache.ClearStaticCacheObjectTypes(); + ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheObjectTypes(); } internal static void ClearContentType(int id) @@ -110,7 +110,7 @@ namespace Umbraco.Core.Models.PublishedContent Logging.LogHelper.Debug("Clear content type w/id {0}.", () => id); // requires a predicate because the key does not contain the ID // faster than key strings comparisons anyway - ApplicationContext.Current.ApplicationCache.ClearStaticCacheObjectTypes( + ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheObjectTypes( (key, value) => value.Id == id); } @@ -121,7 +121,7 @@ namespace Umbraco.Core.Models.PublishedContent // properties ie both its own properties and those that were inherited (it's based upon an // IContentTypeComposition) and so every PublishedContentType having a property based upon // the cleared data type, be it local or inherited, will be cleared. - ApplicationContext.Current.ApplicationCache.ClearStaticCacheObjectTypes( + ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheObjectTypes( (key, value) => value.PropertyTypes.Any(x => x.DataTypeId == id)); } @@ -130,7 +130,7 @@ namespace Umbraco.Core.Models.PublishedContent var key = string.Format("PublishedContentType_{0}_{1}", itemType == PublishedItemType.Content ? "content" : "media", alias.ToLowerInvariant()); - var type = ApplicationContext.Current.ApplicationCache.GetStaticCacheItem(key, + var type = ApplicationContext.Current.ApplicationCache.StaticCache.GetCacheItem(key, () => CreatePublishedContentType(itemType, alias)); return type; @@ -142,8 +142,8 @@ namespace Umbraco.Core.Models.PublishedContent return GetPublishedContentTypeCallback(alias); var contentType = itemType == PublishedItemType.Content - ? (IContentTypeComposition) ApplicationContext.Current.Services.ContentTypeService.GetContentType(alias) - : (IContentTypeComposition) ApplicationContext.Current.Services.ContentTypeService.GetMediaType(alias); + ? (IContentTypeComposition)ApplicationContext.Current.Services.ContentTypeService.GetContentType(alias) + : (IContentTypeComposition)ApplicationContext.Current.Services.ContentTypeService.GetMediaType(alias); if (contentType == null) throw new Exception(string.Format("ContentTypeService failed to find a {0} type with alias \"{1}\".", @@ -161,7 +161,7 @@ namespace Umbraco.Core.Models.PublishedContent { // see note above //ClearAll(); - ApplicationContext.Current.ApplicationCache.ClearStaticCacheByKeySearch("PublishedContentType_"); + ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheByKeySearch("PublishedContentType_"); _getPublishedContentTypeCallBack = value; } diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index b6ccfc3d81..725697fa26 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -23,7 +23,10 @@ namespace Umbraco.Core.Services private Lazy _packagingService; private Lazy _serverRegistrationService; private Lazy _entityService; - private Lazy _relationService; + //private Lazy _relationService; + //private Lazy _treeService; + //private Lazy _sectionService; + //private Lazy _macroService; private Lazy _memberTypeService; /// @@ -38,9 +41,23 @@ namespace Umbraco.Core.Services /// /// /// - public ServiceContext(IContentService contentService, IMediaService mediaService, IContentTypeService contentTypeService, IDataTypeService dataTypeService, IFileService fileService, ILocalizationService localizationService, PackagingService packagingService, IEntityService entityService, RelationService relationService) + /// + /// + /// + public ServiceContext( + IContentService contentService, + IMediaService mediaService, + IContentTypeService contentTypeService, + IDataTypeService dataTypeService, + IFileService fileService, + ILocalizationService localizationService, + PackagingService packagingService, + IEntityService entityService/*, + IRelationService relationService, + ISectionService sectionService, + IApplicationTreeService treeService*/) { - _contentService = new Lazy(() => contentService); + _contentService = new Lazy(() => contentService); _mediaService = new Lazy(() => mediaService); _contentTypeService = new Lazy(() => contentTypeService); _dataTypeService = new Lazy(() => dataTypeService); @@ -48,7 +65,9 @@ namespace Umbraco.Core.Services _localizationService = new Lazy(() => localizationService); _packagingService = new Lazy(() => packagingService); _entityService = new Lazy(() => entityService); - _relationService = new Lazy(() => relationService); + //_relationService = new Lazy(() => relationService); + //_sectionService = new Lazy(() => sectionService); + //_treeService = new Lazy(() => treeService); } /// @@ -57,11 +76,12 @@ namespace Umbraco.Core.Services /// /// /// - internal ServiceContext(IDatabaseUnitOfWorkProvider dbUnitOfWorkProvider, IUnitOfWorkProvider fileUnitOfWorkProvider, BasePublishingStrategy publishingStrategy) + /// + internal ServiceContext(IDatabaseUnitOfWorkProvider dbUnitOfWorkProvider, IUnitOfWorkProvider fileUnitOfWorkProvider, BasePublishingStrategy publishingStrategy, CacheHelper cache) { - BuildServiceCache(dbUnitOfWorkProvider, fileUnitOfWorkProvider, publishingStrategy, - //this needs to be lazy because when we create the service context it's generally before the - //resolvers have been initialized! + BuildServiceCache(dbUnitOfWorkProvider, fileUnitOfWorkProvider, publishingStrategy, cache, + //this needs to be lazy because when we create the service context it's generally before the + //resolvers have been initialized! new Lazy(() => RepositoryResolver.Current.Factory)); } @@ -72,6 +92,7 @@ namespace Umbraco.Core.Services IDatabaseUnitOfWorkProvider dbUnitOfWorkProvider, IUnitOfWorkProvider fileUnitOfWorkProvider, BasePublishingStrategy publishingStrategy, + CacheHelper cache, Lazy repositoryFactory) { var provider = dbUnitOfWorkProvider; @@ -110,11 +131,21 @@ namespace Umbraco.Core.Services if (_entityService == null) _entityService = new Lazy(() => new EntityService(provider, repositoryFactory.Value, _contentService.Value, _contentTypeService.Value, _mediaService.Value, _dataTypeService.Value)); - if (_relationService == null) - _relationService = new Lazy(() => new RelationService(provider, repositoryFactory.Value, _entityService.Value)); + //if (_relationService == null) + // _relationService = new Lazy(() => new RelationService(provider, repositoryFactory.Value, _entityService.Value)); + + //if (_treeService == null) + // _treeService = new Lazy(() => new ApplicationTreeService(cache)); + + //if (_sectionService == null) + // _sectionService = new Lazy(() => new SectionService(_userService.Value, _treeService.Value, cache)); + + //if (_macroService == null) + // _macroService = new Lazy(() => new MacroService(provider, repositoryFactory.Value)); if (_memberTypeService == null) _memberTypeService = new Lazy(() => new MemberTypeService(provider, repositoryFactory.Value)); + } /// @@ -124,22 +155,30 @@ namespace Umbraco.Core.Services { get { return _serverRegistrationService.Value; } } + + ///// + ///// Gets the + ///// + //public IMacroService MacroService + //{ + // get { return _macroService.Value; } + //} /// - /// Gets the + /// Gets the /// public IEntityService EntityService { get { return _entityService.Value; } } - /// - /// Gets the - /// - public RelationService RelationService - { - get { return _relationService.Value; } - } + ///// + ///// Gets the + ///// + //public IRelationService RelationService + //{ + // get { return _relationService.Value; } + //} /// /// Gets the @@ -212,7 +251,23 @@ namespace Umbraco.Core.Services { get { return _memberService.Value; } } - + + ///// + ///// Gets the + ///// + //public ISectionService SectionService + //{ + // get { return _sectionService.Value; } + //} + + ///// + ///// Gets the + ///// + //public IApplicationTreeService ApplicationTreeService + //{ + // get { return _treeService.Value; } + //} + /// /// Gets the MemberTypeService /// diff --git a/src/Umbraco.Core/Standalone/ServiceContextManager.cs b/src/Umbraco.Core/Standalone/ServiceContextManager.cs index 579bde0ff2..955c7a25df 100644 --- a/src/Umbraco.Core/Standalone/ServiceContextManager.cs +++ b/src/Umbraco.Core/Standalone/ServiceContextManager.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using Umbraco.Core.Cache; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.UnitOfWork; @@ -45,13 +46,20 @@ namespace Umbraco.Core.Standalone { if (_serviceContext == null) { + var cacheHelper = new CacheHelper( + new ObjectCacheRuntimeCacheProvider(), + new StaticCacheProvider(), + //we have no request based cache when running standalone + new NullCacheProvider()); + var dbFactory = new DefaultDatabaseFactory(_connectionString, _providerName); var dbContext = new DatabaseContext(dbFactory); Database.Mapper = new PetaPocoMapper(); _serviceContext = new ServiceContext( new PetaPocoUnitOfWorkProvider(dbFactory), new FileUnitOfWorkProvider(), - new PublishingStrategy()); + new PublishingStrategy(), + cacheHelper); //initialize the DatabaseContext dbContext.Initialize(_providerName); diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 84a52c2947..8498e39e4d 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -113,19 +113,20 @@ - + - - + + + @@ -725,7 +726,7 @@ - + diff --git a/src/Umbraco.Tests/BusinessLogic/BaseTest.cs b/src/Umbraco.Tests/BusinessLogic/BaseTest.cs index 7fe009ffd7..9e4e6ccea7 100644 --- a/src/Umbraco.Tests/BusinessLogic/BaseTest.cs +++ b/src/Umbraco.Tests/BusinessLogic/BaseTest.cs @@ -32,7 +32,7 @@ namespace Umbraco.Tests.BusinessLogic [SetUp] public void Initialize() { - ApplicationContext.Current = new ApplicationContext(false){IsReady = true}; + ApplicationContext.Current = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()) { IsReady = true }; InitializeDatabase(); InitializeApps(); InitializeAppConfigFile(); diff --git a/src/Umbraco.Tests/Cache/CacheProviderTests.cs b/src/Umbraco.Tests/Cache/CacheProviderTests.cs index b7ed588bb8..5140995c38 100644 --- a/src/Umbraco.Tests/Cache/CacheProviderTests.cs +++ b/src/Umbraco.Tests/Cache/CacheProviderTests.cs @@ -1,4 +1,5 @@ -using System.Web.UI; +using System.Linq; +using System.Web.UI; using NUnit.Framework; using Umbraco.Core.Cache; using umbraco; @@ -23,12 +24,69 @@ namespace Umbraco.Tests.Cache } [Test] - public void Can_Remove_By_Type_Name() + public void Can_Get_By_Search() { var cacheContent1 = new MacroCacheContent(new LiteralControl(), "Test1"); var cacheContent2 = new MacroCacheContent(new LiteralControl(), "Test2"); var cacheContent3 = new MacroCacheContent(new LiteralControl(), "Test3"); - var cacheContent4 = new MacroCacheContent(new LiteralControl(), "Test4"); + var cacheContent4 = new LiteralControl(); + Provider.GetCacheItem("Test1", () => cacheContent1); + Provider.GetCacheItem("Tester2", () => cacheContent2); + Provider.GetCacheItem("Tes3", () => cacheContent3); + Provider.GetCacheItem("different4", () => cacheContent4); + + Assert.AreEqual(4, GetTotalItemCount); + + var result = Provider.GetCacheItemsByKeySearch("Tes"); + + Assert.AreEqual(3, result.Count()); + } + + [Test] + public void Can_Clear_By_Expression() + { + var cacheContent1 = new MacroCacheContent(new LiteralControl(), "Test1"); + var cacheContent2 = new MacroCacheContent(new LiteralControl(), "Test2"); + var cacheContent3 = new MacroCacheContent(new LiteralControl(), "Test3"); + var cacheContent4 = new LiteralControl(); + Provider.GetCacheItem("TTes1t", () => cacheContent1); + Provider.GetCacheItem("Tester2", () => cacheContent2); + Provider.GetCacheItem("Tes3", () => cacheContent3); + Provider.GetCacheItem("different4", () => cacheContent4); + + Assert.AreEqual(4, GetTotalItemCount); + + Provider.ClearCacheByKeyExpression("^\\w+es\\d.*"); + + Assert.AreEqual(2, GetTotalItemCount); + } + + [Test] + public void Can_Clear_By_Search() + { + var cacheContent1 = new MacroCacheContent(new LiteralControl(), "Test1"); + var cacheContent2 = new MacroCacheContent(new LiteralControl(), "Test2"); + var cacheContent3 = new MacroCacheContent(new LiteralControl(), "Test3"); + var cacheContent4 = new LiteralControl(); + Provider.GetCacheItem("Test1", () => cacheContent1); + Provider.GetCacheItem("Tester2", () => cacheContent2); + Provider.GetCacheItem("Tes3", () => cacheContent3); + Provider.GetCacheItem("different4", () => cacheContent4); + + Assert.AreEqual(4, GetTotalItemCount); + + Provider.ClearCacheByKeySearch("Test"); + + Assert.AreEqual(2, GetTotalItemCount); + } + + [Test] + public void Can_Clear_By_Key() + { + var cacheContent1 = new MacroCacheContent(new LiteralControl(), "Test1"); + var cacheContent2 = new MacroCacheContent(new LiteralControl(), "Test2"); + var cacheContent3 = new MacroCacheContent(new LiteralControl(), "Test3"); + var cacheContent4 = new LiteralControl(); Provider.GetCacheItem("Test1", () => cacheContent1); Provider.GetCacheItem("Test2", () => cacheContent2); Provider.GetCacheItem("Test3", () => cacheContent3); @@ -36,18 +94,76 @@ namespace Umbraco.Tests.Cache Assert.AreEqual(4, GetTotalItemCount); - Provider.ClearCacheObjectTypes("umbraco.MacroCacheContent"); + Provider.ClearCacheItem("Test1"); + Provider.ClearCacheItem("Test2"); + + Assert.AreEqual(2, GetTotalItemCount); + } + + [Test] + public void Can_Clear_All_Items() + { + var cacheContent1 = new MacroCacheContent(new LiteralControl(), "Test1"); + var cacheContent2 = new MacroCacheContent(new LiteralControl(), "Test2"); + var cacheContent3 = new MacroCacheContent(new LiteralControl(), "Test3"); + var cacheContent4 = new LiteralControl(); + Provider.GetCacheItem("Test1", () => cacheContent1); + Provider.GetCacheItem("Test2", () => cacheContent2); + Provider.GetCacheItem("Test3", () => cacheContent3); + Provider.GetCacheItem("Test4", () => cacheContent4); + + Assert.AreEqual(4, GetTotalItemCount); + + Provider.ClearAllCache(); Assert.AreEqual(0, GetTotalItemCount); } + [Test] + public void Can_Add_When_Not_Available() + { + var cacheContent1 = new MacroCacheContent(new LiteralControl(), "Test1"); + Provider.GetCacheItem("Test1", () => cacheContent1); + Assert.AreEqual(1, GetTotalItemCount); + } + + [Test] + public void Can_Get_When_Available() + { + var cacheContent1 = new MacroCacheContent(new LiteralControl(), "Test1"); + var result = Provider.GetCacheItem("Test1", () => cacheContent1); + var result2 = Provider.GetCacheItem("Test1", () => cacheContent1); + Assert.AreEqual(1, GetTotalItemCount); + Assert.AreEqual(result, result2); + } + + [Test] + public void Can_Remove_By_Type_Name() + { + var cacheContent1 = new MacroCacheContent(new LiteralControl(), "Test1"); + var cacheContent2 = new MacroCacheContent(new LiteralControl(), "Test2"); + var cacheContent3 = new MacroCacheContent(new LiteralControl(), "Test3"); + var cacheContent4 = new LiteralControl(); + Provider.GetCacheItem("Test1", () => cacheContent1); + Provider.GetCacheItem("Test2", () => cacheContent2); + Provider.GetCacheItem("Test3", () => cacheContent3); + Provider.GetCacheItem("Test4", () => cacheContent4); + + Assert.AreEqual(4, GetTotalItemCount); + + //Provider.ClearCacheObjectTypes("umbraco.MacroCacheContent"); + Provider.ClearCacheObjectTypes(typeof(MacroCacheContent).ToString()); + + Assert.AreEqual(1, GetTotalItemCount); + } + [Test] public void Can_Remove_By_Strong_Type() { var cacheContent1 = new MacroCacheContent(new LiteralControl(), "Test1"); var cacheContent2 = new MacroCacheContent(new LiteralControl(), "Test2"); var cacheContent3 = new MacroCacheContent(new LiteralControl(), "Test3"); - var cacheContent4 = new MacroCacheContent(new LiteralControl(), "Test4"); + var cacheContent4 = new LiteralControl(); Provider.GetCacheItem("Test1", () => cacheContent1); Provider.GetCacheItem("Test2", () => cacheContent2); Provider.GetCacheItem("Test3", () => cacheContent3); @@ -57,7 +173,7 @@ namespace Umbraco.Tests.Cache Provider.ClearCacheObjectTypes(); - Assert.AreEqual(0, GetTotalItemCount); + Assert.AreEqual(1, GetTotalItemCount); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Cache/RuntimeCacheProviderTests.cs b/src/Umbraco.Tests/Cache/RuntimeCacheProviderTests.cs index 03ad355f42..d123d2dc31 100644 --- a/src/Umbraco.Tests/Cache/RuntimeCacheProviderTests.cs +++ b/src/Umbraco.Tests/Cache/RuntimeCacheProviderTests.cs @@ -16,7 +16,7 @@ namespace Umbraco.Tests.Cache public void Can_Add_And_Expire_Struct_Strongly_Typed_With_Null() { var now = DateTime.Now; - RuntimeProvider.InsertCacheItem("DateTimeTest", CacheItemPriority.Default, new TimeSpan(0, 0, 0, 0, 200), () => now); + RuntimeProvider.InsertCacheItem("DateTimeTest", () => now, new TimeSpan(0, 0, 0, 0, 200)); Assert.AreEqual(now, Provider.GetCacheItem("DateTimeTest")); Assert.AreEqual(now, Provider.GetCacheItem("DateTimeTest")); diff --git a/src/Umbraco.Tests/Macros/MacroTests.cs b/src/Umbraco.Tests/Macros/MacroTests.cs index c4f8977b35..0d822f05a9 100644 --- a/src/Umbraco.Tests/Macros/MacroTests.cs +++ b/src/Umbraco.Tests/Macros/MacroTests.cs @@ -5,6 +5,7 @@ using System.Web.UI; using System.Web.UI.WebControls; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Profiling; using umbraco; using umbraco.cms.businesslogic.macro; @@ -19,11 +20,15 @@ namespace Umbraco.Tests.Macros public void Setup() { //we DO want cache enabled for these tests - ApplicationContext.Current = new ApplicationContext(true); + var cacheHelper = new CacheHelper( + new ObjectCacheRuntimeCacheProvider(), + new StaticCacheProvider(), + new NullCacheProvider()); + ApplicationContext.Current = new ApplicationContext(cacheHelper); ProfilerResolver.Current = new ProfilerResolver(new LogProfiler()) - { - CanResolveBeforeFrozen = true - }; + { + CanResolveBeforeFrozen = true + }; } [TearDown] diff --git a/src/Umbraco.Tests/MockTests.cs b/src/Umbraco.Tests/MockTests.cs index be31d2dbf4..126fad79d8 100644 --- a/src/Umbraco.Tests/MockTests.cs +++ b/src/Umbraco.Tests/MockTests.cs @@ -20,7 +20,7 @@ namespace Umbraco.Tests [Test] public void Can_Create_Empty_App_Context() { - var appCtx = new ApplicationContext(false); + var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()); Assert.Pass(); } @@ -43,11 +43,13 @@ namespace Umbraco.Tests new Mock().Object, new RepositoryFactory(true), new Mock().Object), - new Mock().Object, + new Mock().Object/*, new RelationService( new Mock().Object, new RepositoryFactory(true), - new Mock().Object)); + new Mock().Object), + new Mock().Object, + new Mock().Object*/); Assert.Pass(); } @@ -79,19 +81,19 @@ namespace Umbraco.Tests new Mock().Object, new RepositoryFactory(true), new Mock().Object), - new Mock().Object, + new Mock().Object/*, new RelationService( new Mock().Object, new RepositoryFactory(true), - new Mock().Object)), - false); + new Mock().Object)*/), + CacheHelper.CreateDisabledCacheHelper()); Assert.Pass(); } - + [Test] public void Can_Assign_App_Context_Singleton() { - var appCtx = new ApplicationContext(false); + var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()); var result = ApplicationContext.EnsureContext(appCtx, true); Assert.AreEqual(appCtx, result); } @@ -99,8 +101,8 @@ namespace Umbraco.Tests [Test] public void Does_Not_Overwrite_App_Context_Singleton() { - ApplicationContext.EnsureContext(new ApplicationContext(false), true); - var appCtx = new ApplicationContext(false); + ApplicationContext.EnsureContext(new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()), true); + var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()); var result = ApplicationContext.EnsureContext(appCtx, false); Assert.AreNotEqual(appCtx, result); } @@ -109,14 +111,14 @@ namespace Umbraco.Tests [Test] public void Can_Get_Umbraco_Context() { - var appCtx = new ApplicationContext(false); + var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()); ApplicationContext.EnsureContext(appCtx, true); - + var umbCtx = UmbracoContext.EnsureContext( new Mock().Object, appCtx, true); - + Assert.AreEqual(umbCtx, UmbracoContext.Current); } diff --git a/src/Umbraco.Tests/Mvc/UmbracoViewPageTests.cs b/src/Umbraco.Tests/Mvc/UmbracoViewPageTests.cs index 462936c26e..1d33949df2 100644 --- a/src/Umbraco.Tests/Mvc/UmbracoViewPageTests.cs +++ b/src/Umbraco.Tests/Mvc/UmbracoViewPageTests.cs @@ -402,7 +402,7 @@ namespace Umbraco.Tests.Mvc //PublishedContentCache.UnitTesting = true; // ApplicationContext.Current = new ApplicationContext(false) { IsReady = true }; - var appCtx = new ApplicationContext(false) { IsReady = true }; + var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()) { IsReady = true }; var ctx = new UmbracoContext( GetHttpContextFactory(url, routeData).HttpContext, diff --git a/src/Umbraco.Tests/Persistence/BaseTableByTableTest.cs b/src/Umbraco.Tests/Persistence/BaseTableByTableTest.cs index f8f56d6dcb..bb0ac581bc 100644 --- a/src/Umbraco.Tests/Persistence/BaseTableByTableTest.cs +++ b/src/Umbraco.Tests/Persistence/BaseTableByTableTest.cs @@ -27,17 +27,19 @@ namespace Umbraco.Tests.Persistence RepositoryResolver.Current = new RepositoryResolver( new RepositoryFactory()); - + + //disable cache + var cacheHelper = CacheHelper.CreateDisabledCacheHelper(); + ApplicationContext.Current = new ApplicationContext( //assign the db context new DatabaseContext(new DefaultDatabaseFactory()), //assign the service context - new ServiceContext(new PetaPocoUnitOfWorkProvider(), new FileUnitOfWorkProvider(), new PublishingStrategy()), - //disable cache - false) - { - IsReady = true - }; + new ServiceContext(new PetaPocoUnitOfWorkProvider(), new FileUnitOfWorkProvider(), new PublishingStrategy(), cacheHelper), + cacheHelper) + { + IsReady = true + }; Resolution.Freeze(); } diff --git a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs index 25fe36cbf7..5bf2264c5c 100644 --- a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs +++ b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs @@ -15,18 +15,18 @@ namespace Umbraco.Tests.Persistence { private DatabaseContext _dbContext; - [SetUp] - public void Setup() - { - _dbContext = new DatabaseContext(new DefaultDatabaseFactory()); + [SetUp] + public void Setup() + { + _dbContext = new DatabaseContext(new DefaultDatabaseFactory()); - //unfortunately we have to set this up because the PetaPocoExtensions require singleton access - ApplicationContext.Current = new ApplicationContext(false) - { - DatabaseContext = _dbContext, - IsReady = true - }; - } + //unfortunately we have to set this up because the PetaPocoExtensions require singleton access + ApplicationContext.Current = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()) + { + DatabaseContext = _dbContext, + IsReady = true + }; + } [TearDown] public void TearDown() diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs index 18cf4bc278..6d5c9065d7 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs @@ -40,7 +40,7 @@ namespace Umbraco.Tests.PublishedContent var caches = CreatePublishedContent(); - ApplicationContext.Current = new ApplicationContext(false) { IsReady = true }; + ApplicationContext.Current = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()) { IsReady = true }; var factory = new FakeHttpContextFactory("http://umbraco.local/"); StateHelper.HttpContext = factory.HttpContext; var context = new UmbracoContext( diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index 2806b1a931..610d135870 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -40,7 +40,7 @@ namespace Umbraco.Tests.PublishedContent .Union(new[] { typeof(PublishedContentTests).Assembly }) }; - ApplicationContext.Current = new ApplicationContext(false) { IsReady = true }; + ApplicationContext.Current = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()) { IsReady = true }; // need to specify a custom callback for unit tests // AutoPublishedContentTypes generates properties automatically diff --git a/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs b/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs index 1c7f2a39e6..eaa3c9c65c 100644 --- a/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs +++ b/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs @@ -24,27 +24,30 @@ namespace Umbraco.Tests.Services private PerThreadPetaPocoUnitOfWorkProvider _uowProvider; private PerThreadDatabaseFactory _dbFactory; - [SetUp] - public override void Initialize() - { - base.Initialize(); - - //we need to use our own custom IDatabaseFactory for the DatabaseContext because we MUST ensure that - //a Database instance is created per thread, whereas the default implementation which will work in an HttpContext - //threading environment, or a single apartment threading environment will not work for this test because - //it is multi-threaded. - _dbFactory = new PerThreadDatabaseFactory(); - //overwrite the local object - ApplicationContext.DatabaseContext = new DatabaseContext(_dbFactory); + [SetUp] + public override void Initialize() + { + base.Initialize(); - //here we are going to override the ServiceContext because normally with our test cases we use a - //global Database object but this is NOT how it should work in the web world or in any multi threaded scenario. - //we need a new Database object for each thread. - _uowProvider = new PerThreadPetaPocoUnitOfWorkProvider(_dbFactory); - ApplicationContext.Services = new ServiceContext(_uowProvider, new FileUnitOfWorkProvider(), new PublishingStrategy()); + //we need to use our own custom IDatabaseFactory for the DatabaseContext because we MUST ensure that + //a Database instance is created per thread, whereas the default implementation which will work in an HttpContext + //threading environment, or a single apartment threading environment will not work for this test because + //it is multi-threaded. + _dbFactory = new PerThreadDatabaseFactory(); + //overwrite the local object + ApplicationContext.DatabaseContext = new DatabaseContext(_dbFactory); - CreateTestData(); - } + //disable cache + var cacheHelper = CacheHelper.CreateDisabledCacheHelper(); + + //here we are going to override the ServiceContext because normally with our test cases we use a + //global Database object but this is NOT how it should work in the web world or in any multi threaded scenario. + //we need a new Database object for each thread. + _uowProvider = new PerThreadPetaPocoUnitOfWorkProvider(_dbFactory); + ApplicationContext.Services = new ServiceContext(_uowProvider, new FileUnitOfWorkProvider(), new PublishingStrategy(), cacheHelper); + + CreateTestData(); + } [TearDown] public override void TearDown() diff --git a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs index 755c6e5714..6bd46836ae 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs @@ -53,34 +53,38 @@ namespace Umbraco.Tests.TestHelpers public override void Initialize() { InitializeFirstRunFlags(); - + var path = TestHelper.CurrentAssemblyDirectory; AppDomain.CurrentDomain.SetData("DataDirectory", path); + //disable cache + var cacheHelper = CacheHelper.CreateDisabledCacheHelper(); + var dbFactory = new DefaultDatabaseFactory( GetDbConnectionString(), GetDbProviderName()); + _appContext = new ApplicationContext( - //assign the db context - new DatabaseContext(dbFactory), - //assign the service context - new ServiceContext(new PetaPocoUnitOfWorkProvider(dbFactory), new FileUnitOfWorkProvider(), new PublishingStrategy()), - //disable cache - false) - { - IsReady = true - }; + //assign the db context + new DatabaseContext(dbFactory), + //assign the service context + new ServiceContext(new PetaPocoUnitOfWorkProvider(dbFactory), new FileUnitOfWorkProvider(), new PublishingStrategy(), cacheHelper), + cacheHelper) + { + IsReady = true + }; base.Initialize(); - DatabaseContext.Initialize(dbFactory.ProviderName, dbFactory.ConnectionString); + using (DisposableTimer.TraceDuration("init")) + { + DatabaseContext.Initialize(dbFactory.ProviderName, dbFactory.ConnectionString); + CreateSqlCeDatabase(); + InitializeDatabase(); - CreateSqlCeDatabase(); - - InitializeDatabase(); - - //ensure the configuration matches the current version for tests - SettingsForTests.ConfigurationStatus = UmbracoVersion.Current.ToString(3); + //ensure the configuration matches the current version for tests + SettingsForTests.ConfigurationStatus = UmbracoVersion.Current.ToString(3); + } } protected override void SetupApplicationContext() diff --git a/src/Umbraco.Tests/TestHelpers/BaseDatabaseTest.cs b/src/Umbraco.Tests/TestHelpers/BaseDatabaseTest.cs index ffe068e5ce..3142394db4 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseDatabaseTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseDatabaseTest.cs @@ -34,24 +34,24 @@ namespace Umbraco.Tests.TestHelpers string path = TestHelper.CurrentAssemblyDirectory; AppDomain.CurrentDomain.SetData("DataDirectory", path); - - try + + try + { + //Delete database file before continueing + string filePath = string.Concat(path, "\\test.sdf"); + if (File.Exists(filePath)) { - //Delete database file before continueing - string filePath = string.Concat(path, "\\test.sdf"); - if (File.Exists(filePath)) - { - File.Delete(filePath); - } + File.Delete(filePath); } - catch (Exception) - { - //if this doesn't work we have to make sure everything is reset! otherwise - // well run into issues because we've already set some things up - TearDown(); - throw; - } - + } + catch (Exception) + { + //if this doesn't work we have to make sure everything is reset! otherwise + // well run into issues because we've already set some things up + TearDown(); + throw; + } + RepositoryResolver.Current = new RepositoryResolver( new RepositoryFactory(true)); //disable all repo caches for tests! @@ -60,16 +60,19 @@ namespace Umbraco.Tests.TestHelpers new List { typeof(MySqlSyntaxProvider), typeof(SqlCeSyntaxProvider), typeof(SqlServerSyntaxProvider) }) { CanResolveBeforeFrozen = true }; Resolution.Freeze(); + + //disable cache + var cacheHelper = CacheHelper.CreateDisabledCacheHelper(); + ApplicationContext.Current = new ApplicationContext( //assign the db context new DatabaseContext(new DefaultDatabaseFactory()), //assign the service context - new ServiceContext(new PetaPocoUnitOfWorkProvider(), new FileUnitOfWorkProvider(), new PublishingStrategy()), - //disable cache - false) - { - IsReady = true - }; + new ServiceContext(new PetaPocoUnitOfWorkProvider(), new FileUnitOfWorkProvider(), new PublishingStrategy(), cacheHelper), + cacheHelper) + { + IsReady = true + }; SqlSyntaxContext.SqlSyntaxProvider = SyntaxProvider; diff --git a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs index a3e93bfc07..22d39d4559 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs @@ -2,6 +2,10 @@ using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.ObjectResolution; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Publishing; +using Umbraco.Core.Services; using Umbraco.Web; namespace Umbraco.Tests.TestHelpers @@ -71,8 +75,18 @@ namespace Umbraco.Tests.TestHelpers /// protected virtual void SetupApplicationContext() { - //DO NOT ENABLE CACHE - ApplicationContext.Current = new ApplicationContext(false) {IsReady = true}; + //disable cache + var cacheHelper = CacheHelper.CreateDisabledCacheHelper(); + + ApplicationContext.Current = new ApplicationContext( + //assign the db context + new DatabaseContext(new DefaultDatabaseFactory()), + //assign the service context + new ServiceContext(new PetaPocoUnitOfWorkProvider(), new FileUnitOfWorkProvider(), new PublishingStrategy(), cacheHelper), + cacheHelper) + { + IsReady = true + }; } /// diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index c04eb1a868..dbe4915ef1 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -104,7 +104,7 @@ - + True ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.Helpers.dll @@ -146,17 +146,17 @@ - + + + + + + - - - - - diff --git a/src/Umbraco.Web/Standalone/ServiceContextManager.cs b/src/Umbraco.Web/Standalone/ServiceContextManager.cs index aa53b586ee..561fadfc2c 100644 --- a/src/Umbraco.Web/Standalone/ServiceContextManager.cs +++ b/src/Umbraco.Web/Standalone/ServiceContextManager.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.UnitOfWork; @@ -34,13 +35,23 @@ namespace Umbraco.Web.Standalone { if (_serviceContext == null) { + var cacheHelper = new CacheHelper( + //SD: Not sure if this is correct? Should we be using HttpRuntime.Cache here since this is for 'Web' ? + // just not quite sure what this standalone stuff is. + new ObjectCacheRuntimeCacheProvider(), + new StaticCacheProvider(), + //SD: Not sure if this is correct? Should we be using request cache here since this is for 'Web' ? + // just not quite sure what this standalone stuff is. + new NullCacheProvider()); + var dbFactory = new DefaultDatabaseFactory(_connectionString, _providerName); var dbContext = new DatabaseContext(dbFactory); Database.Mapper = new PetaPocoMapper(); _serviceContext = new ServiceContext( new PetaPocoUnitOfWorkProvider(dbFactory), new FileUnitOfWorkProvider(), - new PublishingStrategy()); + new PublishingStrategy(), + cacheHelper); //initialize the DatabaseContext dbContext.Initialize(_providerName); From 8210702474d45359ae84ab08d88fb67b691b6786 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 16 Dec 2013 13:22:52 +1100 Subject: [PATCH 0071/1046] removes the xquery tests - we don't need these and r# keeps wanting to run them --- .../CodeMirror/js/mode/xquery/test/index.html | 27 ------ .../js/mode/xquery/test/testBase.js | 42 --------- .../xquery/test/testEmptySequenceKeyword.js | 16 ---- .../js/mode/xquery/test/testMultiAttr.js | 16 ---- .../js/mode/xquery/test/testNamespaces.js | 91 ------------------- .../xquery/test/testProcessingInstructions.js | 16 ---- .../js/mode/xquery/test/testQuotes.js | 19 ---- 7 files changed, 227 deletions(-) delete mode 100644 src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/test/index.html delete mode 100644 src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/test/testBase.js delete mode 100644 src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/test/testEmptySequenceKeyword.js delete mode 100644 src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/test/testMultiAttr.js delete mode 100644 src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/test/testNamespaces.js delete mode 100644 src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/test/testProcessingInstructions.js delete mode 100644 src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/test/testQuotes.js diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/test/index.html b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/test/index.html deleted file mode 100644 index ba82e54f37..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/test/index.html +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - -

    XQuery CodeMirror Mode

    -

    -

    -
      -
    -
    - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/test/testBase.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/test/testBase.js deleted file mode 100644 index d40e9eeabc..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/test/testBase.js +++ /dev/null @@ -1,42 +0,0 @@ - $(document).ready(function(){ - module("testBase"); - test("eviltest", function() { - expect(1); - - var input = 'xquery version "1.0-ml";\ - (: this is\ - : a \ - "comment" :)\ - let $let := <x attr="value">"test"<func>function() $var {function()} {$var}</func></x>\ - let $joe:=1\ - return element element {\ - attribute attribute { 1 },\ - element test { 'a' }, \ - attribute foo { "bar" },\ - fn:doc()[ foo/@bar eq $let ],\ - //x } \ - \ - (: a more \'evil\' test :)\ - (: Modified Blakeley example (: with nested comment :) ... :)\ - declare private function local:declare() {()};\ - declare private function local:private() {()};\ - declare private function local:function() {()};\ - declare private function local:local() {()};\ - let $let := <let>let $let := "let"</let>\ - return element element {\ - attribute attribute { try { xdmp:version() } catch($e) { xdmp:log($e) } },\ - attribute fn:doc { "bar" castable as xs:string },\ - element text { text { "text" } },\ - fn:doc()[ child::eq/(@bar | attribute::attribute) eq $let ],\ - //fn:doc\ - }'; - var expected = 'xquery version "1.0-ml"; (: this is : a "comment" :) let $let := <x attr="value">"test"<func>function() $var {function()} {$var}</func></x> let $joe:=1 return element element { attribute attribute { 1 }, element test { \'a\' }, attribute foo { "bar" }, fn:doc()[ foo/@bar eq $let ], //x } (: a more \'evil\' test :) (: Modified Blakeley example (: with nested comment :) ... :) declare private function local:declare() {()}; declare private function local:private() {()}; declare private function local:function() {()}; declare private function local:local() {()}; let $let := <let>let $let := "let"</let> return element element { attribute attribute { try { xdmp:version() } catch($e) { xdmp:log($e) } }, attribute fn:doc { "bar" castable as xs:string }, element text { text { "text" } }, fn:doc()[ child::eq/(@bar | attribute::attribute) eq $let ], //fn:doc }'; - - $("#sandbox").html(''); - var editor = CodeMirror.fromTextArea($("#editor")[0]); - var result = $(".CodeMirror-lines div div pre")[0].innerHTML; - - equal(result, expected); - $("#editor").html(""); - }); - }); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/test/testEmptySequenceKeyword.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/test/testEmptySequenceKeyword.js deleted file mode 100644 index 39ed090500..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/test/testEmptySequenceKeyword.js +++ /dev/null @@ -1,16 +0,0 @@ -$(document).ready(function(){ - module("testEmptySequenceKeyword"); - test("testEmptySequenceKeyword", function() { - expect(1); - - var input = '"foo" instance of empty-sequence()'; - var expected = '"foo" instance of empty-sequence()'; - - $("#sandbox").html(''); - var editor = CodeMirror.fromTextArea($("#editor")[0]); - var result = $(".CodeMirror-lines div div pre")[0].innerHTML; - - equal(result, expected); - $("#editor").html(""); - }); -}); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/test/testMultiAttr.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/test/testMultiAttr.js deleted file mode 100644 index 8e98c47d62..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/test/testMultiAttr.js +++ /dev/null @@ -1,16 +0,0 @@ - $(document).ready(function(){ - module("testMultiAttr"); - test("test1", function() { - expect(1); - - var expected = '<p a1="foo" a2="bar">hello world</p>'; - - $("#sandbox").html(''); - $("#editor").html('

    hello world

    '); - var editor = CodeMirror.fromTextArea($("#editor")[0]); - var result = $(".CodeMirror-lines div div pre")[0].innerHTML; - - equal(result, expected); - $("#editor").html(""); - }); - }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/test/testNamespaces.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/test/testNamespaces.js deleted file mode 100644 index 4efea63e0d..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/test/testNamespaces.js +++ /dev/null @@ -1,91 +0,0 @@ -$(document).ready(function(){ - module("test namespaces"); - -// -------------------------------------------------------------------------------- -// this test is based on this: -//http://mbrevoort.github.com/CodeMirror2/#!exprSeqTypes/PrologExpr/VariableProlog/ExternalVariablesWith/K2-ExternalVariablesWith-10.xq -// -------------------------------------------------------------------------------- - test("test namespaced variable", function() { - expect(1); - - var input = 'declare namespace e = "http://example.com/ANamespace";\ -declare variable $e:exampleComThisVarIsNotRecognized as element(*) external;'; - - var expected = 'declare namespace e = "http://example.com/ANamespace";declare variable $e:exampleComThisVarIsNotRecognized as element(*) external;'; - - $("#sandbox").html(''); - var editor = CodeMirror.fromTextArea($("#editor")[0]); - var result = $(".CodeMirror-lines div div pre")[0].innerHTML; - - equal(result, expected); - $("#editor").html(""); - }); - - -// -------------------------------------------------------------------------------- -// this test is based on: -// http://mbrevoort.github.com/CodeMirror2/#!Basics/EQNames/eqname-002.xq -// -------------------------------------------------------------------------------- - test("test EQName variable", function() { - expect(1); - - var input = 'declare variable $"http://www.example.com/ns/my":var := 12;\ -{$"http://www.example.com/ns/my":var}'; - - var expected = 'declare variable $"http://www.example.com/ns/my":var := 12;<out>{$"http://www.example.com/ns/my":var}</out>'; - - $("#sandbox").html(''); - var editor = CodeMirror.fromTextArea($("#editor")[0]); - var result = $(".CodeMirror-lines div div pre")[0].innerHTML; - - equal(result, expected); - $("#editor").html(""); - }); - -// -------------------------------------------------------------------------------- -// this test is based on: -// http://mbrevoort.github.com/CodeMirror2/#!Basics/EQNames/eqname-003.xq -// -------------------------------------------------------------------------------- - test("test EQName function", function() { - expect(1); - - var input = 'declare function "http://www.example.com/ns/my":fn ($a as xs:integer) as xs:integer {\ - $a + 2\ -};\ -{"http://www.example.com/ns/my":fn(12)}'; - - var expected = 'declare function "http://www.example.com/ns/my":fn ($a as xs:integer) as xs:integer { $a + 2};<out>{"http://www.example.com/ns/my":fn(12)}</out>'; - - $("#sandbox").html(''); - var editor = CodeMirror.fromTextArea($("#editor")[0]); - var result = $(".CodeMirror-lines div div pre")[0].innerHTML; - - equal(result, expected); - $("#editor").html(""); - }); - -// -------------------------------------------------------------------------------- -// this test is based on: -// http://mbrevoort.github.com/CodeMirror2/#!Basics/EQNames/eqname-003.xq -// -------------------------------------------------------------------------------- - test("test EQName function with single quotes", function() { - expect(1); - - var input = 'declare function \'http://www.example.com/ns/my\':fn ($a as xs:integer) as xs:integer {\ - $a + 2\ -};\ -{\'http://www.example.com/ns/my\':fn(12)}'; - - var expected = 'declare function \'http://www.example.com/ns/my\':fn ($a as xs:integer) as xs:integer { $a + 2};<out>{\'http://www.example.com/ns/my\':fn(12)}</out>'; - - $("#sandbox").html(''); - var editor = CodeMirror.fromTextArea($("#editor")[0]); - var result = $(".CodeMirror-lines div div pre")[0].innerHTML; - - equal(result, expected); - $("#editor").html(""); - }); - -}); - - diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/test/testProcessingInstructions.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/test/testProcessingInstructions.js deleted file mode 100644 index 9b75305283..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/test/testProcessingInstructions.js +++ /dev/null @@ -1,16 +0,0 @@ -$(document).ready(function(){ - module("testProcessingInstructions"); - test("testProcessingInstructions", function() { - expect(1); - - var input = 'data() instance of xs:string'; - var expected = 'data(<?target content?>) instance of xs:string'; - - $("#sandbox").html(''); - var editor = CodeMirror.fromTextArea($("#editor")[0]); - var result = $(".CodeMirror-lines div div pre")[0].innerHTML; - - equal(result, expected); - $("#editor").html(""); - }); -}); diff --git a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/test/testQuotes.js b/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/test/testQuotes.js deleted file mode 100644 index 79e5142d9f..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/CodeMirror/js/mode/xquery/test/testQuotes.js +++ /dev/null @@ -1,19 +0,0 @@ - $(document).ready(function(){ - module("testQuoteEscape"); - test("testQuoteEscapeDouble", function() { - expect(1); - - var input = 'let $rootfolder := "c:\\builds\\winnt\\HEAD\\qa\\scripts\\"\ -let $keysfolder := concat($rootfolder, "keys\\")\ -return\ -$keysfolder'; - var expected = 'let $rootfolder := "c:\\builds\\winnt\\HEAD\\qa\\scripts\\"let $keysfolder := concat($rootfolder, "keys\\")return$keysfolder'; - - $("#sandbox").html(''); - var editor = CodeMirror.fromTextArea($("#editor")[0]); - var result = $(".CodeMirror-lines div div pre")[0].innerHTML; - - equal(result, expected); - $("#editor").html(""); - }); - }); From 11103c0bc500577aa0f0091b8168112c3e7d8767 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 16 Dec 2013 13:23:07 +1100 Subject: [PATCH 0072/1046] proj file --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index a11c30a444..83884f6950 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -867,13 +867,6 @@ - - - - - - - From b86cddda94678ff0642f52ba1038d276ff440d46 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 16 Dec 2013 13:55:46 +1100 Subject: [PATCH 0073/1046] fixes up unit tests --- .../Models/Rdbms/PropertyTypeGroupDto.cs | 2 +- src/Umbraco.Tests/unit-test-log4net.config | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupDto.cs index 0bbf3a891f..51cb9872fe 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupDto.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core.Models.Rdbms internal class PropertyTypeGroupDto { [Column("id")] - [PrimaryKeyColumn(IdentitySeed = 6)] + [PrimaryKeyColumn(IdentitySeed = 12)] public int Id { get; set; } [Column("parentGroupId")] diff --git a/src/Umbraco.Tests/unit-test-log4net.config b/src/Umbraco.Tests/unit-test-log4net.config index 0922e4d932..dbc6eb1677 100644 --- a/src/Umbraco.Tests/unit-test-log4net.config +++ b/src/Umbraco.Tests/unit-test-log4net.config @@ -14,16 +14,23 @@ - + + + + + + + + - + - + From 4bac3e19fe8e1e51199297fa473fb271b0969d31 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 16 Dec 2013 14:18:58 +1100 Subject: [PATCH 0074/1046] Fixes up webbootmanager with new cachehelper updates. --- src/Umbraco.Web/WebBootManager.cs | 9 +++++++++ src/umbraco.businesslogic/ui.cs | 19 ++++++++++++++----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index a75b9a2bcd..5be034c791 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -148,6 +148,15 @@ namespace Umbraco.Web return this; } + /// + /// Creates the application cache based on the HttpRuntime cache + /// + protected override void CreateApplicationCache() + { + //create a web-based cache helper + ApplicationCache = new CacheHelper(); + } + /// /// Creates the routes /// diff --git a/src/umbraco.businesslogic/ui.cs b/src/umbraco.businesslogic/ui.cs index 6eb950495e..c22984df11 100644 --- a/src/umbraco.businesslogic/ui.cs +++ b/src/umbraco.businesslogic/ui.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Text.RegularExpressions; using System.Threading; using System.Web; @@ -248,11 +249,14 @@ namespace umbraco { var cacheKey = "uitext_" + language; - return ApplicationContext.Current.ApplicationCache.GetCacheItem( - cacheKey, - CacheItemPriority.Default, - new CacheDependency(IOHelper.MapPath(UmbracoPath + "/config/lang/" + language + ".xml")), - () => + var file = IOHelper.MapPath(UmbracoPath + "/config/lang/" + language + ".xml"); + if (File.Exists(file)) + { + return ApplicationContext.Current.ApplicationCache.GetCacheItem( + cacheKey, + CacheItemPriority.Default, + new CacheDependency(IOHelper.MapPath(UmbracoPath + "/config/lang/" + language + ".xml")), + () => { using (var langReader = new XmlTextReader(IOHelper.MapPath(UmbracoPath + "/config/lang/" + language + ".xml"))) { @@ -269,6 +273,11 @@ namespace umbraco } } }); + } + else + { + return null; + } } From 060e584503039ac020f79602ae9565d7ee0531bb Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 16 Dec 2013 15:57:59 +1100 Subject: [PATCH 0075/1046] backported IContentService updates --- src/Umbraco.Core/Models/Rdbms/NodeDto.cs | 4 +- src/Umbraco.Core/Publishing/PublishStatus.cs | 6 +- .../Publishing/PublishStatusType.cs | 2 +- src/Umbraco.Core/Services/ContentService.cs | 69 +++++- src/Umbraco.Core/Services/IContentService.cs | 42 +++- .../Services/ContentServiceTests.cs | 225 +++++++++--------- 6 files changed, 220 insertions(+), 128 deletions(-) diff --git a/src/Umbraco.Core/Models/Rdbms/NodeDto.cs b/src/Umbraco.Core/Models/Rdbms/NodeDto.cs index ccbe9a1639..a89879d8b5 100644 --- a/src/Umbraco.Core/Models/Rdbms/NodeDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/NodeDto.cs @@ -9,8 +9,10 @@ namespace Umbraco.Core.Models.Rdbms [ExplicitColumns] internal class NodeDto { + public const int NodeIdSeed = 1050; + [Column("id")] - [PrimaryKeyColumn(Name = "PK_structure", IdentitySeed = 1045)] + [PrimaryKeyColumn(Name = "PK_structure", IdentitySeed = NodeIdSeed)] public int NodeId { get; set; } [Column("trashed")] diff --git a/src/Umbraco.Core/Publishing/PublishStatus.cs b/src/Umbraco.Core/Publishing/PublishStatus.cs index aee9a1fafe..519046f5f9 100644 --- a/src/Umbraco.Core/Publishing/PublishStatus.cs +++ b/src/Umbraco.Core/Publishing/PublishStatus.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.Publishing /// /// The result of publishing a content item /// - internal class PublishStatus + public class PublishStatus { public PublishStatus() { @@ -26,9 +26,9 @@ namespace Umbraco.Core.Publishing ///
    public PublishStatus(IContent content) : this(content, PublishStatusType.Success) - { + { } - + public IContent ContentItem { get; private set; } public PublishStatusType StatusType { get; internal set; } diff --git a/src/Umbraco.Core/Publishing/PublishStatusType.cs b/src/Umbraco.Core/Publishing/PublishStatusType.cs index c3cb76e245..0d9ffcfa02 100644 --- a/src/Umbraco.Core/Publishing/PublishStatusType.cs +++ b/src/Umbraco.Core/Publishing/PublishStatusType.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.Publishing /// /// Anything less than 10 = Success! /// - internal enum PublishStatusType + public enum PublishStatusType { /// /// The publishing was successful. diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 9d80369e93..7c54af38b3 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -220,6 +220,19 @@ namespace Umbraco.Core.Services } } + /// + /// Gets an object by Id + /// + /// Ids of the Content to retrieve + /// + public IEnumerable GetByIds(IEnumerable ids) + { + using (var repository = _repositoryFactory.CreateContentRepository(_uowProvider.GetUnitOfWork())) + { + return repository.GetAll(ids.ToArray()); + } + } + /// /// Gets an object by its 'UniqueId' /// @@ -597,10 +610,22 @@ namespace Umbraco.Core.Services /// The to publish /// Optional Id of the User issueing the publishing /// True if publishing succeeded, otherwise False + [Obsolete("Use PublishWithStatus instead, that method will provide more detailed information on the outcome")] public bool Publish(IContent content, int userId = 0) { var result = SaveAndPublishDo(content, userId); - return result.Success; + return result.Success; + } + + /// + /// Publishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// True if publishing succeeded, otherwise False + public Attempt PublishWithStatus(IContent content, int userId = 0) + { + return SaveAndPublishDo(content, userId); } /// @@ -609,16 +634,29 @@ namespace Umbraco.Core.Services /// The to publish along with its children /// Optional Id of the User issueing the publishing /// True if publishing succeeded, otherwise False + [Obsolete("Use PublishWithChildrenWithStatus instead, that method will provide more detailed information on the outcome and also allows the includeUnpublished flag")] public bool PublishWithChildren(IContent content, int userId = 0) { var result = PublishWithChildrenDo(content, userId, true); - + //This used to just return false only when the parent content failed, otherwise would always return true so we'll // do the same thing for the moment - if (!result.Any(x => x.Result.ContentItem.Id == content.Id)) - return false; + if (!result.Any(x => x.Result.ContentItem.Id == content.Id)) + return false; - return result.Single(x => x.Result.ContentItem.Id == content.Id).Success; + return result.Single(x => x.Result.ContentItem.Id == content.Id).Success; + } + + /// + /// Publishes a object and all its children + /// + /// The to publish along with its children + /// Optional Id of the User issueing the publishing + /// set to true if you want to also publish children that are currently unpublished + /// True if publishing succeeded, otherwise False + public IEnumerable> PublishWithChildrenWithStatus(IContent content, int userId = 0, bool includeUnpublished = false) + { + return PublishWithChildrenDo(content, userId, includeUnpublished); } /// @@ -639,10 +677,23 @@ namespace Umbraco.Core.Services /// Optional Id of the User issueing the publishing /// Optional boolean indicating whether or not to raise save events. /// True if publishing succeeded, otherwise False + [Obsolete("Use SaveAndPublishWithStatus instead, that method will provide more detailed information on the outcome")] public bool SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true) { var result = SaveAndPublishDo(content, userId, raiseEvents); - return result.Success; + return result.Success; + } + + /// + /// Saves and Publishes a single object + /// + /// The to save and publish + /// Optional Id of the User issueing the publishing + /// Optional boolean indicating whether or not to raise save events. + /// True if publishing succeeded, otherwise False + public Attempt SaveAndPublishWithStatus(IContent content, int userId = 0, bool raiseEvents = true) + { + return SaveAndPublishDo(content, userId, raiseEvents); } /// @@ -1160,18 +1211,20 @@ namespace Umbraco.Core.Services /// The to send to publication /// Optional Id of the User issueing the send to publication /// True if sending publication was succesfull otherwise false - internal bool SendToPublication(IContent content, int userId = 0) + public bool SendToPublication(IContent content, int userId = 0) { if (SendingToPublish.IsRaisedEventCancelled(new SendToPublishEventArgs(content), this)) return false; - //TODO: Do some stuff here.. RunActionHandlers + //TODO: Do some stuff here.. ... what is it supposed to do ? + // pretty sure all that legacy stuff did was raise an event? and we no longer have IActionHandlers so no worries there. SentToPublish.RaiseEvent(new SendToPublishEventArgs(content, false), this); Audit.Add(AuditTypes.SendToPublish, "Send to Publish performed by user", content.WriterId, content.Id); + //TODO: will this ever be false?? return true; } diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 6525a9d825..0a19deda6e 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Publishing; namespace Umbraco.Core.Services { @@ -9,6 +11,11 @@ namespace Umbraco.Core.Services /// public interface IContentService : IService { + + bool SendToPublication(IContent content, int userId = 0); + + IEnumerable GetByIds(IEnumerable ids); + /// /// Creates an object using the alias of the /// that this Content should based on. @@ -121,7 +128,7 @@ namespace Umbraco.Core.Services /// Collection of to save /// Optional Id of the User saving the Content /// Optional boolean indicating whether or not to raise events. - void Save(IEnumerable contents, int userId = 0, bool raiseEvents = true); + void Save(IEnumerable contents, int userId = 0, bool raiseEvents = true); /// /// Deletes all content of specified type. All children of deleted content is moved to Recycle Bin. @@ -242,16 +249,35 @@ namespace Umbraco.Core.Services /// The to publish /// Optional Id of the User issueing the publishing /// True if publishing succeeded, otherwise False + [Obsolete("Use PublishWithStatus instead, that method will provide more detailed information on the outcome")] bool Publish(IContent content, int userId = 0); + /// + /// Publishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// The published status attempt + Attempt PublishWithStatus(IContent content, int userId = 0); + /// /// Publishes a object and all its children /// /// The to publish along with its children /// Optional Id of the User issueing the publishing /// True if publishing succeeded, otherwise False + [Obsolete("Use PublishWithChildrenWithStatus instead, that method will provide more detailed information on the outcome and also allows the includeUnpublished flag")] bool PublishWithChildren(IContent content, int userId = 0); + /// + /// Publishes a object and all its children + /// + /// The to publish along with its children + /// Optional Id of the User issueing the publishing + /// + /// The list of statuses for all published items + IEnumerable> PublishWithChildrenWithStatus(IContent content, int userId = 0, bool includeUnpublished = false); + /// /// UnPublishes a single object /// @@ -267,8 +293,18 @@ namespace Umbraco.Core.Services /// Optional Id of the User issueing the publishing /// Optional boolean indicating whether or not to raise save events. /// True if publishing succeeded, otherwise False + [Obsolete("Use SaveAndPublishWithStatus instead, that method will provide more detailed information on the outcome")] bool SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true); + /// + /// Saves and Publishes a single object + /// + /// The to save and publish + /// Optional Id of the User issueing the publishing + /// Optional boolean indicating whether or not to raise save events. + /// True if publishing succeeded, otherwise False + Attempt SaveAndPublishWithStatus(IContent content, int userId = 0, bool raiseEvents = true); + /// /// Permanently deletes an object. /// @@ -311,6 +347,8 @@ namespace Umbraco.Core.Services /// to retrieve ancestors for /// An Enumerable list of objects IEnumerable GetAncestors(IContent content); + + /// /// Sorts a collection of objects by updating the SortOrder according /// to the ordering of items in the passed in . /// @@ -337,6 +375,8 @@ namespace Umbraco.Core.Services /// to retrieve the parent from /// Parent object IContent GetParent(IContent content); + + /// /// Creates and saves an object using the alias of the /// that this Content should based on. /// diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 047fc202ed..1f693509a3 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -5,6 +5,7 @@ using System.Threading; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.UnitOfWork; @@ -24,21 +25,21 @@ namespace Umbraco.Tests.Services [SetUp] public override void Initialize() { - base.Initialize(); + base.Initialize(); + } + + [TearDown] + public override void TearDown() + { + base.TearDown(); } - - [TearDown] - public override void TearDown() - { - base.TearDown(); - } //TODO Add test to verify there is only ONE newest document/content in cmsDocument table after updating. //TODO Add test to delete specific version (with and without deleting prior versions) and versions by date. - - [Test] - public void Can_Remove_Property_Type() - { + + [Test] + public void Can_Remove_Property_Type() + { // Arrange var contentService = ServiceContext.ContentService; @@ -48,9 +49,9 @@ namespace Umbraco.Tests.Services // Assert Assert.That(content, Is.Not.Null); Assert.That(content.HasIdentity, Is.False); - } + } - [Test] + [Test] public void Can_Create_Content() { // Arrange @@ -63,7 +64,7 @@ namespace Umbraco.Tests.Services Assert.That(content, Is.Not.Null); Assert.That(content.HasIdentity, Is.False); } - + [Test] public void Can_Create_Content_Without_Explicitly_Set_User() { @@ -83,12 +84,12 @@ namespace Umbraco.Tests.Services public void Can_Save_New_Content_With_Explicit_User() { var user = new User(ServiceContext.UserService.GetUserTypeByAlias("admin")) - { - Name = "Test", - Email = "test@test.com", - Username = "test", - Password = "test" - }; + { + Name = "Test", + Email = "test@test.com", + Username = "test", + Password = "test" + }; ServiceContext.UserService.SaveUser(user); var content = new Content("Test", -1, ServiceContext.ContentTypeService.GetContentType("umbTextpage")); @@ -117,11 +118,11 @@ namespace Umbraco.Tests.Services var contentService = ServiceContext.ContentService; // Act - var content = contentService.GetById(1046); + var content = contentService.GetById(NodeDto.NodeIdSeed + 1); // Assert Assert.That(content, Is.Not.Null); - Assert.That(content.Id, Is.EqualTo(1046)); + Assert.That(content.Id, Is.EqualTo(NodeDto.NodeIdSeed + 1)); } [Test] @@ -135,7 +136,7 @@ namespace Umbraco.Tests.Services // Assert Assert.That(content, Is.Not.Null); - Assert.That(content.Id, Is.EqualTo(1046)); + Assert.That(content.Id, Is.EqualTo(NodeDto.NodeIdSeed + 1)); } [Test] @@ -160,7 +161,7 @@ namespace Umbraco.Tests.Services var contentService = ServiceContext.ContentService; // Act - var contents = contentService.GetChildren(1046); + var contents = contentService.GetChildren(NodeDto.NodeIdSeed + 1); // Assert Assert.That(contents, Is.Not.Null); @@ -177,7 +178,7 @@ namespace Umbraco.Tests.Services contentService.Save(hierarchy, 0); // Act - var contents = contentService.GetDescendants(1046); + var contents = contentService.GetDescendants(NodeDto.NodeIdSeed + 1); // Assert Assert.That(contents, Is.Not.Null); @@ -190,15 +191,15 @@ namespace Umbraco.Tests.Services { // Arrange var contentService = ServiceContext.ContentService; - var parent = ServiceContext.ContentService.GetById(1046); + var parent = ServiceContext.ContentService.GetById(NodeDto.NodeIdSeed + 1); ServiceContext.ContentService.Publish(parent);//Publishing root, so Text Page 2 can be updated. - var subpage2 = contentService.GetById(1048); + var subpage2 = contentService.GetById(NodeDto.NodeIdSeed + 3); subpage2.Name = "Text Page 2 Updated"; subpage2.SetValue("author", "Jane Doe"); contentService.SaveAndPublish(subpage2, 0);//NOTE New versions are only added between publish-state-changed, so publishing to ensure addition version. // Act - var versions = contentService.GetVersions(1048); + var versions = contentService.GetVersions(NodeDto.NodeIdSeed + 3); // Assert Assert.That(versions.Any(), Is.True); @@ -225,9 +226,9 @@ namespace Umbraco.Tests.Services { // Arrange var contentService = ServiceContext.ContentService; - var root = contentService.GetById(1046); + var root = contentService.GetById(NodeDto.NodeIdSeed + 1); contentService.SaveAndPublish(root); - var content = contentService.GetById(1048); + var content = contentService.GetById(NodeDto.NodeIdSeed + 3); content.ExpireDate = DateTime.Now.AddSeconds(1); contentService.SaveAndPublish(content); @@ -278,7 +279,7 @@ namespace Umbraco.Tests.Services { // Arrange var contentService = ServiceContext.ContentService; - var content = contentService.GetById(1046); + var content = contentService.GetById(NodeDto.NodeIdSeed + 1); bool published = contentService.Publish(content, 0); // Act @@ -304,24 +305,24 @@ namespace Umbraco.Tests.Services // Arrange var contentService = ServiceContext.ContentService; var published = contentService.RePublishAll(0); - var content = contentService.GetById(1046); + var content = contentService.GetById(NodeDto.NodeIdSeed + 1); // Act bool unpublished = contentService.UnPublish(content, 0); - var children = contentService.GetChildren(1046); + var children = contentService.GetChildren(NodeDto.NodeIdSeed + 1); // Assert Assert.That(published, Is.True);//Verify that everything was published - - //Verify that content with Id 1046 was unpublished + + //Verify that content with Id (NodeDto.NodeIdSeed + 1) was unpublished Assert.That(unpublished, Is.True); Assert.That(content.Published, Is.False); //Verify that all children was unpublished Assert.That(children.Any(x => x.Published), Is.False); - Assert.That(children.First(x => x.Id == 1047).Published, Is.False);//Released 5 mins ago, but should be unpublished - Assert.That(children.First(x => x.Id == 1047).ReleaseDate.HasValue, Is.False);//Verify that the release date has been removed - Assert.That(children.First(x => x.Id == 1048).Published, Is.False);//Expired 5 mins ago, so isn't be published + Assert.That(children.First(x => x.Id == NodeDto.NodeIdSeed + 2).Published, Is.False);//Released 5 mins ago, but should be unpublished + Assert.That(children.First(x => x.Id == NodeDto.NodeIdSeed + 2).ReleaseDate.HasValue, Is.False);//Verify that the release date has been removed + Assert.That(children.First(x => x.Id == NodeDto.NodeIdSeed + 3).Published, Is.False);//Expired 5 mins ago, so isn't be published } [Test] @@ -337,7 +338,7 @@ namespace Umbraco.Tests.Services var allContent = rootContent.Concat(rootContent.SelectMany(x => x.Descendants())); //for testing we need to clear out the contentXml table so we can see if it worked var provider = new PetaPocoUnitOfWorkProvider(); - var uow = provider.GetUnitOfWork(); + var uow = provider.GetUnitOfWork(); using (RepositoryResolver.Current.ResolveByType(uow)) { uow.Database.TruncateTable("cmsContentXml"); @@ -345,7 +346,7 @@ namespace Umbraco.Tests.Services //for this test we are also going to save a revision for a content item that is not published, this is to ensure //that it's published version still makes it into the cmsContentXml table! contentService.Save(allContent.Last()); - + // Act var published = contentService.RePublishAll(0); @@ -382,7 +383,7 @@ namespace Umbraco.Tests.Services // Act - contentService.RePublishAll(new int[]{allContent.Last().ContentTypeId}); + contentService.RePublishAll(new int[] { allContent.Last().ContentTypeId }); // Assert uow = provider.GetUnitOfWork(); @@ -397,7 +398,7 @@ namespace Umbraco.Tests.Services { // Arrange var contentService = ServiceContext.ContentService; - var content = contentService.GetById(1046); + var content = contentService.GetById(NodeDto.NodeIdSeed + 1); // Act bool published = contentService.Publish(content, 0); @@ -416,12 +417,12 @@ namespace Umbraco.Tests.Services var contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory", "Mandatory Doc Type", true); contentTypeService.Save(contentType); - Content content = MockedContent.CreateSimpleContent(contentType, "Invalid Content", 1046); + Content content = MockedContent.CreateSimpleContent(contentType, "Invalid Content", NodeDto.NodeIdSeed + 1); content.SetValue("author", string.Empty); contentService.Save(content, 0); // Act - var parent = contentService.GetById(1046); + var parent = contentService.GetById(NodeDto.NodeIdSeed + 1); bool parentPublished = contentService.Publish(parent, 0); bool published = contentService.Publish(content, 0); @@ -438,16 +439,16 @@ namespace Umbraco.Tests.Services { // Arrange var contentService = ServiceContext.ContentService; - var content = contentService.GetById(1046); + var content = contentService.GetById(NodeDto.NodeIdSeed + 1); // Act bool published = contentService.PublishWithChildren(content, 0); - var children = contentService.GetChildren(1046); + var children = contentService.GetChildren(NodeDto.NodeIdSeed + 1); // Assert Assert.That(published, Is.True);//Nothing was cancelled, so should be true Assert.That(content.Published, Is.True);//No restrictions, so should be published - Assert.That(children.First(x => x.Id == 1047).Published, Is.True);//Released 5 mins ago, so should be published + Assert.That(children.First(x => x.Id == NodeDto.NodeIdSeed + 2).Published, Is.True);//Released 5 mins ago, so should be published } [Test] @@ -455,11 +456,11 @@ namespace Umbraco.Tests.Services { // Arrange var contentService = ServiceContext.ContentService; - var content = contentService.GetById(1048); //This Content expired 5min ago + var content = contentService.GetById(NodeDto.NodeIdSeed + 3); //This Content expired 5min ago content.ExpireDate = DateTime.Now.AddMinutes(-5); contentService.Save(content); - var parent = contentService.GetById(1046); + var parent = contentService.GetById(NodeDto.NodeIdSeed + 1); bool parentPublished = contentService.Publish(parent, 0);//Publish root Home node to enable publishing of '1048' // Act @@ -476,11 +477,11 @@ namespace Umbraco.Tests.Services { // Arrange var contentService = ServiceContext.ContentService; - var content = contentService.GetById(1047); + var content = contentService.GetById(NodeDto.NodeIdSeed + 2); content.ReleaseDate = DateTime.Now.AddHours(2); contentService.Save(content, 0); - var parent = contentService.GetById(1046); + var parent = contentService.GetById(NodeDto.NodeIdSeed + 1); bool parentPublished = contentService.Publish(parent, 0);//Publish root Home node to enable publishing of '1048' // Act @@ -497,7 +498,7 @@ namespace Umbraco.Tests.Services { // Arrange var contentService = ServiceContext.ContentService; - var content = contentService.CreateContent("Subpage with Unpublisehed Parent", 1046, "umbTextpage", 0); + var content = contentService.CreateContent("Subpage with Unpublisehed Parent", NodeDto.NodeIdSeed + 1, "umbTextpage", 0); contentService.Save(content, 0); // Act @@ -513,7 +514,7 @@ namespace Umbraco.Tests.Services { // Arrange var contentService = ServiceContext.ContentService; - var content = contentService.GetById(1049); + var content = contentService.GetById(NodeDto.NodeIdSeed + 4); // Act bool published = contentService.Publish(content, 0); @@ -529,7 +530,7 @@ namespace Umbraco.Tests.Services { // Arrange var contentService = ServiceContext.ContentService; - var content = contentService.CreateContent("Home US", - 1, "umbTextpage", 0); + var content = contentService.CreateContent("Home US", -1, "umbTextpage", 0); content.SetValue("author", "Barack Obama"); // Act @@ -546,9 +547,9 @@ namespace Umbraco.Tests.Services { // Arrange var contentService = ServiceContext.ContentService; - var root = contentService.GetById(1046); + var root = contentService.GetById(NodeDto.NodeIdSeed + 1); var rootPublished = contentService.Publish(root); - var content = contentService.GetById(1048); + var content = contentService.GetById(NodeDto.NodeIdSeed + 3); content.Properties["title"].Value = content.Properties["title"].Value + " Published"; bool published = contentService.SaveAndPublish(content); @@ -560,7 +561,7 @@ namespace Umbraco.Tests.Services var savedVersion = content.Version; // Act - var publishedDescendants = ((ContentService) contentService).GetPublishedDescendants(root); + var publishedDescendants = ((ContentService)contentService).GetPublishedDescendants(root); // Assert Assert.That(rootPublished, Is.True); @@ -579,7 +580,7 @@ namespace Umbraco.Tests.Services Assert.That(savedContentVersion.Properties["title"].Value, Contains.Substring("Saved")); //Ensure that the latest version of the content is the saved and not-yet-published one - var currentContent = contentService.GetById(1048); + var currentContent = contentService.GetById(NodeDto.NodeIdSeed + 3); Assert.That(currentContent.Published, Is.False); Assert.That(currentContent.Properties["title"].Value, Contains.Substring("Saved")); Assert.That(currentContent.Version, Is.EqualTo(savedVersion)); @@ -590,7 +591,7 @@ namespace Umbraco.Tests.Services { // Arrange var contentService = ServiceContext.ContentService; - var content = contentService.CreateContent("Home US", - 1, "umbTextpage", 0); + var content = contentService.CreateContent("Home US", -1, "umbTextpage", 0); content.SetValue("author", "Barack Obama"); // Act @@ -608,9 +609,9 @@ namespace Umbraco.Tests.Services var contentTypeService = ServiceContext.ContentTypeService; var contentType = contentTypeService.GetContentType("umbTextpage"); - Content subpage = MockedContent.CreateSimpleContent(contentType, "Text Subpage 1", 1047); - Content subpage2 = MockedContent.CreateSimpleContent(contentType, "Text Subpage 2", 1047); - var list = new List {subpage, subpage2}; + Content subpage = MockedContent.CreateSimpleContent(contentType, "Text Subpage 1", NodeDto.NodeIdSeed + 2); + Content subpage2 = MockedContent.CreateSimpleContent(contentType, "Text Subpage 2", NodeDto.NodeIdSeed + 2); + var list = new List { subpage, subpage2 }; // Act contentService.Save(list, 0); @@ -630,9 +631,9 @@ namespace Umbraco.Tests.Services contentService.Save(hierarchy, 0); Assert.That(hierarchy.Any(), Is.True); - Assert.That(hierarchy.Any(x => x.HasIdentity == false), Is.False); - //all parent id's should be ok, they are lazy and if they equal zero an exception will be thrown - Assert.DoesNotThrow(() => hierarchy.Any(x => x.ParentId != 0)); + Assert.That(hierarchy.Any(x => x.HasIdentity == false), Is.False); + //all parent id's should be ok, they are lazy and if they equal zero an exception will be thrown + Assert.DoesNotThrow(() => hierarchy.Any(x => x.ParentId != 0)); } @@ -659,11 +660,11 @@ namespace Umbraco.Tests.Services { // Arrange var contentService = ServiceContext.ContentService; - var content = contentService.GetById(1049); + var content = contentService.GetById(NodeDto.NodeIdSeed + 4); // Act contentService.Delete(content, 0); - var deleted = contentService.GetById(1049); + var deleted = contentService.GetById(NodeDto.NodeIdSeed + 4); // Assert Assert.That(deleted, Is.Null); @@ -674,7 +675,7 @@ namespace Umbraco.Tests.Services { // Arrange var contentService = ServiceContext.ContentService; - var content = contentService.GetById(1048); + var content = contentService.GetById(NodeDto.NodeIdSeed + 3); // Act contentService.MoveToRecycleBin(content, 0); @@ -690,10 +691,10 @@ namespace Umbraco.Tests.Services // Arrange var contentService = ServiceContext.ContentService; var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage"); - Content subsubpage = MockedContent.CreateSimpleContent(contentType, "Text Page 3", 1047); + Content subsubpage = MockedContent.CreateSimpleContent(contentType, "Text Page 3", NodeDto.NodeIdSeed + 2); contentService.Save(subsubpage, 0); - var content = contentService.GetById(1046); + var content = contentService.GetById(NodeDto.NodeIdSeed + 1); // Act contentService.MoveToRecycleBin(content, 0); @@ -731,13 +732,13 @@ namespace Umbraco.Tests.Services { // Arrange var contentService = ServiceContext.ContentService; - var content = contentService.GetById(1049); + var content = contentService.GetById(NodeDto.NodeIdSeed + 4); // Act - moving out of recycle bin - contentService.Move(content, 1046, 0); + contentService.Move(content, NodeDto.NodeIdSeed + 1, 0); // Assert - Assert.That(content.ParentId, Is.EqualTo(1046)); + Assert.That(content.ParentId, Is.EqualTo(NodeDto.NodeIdSeed + 1)); Assert.That(content.Trashed, Is.False); Assert.That(content.Published, Is.False); } @@ -747,11 +748,11 @@ namespace Umbraco.Tests.Services { // Arrange var contentService = ServiceContext.ContentService; - var temp = contentService.GetById(1048); + var temp = contentService.GetById(NodeDto.NodeIdSeed + 3); // Act var copy = contentService.Copy(temp, temp.ParentId, false, 0); - var content = contentService.GetById(1048); + var content = contentService.GetById(NodeDto.NodeIdSeed + 3); // Assert Assert.That(copy, Is.Not.Null); @@ -764,7 +765,7 @@ namespace Umbraco.Tests.Services } //Assert.AreNotEqual(content.Name, copy.Name); } - + [Test, NUnit.Framework.Ignore] public void Can_Send_To_Publication() { } @@ -774,9 +775,9 @@ namespace Umbraco.Tests.Services { // Arrange var contentService = ServiceContext.ContentService; - var parent = ServiceContext.ContentService.GetById(1046); + var parent = ServiceContext.ContentService.GetById(NodeDto.NodeIdSeed + 1); ServiceContext.ContentService.Publish(parent);//Publishing root, so Text Page 2 can be updated. - var subpage2 = contentService.GetById(1048); + var subpage2 = contentService.GetById(NodeDto.NodeIdSeed + 3); var version = subpage2.Version; var nameBeforeRollback = subpage2.Name; subpage2.Name = "Text Page 2 Updated"; @@ -784,7 +785,7 @@ namespace Umbraco.Tests.Services contentService.SaveAndPublish(subpage2, 0);//Saving and publishing, so a new version is created // Act - var rollback = contentService.Rollback(1048, version, 0); + var rollback = contentService.Rollback(NodeDto.NodeIdSeed + 3, version, 0); // Assert Assert.That(rollback, Is.Not.Null); @@ -795,14 +796,14 @@ namespace Umbraco.Tests.Services [Test] public void Can_Save_Lazy_Content() - { - var unitOfWork = PetaPocoUnitOfWorkProvider.CreateUnitOfWork(); + { + var unitOfWork = PetaPocoUnitOfWorkProvider.CreateUnitOfWork(); var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage"); - var root = ServiceContext.ContentService.GetById(1046); + var root = ServiceContext.ContentService.GetById(NodeDto.NodeIdSeed + 1); var c = new Lazy(() => MockedContent.CreateSimpleContent(contentType, "Hierarchy Simple Text Page", root.Id)); var c2 = new Lazy(() => MockedContent.CreateSimpleContent(contentType, "Hierarchy Simple Text Subpage", c.Value.Id)); - var list = new List> {c, c2}; + var list = new List> { c, c2 }; var repository = RepositoryResolver.Current.ResolveByType(unitOfWork); foreach (var content in list) @@ -826,14 +827,14 @@ namespace Umbraco.Tests.Services { // Arrange var contentService = ServiceContext.ContentService; - var content = contentService.GetById(1046); + var content = contentService.GetById(NodeDto.NodeIdSeed + 1); bool published = contentService.PublishWithChildren(content, 0); - var homepage = contentService.GetById(1046); + var homepage = contentService.GetById(NodeDto.NodeIdSeed + 1); homepage.Name = "Homepage"; ServiceContext.ContentService.Save(homepage); // Act - bool hasPublishedVersion = ServiceContext.ContentService.HasPublishedVersion(1046); + bool hasPublishedVersion = ServiceContext.ContentService.HasPublishedVersion(NodeDto.NodeIdSeed + 1); // Assert Assert.That(published, Is.True); @@ -841,20 +842,20 @@ namespace Umbraco.Tests.Services Assert.That(hasPublishedVersion, Is.True); } - [Test] - public void Can_Verify_Property_Types_On_Content() - { + [Test] + public void Can_Verify_Property_Types_On_Content() + { // Arrange - var contentTypeService = ServiceContext.ContentTypeService; + var contentTypeService = ServiceContext.ContentTypeService; var contentType = MockedContentTypes.CreateAllTypesContentType("allDataTypes", "All DataTypes"); contentTypeService.Save(contentType); - var contentService = ServiceContext.ContentService; - var content = MockedContent.CreateAllTypesContent(contentType, "Random Content", -1); + var contentService = ServiceContext.ContentService; + var content = MockedContent.CreateAllTypesContent(contentType, "Random Content", -1); contentService.Save(content); - var id = content.Id; + var id = content.Id; // Act - var sut = contentService.GetById(id); + var sut = contentService.GetById(id); // Arrange Assert.That(sut.GetValue("isTrue"), Is.True); @@ -868,7 +869,7 @@ namespace Umbraco.Tests.Services //MCH: I'm guessing this is an issue because of the format the date is actually stored as, right? Cause we don't do any formatting when saving or loading Assert.That(sut.GetValue("dateTime").ToString("G"), Is.EqualTo(content.GetValue("dateTime").ToString("G"))); Assert.That(sut.GetValue("colorPicker"), Is.EqualTo("black")); - Assert.That(sut.GetValue("folderBrowser"), Is.Empty); + Assert.That(sut.GetValue("folderBrowser"), Is.Empty); Assert.That(sut.GetValue("ddlMultiple"), Is.EqualTo("1234,1235")); Assert.That(sut.GetValue("rbList"), Is.EqualTo("random")); Assert.That(sut.GetValue("date").ToString("G"), Is.EqualTo(content.GetValue("date").ToString("G"))); @@ -877,40 +878,36 @@ namespace Umbraco.Tests.Services Assert.That(sut.GetValue("contentPicker"), Is.EqualTo(1090)); Assert.That(sut.GetValue("mediaPicker"), Is.EqualTo(1091)); Assert.That(sut.GetValue("memberPicker"), Is.EqualTo(1092)); - Assert.That(sut.GetValue("simpleEditor"), Is.EqualTo("This is simply edited")); - Assert.That(sut.GetValue("ultimatePicker"), Is.EqualTo("1234,1235")); Assert.That(sut.GetValue("relatedLinks"), Is.EqualTo("")); Assert.That(sut.GetValue("tags"), Is.EqualTo("this,is,tags")); - Assert.That(sut.GetValue("macroContainer"), Is.Empty); - Assert.That(sut.GetValue("imgCropper"), Is.Empty); - } + } - [Test] - public void Can_Delete_Previous_Versions_Not_Latest() - { + [Test] + public void Can_Delete_Previous_Versions_Not_Latest() + { // Arrange var contentService = ServiceContext.ContentService; - var content = contentService.GetById(1049); - var version = content.Version; + var content = contentService.GetById(NodeDto.NodeIdSeed + 4); + var version = content.Version; - // Act - contentService.DeleteVersion(1049, version, true, 0); - var sut = contentService.GetById(1049); + // Act + contentService.DeleteVersion(NodeDto.NodeIdSeed + 4, version, true, 0); + var sut = contentService.GetById(NodeDto.NodeIdSeed + 4); // Assert Assert.That(sut.Version, Is.EqualTo(version)); - } + } private IEnumerable CreateContentHierarchy() { var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage"); - var root = ServiceContext.ContentService.GetById(1046); + var root = ServiceContext.ContentService.GetById(NodeDto.NodeIdSeed + 1); - var list = new List(); + var list = new List(); for (int i = 0; i < 10; i++) { - var content = MockedContent.CreateSimpleContent(contentType, "Hierarchy Simple Text Page " + i, root); + var content = MockedContent.CreateSimpleContent(contentType, "Hierarchy Simple Text Page " + i, root); list.Add(content); list.AddRange(CreateChildrenOf(contentType, content, 4)); @@ -921,12 +918,12 @@ namespace Umbraco.Tests.Services return list; } - private IEnumerable CreateChildrenOf(IContentType contentType, IContent content, int depth) + private IEnumerable CreateChildrenOf(IContentType contentType, IContent content, int depth) { var list = new List(); for (int i = 0; i < depth; i++) { - var c = MockedContent.CreateSimpleContent(contentType, "Hierarchy Simple Text Subpage " + i, content); + var c = MockedContent.CreateSimpleContent(contentType, "Hierarchy Simple Text Subpage " + i, content); list.Add(c); Console.WriteLine("Created: 'Hierarchy Simple Text Subpage {0}' - Depth: {1}", i, depth); From 0f25029ff6531095782a567bf054d6bc209e2a56 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 16 Dec 2013 16:02:34 +1100 Subject: [PATCH 0076/1046] Fixed typo --- ...onaryCacheProdiverBase.cs => DictionaryCacheProviderBase.cs} | 2 +- src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs | 2 +- src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename src/Umbraco.Core/Cache/{DictionaryCacheProdiverBase.cs => DictionaryCacheProviderBase.cs} (99%) diff --git a/src/Umbraco.Core/Cache/DictionaryCacheProdiverBase.cs b/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs similarity index 99% rename from src/Umbraco.Core/Cache/DictionaryCacheProdiverBase.cs rename to src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs index c1a4d17592..e38dee57c4 100644 --- a/src/Umbraco.Core/Cache/DictionaryCacheProdiverBase.cs +++ b/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs @@ -6,7 +6,7 @@ using System.Threading; namespace Umbraco.Core.Cache { - internal abstract class DictionaryCacheProdiverBase : ICacheProvider + internal abstract class DictionaryCacheProviderBase : ICacheProvider { protected static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); protected abstract DictionaryCacheWrapper DictionaryCache { get; } diff --git a/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs b/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs index 26c6dc0af8..d9f037d264 100644 --- a/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs +++ b/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core.Cache /// /// A cache provider that caches items in the HttpContext.Items /// - internal class HttpRequestCacheProvider : DictionaryCacheProdiverBase + internal class HttpRequestCacheProvider : DictionaryCacheProviderBase { private readonly Func _context; diff --git a/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs index 9838f8d1e6..2647e6920f 100644 --- a/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs @@ -12,7 +12,7 @@ namespace Umbraco.Core.Cache /// /// A CacheProvider that wraps the logic of the HttpRuntime.Cache /// - internal class HttpRuntimeCacheProvider : DictionaryCacheProdiverBase, IRuntimeCacheProvider + internal class HttpRuntimeCacheProvider : DictionaryCacheProviderBase, IRuntimeCacheProvider { private readonly System.Web.Caching.Cache _cache; private readonly DictionaryCacheWrapper _wrapper; diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 8498e39e4d..a50b483d73 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -116,7 +116,7 @@ - + From af86a5d7b19e0ddfff70f8421a110b1c51fefed7 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 16 Dec 2013 16:04:37 +1100 Subject: [PATCH 0077/1046] backporting imediaservice changes --- src/Umbraco.Core/Services/IMediaService.cs | 4 +++- src/Umbraco.Core/Services/MediaService.cs | 13 +++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs index c8b9db6967..63248b3974 100644 --- a/src/Umbraco.Core/Services/IMediaService.cs +++ b/src/Umbraco.Core/Services/IMediaService.cs @@ -9,6 +9,8 @@ namespace Umbraco.Core.Services /// public interface IMediaService : IService { + IEnumerable GetByIds(IEnumerable ids); + /// /// Creates an object using the alias of the /// that this Media should based on. @@ -193,7 +195,7 @@ namespace Umbraco.Core.Services /// /// Path of the media item to retreive (for example: /media/1024/koala_403x328.jpg) /// - IMedia GetMediaByPath(string mediaPath); + IMedia GetMediaByPath(string mediaPath); /// /// Gets a collection of objects, which are ancestors of the current media. diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 8d85007a16..0e7d9dafd8 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -206,6 +206,19 @@ namespace Umbraco.Core.Services } } + /// + /// Gets an object by Id + /// + /// Ids of the Media to retrieve + /// + public IEnumerable GetByIds(IEnumerable ids) + { + using (var repository = _repositoryFactory.CreateMediaRepository(_uowProvider.GetUnitOfWork())) + { + return repository.GetAll(ids.ToArray()); + } + } + /// /// Gets an object by its 'UniqueId' /// From 68d581ac82857651c98cf41d990dfb7440643ae6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 16 Dec 2013 16:18:44 +1100 Subject: [PATCH 0078/1046] fixes build (merge issue) --- src/Umbraco.Core/Strings/DefaultShortStringHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs index b02a8f1521..c11d24477e 100644 --- a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs +++ b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs @@ -62,7 +62,7 @@ namespace Umbraco.Core.Strings foreach (var node in UmbracoConfig.For.UmbracoSettings().RequestHandler.CharCollection) { if (node.Char.IsNullOrWhiteSpace() == false) - UrlReplaceCharacters[node.Char] = node.Replacement; + _urlReplaceCharacters[node.Char] = node.Replacement; } } From 6a9b8d5540c555e3277e35005340fd04d868e486 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 16 Dec 2013 16:27:59 +1100 Subject: [PATCH 0079/1046] Fixes a unit test issue with the mntp editor initialization. --- .../MultiNodeTreePicker/MNTP_DataEditor.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/umbraco.editorControls/MultiNodeTreePicker/MNTP_DataEditor.cs b/src/umbraco.editorControls/MultiNodeTreePicker/MNTP_DataEditor.cs index b002218bd8..d05b45e4b3 100644 --- a/src/umbraco.editorControls/MultiNodeTreePicker/MNTP_DataEditor.cs +++ b/src/umbraco.editorControls/MultiNodeTreePicker/MNTP_DataEditor.cs @@ -45,7 +45,11 @@ namespace umbraco.editorControls.MultiNodeTreePicker //need to add our tree definitions to the collection. //find the content tree to duplicate - var contentTree = TreeDefinitionCollection.Instance.Single(x => string.Equals(x.Tree.Alias, Umbraco.Core.Constants.Applications.Content, StringComparison.OrdinalIgnoreCase)); + var contentTree = TreeDefinitionCollection.Instance.SingleOrDefault(x => string.Equals(x.Tree.Alias, Umbraco.Core.Constants.Applications.Content, StringComparison.OrdinalIgnoreCase)); + //we cannot continue + if (contentTree == null) + return; + var filteredContentTree = new TreeDefinition(typeof(FilteredContentTree), new umbraco.BusinessLogic.ApplicationTree(true, false, 0, contentTree.Tree.ApplicationAlias, From 2ce952bdbb4065be7f7d4cc73f62a25469ce656d Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 16 Dec 2013 17:17:23 +1100 Subject: [PATCH 0080/1046] backports data type service updates and fixes unit tests with correct seeding value. --- src/Umbraco.Core/Models/PreValue.cs | 29 ++ src/Umbraco.Core/Models/PreValueCollection.cs | 86 ++++++ .../UnitOfWork/PetaPocoUnitOfWork.cs | 83 +++--- src/Umbraco.Core/Services/DataTypeService.cs | 249 ++++++++++++++---- src/Umbraco.Core/Services/IDataTypeService.cs | 23 ++ src/Umbraco.Core/Services/PackagingService.cs | 17 +- src/Umbraco.Core/StringExtensions.cs | 10 + src/Umbraco.Core/Umbraco.Core.csproj | 2 + src/Umbraco.Tests/CodeFirst/CodeFirstTests.cs | 9 +- src/Umbraco.Tests/LegacyApi/MediaTypeTests.cs | 7 +- .../Repositories/ContentRepositoryTest.cs | 46 ++-- .../Repositories/ContentTypeRepositoryTest.cs | 37 +-- .../Repositories/MediaRepositoryTest.cs | 31 +-- .../Repositories/MediaTypeRepositoryTest.cs | 9 +- .../Repositories/RelationRepositoryTest.cs | 19 +- .../Services/ContentServicePerformanceTest.cs | 15 +- .../Services/ContentServiceTests.cs | 4 +- .../Services/DataTypeServiceTests.cs | 3 +- 18 files changed, 502 insertions(+), 177 deletions(-) create mode 100644 src/Umbraco.Core/Models/PreValue.cs create mode 100644 src/Umbraco.Core/Models/PreValueCollection.cs diff --git a/src/Umbraco.Core/Models/PreValue.cs b/src/Umbraco.Core/Models/PreValue.cs new file mode 100644 index 0000000000..d0bdcc6d11 --- /dev/null +++ b/src/Umbraco.Core/Models/PreValue.cs @@ -0,0 +1,29 @@ +namespace Umbraco.Core.Models +{ + /// + /// Represents a stored pre-value field value + /// + public class PreValue + { + public PreValue(int id, string value) + { + Value = value; + Id = id; + } + + public PreValue(string value) + { + Value = value; + } + + /// + /// The value stored for the pre-value field + /// + public string Value { get; private set; } + + /// + /// The database id for the pre-value field value + /// + public int Id { get; private set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PreValueCollection.cs b/src/Umbraco.Core/Models/PreValueCollection.cs new file mode 100644 index 0000000000..c6e07d6e34 --- /dev/null +++ b/src/Umbraco.Core/Models/PreValueCollection.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Core.Models +{ + /// + /// Represents the pre-value data for a DataType + /// + /// + /// Due to the legacy nature of the data that can be stored for pre-values, we have this class which encapsulates the 2 different + /// ways that pre-values are stored: A string array or a Dictionary. + /// + /// Most legacy property editors won't support the dictionary format but new property editors should always use the dictionary format. + /// In order to get overrideable pre-values working we need a dictionary since we'll have to reference a pre-value by a key. + /// + public class PreValueCollection + { + private IDictionary _preValuesAsDictionary; + private IEnumerable _preValuesAsArray; + public IEnumerable PreValuesAsArray + { + get + { + if (_preValuesAsArray == null) + { + throw new InvalidOperationException("The current pre-value collection is dictionary based, use the PreValuesAsDictionary property instead"); + } + return _preValuesAsArray; + } + set { _preValuesAsArray = value; } + } + + public IDictionary PreValuesAsDictionary + { + get + { + if (_preValuesAsDictionary == null) + { + throw new InvalidOperationException("The current pre-value collection is array based, use the PreValuesAsArray property instead"); + } + return _preValuesAsDictionary; + } + set { _preValuesAsDictionary = value; } + } + + /// + /// Check if it is a dictionary based collection + /// + public bool IsDictionaryBased + { + get { return _preValuesAsDictionary != null; } + } + + public PreValueCollection(IEnumerable preVals) + { + _preValuesAsArray = preVals; + } + + public PreValueCollection(IDictionary preVals) + { + _preValuesAsDictionary = preVals; + } + + /// + /// Regardless of how the pre-values are stored this will return as a dictionary, it will convert an array based to a dictionary + /// + /// + public IDictionary FormatAsDictionary() + { + if (IsDictionaryBased) + { + return PreValuesAsDictionary; + } + + //it's an array so need to format it, the alias will just be an iteration + var result = new Dictionary(); + var asArray = PreValuesAsArray.ToArray(); + for (var i = 0; i < asArray.Length; i++) + { + result.Add(i.ToInvariantString(), asArray[i]); + } + return result; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs b/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs index 4cdf4de85e..eb5dc31059 100644 --- a/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs +++ b/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs @@ -84,39 +84,58 @@ namespace Umbraco.Core.Persistence.UnitOfWork }); } - /// - /// Commits all batched changes within the scope of a PetaPoco transaction - /// - /// - /// Unlike a typical unit of work, this UOW will let you commit more than once since a new transaction is creaed per - /// Commit() call instead of having one Transaction per UOW. - /// - public void Commit() - { - using (var transaction = Database.GetTransaction()) - { - foreach (var operation in _operations.OrderBy(o => o.ProcessDate)) - { - switch (operation.Type) - { - case TransactionType.Insert: - operation.Repository.PersistNewItem(operation.Entity); - break; - case TransactionType.Delete: - operation.Repository.PersistDeletedItem(operation.Entity); - break; - case TransactionType.Update: - operation.Repository.PersistUpdatedItem(operation.Entity); - break; - } - } - transaction.Complete(); - } + /// + /// Commits all batched changes within the scope of a PetaPoco transaction + /// + /// + /// Unlike a typical unit of work, this UOW will let you commit more than once since a new transaction is creaed per + /// Commit() call instead of having one Transaction per UOW. + /// + public void Commit() + { + Commit(null); + } - // Clear everything - _operations.Clear(); - _key = Guid.NewGuid(); - } + /// + /// Commits all batched changes within the scope of a PetaPoco transaction + /// + /// + /// Allows you to set a callback which is executed before the transaction is committed, allow you to add additional SQL + /// operations to the overall commit process after the queue has been processed. + /// + internal void Commit(Action transactionCompleting) + { + using (var transaction = Database.GetTransaction()) + { + foreach (var operation in _operations.OrderBy(o => o.ProcessDate)) + { + switch (operation.Type) + { + case TransactionType.Insert: + operation.Repository.PersistNewItem(operation.Entity); + break; + case TransactionType.Delete: + operation.Repository.PersistDeletedItem(operation.Entity); + break; + case TransactionType.Update: + operation.Repository.PersistUpdatedItem(operation.Entity); + break; + } + } + + //Execute the callback if there is one + if (transactionCompleting != null) + { + transactionCompleting(Database); + } + + transaction.Complete(); + } + + // Clear everything + _operations.Clear(); + _key = Guid.NewGuid(); + } public object Key { diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index cb5d5f0cc0..f21b76a899 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.PropertyEditors; using umbraco.interfaces; namespace Umbraco.Core.Services @@ -18,16 +19,16 @@ namespace Umbraco.Core.Services /// public class DataTypeService : IDataTypeService { - private readonly RepositoryFactory _repositoryFactory; + private readonly RepositoryFactory _repositoryFactory; private readonly IDatabaseUnitOfWorkProvider _uowProvider; private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); public DataTypeService() : this(new RepositoryFactory()) - {} + { } public DataTypeService(RepositoryFactory repositoryFactory) - : this(new PetaPocoUnitOfWorkProvider(), repositoryFactory) + : this(new PetaPocoUnitOfWorkProvider(), repositoryFactory) { } @@ -36,9 +37,9 @@ namespace Umbraco.Core.Services { } - public DataTypeService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory) + public DataTypeService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory) { - _repositoryFactory = repositoryFactory; + _repositoryFactory = repositoryFactory; _uowProvider = provider; } @@ -74,7 +75,7 @@ namespace Umbraco.Core.Services /// /// Gets a by its control Id /// - /// Id of the DataType control + /// Id of the property editor /// Collection of objects with a matching contorl id public IEnumerable GetDataTypeDefinitionByControlId(Guid id) { @@ -109,27 +110,25 @@ namespace Umbraco.Core.Services { using (var uow = _uowProvider.GetUnitOfWork()) { - var dtos = uow.Database.Fetch("WHERE datatypeNodeId = @Id", new {Id = id}); + var dtos = uow.Database.Fetch("WHERE datatypeNodeId = @Id", new { Id = id }); var list = dtos.Select(x => x.Value).ToList(); return list; } } /// - /// Gets all prevalues for an + /// Returns the PreValueCollection for the specified data type /// - /// - /// This method should be kept internal until a proper PreValue object model is introduced. - /// - /// Id of the to retrieve prevalues from - /// An enumerable list of Tuples containing Id, Alias, SortOrder, Value - internal IEnumerable> GetDetailedPreValuesByDataTypeId(int id) + /// + /// + public PreValueCollection GetPreValuesCollectionByDataTypeId(int id) { using (var uow = _uowProvider.GetUnitOfWork()) { var dtos = uow.Database.Fetch("WHERE datatypeNodeId = @Id", new { Id = id }); - var list = dtos.Select(x => new Tuple(x.Id, x.Alias, x.SortOrder, x.Value)).ToList(); - return list; + var list = dtos.Select(x => new Tuple(new PreValue(x.Id, x.Value), x.Alias, x.SortOrder)).ToList(); + + return PreValueConverter.ConvertToPreValuesCollection(list); } } @@ -154,8 +153,8 @@ namespace Umbraco.Core.Services /// Id of the user issueing the save public void Save(IDataTypeDefinition dataTypeDefinition, int userId = 0) { - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(dataTypeDefinition), this)) - return; + if (Saving.IsRaisedEventCancelled(new SaveEventArgs(dataTypeDefinition), this)) + return; using (new WriteLock(Locker)) { @@ -206,8 +205,11 @@ namespace Umbraco.Core.Services /// /// Id of the DataTypeDefinition to save PreValues for /// List of string values to save + [Obsolete("This should no longer be used, use the alternative SavePreValues or SaveDataTypeAndPreValues methods instead. This will only insert pre-values without keys")] public void SavePreValues(int id, IEnumerable values) { + //TODO: Should we raise an event here since we are really saving values for the data type? + using (new WriteLock(Locker)) { using (var uow = _uowProvider.GetUnitOfWork()) @@ -236,6 +238,111 @@ namespace Umbraco.Core.Services } } + /// + /// Saves/updates the pre-values + /// + /// + /// + /// + /// We need to actually look up each pre-value and maintain it's id if possible - this is because of silly property editors + /// like 'dropdown list publishing keys' + /// + public void SavePreValues(int id, IDictionary values) + { + //TODO: Should we raise an event here since we are really saving values for the data type? + + using (new WriteLock(Locker)) + { + using (var uow = _uowProvider.GetUnitOfWork()) + { + using (var transaction = uow.Database.GetTransaction()) + { + AddOrUpdatePreValues(id, values, uow); + transaction.Complete(); + } + } + } + } + + /// + /// This will save a data type and it's pre-values in one transaction + /// + /// + /// + /// + public void SaveDataTypeAndPreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values, int userId = 0) + { + if (Saving.IsRaisedEventCancelled(new SaveEventArgs(dataTypeDefinition), this)) + return; + + using (new WriteLock(Locker)) + { + var uow = (PetaPocoUnitOfWork)_uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateDataTypeDefinitionRepository(uow)) + { + dataTypeDefinition.CreatorId = userId; + repository.AddOrUpdate(dataTypeDefinition); + + //complete the transaction, but run the delegate before the db transaction is finalized + uow.Commit(database => AddOrUpdatePreValues(dataTypeDefinition.Id, values, uow)); + + Saved.RaiseEvent(new SaveEventArgs(dataTypeDefinition, false), this); + } + } + + Audit.Add(AuditTypes.Save, string.Format("Save DataTypeDefinition performed by user"), userId, dataTypeDefinition.Id); + } + + private void AddOrUpdatePreValues(int id, IDictionary preValueCollection, IDatabaseUnitOfWork uow) + { + //first just get all pre-values for this data type so we can compare them to see if we need to insert or update or replace + var sql = new Sql().Select("*") + .From() + .Where(dto => dto.DataTypeNodeId == id) + .OrderBy(dto => dto.SortOrder); + var currentVals = uow.Database.Fetch(sql).ToArray(); + + //already existing, need to be updated + var valueIds = preValueCollection.Where(x => x.Value.Id > 0).Select(x => x.Value.Id).ToArray(); + var existingByIds = currentVals.Where(x => valueIds.Contains(x.Id)).ToArray(); + + //These ones need to be removed from the db, they no longer exist in the new values + var deleteById = currentVals.Where(x => valueIds.Contains(x.Id) == false); + + foreach (var d in deleteById) + { + uow.Database.Execute( + "DELETE FROM cmsDataTypePreValues WHERE datatypeNodeId = @DataTypeId AND id=@Id", + new { DataTypeId = id, Id = d.Id }); + } + + var sortOrder = 1; + + foreach (var pre in preValueCollection) + { + var existing = existingByIds.FirstOrDefault(valueDto => valueDto.Id == pre.Value.Id); + if (existing != null) + { + existing.Value = pre.Value.Value; + existing.SortOrder = sortOrder; + uow.Database.Update(existing); + } + else + { + var dto = new DataTypePreValueDto + { + DataTypeNodeId = id, + Value = pre.Value.Value, + SortOrder = sortOrder, + Alias = pre.Key + }; + uow.Database.Insert(dto); + } + + sortOrder++; + } + } + /// /// Deletes an /// @@ -246,43 +353,43 @@ namespace Umbraco.Core.Services /// to delete /// Optional Id of the user issueing the deletion public void Delete(IDataTypeDefinition dataTypeDefinition, int userId = 0) - { - if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(dataTypeDefinition), this)) - return; - - var uow = _uowProvider.GetUnitOfWork(); - using (var repository = _repositoryFactory.CreateContentTypeRepository(uow)) - { - //Find ContentTypes using this IDataTypeDefinition on a PropertyType - var query = Query.Builder.Where(x => x.DataTypeDefinitionId == dataTypeDefinition.Id); - var contentTypes = repository.GetByQuery(query); + { + if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(dataTypeDefinition), this)) + return; - //Loop through the list of results and remove the PropertyTypes that references the DataTypeDefinition that is being deleted - foreach (var contentType in contentTypes) - { - if (contentType == null) continue; + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateContentTypeRepository(uow)) + { + //Find ContentTypes using this IDataTypeDefinition on a PropertyType + var query = Query.Builder.Where(x => x.DataTypeDefinitionId == dataTypeDefinition.Id); + var contentTypes = repository.GetByQuery(query); - foreach (var group in contentType.PropertyGroups) - { - var types = @group.PropertyTypes.Where(x => x.DataTypeDefinitionId == dataTypeDefinition.Id).ToList(); - foreach (var propertyType in types) - { - @group.PropertyTypes.Remove(propertyType); - } - } + //Loop through the list of results and remove the PropertyTypes that references the DataTypeDefinition that is being deleted + foreach (var contentType in contentTypes) + { + if (contentType == null) continue; - repository.AddOrUpdate(contentType); - } + foreach (var group in contentType.PropertyGroups) + { + var types = @group.PropertyTypes.Where(x => x.DataTypeDefinitionId == dataTypeDefinition.Id).ToList(); + foreach (var propertyType in types) + { + @group.PropertyTypes.Remove(propertyType); + } + } - var dataTypeRepository = _repositoryFactory.CreateDataTypeDefinitionRepository(uow); - dataTypeRepository.Delete(dataTypeDefinition); + repository.AddOrUpdate(contentType); + } - uow.Commit(); + var dataTypeRepository = _repositoryFactory.CreateDataTypeDefinitionRepository(uow); + dataTypeRepository.Delete(dataTypeDefinition); - Deleted.RaiseEvent(new DeleteEventArgs(dataTypeDefinition, false), this); - } + uow.Commit(); - Audit.Add(AuditTypes.Delete, string.Format("Delete DataTypeDefinition performed by user"), userId, dataTypeDefinition.Id); + Deleted.RaiseEvent(new DeleteEventArgs(dataTypeDefinition, false), this); + } + + Audit.Add(AuditTypes.Delete, string.Format("Delete DataTypeDefinition performed by user"), userId, dataTypeDefinition.Id); } /// @@ -290,6 +397,7 @@ namespace Umbraco.Core.Services /// /// Id of the DataType, which corresponds to the Guid Id of the control /// object + [Obsolete("IDataType is obsolete and is no longer used, it will be removed from the codebase in future versions")] public IDataType GetDataTypeById(Guid id) { return DataTypesResolver.Current.GetById(id); @@ -299,6 +407,7 @@ namespace Umbraco.Core.Services /// Gets a complete list of all registered 's /// /// An enumerable list of objects + [Obsolete("IDataType is obsolete and is no longer used, it will be removed from the codebase in future versions")] public IEnumerable GetAllDataTypes() { return DataTypesResolver.Current.DataTypes; @@ -308,22 +417,58 @@ namespace Umbraco.Core.Services /// /// Occurs before Delete /// - public static event TypedEventHandler> Deleting; + public static event TypedEventHandler> Deleting; /// /// Occurs after Delete /// - public static event TypedEventHandler> Deleted; + public static event TypedEventHandler> Deleted; /// /// Occurs before Save /// - public static event TypedEventHandler> Saving; + public static event TypedEventHandler> Saving; /// /// Occurs after Save /// - public static event TypedEventHandler> Saved; + public static event TypedEventHandler> Saved; #endregion + + internal static class PreValueConverter + { + /// + /// Converts the tuple to a pre-value collection + /// + /// + /// + internal static PreValueCollection ConvertToPreValuesCollection(IEnumerable> list) + { + //now we need to determine if they are dictionary based, otherwise they have to be array based + var dictionary = new Dictionary(); + + //need to check all of the keys, if there's only one and it is empty then it's an array + var keys = list.Select(x => x.Item2).Distinct().ToArray(); + if (keys.Length == 1 && keys[0].IsNullOrWhiteSpace()) + { + return new PreValueCollection(list.OrderBy(x => x.Item3).Select(x => x.Item1)); + } + + foreach (var item in list + .OrderBy(x => x.Item3) //we'll order them first so we maintain the order index in the dictionary + .GroupBy(x => x.Item2)) //group by alias + { + if (item.Count() > 1) + { + //if there's more than 1 item per key, then it cannot be a dictionary, just return the array + return new PreValueCollection(list.OrderBy(x => x.Item3).Select(x => x.Item1)); + } + + dictionary.Add(item.Key, item.First().Item1); + } + + return new PreValueCollection(dictionary); + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index 5fac95a25c..a0c53bc38a 100644 --- a/src/Umbraco.Core/Services/IDataTypeService.cs +++ b/src/Umbraco.Core/Services/IDataTypeService.cs @@ -83,13 +83,36 @@ namespace Umbraco.Core.Services /// An enumerable list of string values IEnumerable GetPreValuesByDataTypeId(int id); + /// + /// Gets a pre-value collection by data type id + /// + /// + /// + PreValueCollection GetPreValuesCollectionByDataTypeId(int id); + /// /// Saves a list of PreValues for a given DataTypeDefinition /// /// Id of the DataTypeDefinition to save PreValues for /// List of string values to save + [Obsolete("This should no longer be used, use the alternative SavePreValues or SaveDataTypeAndPreValues methods instead. This will only insert pre-values without keys")] void SavePreValues(int id, IEnumerable values); + /// + /// Saves a list of PreValues for a given DataTypeDefinition + /// + /// Id of the DataTypeDefinition to save PreValues for + /// List of key/value pairs to save + void SavePreValues(int id, IDictionary values); + + /// + /// Saves the data type and it's prevalues + /// + /// + /// + /// + void SaveDataTypeAndPreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values, int userId = 0); + /// /// Gets a specific PreValue by its Id /// diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index e50daa6a61..7d683cd275 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -707,20 +707,23 @@ namespace Umbraco.Core.Services { var prevalues = new XElement("PreValues"); - var prevalueList = ((DataTypeService)_dataTypeService).GetDetailedPreValuesByDataTypeId(dataTypeDefinition.Id); - foreach (var tuple in prevalueList) + var prevalueList = _dataTypeService.GetPreValuesCollectionByDataTypeId(dataTypeDefinition.Id) + .FormatAsDictionary(); + + var sort = 0; + foreach (var pv in prevalueList) { var prevalue = new XElement("PreValue"); - prevalue.Add(new XAttribute("Id", tuple.Item1)); - prevalue.Add(new XAttribute("Value", tuple.Item4)); - prevalue.Add(new XAttribute("Alias", tuple.Item2)); - prevalue.Add(new XAttribute("SortOrder", tuple.Item3)); + prevalue.Add(new XAttribute("Id", pv.Value.Id)); + prevalue.Add(new XAttribute("Value", pv.Value.Value)); + prevalue.Add(new XAttribute("Alias", pv.Key)); + prevalue.Add(new XAttribute("SortOrder", sort)); prevalues.Add(prevalue); + sort++; } var xml = new XElement("DataType", prevalues); xml.Add(new XAttribute("Name", dataTypeDefinition.Name)); - //The 'ID' when exporting is actually the property editor alias (in pre v7 it was the IDataType GUID id) xml.Add(new XAttribute("Id", dataTypeDefinition.ControlId)); xml.Add(new XAttribute("Definition", dataTypeDefinition.Key)); xml.Add(new XAttribute("DatabaseType", dataTypeDefinition.DatabaseType.ToString())); diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index 8f4c7a57b1..1832e6906b 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -439,6 +439,16 @@ namespace Umbraco.Core return String.Format(CultureInfo.InvariantCulture, format, args); } + /// + /// Converts an integer to an invariant formatted string + /// + /// + /// + public static string ToInvariantString(this int str) + { + return str.ToString(CultureInfo.InvariantCulture); + } + /// /// Compares 2 strings with invariant culture and case ignored /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index a50b483d73..f615187f45 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -189,6 +189,8 @@ + + diff --git a/src/Umbraco.Tests/CodeFirst/CodeFirstTests.cs b/src/Umbraco.Tests/CodeFirst/CodeFirstTests.cs index d8b0909312..5c388e7784 100644 --- a/src/Umbraco.Tests/CodeFirst/CodeFirstTests.cs +++ b/src/Umbraco.Tests/CodeFirst/CodeFirstTests.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.IO; +using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Serialization; using Umbraco.Tests.CodeFirst.Definitions; using Umbraco.Tests.CodeFirst.TestModels; @@ -39,7 +40,7 @@ namespace Umbraco.Tests.CodeFirst var mappedContentTypes = ContentTypeDefinitionFactory.RetrieveMappedContentTypes(); ServiceContext.ContentTypeService.Save(mappedContentTypes); - var model = ServiceContext.ContentTypeService.GetContentType(1046); + var model = ServiceContext.ContentTypeService.GetContentType(NodeDto.NodeIdSeed + 1); Assert.That(model, Is.Not.Null); } @@ -145,8 +146,8 @@ namespace Umbraco.Tests.CodeFirst var mappedContentTypes = ContentTypeDefinitionFactory.RetrieveMappedContentTypes().ToList(); ServiceContext.ContentTypeService.Save(mappedContentTypes); - var type1 = ServiceContext.ContentTypeService.GetContentType(1045); - var type2 = ServiceContext.ContentTypeService.GetContentType(1046); + var type1 = ServiceContext.ContentTypeService.GetContentType(NodeDto.NodeIdSeed); + var type2 = ServiceContext.ContentTypeService.GetContentType(NodeDto.NodeIdSeed + 1); Assert.That(type1, Is.Not.Null); Assert.That(type2, Is.Not.Null); @@ -163,7 +164,7 @@ namespace Umbraco.Tests.CodeFirst var mappedContentTypes = ContentTypeDefinitionFactory.RetrieveMappedContentTypes().ToList(); ServiceContext.ContentTypeService.Save(mappedContentTypes); - var type1 = ServiceContext.ContentTypeService.GetContentType(1047); + var type1 = ServiceContext.ContentTypeService.GetContentType(NodeDto.NodeIdSeed + 2); Assert.That(type1, Is.Not.Null); Assert.That(type1.PropertyGroups.Count(), Is.EqualTo(2)); diff --git a/src/Umbraco.Tests/LegacyApi/MediaTypeTests.cs b/src/Umbraco.Tests/LegacyApi/MediaTypeTests.cs index 6664d86eb8..26e93961ac 100644 --- a/src/Umbraco.Tests/LegacyApi/MediaTypeTests.cs +++ b/src/Umbraco.Tests/LegacyApi/MediaTypeTests.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using NUnit.Framework; +using Umbraco.Core.Models.Rdbms; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; using umbraco.BusinessLogic; @@ -27,7 +28,7 @@ namespace Umbraco.Tests.LegacyApi // Arrange var folder = MediaType.GetByAlias(Constants.Conventions.MediaTypes.Folder); var folderStructure = folder.AllowedChildContentTypeIDs.ToList(); - folderStructure.Add(1045); + folderStructure.Add(NodeDto.NodeIdSeed); // Act folder.AllowedChildContentTypeIDs = folderStructure.ToArray(); @@ -37,7 +38,7 @@ namespace Umbraco.Tests.LegacyApi var updated = MediaType.GetByAlias(Constants.Conventions.MediaTypes.Folder); Assert.That(updated.AllowedChildContentTypeIDs.Any(), Is.True); - Assert.That(updated.AllowedChildContentTypeIDs.Any(x => x == 1045), Is.True); + Assert.That(updated.AllowedChildContentTypeIDs.Any(x => x == NodeDto.NodeIdSeed), Is.True); } [Test] @@ -85,7 +86,7 @@ namespace Umbraco.Tests.LegacyApi public void CreateTestData() { - //Create and Save ContentType "video" -> 1045 + //Create and Save ContentType "video" -> NodeDto.NodeIdSeed var videoMediaType = MockedContentTypes.CreateVideoMediaType(); ServiceContext.ContentTypeService.Save(videoMediaType); } diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs index 5ce7db21ff..a1adedf149 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs @@ -224,7 +224,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) { // Act - var content = repository.Get(1048); + var content = repository.Get(NodeDto.NodeIdSeed + 3); bool dirty = ((Content)content).IsDirty(); // Assert @@ -242,11 +242,11 @@ namespace Umbraco.Tests.Persistence.Repositories using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) { // Act - var content = repository.Get(1047); + var content = repository.Get(NodeDto.NodeIdSeed + 2); content.Name = "About 2"; repository.AddOrUpdate(content); unitOfWork.Commit(); - var updatedContent = repository.Get(1047); + var updatedContent = repository.Get(NodeDto.NodeIdSeed + 2); // Assert Assert.That(updatedContent.Id, Is.EqualTo(content.Id)); @@ -264,8 +264,8 @@ namespace Umbraco.Tests.Persistence.Repositories ContentTypeRepository contentTypeRepository; using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) { - var contentType = contentTypeRepository.Get(1045); - var content = new Content("Textpage 2 Child Node", 1048, contentType); + var contentType = contentTypeRepository.Get(NodeDto.NodeIdSeed); + var content = new Content("Textpage 2 Child Node", NodeDto.NodeIdSeed + 3, contentType); content.CreatorId = 0; content.WriterId = 0; @@ -295,17 +295,17 @@ namespace Umbraco.Tests.Persistence.Repositories using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) { // Act - var content = repository.Get(1048); + var content = repository.Get(NodeDto.NodeIdSeed + 3); // Assert - Assert.That(content.Id, Is.EqualTo(1048)); + Assert.That(content.Id, Is.EqualTo(NodeDto.NodeIdSeed + 3)); Assert.That(content.CreateDate, Is.GreaterThan(DateTime.MinValue)); Assert.That(content.UpdateDate, Is.GreaterThan(DateTime.MinValue)); Assert.That(content.ParentId, Is.Not.EqualTo(0)); Assert.That(content.Name, Is.EqualTo("Text Page 2")); Assert.That(content.SortOrder, Is.EqualTo(1)); Assert.That(content.Version, Is.Not.EqualTo(Guid.Empty)); - Assert.That(content.ContentTypeId, Is.EqualTo(1045)); + Assert.That(content.ContentTypeId, Is.EqualTo(NodeDto.NodeIdSeed)); Assert.That(content.Path, Is.Not.Empty); Assert.That(content.Properties.Any(), Is.True); } @@ -339,7 +339,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) { // Act - var contents = repository.GetAll(1047, 1048); + var contents = repository.GetAll(NodeDto.NodeIdSeed + 2, NodeDto.NodeIdSeed + 3); // Assert Assert.That(contents, Is.Not.Null); @@ -379,7 +379,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) { // Act - var exists = repository.Exists(1046); + var exists = repository.Exists(NodeDto.NodeIdSeed + 1); // Assert Assert.That(exists, Is.True); @@ -417,9 +417,9 @@ namespace Umbraco.Tests.Persistence.Repositories using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) { // Act - var textpage = repository.Get(1046); - var subpage = repository.Get(1047); - var trashed = repository.Get(1049); + var textpage = repository.Get(NodeDto.NodeIdSeed + 1); + var subpage = repository.Get(NodeDto.NodeIdSeed + 2); + var trashed = repository.Get(NodeDto.NodeIdSeed + 4); // Assert Assert.That(textpage.Key.ToString().ToUpper(), Is.EqualTo("B58B3AD4-62C2-4E27-B1BE-837BD7C533E0")); @@ -443,7 +443,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Assert Assert.That(content, Is.Not.Null); - Assert.That(content.Id, Is.EqualTo(1046)); + Assert.That(content.Id, Is.EqualTo(NodeDto.NodeIdSeed + 1)); } } @@ -457,7 +457,7 @@ namespace Umbraco.Tests.Persistence.Repositories ContentTypeRepository contentTypeRepository; using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) { - var content = repository.Get(1047); + var content = repository.Get(NodeDto.NodeIdSeed + 2); // Act content.Language = "da-DK"; @@ -465,9 +465,9 @@ namespace Umbraco.Tests.Persistence.Repositories repository.AddOrUpdate(content); unitOfWork.Commit(); - var latest = repository.Get(1047); - var english = repository.GetByLanguage(1047, "en-US"); - var danish = repository.GetByLanguage(1047, "da-DK"); + var latest = repository.Get(NodeDto.NodeIdSeed + 2); + var english = repository.GetByLanguage(NodeDto.NodeIdSeed + 2, "en-US"); + var danish = repository.GetByLanguage(NodeDto.NodeIdSeed + 2, "da-DK"); // Assert Assert.That(latest.Name, Is.EqualTo("Tekst Side 1")); @@ -478,26 +478,26 @@ namespace Umbraco.Tests.Persistence.Repositories public void CreateTestData() { - //Create and Save ContentType "umbTextpage" -> 1045 + //Create and Save ContentType "umbTextpage" -> NodeDto.NodeIdSeed ContentType contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage", "Textpage"); contentType.Key = new Guid("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"); ServiceContext.ContentTypeService.Save(contentType); - //Create and Save Content "Homepage" based on "umbTextpage" -> 1046 + //Create and Save Content "Homepage" based on "umbTextpage" -> NodeDto.NodeIdSeed + 1 Content textpage = MockedContent.CreateSimpleContent(contentType); textpage.Key = new Guid("B58B3AD4-62C2-4E27-B1BE-837BD7C533E0"); ServiceContext.ContentService.Save(textpage, 0); - //Create and Save Content "Text Page 1" based on "umbTextpage" -> 1047 + //Create and Save Content "Text Page 1" based on "umbTextpage" -> NodeDto.NodeIdSeed + 2 Content subpage = MockedContent.CreateSimpleContent(contentType, "Text Page 1", textpage.Id); subpage.Key = new Guid("FF11402B-7E53-4654-81A7-462AC2108059"); ServiceContext.ContentService.Save(subpage, 0); - //Create and Save Content "Text Page 1" based on "umbTextpage" -> 1048 + //Create and Save Content "Text Page 1" based on "umbTextpage" -> NodeDto.NodeIdSeed + 3 Content subpage2 = MockedContent.CreateSimpleContent(contentType, "Text Page 2", textpage.Id); ServiceContext.ContentService.Save(subpage2, 0); - //Create and Save Content "Text Page Deleted" based on "umbTextpage" -> 1049 + //Create and Save Content "Text Page Deleted" based on "umbTextpage" -> NodeDto.NodeIdSeed + 4 Content trashed = MockedContent.CreateSimpleContent(contentType, "Text Page Deleted", -20); trashed.Trashed = true; ServiceContext.ContentService.Save(trashed, 0); diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index e5c782c00d..9c593d0714 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Caching; using Umbraco.Core.Persistence.Repositories; @@ -93,7 +94,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (var repository = CreateRepository(unitOfWork)) { // Act - var contentType = repository.Get(1046); + var contentType = repository.Get(NodeDto.NodeIdSeed + 1); contentType.Thumbnail = "Doc2.png"; contentType.PropertyGroups["Content"].PropertyTypes.Add(new PropertyType(new Guid(), DataTypeDatabaseType.Ntext) @@ -155,11 +156,11 @@ namespace Umbraco.Tests.Persistence.Repositories { // Act - var contentType = repository.Get(1046); + var contentType = repository.Get(NodeDto.NodeIdSeed + 1); // Assert Assert.That(contentType, Is.Not.Null); - Assert.That(contentType.Id, Is.EqualTo(1046)); + Assert.That(contentType.Id, Is.EqualTo(NodeDto.NodeIdSeed + 1)); } } @@ -195,7 +196,7 @@ namespace Umbraco.Tests.Persistence.Repositories { // Act - var exists = repository.Exists(1045); + var exists = repository.Exists(NodeDto.NodeIdSeed); // Assert Assert.That(exists, Is.True); @@ -210,14 +211,14 @@ namespace Umbraco.Tests.Persistence.Repositories var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { - var contentType = repository.Get(1046); + var contentType = repository.Get(NodeDto.NodeIdSeed + 1); // Act contentType.PropertyGroups["Meta"].PropertyTypes.Remove("metaDescription"); repository.AddOrUpdate(contentType); unitOfWork.Commit(); - var result = repository.Get(1046); + var result = repository.Get(NodeDto.NodeIdSeed + 1); // Assert Assert.That(result.PropertyTypes.Any(x => x.Alias == "metaDescription"), Is.False); @@ -236,7 +237,7 @@ namespace Umbraco.Tests.Persistence.Repositories { // Act - var contentType = repository.Get(1045); + var contentType = repository.Get(NodeDto.NodeIdSeed); // Assert Assert.That(contentType.PropertyTypes.Count(), Is.EqualTo(3)); @@ -254,7 +255,7 @@ namespace Umbraco.Tests.Persistence.Repositories { // Act - var contentType = repository.Get(1046); + var contentType = repository.Get(NodeDto.NodeIdSeed + 1); // Assert Assert.That(contentType.PropertyTypes.Count(), Is.EqualTo(4)); @@ -270,7 +271,7 @@ namespace Umbraco.Tests.Persistence.Repositories var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { - var contentType = repository.Get(1046); + var contentType = repository.Get(NodeDto.NodeIdSeed + 1); // Act var urlAlias = new PropertyType(new Guid(), DataTypeDatabaseType.Nvarchar) @@ -289,7 +290,7 @@ namespace Umbraco.Tests.Persistence.Repositories unitOfWork.Commit(); // Assert - var updated = repository.Get(1046); + var updated = repository.Get(NodeDto.NodeIdSeed + 1); Assert.That(addedPropertyType, Is.True); Assert.That(updated.PropertyGroups.Count(), Is.EqualTo(2)); Assert.That(updated.PropertyTypes.Count(), Is.EqualTo(5)); @@ -314,7 +315,7 @@ namespace Umbraco.Tests.Persistence.Repositories unitOfWork.Commit(); // Act - var contentType = repository.Get(1045); + var contentType = repository.Get(NodeDto.NodeIdSeed); contentType.AllowedContentTypes = new List { new ContentTypeSort @@ -334,7 +335,7 @@ namespace Umbraco.Tests.Persistence.Repositories unitOfWork.Commit(); //Assert - var updated = repository.Get(1045); + var updated = repository.Get(NodeDto.NodeIdSeed); Assert.That(updated.AllowedContentTypes.Any(), Is.True); Assert.That(updated.AllowedContentTypes.Any(x => x.Alias == subpageContentType.Alias), Is.True); @@ -351,7 +352,7 @@ namespace Umbraco.Tests.Persistence.Repositories ContentTypeRepository repository; using (var contentRepository = CreateRepository(unitOfWork, out repository)) { - var contentType = repository.Get(1046); + var contentType = repository.Get(NodeDto.NodeIdSeed + 1); var subpage = MockedContent.CreateTextpageContent(contentType, "Text Page 1", contentType.Id); contentRepository.AddOrUpdate(subpage); @@ -378,7 +379,7 @@ namespace Umbraco.Tests.Persistence.Repositories ContentTypeRepository repository; using (var contentRepository = CreateRepository(unitOfWork, out repository)) { - var contentType = repository.Get(1046); + var contentType = repository.Get(NodeDto.NodeIdSeed + 1); var subpage = MockedContent.CreateTextpageContent(contentType, "Text Page 1", contentType.Id); contentRepository.AddOrUpdate(subpage); @@ -406,7 +407,7 @@ namespace Umbraco.Tests.Persistence.Repositories ContentTypeRepository repository; using (var contentRepository = CreateRepository(unitOfWork, out repository)) { - var contentType = repository.Get(1046); + var contentType = repository.Get(NodeDto.NodeIdSeed + 1); var subpage = MockedContent.CreateTextpageContent(contentType, "Text Page 1", contentType.Id); contentRepository.AddOrUpdate(subpage); @@ -442,7 +443,7 @@ namespace Umbraco.Tests.Persistence.Repositories ContentTypeRepository repository; using (var contentRepository = CreateRepository(unitOfWork, out repository)) { - var contentType = repository.Get(1046); + var contentType = repository.Get(NodeDto.NodeIdSeed + 1); var subpage = MockedContent.CreateTextpageContent(contentType, "Text Page 1", contentType.Id); contentRepository.AddOrUpdate(subpage); @@ -475,11 +476,11 @@ namespace Umbraco.Tests.Persistence.Repositories public void CreateTestData() { - //Create and Save ContentType "umbTextpage" -> 1045 + //Create and Save ContentType "umbTextpage" -> NodeDto.NodeIdSeed ContentType simpleContentType = MockedContentTypes.CreateSimpleContentType("umbTextpage", "Textpage"); ServiceContext.ContentTypeService.Save(simpleContentType); - //Create and Save ContentType "textPage" -> 1046 + //Create and Save ContentType "textPage" -> NodeDto.NodeIdSeed + 1 ContentType textpageContentType = MockedContentTypes.CreateTextpageContentType(); ServiceContext.ContentTypeService.Save(textpageContentType); } diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs index 28d89f4baf..884ba5b383 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Caching; using Umbraco.Core.Persistence.Querying; @@ -145,7 +146,7 @@ namespace Umbraco.Tests.Persistence.Repositories { // Act - var media = repository.Get(1046); + var media = repository.Get(NodeDto.NodeIdSeed + 1); bool dirty = ((ICanBeDirty) media).IsDirty(); // Assert @@ -164,12 +165,12 @@ namespace Umbraco.Tests.Persistence.Repositories { // Act - var content = repository.Get(1047); + var content = repository.Get(NodeDto.NodeIdSeed + 2); content.Name = "Test File Updated"; repository.AddOrUpdate(content); unitOfWork.Commit(); - var updatedContent = repository.Get(1047); + var updatedContent = repository.Get(NodeDto.NodeIdSeed + 2); // Assert Assert.That(updatedContent.Id, Is.EqualTo(content.Id)); @@ -188,12 +189,12 @@ namespace Umbraco.Tests.Persistence.Repositories { // Act - var media = repository.Get(1047); + var media = repository.Get(NodeDto.NodeIdSeed + 2); repository.Delete(media); unitOfWork.Commit(); - var deleted = repository.Get(1047); - var exists = repository.Exists(1047); + var deleted = repository.Get(NodeDto.NodeIdSeed + 2); + var exists = repository.Exists(NodeDto.NodeIdSeed + 2); // Assert Assert.That(deleted, Is.Null); @@ -212,10 +213,10 @@ namespace Umbraco.Tests.Persistence.Repositories { // Act - var media = repository.Get(1046); + var media = repository.Get(NodeDto.NodeIdSeed + 1); // Assert - Assert.That(media.Id, Is.EqualTo(1046)); + Assert.That(media.Id, Is.EqualTo(NodeDto.NodeIdSeed + 1)); Assert.That(media.CreateDate, Is.GreaterThan(DateTime.MinValue)); Assert.That(media.UpdateDate, Is.GreaterThan(DateTime.MinValue)); Assert.That(media.ParentId, Is.Not.EqualTo(0)); @@ -258,7 +259,7 @@ namespace Umbraco.Tests.Persistence.Repositories { // Act - var medias = repository.GetAll(1046, 1047); + var medias = repository.GetAll(NodeDto.NodeIdSeed + 1, NodeDto.NodeIdSeed + 2); // Assert Assert.That(medias, Is.Not.Null); @@ -298,9 +299,9 @@ namespace Umbraco.Tests.Persistence.Repositories { // Act - var exists = repository.Exists(1046); - var existsToo = repository.Exists(1046); - var doesntExists = repository.Exists(1050); + var exists = repository.Exists(NodeDto.NodeIdSeed + 1); + var existsToo = repository.Exists(NodeDto.NodeIdSeed + 1); + var doesntExists = repository.Exists(NodeDto.NodeIdSeed + 5); // Assert Assert.That(exists, Is.True); @@ -337,17 +338,17 @@ namespace Umbraco.Tests.Persistence.Repositories public void CreateTestData() { - //Create and Save folder-Media -> 1045 + //Create and Save folder-Media -> NodeDto.NodeIdSeed var folderMediaType = ServiceContext.ContentTypeService.GetMediaType(1031); var folder = MockedMedia.CreateMediaFolder(folderMediaType, -1); ServiceContext.MediaService.Save(folder, 0); - //Create and Save image-Media -> 1046 + //Create and Save image-Media -> NodeDto.NodeIdSeed + 1 var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); var image = MockedMedia.CreateMediaImage(imageMediaType, folder.Id); ServiceContext.MediaService.Save(image, 0); - //Create and Save file-Media -> 1047 + //Create and Save file-Media -> NodeDto.NodeIdSeed + 2 var fileMediaType = ServiceContext.ContentTypeService.GetMediaType(1033); var file = MockedMedia.CreateMediaFile(fileMediaType, folder.Id); ServiceContext.MediaService.Save(file, 0); diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs index acb96da490..d10884cb39 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs @@ -3,6 +3,7 @@ using System.Linq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Caching; using Umbraco.Core.Persistence.Repositories; @@ -76,7 +77,7 @@ namespace Umbraco.Tests.Persistence.Repositories unitOfWork.Commit(); // Act - var mediaType = repository.Get(1045); + var mediaType = repository.Get(NodeDto.NodeIdSeed); mediaType.Thumbnail = "Doc2.png"; mediaType.PropertyGroups["Media"].PropertyTypes.Add(new PropertyType(new Guid(), DataTypeDatabaseType.Ntext) @@ -197,12 +198,12 @@ namespace Umbraco.Tests.Persistence.Repositories unitOfWork.Commit(); // Act - var mediaTypeV2 = repository.Get(1045); + var mediaTypeV2 = repository.Get(NodeDto.NodeIdSeed); mediaTypeV2.PropertyGroups["Media"].PropertyTypes.Remove("title"); repository.AddOrUpdate(mediaTypeV2); unitOfWork.Commit(); - var mediaTypeV3 = repository.Get(1045); + var mediaTypeV3 = repository.Get(NodeDto.NodeIdSeed); // Assert Assert.That(mediaTypeV3.PropertyTypes.Any(x => x.Alias == "title"), Is.False); @@ -224,7 +225,7 @@ namespace Umbraco.Tests.Persistence.Repositories unitOfWork.Commit(); // Act - var contentType = repository.Get(1045); + var contentType = repository.Get(NodeDto.NodeIdSeed); // Assert Assert.That(contentType.PropertyTypes.Count(), Is.EqualTo(2)); diff --git a/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs index c727e04fd4..2eced7ef4d 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs @@ -3,6 +3,7 @@ using System.Linq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Caching; using Umbraco.Core.Persistence.Querying; @@ -59,7 +60,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var relationType = repositoryType.Get(1); - var relation = new Relation(1047, 1048, relationType); + var relation = new Relation(NodeDto.NodeIdSeed + 2, NodeDto.NodeIdSeed + 3, relationType); repository.AddOrUpdate(relation); unitOfWork.Commit(); @@ -132,8 +133,8 @@ namespace Umbraco.Tests.Persistence.Repositories // Assert Assert.That(relation, Is.Not.Null); Assert.That(relation.HasIdentity, Is.True); - Assert.That(relation.ChildId, Is.EqualTo(1047)); - Assert.That(relation.ParentId, Is.EqualTo(1046)); + Assert.That(relation.ChildId, Is.EqualTo(NodeDto.NodeIdSeed + 2)); + Assert.That(relation.ParentId, Is.EqualTo(NodeDto.NodeIdSeed + 1)); Assert.That(relation.RelationType.Alias, Is.EqualTo("relateContentOnCopy")); } } @@ -211,7 +212,7 @@ namespace Umbraco.Tests.Persistence.Repositories { // Act - var query = Query.Builder.Where(x => x.ParentId == 1046); + var query = Query.Builder.Where(x => x.ParentId == NodeDto.NodeIdSeed + 1); int count = repository.Count(query); // Assert @@ -251,7 +252,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (var repository = CreateRepository(unitOfWork, out repositoryType)) { - var content = ServiceContext.ContentService.GetById(1047); + var content = ServiceContext.ContentService.GetById(NodeDto.NodeIdSeed + 2); ServiceContext.ContentService.Delete(content, 0); // Act @@ -284,19 +285,19 @@ namespace Umbraco.Tests.Persistence.Repositories relationTypeRepository.AddOrUpdate(relateContentType); unitOfWork.Commit(); - //Create and Save ContentType "umbTextpage" -> 1045 + //Create and Save ContentType "umbTextpage" -> NodeDto.NodeIdSeed ContentType contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage", "Textpage"); ServiceContext.ContentTypeService.Save(contentType); - //Create and Save Content "Homepage" based on "umbTextpage" -> 1046 + //Create and Save Content "Homepage" based on "umbTextpage" -> NodeDto.NodeIdSeed + 1 Content textpage = MockedContent.CreateSimpleContent(contentType); ServiceContext.ContentService.Save(textpage, 0); - //Create and Save Content "Text Page 1" based on "umbTextpage" -> 1047 + //Create and Save Content "Text Page 1" based on "umbTextpage" -> NodeDto.NodeIdSeed + 2 Content subpage = MockedContent.CreateSimpleContent(contentType, "Text Page 1", textpage.Id); ServiceContext.ContentService.Save(subpage, 0); - //Create and Save Content "Text Page 1" based on "umbTextpage" -> 1048 + //Create and Save Content "Text Page 1" based on "umbTextpage" -> NodeDto.NodeIdSeed + 3 Content subpage2 = MockedContent.CreateSimpleContent(contentType, "Text Page 2", textpage.Id); ServiceContext.ContentService.Save(subpage2, 0); diff --git a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs index 38da2383f2..c6bc06ae1c 100644 --- a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs +++ b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Linq; using NUnit.Framework; using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.UnitOfWork; @@ -25,7 +26,7 @@ namespace Umbraco.Tests.Services public void Creating_100_Items() { // Arrange - var contentType = ServiceContext.ContentTypeService.GetContentType(1045); + var contentType = ServiceContext.ContentTypeService.GetContentType(NodeDto.NodeIdSeed); var pages = MockedContent.CreateTextpageContent(contentType, -1, 100); // Act @@ -44,7 +45,7 @@ namespace Umbraco.Tests.Services public void Creating_1000_Items() { // Arrange - var contentType = ServiceContext.ContentTypeService.GetContentType(1045); + var contentType = ServiceContext.ContentTypeService.GetContentType(NodeDto.NodeIdSeed); var pages = MockedContent.CreateTextpageContent(contentType, -1, 1000); // Act @@ -63,7 +64,7 @@ namespace Umbraco.Tests.Services public void Getting_100_Uncached_Items() { // Arrange - var contentType = ServiceContext.ContentTypeService.GetContentType(1045); + var contentType = ServiceContext.ContentTypeService.GetContentType(NodeDto.NodeIdSeed); var pages = MockedContent.CreateTextpageContent(contentType, -1, 100); ServiceContext.ContentService.Save(pages, 0); @@ -88,7 +89,7 @@ namespace Umbraco.Tests.Services public void Getting_1000_Uncached_Items() { // Arrange - var contentType = ServiceContext.ContentTypeService.GetContentType(1045); + var contentType = ServiceContext.ContentTypeService.GetContentType(NodeDto.NodeIdSeed); var pages = MockedContent.CreateTextpageContent(contentType, -1, 1000); ServiceContext.ContentService.Save(pages, 0); @@ -113,7 +114,7 @@ namespace Umbraco.Tests.Services public void Getting_100_Cached_Items() { // Arrange - var contentType = ServiceContext.ContentTypeService.GetContentType(1045); + var contentType = ServiceContext.ContentTypeService.GetContentType(NodeDto.NodeIdSeed); var pages = MockedContent.CreateTextpageContent(contentType, -1, 100); ServiceContext.ContentService.Save(pages, 0); @@ -141,7 +142,7 @@ namespace Umbraco.Tests.Services public void Getting_1000_Cached_Items() { // Arrange - var contentType = ServiceContext.ContentTypeService.GetContentType(1045); + var contentType = ServiceContext.ContentTypeService.GetContentType(NodeDto.NodeIdSeed); var pages = MockedContent.CreateTextpageContent(contentType, -1, 1000); ServiceContext.ContentService.Save(pages, 0); @@ -173,7 +174,7 @@ namespace Umbraco.Tests.Services public void CreateTestData() { - //Create and Save ContentType "textpage" -> 1045 + //Create and Save ContentType "textpage" -> NodeDto.NodeIdSeed ContentType contentType = MockedContentTypes.CreateTextpageContentType(); ServiceContext.ContentTypeService.Save(contentType); } diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 1f693509a3..2bf41db197 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -461,7 +461,7 @@ namespace Umbraco.Tests.Services contentService.Save(content); var parent = contentService.GetById(NodeDto.NodeIdSeed + 1); - bool parentPublished = contentService.Publish(parent, 0);//Publish root Home node to enable publishing of '1048' + bool parentPublished = contentService.Publish(parent, 0);//Publish root Home node to enable publishing of 'NodeDto.NodeIdSeed + 3' // Act bool published = contentService.Publish(content, 0); @@ -482,7 +482,7 @@ namespace Umbraco.Tests.Services contentService.Save(content, 0); var parent = contentService.GetById(NodeDto.NodeIdSeed + 1); - bool parentPublished = contentService.Publish(parent, 0);//Publish root Home node to enable publishing of '1048' + bool parentPublished = contentService.Publish(parent, 0);//Publish root Home node to enable publishing of 'NodeDto.NodeIdSeed + 3' // Act bool published = contentService.Publish(content, 0); diff --git a/src/Umbraco.Tests/Services/DataTypeServiceTests.cs b/src/Umbraco.Tests/Services/DataTypeServiceTests.cs index 31b0489949..a05ea8aa83 100644 --- a/src/Umbraco.Tests/Services/DataTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/DataTypeServiceTests.cs @@ -2,6 +2,7 @@ using System.Linq; using NUnit.Framework; using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; namespace Umbraco.Tests.Services { @@ -58,7 +59,7 @@ namespace Umbraco.Tests.Services Assert.That(deletedDefinition, Is.Null); //Further assertions against the ContentType that contains PropertyTypes based on the TextField - var contentType = ServiceContext.ContentTypeService.GetContentType(1045); + var contentType = ServiceContext.ContentTypeService.GetContentType(NodeDto.NodeIdSeed); Assert.That(contentType.Alias, Is.EqualTo("umbTextpage")); Assert.That(contentType.PropertyTypes.Count(), Is.EqualTo(1)); } From d1e071b9cb96daa51df7775954d8d0063c9732c9 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 16 Dec 2013 17:47:40 +1100 Subject: [PATCH 0081/1046] adds additional unit tests for the data type service to include pre-value handling. --- .../Services/DataTypeServiceTests.cs | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/Umbraco.Tests/Services/DataTypeServiceTests.cs b/src/Umbraco.Tests/Services/DataTypeServiceTests.cs index a05ea8aa83..9ff9012062 100644 --- a/src/Umbraco.Tests/Services/DataTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/DataTypeServiceTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using Umbraco.Core.Models; @@ -63,5 +64,58 @@ namespace Umbraco.Tests.Services Assert.That(contentType.Alias, Is.EqualTo("umbTextpage")); Assert.That(contentType.PropertyTypes.Count(), Is.EqualTo(1)); } + + [Test] + public void DataTypeService_Can_Persist_Dictionary_Based_Pre_Values() + { + // Arrange + var dataTypeService = ServiceContext.DataTypeService; + var textfieldId = new Guid("ec15c1e5-9d90-422a-aa52-4f7622c63bea"); + + // Act + IDataTypeDefinition dataTypeDefinition = new DataTypeDefinition(-1, textfieldId) { Name = "Testing prevals", DatabaseType = DataTypeDatabaseType.Ntext }; + dataTypeService.SaveDataTypeAndPreValues(dataTypeDefinition, new Dictionary + { + {"preVal1", new PreValue("Hello")}, + {"preVal2", new PreValue("World")} + }); + //re-get + dataTypeDefinition = dataTypeService.GetDataTypeDefinitionById(dataTypeDefinition.Id); + var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(dataTypeDefinition.Id); + + // Assert + Assert.That(dataTypeDefinition, Is.Not.Null); + Assert.That(dataTypeDefinition.HasIdentity, Is.True); + Assert.AreEqual(true, preVals.IsDictionaryBased); + Assert.AreEqual(2, preVals.PreValuesAsDictionary.Keys.Count); + Assert.AreEqual("preVal1", preVals.PreValuesAsDictionary.Keys.First()); + Assert.AreEqual("preVal2", preVals.PreValuesAsDictionary.Keys.Last()); + Assert.AreEqual("Hello", preVals.PreValuesAsDictionary["preVal1"].Value); + Assert.AreEqual("World", preVals.PreValuesAsDictionary["preVal2"].Value); + } + + [Test] + public void DataTypeService_Can_Persist_Array_Based_Pre_Values() + { + // Arrange + var dataTypeService = ServiceContext.DataTypeService; + var textfieldId = new Guid("ec15c1e5-9d90-422a-aa52-4f7622c63bea"); + + // Act + IDataTypeDefinition dataTypeDefinition = new DataTypeDefinition(-1, textfieldId) { Name = "Testing prevals", DatabaseType = DataTypeDatabaseType.Ntext }; + dataTypeService.Save(dataTypeDefinition); + dataTypeService.SavePreValues(dataTypeDefinition.Id, new[] {"preVal1", "preVal2"}); + + //re-get + var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(dataTypeDefinition.Id); + + // Assert + Assert.That(dataTypeDefinition, Is.Not.Null); + Assert.That(dataTypeDefinition.HasIdentity, Is.True); + Assert.AreEqual(false, preVals.IsDictionaryBased); + Assert.AreEqual(2, preVals.PreValuesAsArray.Count()); + Assert.AreEqual("preVal1", preVals.PreValuesAsArray.First().Value); + Assert.AreEqual("preVal2", preVals.PreValuesAsArray.Last().Value); + } } } \ No newline at end of file From 7e6fdcb05e0113bb8b006b8743eb16a3e07fa64d Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 16 Dec 2013 18:10:44 +1100 Subject: [PATCH 0082/1046] back ported template repo and file service updates --- src/Umbraco.Core/Models/TemplateNode.cs | 34 ++ .../Interfaces/ITemplateRepository.cs | 15 + .../Repositories/TemplateRepository.cs | 151 ++++++++ src/Umbraco.Core/Services/FileService.cs | 215 ++++++----- src/Umbraco.Core/Services/IFileService.cs | 15 + src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../Repositories/TemplateRepositoryTest.cs | 336 ++++++++++++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + 8 files changed, 674 insertions(+), 94 deletions(-) create mode 100644 src/Umbraco.Core/Models/TemplateNode.cs create mode 100644 src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs diff --git a/src/Umbraco.Core/Models/TemplateNode.cs b/src/Umbraco.Core/Models/TemplateNode.cs new file mode 100644 index 0000000000..1fc51f8498 --- /dev/null +++ b/src/Umbraco.Core/Models/TemplateNode.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a template in a template tree + /// + public class TemplateNode + { + public TemplateNode(ITemplate template) + { + Template = template; + Children = new List(); + } + + /// + /// The current template + /// + public ITemplate Template { get; set; } + + /// + /// The children of the current template + /// + public IEnumerable Children { get; set; } + + /// + /// The parent template to the current template + /// + /// + /// Will be null if there is no parent + /// + public TemplateNode Parent { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs index 90d6f68adc..318b840819 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs @@ -7,5 +7,20 @@ namespace Umbraco.Core.Persistence.Repositories { ITemplate Get(string alias); IEnumerable GetAll(params string[] aliases); + + /// + /// Returns a template as a template node which can be traversed (parent, children) + /// + /// + /// + TemplateNode GetTemplateNode(string alias); + + /// + /// Given a template node in a tree, this will find the template node with the given alias if it is found in the hierarchy, otherwise null + /// + /// + /// + /// + TemplateNode FindTemplateInTree(TemplateNode anyNode, string alias); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs index 5cc827680b..e3b69a786e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs @@ -273,6 +273,32 @@ namespace Umbraco.Core.Persistence.Repositories protected override void PersistDeletedItem(ITemplate entity) { + + //TODO: This isn't the most ideal way to delete a template tree, because below it will actually end up + // recursing back to this method for each descendant and re-looking up the template list causing an extrac + // SQL call - not ideal but there shouldn't ever be a heaping list of descendant templates. + //The easiest way to overcome this is to expose the underlying cache upwards so that the repository has access + // to it, then in the PersistDeletedTemplate we wouldn't recurse the underlying function, we'd just call + // PersistDeletedItem with a Template object and clear it's cache. + + var sql = new Sql(); + sql.Select("*").From().Where(dto => dto.Master != null || dto.NodeId == entity.Id); + var dtos = Database.Fetch(sql); + var self = dtos.Single(x => x.NodeId == entity.Id); + var allChildren = dtos.Except(new[] { self }); + var hierarchy = GenerateTemplateHierarchy(self, allChildren); + //remove ourselves + hierarchy.Remove(self); + //change the order so it goes bottom up! + hierarchy.Reverse(); + + //delete the hierarchy + foreach (var descendant in hierarchy) + { + PersistDeletedTemplate(descendant); + } + + //now we can delete this one base.PersistDeletedItem(entity); //Check for file under the Masterpages filesystem @@ -298,6 +324,34 @@ namespace Umbraco.Core.Persistence.Repositories #endregion + private void PersistDeletedTemplate(TemplateDto dto) + { + //we need to get the real template for this item unfortunately to remove it + var template = Get(dto.NodeId); + if (template != null) + { + //NOTE: We must cast here so that it goes to the outter method to + // ensure the cache is updated. + PersistDeletedItem((IEntity)template); + } + } + + /// + /// Returns a list of templates in order of descendants from the parent + /// + /// + /// + /// + private static List GenerateTemplateHierarchy(TemplateDto template, IEnumerable allChildTemplates) + { + var hierarchy = new List { template }; + foreach (var t in allChildTemplates.Where(x => x.Master == template.NodeId)) + { + hierarchy.AddRange(GenerateTemplateHierarchy(t, allChildTemplates)); + } + return hierarchy; + } + private void PopulateViewTemplate(ITemplate template, string fileName) { string content = string.Empty; @@ -379,6 +433,103 @@ namespace Umbraco.Core.Persistence.Repositories } + /// + /// Returns a template as a template node which can be traversed (parent, children) + /// + /// + /// + public TemplateNode GetTemplateNode(string alias) + { + //in order to do this we need to get all of the templates and then organize, unfortunately + // our db structure does not use the path correctly for templates so we cannot just look + // up a template tree easily. + + //first get all template objects + var allTemplates = GetAll().ToArray(); + + var selfTemplate = allTemplates.SingleOrDefault(x => x.Alias == alias); + if (selfTemplate == null) + { + return null; + } + + //then we need to get all template Dto's because those contain the master property + var sql = new Sql(); + sql.Select("*").From(); + var allDtos = Database.Fetch(sql).ToArray(); + var selfDto = allDtos.Single(x => x.NodeId == selfTemplate.Id); + + //need to get the top-most node of the current tree + var top = selfDto; + while (top.Master.HasValue) + { + top = allDtos.Single(x => x.NodeId == top.Master.Value); + } + + var topNode = new TemplateNode(allTemplates.Single(x => x.Id == top.NodeId)); + var childIds = allDtos.Where(x => x.Master == top.NodeId).Select(x => x.NodeId); + //This now creates the hierarchy recursively + topNode.Children = CreateChildren(topNode, childIds, allTemplates, allDtos); + + //now we'll return the TemplateNode requested + return FindTemplateInTree(topNode, alias); + } + + private static TemplateNode WalkTree(TemplateNode current, string alias) + { + //now walk the tree to find the node + if (current.Template.Alias == alias) + { + return current; + } + foreach (var c in current.Children) + { + var found = WalkTree(c, alias); + if (found != null) return found; + } + return null; + } + + /// + /// Given a template node in a tree, this will find the template node with the given alias if it is found in the hierarchy, otherwise null + /// + /// + /// + /// + public TemplateNode FindTemplateInTree(TemplateNode anyNode, string alias) + { + //first get the root + var top = anyNode; + while (top.Parent != null) + { + top = top.Parent; + } + return WalkTree(top, alias); + } + + private static IEnumerable CreateChildren(TemplateNode parent, IEnumerable childIds, ITemplate[] allTemplates, TemplateDto[] allDtos) + { + var children = new List(); + foreach (var i in childIds) + { + var template = allTemplates.Single(x => x.Id == i); + var child = new TemplateNode(template) + { + Parent = parent + }; + + //add to our list + children.Add(child); + + //get this node's children + var kids = allDtos.Where(x => x.Master == i).Select(x => x.NodeId).ToArray(); + + //recurse + child.Children = CreateChildren(child, kids, allTemplates, allDtos); + } + return children; + } + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/FileService.cs b/src/Umbraco.Core/Services/FileService.cs index c912793e05..5d4f2b1da8 100644 --- a/src/Umbraco.Core/Services/FileService.cs +++ b/src/Umbraco.Core/Services/FileService.cs @@ -13,24 +13,24 @@ namespace Umbraco.Core.Services /// public class FileService : IFileService { - private readonly RepositoryFactory _repositoryFactory; + private readonly RepositoryFactory _repositoryFactory; private readonly IUnitOfWorkProvider _fileUowProvider; private readonly IDatabaseUnitOfWorkProvider _dataUowProvider; public FileService() : this(new RepositoryFactory()) - {} + { } public FileService(RepositoryFactory repositoryFactory) - : this(new FileUnitOfWorkProvider(), new PetaPocoUnitOfWorkProvider(), repositoryFactory) + : this(new FileUnitOfWorkProvider(), new PetaPocoUnitOfWorkProvider(), repositoryFactory) { } - public FileService(IUnitOfWorkProvider fileProvider, IDatabaseUnitOfWorkProvider dataProvider, RepositoryFactory repositoryFactory) + public FileService(IUnitOfWorkProvider fileProvider, IDatabaseUnitOfWorkProvider dataProvider, RepositoryFactory repositoryFactory) { - _repositoryFactory = repositoryFactory; - _fileUowProvider = fileProvider; - _dataUowProvider = dataProvider; + _repositoryFactory = repositoryFactory; + _fileUowProvider = fileProvider; + _dataUowProvider = dataProvider; } /// @@ -65,19 +65,19 @@ namespace Umbraco.Core.Services /// public void SaveStylesheet(Stylesheet stylesheet, int userId = 0) { - if (SavingStylesheet.IsRaisedEventCancelled(new SaveEventArgs(stylesheet), this)) - return; - - var uow = _fileUowProvider.GetUnitOfWork(); + if (SavingStylesheet.IsRaisedEventCancelled(new SaveEventArgs(stylesheet), this)) + return; + + var uow = _fileUowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateStylesheetRepository(uow, _dataUowProvider.GetUnitOfWork())) - { - repository.AddOrUpdate(stylesheet); - uow.Commit(); + { + repository.AddOrUpdate(stylesheet); + uow.Commit(); - SavedStylesheet.RaiseEvent(new SaveEventArgs(stylesheet, false), this); - } + SavedStylesheet.RaiseEvent(new SaveEventArgs(stylesheet, false), this); + } - Audit.Add(AuditTypes.Save, string.Format("Save Stylesheet performed by user"), userId, -1); + Audit.Add(AuditTypes.Save, string.Format("Save Stylesheet performed by user"), userId, -1); } /// @@ -92,15 +92,15 @@ namespace Umbraco.Core.Services { var stylesheet = repository.Get(name); - if (DeletingStylesheet.IsRaisedEventCancelled(new DeleteEventArgs(stylesheet), this)) - return; + if (DeletingStylesheet.IsRaisedEventCancelled(new DeleteEventArgs(stylesheet), this)) + return; - repository.Delete(stylesheet); - uow.Commit(); + repository.Delete(stylesheet); + uow.Commit(); - DeletedStylesheet.RaiseEvent(new DeleteEventArgs(stylesheet, false), this); + DeletedStylesheet.RaiseEvent(new DeleteEventArgs(stylesheet, false), this); - Audit.Add(AuditTypes.Delete, string.Format("Delete Stylesheet performed by user"), userId, -1); + Audit.Add(AuditTypes.Delete, string.Format("Delete Stylesheet performed by user"), userId, -1); } } @@ -146,19 +146,19 @@ namespace Umbraco.Core.Services /// public void SaveScript(Script script, int userId = 0) { - if (SavingScript.IsRaisedEventCancelled(new SaveEventArgs - - -
    -
    -
    -
    \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/TreeControl.ascx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/TreeControl.ascx.cs index c9d4cc8f74..8573ae471d 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/TreeControl.ascx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/TreeControl.ascx.cs @@ -325,29 +325,29 @@ namespace umbraco.controls.Tree public string GetLegacyIActionJavascript() { StringBuilder js = new StringBuilder(); - foreach (IAction a in global::umbraco.BusinessLogic.Actions.Action.GetAll()) - { - // NH: Added a try/catch block to this as an error in a 3rd party action can crash the whole menu initialization - try - { - if (!string.IsNullOrEmpty(a.Alias) && (!string.IsNullOrEmpty(a.JsFunctionName) || !string.IsNullOrEmpty(a.JsSource))) - { - // if the action is using invalid javascript we need to do something about this - if (!umbraco.BusinessLogic.Actions.Action.ValidateActionJs(a)) - { - js.AppendLine("function IActionProxy_" + umbraco.cms.helpers.Casing.SafeAlias(a.Alias) + "() {"); - js.AppendLine(umbraco.BusinessLogic.Actions.Action.ConvertLegacyJs(a.JsFunctionName)); // .Replace("openModal", "UmbClientMgr.openModalWindow")); - js.AppendLine("}"); - } - } - } - catch (Exception ee) - { - LogHelper.Error("Error initializing tree action", ee); - } - } + foreach (IAction a in global::umbraco.BusinessLogic.Actions.Action.GetAll()) + { + // NH: Added a try/catch block to this as an error in a 3rd party action can crash the whole menu initialization + try + { + if (!string.IsNullOrEmpty(a.Alias) && (!string.IsNullOrEmpty(a.JsFunctionName) || !string.IsNullOrEmpty(a.JsSource))) + { + // if the action is using invalid javascript we need to do something about this + if (!umbraco.BusinessLogic.Actions.Action.ValidateActionJs(a)) + { + js.AppendLine("function IActionProxy_" + umbraco.cms.helpers.Casing.SafeAlias(a.Alias) + "() {"); + js.AppendLine(umbraco.BusinessLogic.Actions.Action.ConvertLegacyJs(a.JsFunctionName)); // .Replace("openModal", "UmbClientMgr.openModalWindow")); + js.AppendLine("}"); + } + } + } + catch (Exception ee) + { + LogHelper.Error("Error initializing tree action", ee); + } + } - if (js.Length != 0) + if (js.Length != 0) { js.Insert(0, "// This javascript is autogenerated by Umbraco to ensure legacy compatiblity with old context menu items\n\n"); } @@ -450,5 +450,149 @@ namespace umbraco.controls.Tree //if everything is null then return the default app return DEFAULT_APP; } + + /// + /// CssInclude2 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ClientDependency.Core.Controls.CssInclude CssInclude2; + + /// + /// CssInclude3 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ClientDependency.Core.Controls.CssInclude CssInclude3; + + /// + /// CssInclude1 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ClientDependency.Core.Controls.CssInclude CssInclude1; + + /// + /// JsInclude1 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ClientDependency.Core.Controls.JsInclude JsInclude1; + + /// + /// JsInclude2 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ClientDependency.Core.Controls.JsInclude JsInclude2; + + /// + /// JsInclude3 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ClientDependency.Core.Controls.JsInclude JsInclude3; + + /// + /// JsInclude4 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ClientDependency.Core.Controls.JsInclude JsInclude4; + + /// + /// JsInclude5 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ClientDependency.Core.Controls.JsInclude JsInclude5; + + /// + /// JsInclude6 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ClientDependency.Core.Controls.JsInclude JsInclude6; + + /// + /// JsInclude8 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ClientDependency.Core.Controls.JsInclude JsInclude8; + + /// + /// JsInclude11 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ClientDependency.Core.Controls.JsInclude JsInclude11; + + /// + /// JsInclude7 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ClientDependency.Core.Controls.JsInclude JsInclude7; + + /// + /// JsInclude12 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ClientDependency.Core.Controls.JsInclude JsInclude12; + + /// + /// JsInclude9 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ClientDependency.Core.Controls.JsInclude JsInclude9; + + /// + /// JsInclude10 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ClientDependency.Core.Controls.JsInclude JsInclude10; + + /// + /// TreeContainer control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.HtmlControls.HtmlGenericControl TreeContainer; } } \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/TreeControl.ascx.designer.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/TreeControl.ascx.designer.cs deleted file mode 100644 index 829bf5312d..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/TreeControl.ascx.designer.cs +++ /dev/null @@ -1,159 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace umbraco.controls.Tree { - - - public partial class TreeControl { - - /// - /// CssInclude2 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.CssInclude CssInclude2; - - /// - /// CssInclude3 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.CssInclude CssInclude3; - - /// - /// CssInclude1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.CssInclude CssInclude1; - - /// - /// JsInclude1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude1; - - /// - /// JsInclude2 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude2; - - /// - /// JsInclude3 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude3; - - /// - /// JsInclude4 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude4; - - /// - /// JsInclude5 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude5; - - /// - /// JsInclude6 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude6; - - /// - /// JsInclude8 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude8; - - /// - /// JsInclude11 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude11; - - /// - /// JsInclude7 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude7; - - /// - /// JsInclude12 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude12; - - /// - /// JsInclude9 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude9; - - /// - /// JsInclude10 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude10; - - /// - /// TreeContainer control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.HtmlControls.HtmlGenericControl TreeContainer; - } -} diff --git a/src/umbraco.controls/TreePicker/SimpleContentPicker.cs b/src/umbraco.controls/TreePicker/SimpleContentPicker.cs index 3d70cbbef4..21adda53a6 100644 --- a/src/umbraco.controls/TreePicker/SimpleContentPicker.cs +++ b/src/umbraco.controls/TreePicker/SimpleContentPicker.cs @@ -13,8 +13,11 @@ namespace umbraco.uicontrols.TreePicker { get { - if (HttpContext.Current.Request.QueryString["id"] != null) + if (HttpContext.Current != null && HttpContext.Current.Request.QueryString["id"] != null) + { return TreeUrlGenerator.GetPickerUrl(Constants.Applications.Content, "content") + "&selected=" + HttpContext.Current.Request.QueryString["id"]; + } + return TreeUrlGenerator.GetPickerUrl(Constants.Applications.Content, "content"); } diff --git a/src/umbraco.editorControls/pagepicker/pagePicker.cs b/src/umbraco.editorControls/pagepicker/pagePicker.cs index 791bb20352..932fe4fb40 100644 --- a/src/umbraco.editorControls/pagepicker/pagePicker.cs +++ b/src/umbraco.editorControls/pagepicker/pagePicker.cs @@ -10,6 +10,7 @@ using umbraco.interfaces; using System.Web.UI.WebControls; using System.Web.UI.HtmlControls; using umbraco.editorControls.pagepicker; +using umbraco.uicontrols; using umbraco.uicontrols.TreePicker; namespace umbraco.editorControls { @@ -27,10 +28,12 @@ namespace umbraco.editorControls { get { - if(HttpContext.Current.Request.QueryString["id"] != null) - return TreeService.GetPickerUrl(Umbraco.Core.Constants.Applications.Content, "content") + "&selected=" + HttpContext.Current.Request.QueryString["id"]; + if (HttpContext.Current != null && HttpContext.Current.Request.QueryString["id"] != null) + { + return TreeUrlGenerator.GetPickerUrl(Umbraco.Core.Constants.Applications.Content, "content") + "&selected=" + HttpContext.Current.Request.QueryString["id"]; + } - return TreeService.GetPickerUrl(Umbraco.Core.Constants.Applications.Content, "content"); + return TreeUrlGenerator.GetPickerUrl(Umbraco.Core.Constants.Applications.Content, "content"); } } From 904cbbb906a754eace5ed7e78420177cff9de0eb Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 19 Dec 2013 11:41:26 +1100 Subject: [PATCH 0106/1046] missing proj file update --- src/Umbraco.Web/Umbraco.Web.csproj | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 1cec3bb6ec..6f6581298e 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -403,6 +403,9 @@ ASPXCodeBehind + + ASPXCodeBehind + ASPXCodeBehind @@ -873,13 +876,6 @@ Code - - TreeControl.ascx - ASPXCodeBehind - - - TreeControl.ascx - @@ -1833,7 +1829,6 @@ ASPXCodeBehind - @@ -2060,4 +2055,4 @@ - + \ No newline at end of file From 9115aa1a6d7bc244e4c5aa3402c1766bc42006d7 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 19 Dec 2013 11:46:31 +1100 Subject: [PATCH 0107/1046] merge issue with proj file --- src/Umbraco.Web/Umbraco.Web.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 6f6581298e..c4a714687f 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1866,7 +1866,6 @@ - ASPXCodeBehind From c912e8013428d613b12815db75f26d01a3c5daf0 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 19 Dec 2013 12:27:21 +1100 Subject: [PATCH 0108/1046] modified media service tests to not have to create data in teh base tests --- src/Umbraco.Core/Services/ContentService.cs | 2 +- src/Umbraco.Core/Services/MediaService.cs | 12 +++-- src/Umbraco.Tests/Services/BaseServiceTest.cs | 25 +-------- .../Services/MediaServiceTests.cs | 53 +++++++++++++++---- 4 files changed, 54 insertions(+), 38 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 21c6fcef22..443b2839d9 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -1038,7 +1038,7 @@ namespace Umbraco.Core.Services } //Ensure that Path and Level is updated on children - var children = GetChildren(content.Id); + var children = GetChildren(content.Id).ToArray(); if (children.Any()) { foreach (var child in children) diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 7bc7524294..ea0d8464a2 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -480,6 +480,8 @@ namespace Umbraco.Core.Services /// Id of the User moving the Media public void Move(IMedia media, int parentId, int userId = 0) { + if (media == null) throw new ArgumentNullException("media"); + using (new WriteLock(Locker)) { //This ensures that the correct method is called if this method is used to Move to recycle bin. @@ -500,7 +502,7 @@ namespace Umbraco.Core.Services Save(media, userId); //Ensure that relevant properties are updated on children - var children = GetChildren(media.Id); + var children = GetChildren(media.Id).ToArray(); if (children.Any()) { var parentPath = media.Path; @@ -516,14 +518,16 @@ namespace Umbraco.Core.Services } } - /// + /// /// Deletes an object by moving it to the Recycle Bin /// /// The to delete /// Id of the User deleting the Media public void MoveToRecycleBin(IMedia media, int userId = 0) { - if (Trashing.IsRaisedEventCancelled(new MoveEventArgs(media, -21), this)) + if (media == null) throw new ArgumentNullException("media"); + + if (Trashing.IsRaisedEventCancelled(new MoveEventArgs(media, -21), this)) return; //Find Descendants, which will be moved to the recycle bin along with the parent/grandparent. @@ -951,7 +955,7 @@ namespace Umbraco.Core.Services list.Add(child); - var grandkids = GetChildren(child.Id); + var grandkids = GetChildren(child.Id).ToArray(); if (grandkids.Any()) { list.AddRange(UpdatePropertiesOnChildren(grandkids, child.Path, child.Level, child.Trashed)); diff --git a/src/Umbraco.Tests/Services/BaseServiceTest.cs b/src/Umbraco.Tests/Services/BaseServiceTest.cs index 23487eb582..b711f7c3ac 100644 --- a/src/Umbraco.Tests/Services/BaseServiceTest.cs +++ b/src/Umbraco.Tests/Services/BaseServiceTest.cs @@ -51,30 +51,7 @@ namespace Umbraco.Tests.Services Content trashed = MockedContent.CreateSimpleContent(contentType, "Text Page Deleted", -20); trashed.Trashed = true; ServiceContext.ContentService.Save(trashed, 0); - - //Create and Save folder-Media -> 1050 - var folderMediaType = ServiceContext.ContentTypeService.GetMediaType(1031); - var folder = MockedMedia.CreateMediaFolder(folderMediaType, -1); - ServiceContext.MediaService.Save(folder); - - //Create and Save folder-Media -> 1051 - var folder2 = MockedMedia.CreateMediaFolder(folderMediaType, -1); - ServiceContext.MediaService.Save(folder2); - - //Create and Save image-Media -> 1052 - var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); - var image = (Media)MockedMedia.CreateMediaImage(imageMediaType, 1050); - ServiceContext.MediaService.Save(image); - - //Create and Save folder-Media that is trashed -> 1053 - var folderTrashed = (Media)MockedMedia.CreateMediaFolder(folderMediaType, -21); - folderTrashed.Trashed = true; - ServiceContext.MediaService.Save(folderTrashed); - - //Create and Save image-Media child of folderTrashed -> 1054 - var imageTrashed = (Media)MockedMedia.CreateMediaImage(imageMediaType, folderTrashed.Id); - imageTrashed.Trashed = true; - ServiceContext.MediaService.Save(imageTrashed); + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Services/MediaServiceTests.cs b/src/Umbraco.Tests/Services/MediaServiceTests.cs index 658bff9de9..fa9e530f3a 100644 --- a/src/Umbraco.Tests/Services/MediaServiceTests.cs +++ b/src/Umbraco.Tests/Services/MediaServiceTests.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Linq; using System.Text; using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Tests.TestHelpers.Entities; namespace Umbraco.Tests.Services { @@ -25,14 +27,15 @@ namespace Umbraco.Tests.Services public void Can_Move_Media() { // Arrange + var mediaItems = CreateTrashedTestMedia(); var mediaService = ServiceContext.MediaService; - var media = mediaService.GetById(1052); + var media = mediaService.GetById(mediaItems.Item3.Id); // Act - mediaService.Move(media, 1051); + mediaService.Move(media, mediaItems.Item2.Id); // Assert - Assert.That(media.ParentId, Is.EqualTo(1051)); + Assert.That(media.ParentId, Is.EqualTo(mediaItems.Item2.Id)); Assert.That(media.Trashed, Is.False); } @@ -40,8 +43,9 @@ namespace Umbraco.Tests.Services public void Can_Move_Media_To_RecycleBin() { // Arrange + var mediaItems = CreateTrashedTestMedia(); var mediaService = ServiceContext.MediaService; - var media = mediaService.GetById(1050); + var media = mediaService.GetById(mediaItems.Item1.Id); // Act mediaService.MoveToRecycleBin(media); @@ -55,18 +59,49 @@ namespace Umbraco.Tests.Services public void Can_Move_Media_From_RecycleBin() { // Arrange + var mediaItems = CreateTrashedTestMedia(); var mediaService = ServiceContext.MediaService; - var media = mediaService.GetById(1053); + var media = mediaService.GetById(mediaItems.Item4.Id); // Act - moving out of recycle bin - mediaService.Move(media, 1050); - var mediaChild = mediaService.GetById(1054); + mediaService.Move(media, mediaItems.Item1.Id); + var mediaChild = mediaService.GetById(mediaItems.Item5.Id); // Assert - Assert.That(media.ParentId, Is.EqualTo(1050)); + Assert.That(media.ParentId, Is.EqualTo(mediaItems.Item1.Id)); Assert.That(media.Trashed, Is.False); - Assert.That(mediaChild.ParentId, Is.EqualTo(1053)); + Assert.That(mediaChild.ParentId, Is.EqualTo(mediaItems.Item4.Id)); Assert.That(mediaChild.Trashed, Is.False); } + + private Tuple CreateTrashedTestMedia() + { + //Create and Save folder-Media -> 1050 + var folderMediaType = ServiceContext.ContentTypeService.GetMediaType(1031); + var folder = MockedMedia.CreateMediaFolder(folderMediaType, -1); + ServiceContext.MediaService.Save(folder); + + //Create and Save folder-Media -> 1051 + var folder2 = MockedMedia.CreateMediaFolder(folderMediaType, -1); + ServiceContext.MediaService.Save(folder2); + + //Create and Save image-Media -> 1052 + var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); + var image = (Media)MockedMedia.CreateMediaImage(imageMediaType, 1050); + ServiceContext.MediaService.Save(image); + + //Create and Save folder-Media that is trashed -> 1053 + var folderTrashed = (Media)MockedMedia.CreateMediaFolder(folderMediaType, -21); + folderTrashed.Trashed = true; + ServiceContext.MediaService.Save(folderTrashed); + + //Create and Save image-Media child of folderTrashed -> 1054 + var imageTrashed = (Media)MockedMedia.CreateMediaImage(imageMediaType, folderTrashed.Id); + imageTrashed.Trashed = true; + ServiceContext.MediaService.Save(imageTrashed); + + + return new Tuple(folder, folder2, image, folderTrashed, imageTrashed); + } } } From 4558549d5abd5336ed4494bd88eb3a653c8068fe Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 19 Dec 2013 12:40:41 +1100 Subject: [PATCH 0109/1046] Fixes appendformat issue --- src/Umbraco.Web/HtmlHelperRenderExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs index d8af4eecf0..7bf8a0e31e 100644 --- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs @@ -102,7 +102,7 @@ namespace Umbraco.Web if (contextualKeyBuilder != null) { var contextualKey = contextualKeyBuilder(model, viewData); - cacheKey.Append("c{0}-", contextualKey); + cacheKey.AppendFormat("c{0}-", contextualKey); } return ApplicationContext.Current.ApplicationCache.CachedPartialView(htmlHelper, partialViewName, model, cachedSeconds, cacheKey.ToString(), viewData); } From db78c78df2d40654b82cedf070d84af8a75203b6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 19 Dec 2013 12:45:46 +1100 Subject: [PATCH 0110/1046] fixes some merge issues with view engine wrapping PR. --- ...anagerTester.cs => WebBootManagerTests.cs} | 2 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 19 +++---- src/Umbraco.Tests/Views/web.config | 55 +++++++++++++++++++ src/Umbraco.Tests/packages.config | 1 + src/Umbraco.Web/WebBootManager.cs | 4 +- 5 files changed, 68 insertions(+), 13 deletions(-) rename src/Umbraco.Tests/BootManagers/{WebBootManagerTester.cs => WebBootManagerTests.cs} (98%) create mode 100644 src/Umbraco.Tests/Views/web.config diff --git a/src/Umbraco.Tests/BootManagers/WebBootManagerTester.cs b/src/Umbraco.Tests/BootManagers/WebBootManagerTests.cs similarity index 98% rename from src/Umbraco.Tests/BootManagers/WebBootManagerTester.cs rename to src/Umbraco.Tests/BootManagers/WebBootManagerTests.cs index b3c02a058b..9ddba6df5f 100644 --- a/src/Umbraco.Tests/BootManagers/WebBootManagerTester.cs +++ b/src/Umbraco.Tests/BootManagers/WebBootManagerTests.cs @@ -9,7 +9,7 @@ using Umbraco.Web.Mvc; namespace Umbraco.Tests.BootManagers { [TestFixture] - public class WebBootManagerTester + public class WebBootManagerTests { [Test] public void WrapViewEngines_HasEngines_WrapsAll() diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index ab9f15ea6d..22048799ac 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -68,9 +68,8 @@ True ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - False - ..\Umbraco.Web.UI\bin\Microsoft.Web.Mvc.FixedDisplayModes.dll + + ..\packages\Microsoft.AspNet.Mvc.FixedDisplayModes.1.0.1\lib\net40\Microsoft.Web.Mvc.FixedDisplayModes.dll ..\packages\Moq.4.1.1309.1617\lib\net40\Moq.dll @@ -123,17 +122,17 @@ True ..\packages\Microsoft.AspNet.Mvc.4.0.30506.0\lib\net40\System.Web.Mvc.dll - - True + ..\packages\Microsoft.AspNet.Razor.2.0.30506.0\lib\net40\System.Web.Razor.dll - - True + + ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.dll - - True + + ..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Deployment.dll + True True @@ -160,7 +159,7 @@ - + diff --git a/src/Umbraco.Tests/Views/web.config b/src/Umbraco.Tests/Views/web.config new file mode 100644 index 0000000000..aa5e0eb2e3 --- /dev/null +++ b/src/Umbraco.Tests/Views/web.config @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Tests/packages.config b/src/Umbraco.Tests/packages.config index e9ff4d1afc..82175eda41 100644 --- a/src/Umbraco.Tests/packages.config +++ b/src/Umbraco.Tests/packages.config @@ -4,6 +4,7 @@ + diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index 6db91b37bf..1fd2371eb7 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -146,9 +146,9 @@ namespace Umbraco.Web { if (viewEngines == null || viewEngines.Count == 0) return; - var origninaleEngines = viewEngines.Select(e => e).ToArray(); + var originalEngines = viewEngines.Select(e => e).ToArray(); viewEngines.Clear(); - foreach (var engine in origninaleEngines) + foreach (var engine in originalEngines) { var wrappedEngine = engine is ProfilingViewEngine ? engine : new ProfilingViewEngine(engine); viewEngines.Add(wrappedEngine); From 5c59317bf8f6e965f7bfcf8f114eb3467cf79fcd Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 19 Dec 2013 12:52:37 +1100 Subject: [PATCH 0111/1046] changes casing of col names --- src/umbraco.cms/businesslogic/web/Domain.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/umbraco.cms/businesslogic/web/Domain.cs b/src/umbraco.cms/businesslogic/web/Domain.cs index 68268b9bb8..f260ab639f 100644 --- a/src/umbraco.cms/businesslogic/web/Domain.cs +++ b/src/umbraco.cms/businesslogic/web/Domain.cs @@ -151,7 +151,7 @@ namespace umbraco.cms.businesslogic.web () => { var result = new List(); - using (var dr = SqlHelper.ExecuteReader("SELECT Id, DomainName FROM umbracoDomains ORDER BY Id")) + using (var dr = SqlHelper.ExecuteReader("SELECT id, domainName FROM umbracoDomains ORDER BY id")) { while (dr.Read()) { From 763ab26446bb6c73f862dea117ab984dba40c18a Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 19 Dec 2013 13:13:13 +1100 Subject: [PATCH 0112/1046] Updates packaging service to not export empty properties. --- src/Umbraco.Core/Services/PackagingService.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index 82f8a409db..951a9f95da 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -130,7 +130,7 @@ namespace Umbraco.Core.Services } /// - /// Part of the export of IContent and IMedia which is shared + /// Part of the export of IContent and IMedia and IMember which is shared /// /// Base Content or Media to export /// Name of the node @@ -153,8 +153,10 @@ namespace Umbraco.Core.Services new XAttribute("path", contentBase.Path), new XAttribute("isDoc", "")); - foreach (var property in contentBase.Properties.Where(p => p != null)) + foreach (var property in contentBase.Properties.Where(p => p != null && p.Value != null && p.Value.ToString().IsNullOrWhiteSpace() == false)) + { xml.Add(property.ToXml()); + } return xml; } From 67caf81a74bbb697fc766558e80e78298030521d Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 19 Dec 2013 13:14:38 +1100 Subject: [PATCH 0113/1046] adds a couple of null checks. --- src/umbraco.cms/businesslogic/Content.cs | 2 +- src/umbraco.cms/businesslogic/web/Document.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/umbraco.cms/businesslogic/Content.cs b/src/umbraco.cms/businesslogic/Content.cs index 080e6a2665..f2e52a1287 100644 --- a/src/umbraco.cms/businesslogic/Content.cs +++ b/src/umbraco.cms/businesslogic/Content.cs @@ -397,7 +397,7 @@ namespace umbraco.cms.businesslogic { var props = this.GenericProperties; foreach (property.Property p in props) - if (p != null && !string.IsNullOrEmpty(p.Value.ToString())) + if (p != null && p.Value != null && string.IsNullOrEmpty(p.Value.ToString()) == false) x.AppendChild(p.ToXml(xd)); // attributes diff --git a/src/umbraco.cms/businesslogic/web/Document.cs b/src/umbraco.cms/businesslogic/web/Document.cs index f07ea160a5..b95a8402f7 100644 --- a/src/umbraco.cms/businesslogic/web/Document.cs +++ b/src/umbraco.cms/businesslogic/web/Document.cs @@ -1291,7 +1291,7 @@ namespace umbraco.cms.businesslogic.web public override void XmlPopulate(XmlDocument xd, ref XmlNode x, bool Deep) { string urlName = this.Content.GetUrlSegment().ToLower(); - foreach (Property p in GenericProperties.Where(p => p != null && !string.IsNullOrEmpty(p.Value.ToString()))) + foreach (Property p in GenericProperties.Where(p => p != null && p.Value != null && string.IsNullOrEmpty(p.Value.ToString()) == false)) x.AppendChild(p.ToXml(xd)); // attributes From 8aceae988067d2cde828bd6535f9432ae92ad8ac Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 19 Dec 2013 13:22:33 +1100 Subject: [PATCH 0114/1046] re-formats some code, simplifies calls using InvariantEquals extension method --- src/Umbraco.Web/Mvc/RenderRouteHandler.cs | 558 +++++++++++----------- 1 file changed, 279 insertions(+), 279 deletions(-) diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index b80523a4a9..bfa2063f7a 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -16,90 +16,90 @@ using System.Collections.Generic; namespace Umbraco.Web.Mvc { - public class RenderRouteHandler : IRouteHandler - { - // Define reserved dictionary keys for controller, action and area specified in route additional values data - private static class ReservedAdditionalKeys - { - internal const string Controller = "c"; - internal const string Action = "a"; - internal const string Area = "ar"; - } + public class RenderRouteHandler : IRouteHandler + { + // Define reserved dictionary keys for controller, action and area specified in route additional values data + private static class ReservedAdditionalKeys + { + internal const string Controller = "c"; + internal const string Action = "a"; + internal const string Area = "ar"; + } - public RenderRouteHandler(IControllerFactory controllerFactory) - { - if (controllerFactory == null) throw new ArgumentNullException("controllerFactory"); - _controllerFactory = controllerFactory; - } + public RenderRouteHandler(IControllerFactory controllerFactory) + { + if (controllerFactory == null) throw new ArgumentNullException("controllerFactory"); + _controllerFactory = controllerFactory; + } - /// - /// Contructor generally used for unit testing - /// - /// - /// - internal RenderRouteHandler(IControllerFactory controllerFactory, UmbracoContext umbracoContext) - { - if (controllerFactory == null) throw new ArgumentNullException("controllerFactory"); - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); - _controllerFactory = controllerFactory; - _umbracoContext = umbracoContext; - } + /// + /// Contructor generally used for unit testing + /// + /// + /// + internal RenderRouteHandler(IControllerFactory controllerFactory, UmbracoContext umbracoContext) + { + if (controllerFactory == null) throw new ArgumentNullException("controllerFactory"); + if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); + _controllerFactory = controllerFactory; + _umbracoContext = umbracoContext; + } - private readonly IControllerFactory _controllerFactory; - private readonly UmbracoContext _umbracoContext; - - /// - /// Returns the current UmbracoContext - /// - public UmbracoContext UmbracoContext - { - get { return _umbracoContext ?? UmbracoContext.Current; } - } + private readonly IControllerFactory _controllerFactory; + private readonly UmbracoContext _umbracoContext; - #region IRouteHandler Members + /// + /// Returns the current UmbracoContext + /// + public UmbracoContext UmbracoContext + { + get { return _umbracoContext ?? UmbracoContext.Current; } + } - /// - /// Assigns the correct controller based on the Umbraco request and returns a standard MvcHandler to prcess the response, - /// this also stores the render model into the data tokens for the current RouteData. - /// - /// - /// - public IHttpHandler GetHttpHandler(RequestContext requestContext) - { - if (UmbracoContext == null) - { - throw new NullReferenceException("There is not current UmbracoContext, it must be initialized before the RenderRouteHandler executes"); - } - var docRequest = UmbracoContext.PublishedContentRequest; - if (docRequest == null) - { - throw new NullReferenceException("There is not current PublishedContentRequest, it must be initialized before the RenderRouteHandler executes"); - } - - SetupRouteDataForRequest( - new RenderModel(docRequest.PublishedContent, docRequest.Culture), - requestContext, - docRequest); + #region IRouteHandler Members - return GetHandlerForRoute(requestContext, docRequest); - - } + /// + /// Assigns the correct controller based on the Umbraco request and returns a standard MvcHandler to prcess the response, + /// this also stores the render model into the data tokens for the current RouteData. + /// + /// + /// + public IHttpHandler GetHttpHandler(RequestContext requestContext) + { + if (UmbracoContext == null) + { + throw new NullReferenceException("There is not current UmbracoContext, it must be initialized before the RenderRouteHandler executes"); + } + var docRequest = UmbracoContext.PublishedContentRequest; + if (docRequest == null) + { + throw new NullReferenceException("There is not current PublishedContentRequest, it must be initialized before the RenderRouteHandler executes"); + } - #endregion + SetupRouteDataForRequest( + new RenderModel(docRequest.PublishedContent, docRequest.Culture), + requestContext, + docRequest); - /// - /// Ensures that all of the correct DataTokens are added to the route values which are all required for rendering front-end umbraco views - /// - /// - /// - /// - internal void SetupRouteDataForRequest(RenderModel renderModel, RequestContext requestContext, PublishedContentRequest docRequest) - { - //put essential data into the data tokens, the 'umbraco' key is required to be there for the view engine - requestContext.RouteData.DataTokens.Add("umbraco", renderModel); //required for the RenderModelBinder and view engine - requestContext.RouteData.DataTokens.Add("umbraco-doc-request", docRequest); //required for RenderMvcController - requestContext.RouteData.DataTokens.Add("umbraco-context", UmbracoContext); //required for UmbracoTemplatePage - } + return GetHandlerForRoute(requestContext, docRequest); + + } + + #endregion + + /// + /// Ensures that all of the correct DataTokens are added to the route values which are all required for rendering front-end umbraco views + /// + /// + /// + /// + internal void SetupRouteDataForRequest(RenderModel renderModel, RequestContext requestContext, PublishedContentRequest docRequest) + { + //put essential data into the data tokens, the 'umbraco' key is required to be there for the view engine + requestContext.RouteData.DataTokens.Add("umbraco", renderModel); //required for the RenderModelBinder and view engine + requestContext.RouteData.DataTokens.Add("umbraco-doc-request", docRequest); //required for RenderMvcController + requestContext.RouteData.DataTokens.Add("umbraco-context", UmbracoContext); //required for UmbracoTemplatePage + } private void UpdateRouteDataForRequest(RenderModel renderModel, RequestContext requestContext) { @@ -110,41 +110,41 @@ namespace Umbraco.Web.Mvc // the rest should not change -- it's only the published content that has changed } - /// - /// Checks the request and query strings to see if it matches the definition of having a Surface controller - /// posted/get value, if so, then we return a PostedDataProxyInfo object with the correct information. - /// - /// - /// - private static PostedDataProxyInfo GetFormInfo(RequestContext requestContext) - { - if (requestContext == null) throw new ArgumentNullException("requestContext"); + /// + /// Checks the request and query strings to see if it matches the definition of having a Surface controller + /// posted/get value, if so, then we return a PostedDataProxyInfo object with the correct information. + /// + /// + /// + private static PostedDataProxyInfo GetFormInfo(RequestContext requestContext) + { + if (requestContext == null) throw new ArgumentNullException("requestContext"); - //if it is a POST/GET then a value must be in the request - if (requestContext.HttpContext.Request.QueryString["ufprt"].IsNullOrWhiteSpace() + //if it is a POST/GET then a value must be in the request + if (requestContext.HttpContext.Request.QueryString["ufprt"].IsNullOrWhiteSpace() && requestContext.HttpContext.Request.Form["ufprt"].IsNullOrWhiteSpace()) - { - return null; - } + { + return null; + } - string encodedVal; - - switch (requestContext.HttpContext.Request.RequestType) - { + string encodedVal; + + switch (requestContext.HttpContext.Request.RequestType) + { case "POST": //get the value from the request. //this field will contain an encrypted version of the surface route vals. encodedVal = requestContext.HttpContext.Request.Form["ufprt"]; - break; + break; case "GET": //this field will contain an encrypted version of the surface route vals. encodedVal = requestContext.HttpContext.Request.QueryString["ufprt"]; - break; + break; default: - return null; - } + return null; + } + - string decryptedString; try { @@ -157,99 +157,99 @@ namespace Umbraco.Web.Mvc } var parsedQueryString = HttpUtility.ParseQueryString(decryptedString); - var decodedParts = new Dictionary(); + var decodedParts = new Dictionary(); - foreach (var key in parsedQueryString.AllKeys) - { - decodedParts[key] = parsedQueryString[key]; - } + foreach (var key in parsedQueryString.AllKeys) + { + decodedParts[key] = parsedQueryString[key]; + } - //validate all required keys exist + //validate all required keys exist - //the controller - if (decodedParts.All(x => x.Key != ReservedAdditionalKeys.Controller)) - return null; - //the action - if (decodedParts.All(x => x.Key != ReservedAdditionalKeys.Action)) - return null; - //the area - if (decodedParts.All(x => x.Key != ReservedAdditionalKeys.Area)) - return null; - - foreach (var item in decodedParts.Where(x => new[] { + //the controller + if (decodedParts.All(x => x.Key != ReservedAdditionalKeys.Controller)) + return null; + //the action + if (decodedParts.All(x => x.Key != ReservedAdditionalKeys.Action)) + return null; + //the area + if (decodedParts.All(x => x.Key != ReservedAdditionalKeys.Area)) + return null; + + foreach (var item in decodedParts.Where(x => new[] { ReservedAdditionalKeys.Controller, ReservedAdditionalKeys.Action, ReservedAdditionalKeys.Area }.Contains(x.Key) == false)) - { - // Populate route with additional values which aren't reserved values so they eventually to action parameters - requestContext.RouteData.Values[item.Key] = item.Value; - } + { + // Populate route with additional values which aren't reserved values so they eventually to action parameters + requestContext.RouteData.Values[item.Key] = item.Value; + } - //return the proxy info without the surface id... could be a local controller. - return new PostedDataProxyInfo - { - ControllerName = HttpUtility.UrlDecode(decodedParts.Single(x => x.Key == ReservedAdditionalKeys.Controller).Value), + //return the proxy info without the surface id... could be a local controller. + return new PostedDataProxyInfo + { + ControllerName = HttpUtility.UrlDecode(decodedParts.Single(x => x.Key == ReservedAdditionalKeys.Controller).Value), ActionName = HttpUtility.UrlDecode(decodedParts.Single(x => x.Key == ReservedAdditionalKeys.Action).Value), Area = HttpUtility.UrlDecode(decodedParts.Single(x => x.Key == ReservedAdditionalKeys.Area).Value), - }; - } + }; + } - /// - /// Handles a posted form to an Umbraco Url and ensures the correct controller is routed to and that - /// the right DataTokens are set. - /// - /// - /// - private IHttpHandler HandlePostedValues(RequestContext requestContext, PostedDataProxyInfo postedInfo) - { - if (requestContext == null) throw new ArgumentNullException("requestContext"); - if (postedInfo == null) throw new ArgumentNullException("postedInfo"); + /// + /// Handles a posted form to an Umbraco Url and ensures the correct controller is routed to and that + /// the right DataTokens are set. + /// + /// + /// + private IHttpHandler HandlePostedValues(RequestContext requestContext, PostedDataProxyInfo postedInfo) + { + if (requestContext == null) throw new ArgumentNullException("requestContext"); + if (postedInfo == null) throw new ArgumentNullException("postedInfo"); - //set the standard route values/tokens - requestContext.RouteData.Values["controller"] = postedInfo.ControllerName; - requestContext.RouteData.Values["action"] = postedInfo.ActionName; + //set the standard route values/tokens + requestContext.RouteData.Values["controller"] = postedInfo.ControllerName; + requestContext.RouteData.Values["action"] = postedInfo.ActionName; IHttpHandler handler; - - //get the route from the defined routes - using (RouteTable.Routes.GetReadLock()) - { - Route surfaceRoute; - if (postedInfo.Area.IsNullOrWhiteSpace()) - { - //find the controller in the route table without an area - var surfaceRoutes = RouteTable.Routes.OfType() - .Where(x => x.Defaults != null && - x.Defaults.ContainsKey("controller") && - String.Equals(x.Defaults["controller"].ToString(), postedInfo.ControllerName, StringComparison.InvariantCultureIgnoreCase) && - !x.DataTokens.ContainsKey("area")).ToList(); - // If more than one route is found, find one with a matching action - if (surfaceRoutes.Count() > 1) - { - surfaceRoute = surfaceRoutes.SingleOrDefault(x => - String.Equals(x.Defaults["action"].ToString(), postedInfo.ActionName, StringComparison.InvariantCultureIgnoreCase)); - } - else - { - surfaceRoute = surfaceRoutes.SingleOrDefault(); - } - - } - else - { + //get the route from the defined routes + using (RouteTable.Routes.GetReadLock()) + { + Route surfaceRoute; + if (postedInfo.Area.IsNullOrWhiteSpace()) + { + //find the controller in the route table without an area + var surfaceRoutes = RouteTable.Routes.OfType() + .Where(x => x.Defaults != null && + x.Defaults.ContainsKey("controller") && + x.Defaults["controller"].ToString().InvariantEquals(postedInfo.ControllerName) && + x.DataTokens.ContainsKey("area") == false).ToList(); + + // If more than one route is found, find one with a matching action + if (surfaceRoutes.Count() > 1) + { + surfaceRoute = surfaceRoutes.SingleOrDefault(x => + x.Defaults["action"].ToString().InvariantEquals(postedInfo.ActionName)); + } + else + { + surfaceRoute = surfaceRoutes.SingleOrDefault(); + } + + } + else + { //find the controller in the route table with the specified area - surfaceRoute = RouteTable.Routes.OfType() - .SingleOrDefault(x => x.Defaults != null && - x.Defaults.ContainsKey("controller") && - x.Defaults["controller"].ToString().InvariantEquals(postedInfo.ControllerName) && - x.DataTokens.ContainsKey("area") && - x.DataTokens["area"].ToString().InvariantEquals(postedInfo.Area)); - } + surfaceRoute = RouteTable.Routes.OfType() + .SingleOrDefault(x => x.Defaults != null && + x.Defaults.ContainsKey("controller") && + x.Defaults["controller"].ToString().InvariantEquals(postedInfo.ControllerName) && + x.DataTokens.ContainsKey("area") && + x.DataTokens["area"].ToString().InvariantEquals(postedInfo.Area)); + } if (surfaceRoute == null) throw new InvalidOperationException("Could not find a Surface controller route in the RouteTable for controller name " + postedInfo.ControllerName); - + //set the area if one is there. if (surfaceRoute.DataTokens.ContainsKey("area")) { @@ -263,33 +263,33 @@ namespace Umbraco.Web.Mvc } handler = surfaceRoute.RouteHandler.GetHttpHandler(requestContext); - } + } - return handler; - } + return handler; + } - /// - /// Returns a RouteDefinition object based on the current renderModel - /// - /// - /// - /// - internal virtual RouteDefinition GetUmbracoRouteDefinition(RequestContext requestContext, PublishedContentRequest publishedContentRequest) - { - if (requestContext == null) throw new ArgumentNullException("requestContext"); - if (publishedContentRequest == null) throw new ArgumentNullException("publishedContentRequest"); + /// + /// Returns a RouteDefinition object based on the current renderModel + /// + /// + /// + /// + internal virtual RouteDefinition GetUmbracoRouteDefinition(RequestContext requestContext, PublishedContentRequest publishedContentRequest) + { + if (requestContext == null) throw new ArgumentNullException("requestContext"); + if (publishedContentRequest == null) throw new ArgumentNullException("publishedContentRequest"); - var defaultControllerType = DefaultRenderMvcControllerResolver.Current.GetDefaultControllerType(); + var defaultControllerType = DefaultRenderMvcControllerResolver.Current.GetDefaultControllerType(); var defaultControllerName = ControllerExtensions.GetControllerName(defaultControllerType); - //creates the default route definition which maps to the 'UmbracoController' controller - var def = new RouteDefinition - { - ControllerName = defaultControllerName, + //creates the default route definition which maps to the 'UmbracoController' controller + var def = new RouteDefinition + { + ControllerName = defaultControllerName, ControllerType = defaultControllerType, - PublishedContentRequest = publishedContentRequest, - ActionName = ((Route)requestContext.RouteData.Route).Defaults["action"].ToString(), - HasHijackedRoute = false - }; + PublishedContentRequest = publishedContentRequest, + ActionName = ((Route)requestContext.RouteData.Route).Defaults["action"].ToString(), + HasHijackedRoute = false + }; //check that a template is defined), if it doesn't and there is a hijacked route it will just route // to the index Action @@ -302,60 +302,60 @@ namespace Umbraco.Web.Mvc def.ActionName = templateName; } - //check if there's a custom controller assigned, base on the document type alias. + //check if there's a custom controller assigned, base on the document type alias. var controllerType = _controllerFactory.GetControllerTypeInternal(requestContext, publishedContentRequest.PublishedContent.DocumentTypeAlias); - //check if that controller exists - if (controllerType != null) - { + //check if that controller exists + if (controllerType != null) + { //ensure the controller is of type 'IRenderMvcController' and ControllerBase if (TypeHelper.IsTypeAssignableFrom(controllerType) && TypeHelper.IsTypeAssignableFrom(controllerType)) - { - //set the controller and name to the custom one - def.ControllerType = controllerType; - def.ControllerName = ControllerExtensions.GetControllerName(controllerType); - if (def.ControllerName != defaultControllerName) - { - def.HasHijackedRoute = true; - } - } - else - { - LogHelper.Warn( - "The current Document Type {0} matches a locally declared controller of type {1}. Custom Controllers for Umbraco routing must implement '{2}' and inherit from '{3}'.", - () => publishedContentRequest.PublishedContent.DocumentTypeAlias, - () => controllerType.FullName, + { + //set the controller and name to the custom one + def.ControllerType = controllerType; + def.ControllerName = ControllerExtensions.GetControllerName(controllerType); + if (def.ControllerName != defaultControllerName) + { + def.HasHijackedRoute = true; + } + } + else + { + LogHelper.Warn( + "The current Document Type {0} matches a locally declared controller of type {1}. Custom Controllers for Umbraco routing must implement '{2}' and inherit from '{3}'.", + () => publishedContentRequest.PublishedContent.DocumentTypeAlias, + () => controllerType.FullName, () => typeof(IRenderMvcController).FullName, () => typeof(ControllerBase).FullName); - + //we cannot route to this custom controller since it is not of the correct type so we'll continue with the defaults // that have already been set above. - } - } + } + } //store the route definition requestContext.RouteData.DataTokens["umbraco-route-def"] = def; - return def; - } + return def; + } - internal IHttpHandler GetHandlerOnMissingTemplate(PublishedContentRequest pcr) - { - if (pcr == null) throw new ArgumentNullException("pcr"); + internal IHttpHandler GetHandlerOnMissingTemplate(PublishedContentRequest pcr) + { + if (pcr == null) throw new ArgumentNullException("pcr"); - // missing template, so we're in a 404 here + // missing template, so we're in a 404 here // so the content, if any, is a custom 404 page of some sort - if (!pcr.HasPublishedContent) - // means the builder could not find a proper document to handle 404 - return new PublishedContentNotFoundHandler(); + if (!pcr.HasPublishedContent) + // means the builder could not find a proper document to handle 404 + return new PublishedContentNotFoundHandler(); - if (!pcr.HasTemplate) - // means the engine could find a proper document, but the document has no template - // at that point there isn't much we can do and there is no point returning - // to Mvc since Mvc can't do much - return new PublishedContentNotFoundHandler("In addition, no template exists to render the custom 404."); + if (!pcr.HasTemplate) + // means the engine could find a proper document, but the document has no template + // at that point there isn't much we can do and there is no point returning + // to Mvc since Mvc can't do much + return new PublishedContentNotFoundHandler("In addition, no template exists to render the custom 404."); // so we have a template, so we should have a rendering engine if (pcr.RenderingEngine == RenderingEngine.WebForms) // back to webforms ? @@ -363,81 +363,81 @@ namespace Umbraco.Web.Mvc else if (pcr.RenderingEngine != RenderingEngine.Mvc) // else ? return new PublishedContentNotFoundHandler("In addition, no rendering engine exists to render the custom 404."); - return null; - } + return null; + } - /// - /// this will determine the controller and set the values in the route data - /// - /// - /// - internal IHttpHandler GetHandlerForRoute(RequestContext requestContext, PublishedContentRequest publishedContentRequest) - { - if (requestContext == null) throw new ArgumentNullException("requestContext"); - if (publishedContentRequest == null) throw new ArgumentNullException("publishedContentRequest"); + /// + /// this will determine the controller and set the values in the route data + /// + /// + /// + internal IHttpHandler GetHandlerForRoute(RequestContext requestContext, PublishedContentRequest publishedContentRequest) + { + if (requestContext == null) throw new ArgumentNullException("requestContext"); + if (publishedContentRequest == null) throw new ArgumentNullException("publishedContentRequest"); - var routeDef = GetUmbracoRouteDefinition(requestContext, publishedContentRequest); - - //Need to check for a special case if there is form data being posted back to an Umbraco URL - var postedInfo = GetFormInfo(requestContext); - if (postedInfo != null) - { - return HandlePostedValues(requestContext, postedInfo); - } + var routeDef = GetUmbracoRouteDefinition(requestContext, publishedContentRequest); - //here we need to check if there is no hijacked route and no template assigned, if this is the case - //we want to return a blank page, but we'll leave that up to the NoTemplateHandler. - if (!publishedContentRequest.HasTemplate && !routeDef.HasHijackedRoute) - { - publishedContentRequest.UpdateOnMissingTemplate(); // will go 404 + //Need to check for a special case if there is form data being posted back to an Umbraco URL + var postedInfo = GetFormInfo(requestContext); + if (postedInfo != null) + { + return HandlePostedValues(requestContext, postedInfo); + } + + //here we need to check if there is no hijacked route and no template assigned, if this is the case + //we want to return a blank page, but we'll leave that up to the NoTemplateHandler. + if (!publishedContentRequest.HasTemplate && !routeDef.HasHijackedRoute) + { + publishedContentRequest.UpdateOnMissingTemplate(); // will go 404 // HandleHttpResponseStatus returns a value indicating that the request should // not be processed any further, eg because it has been redirect. then, exit. - if (UmbracoModule.HandleHttpResponseStatus(requestContext.HttpContext, publishedContentRequest)) - return null; + if (UmbracoModule.HandleHttpResponseStatus(requestContext.HttpContext, publishedContentRequest)) + return null; var handler = GetHandlerOnMissingTemplate(publishedContentRequest); - // if it's not null it can be either the PublishedContentNotFoundHandler (no document was - // found to handle 404, or document with no template was found) or the WebForms handler - // (a document was found and its template is WebForms) + // if it's not null it can be either the PublishedContentNotFoundHandler (no document was + // found to handle 404, or document with no template was found) or the WebForms handler + // (a document was found and its template is WebForms) - // if it's null it means that a document was found and its template is Mvc + // if it's null it means that a document was found and its template is Mvc - // if we have a handler, return now - if (handler != null) - return handler; + // if we have a handler, return now + if (handler != null) + return handler; - // else we are running Mvc + // else we are running Mvc // update the route data - because the PublishedContent has changed UpdateRouteDataForRequest( new RenderModel(publishedContentRequest.PublishedContent, publishedContentRequest.Culture), requestContext); // update the route definition - routeDef = GetUmbracoRouteDefinition(requestContext, publishedContentRequest); - } + routeDef = GetUmbracoRouteDefinition(requestContext, publishedContentRequest); + } - //no post values, just route to the controller/action requried (local) + //no post values, just route to the controller/action requried (local) - requestContext.RouteData.Values["controller"] = routeDef.ControllerName; - if (!string.IsNullOrWhiteSpace(routeDef.ActionName)) - { - requestContext.RouteData.Values["action"] = routeDef.ActionName; - } + requestContext.RouteData.Values["controller"] = routeDef.ControllerName; + if (!string.IsNullOrWhiteSpace(routeDef.ActionName)) + { + requestContext.RouteData.Values["action"] = routeDef.ActionName; + } // Set the session state requirements requestContext.HttpContext.SetSessionStateBehavior(GetSessionStateBehavior(requestContext, routeDef.ControllerName)); - // reset the friendly path so in the controllers and anything occuring after this point in time, - //the URL is reset back to the original request. - requestContext.HttpContext.RewritePath(UmbracoContext.OriginalRequestUrl.PathAndQuery); + // reset the friendly path so in the controllers and anything occuring after this point in time, + //the URL is reset back to the original request. + requestContext.HttpContext.RewritePath(UmbracoContext.OriginalRequestUrl.PathAndQuery); return new UmbracoMvcHandler(requestContext); - } + } private SessionStateBehavior GetSessionStateBehavior(RequestContext requestContext, string controllerName) { return _controllerFactory.GetControllerSessionBehavior(requestContext, controllerName); } - } + } } \ No newline at end of file From 6b810c89f6cf9fed9ab1c1c16a5717d84b7a1a55 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 19 Dec 2013 13:28:17 +1100 Subject: [PATCH 0115/1046] un-ignores PublishedContentExtensionTests --- .gitignore | 1 + .../PublishedContent/PublishedContentExtensionTests.cs | 5 ----- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index b6839b4e04..3faaa25539 100644 --- a/.gitignore +++ b/.gitignore @@ -85,3 +85,4 @@ NDependOut/* *.ndproj QueryResult.htm *.ndproj +src/Umbraco.Tests/Views/web.config diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentExtensionTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentExtensionTests.cs index 9b2ddf736e..2134bec0ce 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentExtensionTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentExtensionTests.cs @@ -26,7 +26,6 @@ namespace Umbraco.Tests.PublishedContent return xmlContent; } - [Ignore] [Test] public void IsDocumentType_NonRecursive_ActualType_ReturnsTrue() { @@ -36,7 +35,6 @@ namespace Umbraco.Tests.PublishedContent Assert.That(publishedContent.IsDocumentType("inherited", false)); } - [Ignore] [Test] public void IsDocumentType_NonRecursive_BaseType_ReturnsFalse() { @@ -46,7 +44,6 @@ namespace Umbraco.Tests.PublishedContent Assert.That(publishedContent.IsDocumentType("base", false), Is.False); } - [Ignore] [Test] public void IsDocumentType_Recursive_ActualType_ReturnsTrue() { @@ -56,7 +53,6 @@ namespace Umbraco.Tests.PublishedContent Assert.That(publishedContent.IsDocumentType("inherited", true)); } - [Ignore] [Test] public void IsDocumentType_Recursive_BaseType_ReturnsTrue() { @@ -66,7 +62,6 @@ namespace Umbraco.Tests.PublishedContent Assert.That(publishedContent.IsDocumentType("base", true)); } - [Ignore] [Test] public void IsDocumentType_Recursive_InvalidBaseType_ReturnsFalse() { From a43f4b7b7fdb98303b881e15ec0c279d1638dfe6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 19 Dec 2013 15:05:05 +1100 Subject: [PATCH 0116/1046] Completes: U4-1521 Updated back office to use ClientDependency bundles for common files --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 1 + .../config/ClientDependency.config | 2 +- .../umbraco/Umbraco.aspx.designer.cs | 160 +----------------- .../umbraco/controls/Tree/TreeControl.ascx | 18 +- src/Umbraco.Web.UI/umbraco/editContent.aspx | 9 +- src/Umbraco.Web.UI/umbraco/login.aspx | 9 +- .../UmbracoDialog.master.designer.cs | 18 -- .../UmbracoPage.master.designer.cs | 27 --- .../umbraco/masterpages/umbracoDialog.Master | 13 +- .../umbraco/masterpages/umbracoPage.Master | 17 +- src/Umbraco.Web.UI/umbraco/umbraco.aspx | 50 ++---- .../Application/HistoryManager.js | 6 +- .../Application/UmbracoUtils.js | 8 +- .../UI/Bundles/JsApplicationLib.cs | 22 +++ src/Umbraco.Web/UI/Bundles/JsJQueryCore.cs | 14 ++ src/Umbraco.Web/UI/Bundles/JsJQueryPlugins.cs | 18 ++ .../UI/Bundles/JsUmbracoApplicationCore.cs | 16 ++ .../UI/Bundles/JsUmbracoApplicationUI.cs | 17 ++ src/Umbraco.Web/UI/Bundles/JsUmbracoTree.cs | 18 ++ src/Umbraco.Web/UI/Controls/FolderBrowser.cs | 11 +- src/Umbraco.Web/Umbraco.Web.csproj | 6 + 21 files changed, 171 insertions(+), 289 deletions(-) create mode 100644 src/Umbraco.Web/UI/Bundles/JsApplicationLib.cs create mode 100644 src/Umbraco.Web/UI/Bundles/JsJQueryCore.cs create mode 100644 src/Umbraco.Web/UI/Bundles/JsJQueryPlugins.cs create mode 100644 src/Umbraco.Web/UI/Bundles/JsUmbracoApplicationCore.cs create mode 100644 src/Umbraco.Web/UI/Bundles/JsUmbracoApplicationUI.cs create mode 100644 src/Umbraco.Web/UI/Bundles/JsUmbracoTree.cs diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 83884f6950..36d53f4a8e 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -728,6 +728,7 @@ + diff --git a/src/Umbraco.Web.UI/config/ClientDependency.config b/src/Umbraco.Web.UI/config/ClientDependency.config index e4b2a98f77..acd5d07260 100644 --- a/src/Umbraco.Web.UI/config/ClientDependency.config +++ b/src/Umbraco.Web.UI/config/ClientDependency.config @@ -10,7 +10,7 @@ NOTES: * Compression/Combination/Minification is not enabled unless debug="false" is specified on the 'compiliation' element in the web.config * A new version will invalidate both client and server cache and create new persisted files --> - + - - - - - - - + + + + + diff --git a/src/Umbraco.Web.UI/umbraco/masterpages/umbracoPage.Master b/src/Umbraco.Web.UI/umbraco/masterpages/umbracoPage.Master index caa5370259..828df214b4 100644 --- a/src/Umbraco.Web.UI/umbraco/masterpages/umbracoPage.Master +++ b/src/Umbraco.Web.UI/umbraco/masterpages/umbracoPage.Master @@ -3,6 +3,7 @@ <%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> <%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> +<%@ Register TagPrefix="umbClient" Namespace="Umbraco.Web.UI.Bundles" Assembly="umbraco" %> @@ -10,16 +11,12 @@ - - - - - - - - - - + + + + + + diff --git a/src/Umbraco.Web.UI/umbraco/umbraco.aspx b/src/Umbraco.Web.UI/umbraco/umbraco.aspx index 649ac15855..efe4aa628f 100644 --- a/src/Umbraco.Web.UI/umbraco/umbraco.aspx +++ b/src/Umbraco.Web.UI/umbraco/umbraco.aspx @@ -9,7 +9,8 @@ <%@ Import Namespace="Umbraco.Web" %> <%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> <%@ Register TagPrefix="uc1" TagName="quickSearch" Src="Search/QuickSearch.ascx" %> -<%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> +<%@ Register TagPrefix="cdf" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> +<%@ Register TagPrefix="umbClient" Namespace="Umbraco.Web.UI.Bundles" Assembly="umbraco" %> @@ -18,41 +19,18 @@ " /> - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI/umbraco_client/Application/HistoryManager.js b/src/Umbraco.Web.UI/umbraco_client/Application/HistoryManager.js index bed8630cf5..5bab77a931 100644 --- a/src/Umbraco.Web.UI/umbraco_client/Application/HistoryManager.js +++ b/src/Umbraco.Web.UI/umbraco_client/Application/HistoryManager.js @@ -23,7 +23,7 @@ Umbraco.Sys.registerNamespace("Umbraco.Controls"); onNavigate: function(e) { var fragment = getHashFragment($.param.fragment()); if (fragment != "") { - jQuery(window.top).trigger("navigating", [fragment]); //raise event! + $(window.top).trigger("navigating", [fragment]); //raise event! } }, @@ -43,11 +43,11 @@ Umbraco.Sys.registerNamespace("Umbraco.Controls"); addEventHandler: function(fnName, fn) { /// Adds an event listener to the event name event - if (typeof (jQuery) != "undefined") jQuery(window.top).bind(fnName, fn); //if there's no jQuery, there is no events + if (typeof ($) != "undefined") $(window.top).bind(fnName, fn); //if there's no jQuery, there is no events }, removeEventHandler: function(fnName, fn) { /// Removes an event listener to the event name event - if (typeof (jQuery) != "undefined") jQuery(window.top).unbind(fnName, fn); //if there's no jQuery, there is no events + if (typeof ($) != "undefined") $(window.top).unbind(fnName, fn); //if there's no jQuery, there is no events } }; diff --git a/src/Umbraco.Web.UI/umbraco_client/Application/UmbracoUtils.js b/src/Umbraco.Web.UI/umbraco_client/Application/UmbracoUtils.js index e5dc8b96b5..3752d2e24e 100644 --- a/src/Umbraco.Web.UI/umbraco_client/Application/UmbracoUtils.js +++ b/src/Umbraco.Web.UI/umbraco_client/Application/UmbracoUtils.js @@ -4,8 +4,8 @@ Umbraco.Sys.registerNamespace("Umbraco.Utils"); Umbraco.Utils.generateRandom = function() { /// Returns a random integer for use with URLs - day = new Date() - z = day.getTime() - y = (z - (parseInt(z / 1000, 10) * 1000)) / 10 - return y + var day = new Date(); + var z = day.getTime(); + var y = (z - (parseInt(z / 1000, 10) * 1000)) / 10; + return y; } \ No newline at end of file diff --git a/src/Umbraco.Web/UI/Bundles/JsApplicationLib.cs b/src/Umbraco.Web/UI/Bundles/JsApplicationLib.cs new file mode 100644 index 0000000000..afe70ad4d1 --- /dev/null +++ b/src/Umbraco.Web/UI/Bundles/JsApplicationLib.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Web.UI; +using ClientDependency.Core; + +namespace Umbraco.Web.UI.Bundles +{ + /// + /// The core libs that have no dependencies + /// + [ClientDependency(ClientDependencyType.Javascript, "Application/NamespaceManager.js", "UmbracoClient", Priority = 0, Group = 0)] + [ClientDependency(ClientDependencyType.Javascript, "Application/UmbracoUtils.js", "UmbracoClient", Priority = 1, Group = 0)] + [ClientDependency(ClientDependencyType.Javascript, "ui/json2.js", "UmbracoClient", Priority = 2, Group = 0)] + [ClientDependency(ClientDependencyType.Javascript, "ui/base2.js", "UmbracoClient", Priority = 3, Group = 0)] + [ClientDependency(ClientDependencyType.Javascript, "UI/knockout.js", "UmbracoClient", Priority = 4, Group = 0)] + [ClientDependency(ClientDependencyType.Javascript, "UI/knockout.mapping.js", "UmbracoClient", Priority = 5, Group = 0)] + public class JsApplicationLib : Control + { + } +} diff --git a/src/Umbraco.Web/UI/Bundles/JsJQueryCore.cs b/src/Umbraco.Web/UI/Bundles/JsJQueryCore.cs new file mode 100644 index 0000000000..621ecd214c --- /dev/null +++ b/src/Umbraco.Web/UI/Bundles/JsJQueryCore.cs @@ -0,0 +1,14 @@ +using System.Web.UI; +using ClientDependency.Core; + +namespace Umbraco.Web.UI.Bundles +{ + /// + /// The core Jquery libs + /// + [ClientDependency(ClientDependencyType.Javascript, "ui/jquery.js", "UmbracoClient", Priority = 0, Group = 1)] + [ClientDependency(ClientDependencyType.Javascript, "ui/jqueryui.js", "UmbracoClient", Priority = 1, Group = 1)] + public class JsJQueryCore : Control + { + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/UI/Bundles/JsJQueryPlugins.cs b/src/Umbraco.Web/UI/Bundles/JsJQueryPlugins.cs new file mode 100644 index 0000000000..48ff2f38dc --- /dev/null +++ b/src/Umbraco.Web/UI/Bundles/JsJQueryPlugins.cs @@ -0,0 +1,18 @@ +using System.Web.UI; +using ClientDependency.Core; + +namespace Umbraco.Web.UI.Bundles +{ + /// + /// The core Jquery libs + /// + [ClientDependency(ClientDependencyType.Javascript, "Application/jQuery/jquery.ba-bbq.min.js", "UmbracoClient", Priority = 0, Group = 4)] + [ClientDependency(ClientDependencyType.Javascript, "ui/jQueryWresize.js", "UmbracoClient", Priority = 1, Group = 4)] + [ClientDependency(ClientDependencyType.Javascript, "modal/jquery.simplemodal.1.4.1.custom.js", "UmbracoClient", Priority = 2, Group = 4)] + [ClientDependency(ClientDependencyType.Javascript, "Application/jQuery/jquery.cookie.js", "UmbracoClient", Priority = 3, Group = 4)] + [ClientDependency(ClientDependencyType.Javascript, "Application/JQuery/jquery.metadata.min.js", "UmbracoClient", Priority = 4, Group = 4)] + [ClientDependency(ClientDependencyType.Javascript, "Application/jQuery/jquery.hotkeys.js", "UmbracoClient", Priority = 5, Group = 4)] + public class JsJQueryPlugins : Control + { + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/UI/Bundles/JsUmbracoApplicationCore.cs b/src/Umbraco.Web/UI/Bundles/JsUmbracoApplicationCore.cs new file mode 100644 index 0000000000..17ae644c81 --- /dev/null +++ b/src/Umbraco.Web/UI/Bundles/JsUmbracoApplicationCore.cs @@ -0,0 +1,16 @@ +using System.Web.UI; +using ClientDependency.Core; + +namespace Umbraco.Web.UI.Bundles +{ + /// + /// The core libs that require JQuery to be loaded + /// + [ClientDependency(ClientDependencyType.Javascript, "Application/Extensions.js", "UmbracoClient", Priority = 0, Group = 2)] + [ClientDependency(ClientDependencyType.Javascript, "Application/UmbracoApplicationActions.js", "UmbracoClient", Priority = 1, Group = 2)] + [ClientDependency(ClientDependencyType.Javascript, "Application/HistoryManager.js", "UmbracoClient", Priority = 2, Group = 2)] + [ClientDependency(ClientDependencyType.Javascript, "Application/UmbracoClientManager.js", "UmbracoClient", Priority = 3, Group = 3)] + public class JsUmbracoApplicationCore : Control + { + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/UI/Bundles/JsUmbracoApplicationUI.cs b/src/Umbraco.Web/UI/Bundles/JsUmbracoApplicationUI.cs new file mode 100644 index 0000000000..c8bce7694a --- /dev/null +++ b/src/Umbraco.Web/UI/Bundles/JsUmbracoApplicationUI.cs @@ -0,0 +1,17 @@ +using System.Web.UI; +using ClientDependency.Core; + +namespace Umbraco.Web.UI.Bundles +{ + /// + /// The UI Umbraco libs + /// + [ClientDependency(ClientDependencyType.Javascript, "ui/default.js", "UmbracoClient", Priority = 0, Group = 5)] + [ClientDependency(ClientDependencyType.Javascript, "js/guiFunctions.js", "UmbracoRoot", Priority = 1, Group = 5)] + [ClientDependency(ClientDependencyType.Javascript, "modal/modal.js", "UmbracoClient", Priority = 2, Group = 5)] + [ClientDependency(ClientDependencyType.Javascript, "js/UmbracoSpeechBubbleBackend.js", "UmbracoRoot", Priority = 3, Group = 5)] + [ClientDependency(ClientDependencyType.Javascript, "modal/modal.js", "UmbracoClient", Priority = 2, Group = 5)] + public class JsUmbracoApplicationUI : Control + { + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/UI/Bundles/JsUmbracoTree.cs b/src/Umbraco.Web/UI/Bundles/JsUmbracoTree.cs new file mode 100644 index 0000000000..7af2046991 --- /dev/null +++ b/src/Umbraco.Web/UI/Bundles/JsUmbracoTree.cs @@ -0,0 +1,18 @@ +using System.Web.UI; +using ClientDependency.Core; + +namespace Umbraco.Web.UI.Bundles +{ + /// + /// The umb tree libs + /// + [ClientDependency(ClientDependencyType.Javascript, "Tree/jquery.tree.js", "UmbracoClient", Priority = 0, Group = 10)] + [ClientDependency(ClientDependencyType.Javascript, "Tree/UmbracoContext.js", "UmbracoClient", Priority = 1, Group = 10)] + [ClientDependency(ClientDependencyType.Javascript, "Tree/jquery.tree.contextmenu.js", "UmbracoClient", Priority = 2, Group = 10)] + [ClientDependency(ClientDependencyType.Javascript, "Tree/jquery.tree.checkbox.js", "UmbracoClient", Priority = 3, Group = 10)] + [ClientDependency(ClientDependencyType.Javascript, "Tree/NodeDefinition.js", "UmbracoClient", Priority = 4, Group = 10)] + [ClientDependency(ClientDependencyType.Javascript, "Tree/UmbracoTree.js", "UmbracoClient", Priority = 5, Group = 10)] + public class JsUmbracoTree : Control + { + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/UI/Controls/FolderBrowser.cs b/src/Umbraco.Web/UI/Controls/FolderBrowser.cs index 305cc702db..f662bd507e 100644 --- a/src/Umbraco.Web/UI/Controls/FolderBrowser.cs +++ b/src/Umbraco.Web/UI/Controls/FolderBrowser.cs @@ -4,17 +4,14 @@ using System.Text; using System.Web.UI; using System.Web.UI.WebControls; using ClientDependency.Core; +using Umbraco.Web.UI.Bundles; using umbraco.BasePages; using Umbraco.Core.IO; namespace Umbraco.Web.UI.Controls { [ClientDependency(ClientDependencyType.Css, "ContextMenu/Css/jquery.contextMenu.css", "UmbracoClient")] - [ClientDependency(ClientDependencyType.Css, "FolderBrowser/Css/folderbrowser.css", "UmbracoClient")] - [ClientDependency(ClientDependencyType.Javascript, "ui/jquery.js", "UmbracoClient", Priority = 1)] - [ClientDependency(ClientDependencyType.Javascript, "ui/base2.js", "UmbracoClient", Priority = 1)] - [ClientDependency(ClientDependencyType.Javascript, "ui/knockout.js", "UmbracoClient", Priority = 3)] - [ClientDependency(ClientDependencyType.Javascript, "ui/knockout.mapping.js", "UmbracoClient", Priority = 4)] + [ClientDependency(ClientDependencyType.Css, "FolderBrowser/Css/folderbrowser.css", "UmbracoClient")] [ClientDependency(ClientDependencyType.Javascript, "ContextMenu/Js/jquery.contextMenu.js", "UmbracoClient", Priority = 5)] [ClientDependency(ClientDependencyType.Javascript, "FileUploader/js/jquery.fileUploader.js", "UmbracoClient", Priority = 6)] [ClientDependency(ClientDependencyType.Javascript, "FolderBrowser/js/folderbrowser.js", "UmbracoClient", Priority = 10)] @@ -68,6 +65,10 @@ namespace Umbraco.Web.UI.Controls /// protected override void CreateChildControls() { + //Ensure the bundles are added + Controls.Add(new JsApplicationLib()); + Controls.Add(new JsJQueryCore()); + // Create the panel surround Panel = new Panel { diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index c4a714687f..89fac8613b 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -393,6 +393,12 @@ + + + + + + From b2c0f3c35aca101e12bf31ec7a4821c0ce76328b Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 19 Dec 2013 15:21:02 +1100 Subject: [PATCH 0117/1046] some file formatting --- .../umbraco/masterpages/umbracoDialog.Master | 16 ++++++++++------ .../umbraco/masterpages/umbracoPage.Master | 7 +++++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/masterpages/umbracoDialog.Master b/src/Umbraco.Web.UI/umbraco/masterpages/umbracoDialog.Master index 1723a9731a..2477d79e60 100644 --- a/src/Umbraco.Web.UI/umbraco/masterpages/umbracoDialog.Master +++ b/src/Umbraco.Web.UI/umbraco/masterpages/umbracoDialog.Master @@ -1,4 +1,8 @@ -<%@ Master Language="C#" AutoEventWireup="True" CodeBehind="UmbracoDialog.master.cs" Inherits="Umbraco.Web.UI.Umbraco.Masterpages.UmbracoDialog" %> +<%@ Master Language="C#" AutoEventWireup="True" CodeBehind="UmbracoDialog.master.cs" Inherits="Umbraco.Web.UI.Umbraco.Masterpages.UmbracoDialog" %> + + + + <%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> <%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> @@ -12,11 +16,11 @@ - - - - - + + + + + diff --git a/src/Umbraco.Web.UI/umbraco/masterpages/umbracoPage.Master b/src/Umbraco.Web.UI/umbraco/masterpages/umbracoPage.Master index 828df214b4..c5f4d66ab7 100644 --- a/src/Umbraco.Web.UI/umbraco/masterpages/umbracoPage.Master +++ b/src/Umbraco.Web.UI/umbraco/masterpages/umbracoPage.Master @@ -1,5 +1,8 @@ -<%@ Master Language="C#" AutoEventWireup="True" CodeBehind="UmbracoPage.master.cs" - Inherits="Umbraco.Web.UI.Umbraco.Masterpages.UmbracoPage" %> +<%@ Master Language="C#" AutoEventWireup="True" CodeBehind="UmbracoPage.master.cs" Inherits="Umbraco.Web.UI.Umbraco.Masterpages.UmbracoPage" %> + + + + <%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> <%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> From d7207f7c2a034ae09fe90d85eba8f8c00af6cd62 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 19 Dec 2013 16:09:33 +1100 Subject: [PATCH 0118/1046] Fixes some merge issues --- .../umbraco/controls/GenericProperties/GenericProperty.ascx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/umbraco/controls/GenericProperties/GenericProperty.ascx b/src/Umbraco.Web.UI/umbraco/controls/GenericProperties/GenericProperty.ascx index 6bce6fb2e2..a89a33f855 100644 --- a/src/Umbraco.Web.UI/umbraco/controls/GenericProperties/GenericProperty.ascx +++ b/src/Umbraco.Web.UI/umbraco/controls/GenericProperties/GenericProperty.ascx @@ -66,6 +66,7 @@ + \ No newline at end of file From 1c901e00cc65e24b9023016cc30f267c1d414f25 Mon Sep 17 00:00:00 2001 From: esunxray Date: Thu, 19 Dec 2013 14:53:50 +0800 Subject: [PATCH 0119/1046] Update zh.xml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit add line 重设密码 --- src/Umbraco.Web.UI/umbraco/config/lang/zh.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml b/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml index 472ac1252c..27cb212bc7 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml @@ -907,6 +907,7 @@ 更改密码 禁用后台管理界面 密码 + 重设密码 您的密码已更改! 重输密码 当前密码 From 2dd04799b216651314e780776e88e547290c7b8f Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 19 Dec 2013 18:33:25 +1100 Subject: [PATCH 0120/1046] WIP membership provider updates --- .../Models/Membership/MembershipExtensions.cs | 6 +- .../Querying/ModelToSqlExpressionHelper.cs | 4 +- .../Querying/SqlStringExtensions.cs | 10 + .../Querying/StringPropertyMatchType.cs | 4 +- .../SqlSyntax/ISqlSyntaxProvider.cs | 1 + .../SqlSyntax/SqlCeSyntaxProvider.cs | 14 + .../SqlSyntax/SqlServerSyntaxProvider.cs | 14 + .../SqlSyntax/SqlSyntaxProviderBase.cs | 6 + .../Security/MembershipProviderBase.cs | 312 ++- src/Umbraco.Core/Services/MemberService.cs | 6 + .../Membership/MembershipProviderBaseTests.cs | 127 ++ .../Providers/MembersMembershipProvider.cs | 745 ++---- .../UsersMembershipProvider.cs | 10 +- ...ovider.cs => UmbracoMembershipProvider.cs} | 1989 ++++++++--------- ...eProvider.cs => UmbracoProfileProvider.cs} | 346 +-- ...RoleProvider.cs => UmbracoRoleProvider.cs} | 509 ++--- .../umbraco.providers.csproj | 6 +- 17 files changed, 2094 insertions(+), 2015 deletions(-) rename src/umbraco.providers/members/{MembersMembershipProvider.cs => UmbracoMembershipProvider.cs} (71%) rename src/umbraco.providers/members/{MembersProfileProvider.cs => UmbracoProfileProvider.cs} (69%) rename src/umbraco.providers/members/{MembersRoleProvider.cs => UmbracoRoleProvider.cs} (97%) diff --git a/src/Umbraco.Core/Models/Membership/MembershipExtensions.cs b/src/Umbraco.Core/Models/Membership/MembershipExtensions.cs index a986869371..118ba46669 100644 --- a/src/Umbraco.Core/Models/Membership/MembershipExtensions.cs +++ b/src/Umbraco.Core/Models/Membership/MembershipExtensions.cs @@ -5,15 +5,15 @@ namespace Umbraco.Core.Models.Membership { internal static class MembershipExtensions { - internal static MembershipUser AsConcreteMembershipUser(this IMember member) + internal static UmbracoMembershipMember AsConcreteMembershipUser(this IMember member) { var membershipMember = new UmbracoMembershipMember(member); return membershipMember; } - internal static IMember AsIMember(this MembershipUser membershipMember) + internal static IMember AsIMember(this UmbracoMembershipMember membershipMember) { - var member = membershipMember as UmbracoMembershipMember; + var member = membershipMember; if (member != null) { return member.Member; diff --git a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs index 611d9c63a3..4dba620ab0 100644 --- a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs @@ -232,6 +232,8 @@ namespace Umbraco.Core.Persistence.Querying { switch (verb) { + case "SqlWildcard": + return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnWildcardComparison(col, EscapeAtArgument(RemoveQuote(val)), columnType); case "Equals": return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnEqualComparison(col, EscapeAtArgument(RemoveQuote(val)), columnType); case "StartsWith": @@ -280,7 +282,7 @@ namespace Umbraco.Core.Persistence.Querying return string.Format("upper({0})", r); case "ToLower": return string.Format("lower({0})", r); - + case "SqlWildcard": case "StartsWith": case "EndsWith": case "Contains": diff --git a/src/Umbraco.Core/Persistence/Querying/SqlStringExtensions.cs b/src/Umbraco.Core/Persistence/Querying/SqlStringExtensions.cs index 82c158a9c6..cbecc0a591 100644 --- a/src/Umbraco.Core/Persistence/Querying/SqlStringExtensions.cs +++ b/src/Umbraco.Core/Persistence/Querying/SqlStringExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Text.RegularExpressions; namespace Umbraco.Core.Persistence.Querying { @@ -7,6 +8,15 @@ namespace Umbraco.Core.Persistence.Querying ///
    internal static class SqlStringExtensions { + public static bool SqlWildcard(this string str, string txt, TextColumnType columnType) + { + var wildcardmatch = new Regex("^" + Regex.Escape(txt). + //deal with any wildcard chars % + Replace(@"\%", ".*") + "$"); + + return wildcardmatch.IsMatch(str); + } + public static bool SqlContains(this string str, string txt, TextColumnType columnType) { return str.InvariantContains(txt); diff --git a/src/Umbraco.Core/Persistence/Querying/StringPropertyMatchType.cs b/src/Umbraco.Core/Persistence/Querying/StringPropertyMatchType.cs index f4245b931a..f45efb412a 100644 --- a/src/Umbraco.Core/Persistence/Querying/StringPropertyMatchType.cs +++ b/src/Umbraco.Core/Persistence/Querying/StringPropertyMatchType.cs @@ -8,6 +8,8 @@ Exact, Contains, StartsWith, - EndsWith + EndsWith, + //Deals with % as wildcard chars in a string + Wildcard } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs index 9fbf0456b6..2b31fb2032 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs @@ -15,6 +15,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType); string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType); string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType); + string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType); string GetQuotedTableName(string tableName); string GetQuotedColumnName(string columnName); diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs index d908f5c9b6..d9e9599dab 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs @@ -116,6 +116,20 @@ namespace Umbraco.Core.Persistence.SqlSyntax } } + public override string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType) + { + switch (columnType) + { + case TextColumnType.NVarchar: + return base.GetStringColumnContainsComparison(column, value, columnType); + case TextColumnType.NText: + //MSSQL doesn't allow for upper methods with NText columns + return string.Format("{0} LIKE '{1}'", column, value); + default: + throw new ArgumentOutOfRangeException("columnType"); + } + } + public override string GetQuotedTableName(string tableName) { return string.Format("[{0}]", tableName); diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index 24177c61d5..4046a7575d 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -90,6 +90,20 @@ namespace Umbraco.Core.Persistence.SqlSyntax } } + public override string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType) + { + switch (columnType) + { + case TextColumnType.NVarchar: + return base.GetStringColumnContainsComparison(column, value, columnType); + case TextColumnType.NText: + //MSSQL doesn't allow for upper methods with NText columns + return string.Format("{0} LIKE '{1}'", column, value); + default: + throw new ArgumentOutOfRangeException("columnType"); + } + } + public override string GetQuotedTableName(string tableName) { return string.Format("[{0}]", tableName); diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs index 8505d1e553..8f9a84437c 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs @@ -127,6 +127,12 @@ namespace Umbraco.Core.Persistence.SqlSyntax return string.Format("upper({0}) like '%{1}%'", column, value.ToUpper()); } + public virtual string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType) + { + //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. + return string.Format("upper({0}) like '{1}'", column, value.ToUpper()); + } + public virtual string GetQuotedTableName(string tableName) { return string.Format("\"{0}\"", tableName); diff --git a/src/Umbraco.Core/Security/MembershipProviderBase.cs b/src/Umbraco.Core/Security/MembershipProviderBase.cs index 10a05d042e..24a0e29409 100644 --- a/src/Umbraco.Core/Security/MembershipProviderBase.cs +++ b/src/Umbraco.Core/Security/MembershipProviderBase.cs @@ -7,9 +7,110 @@ using System.Text.RegularExpressions; using System.Web.Configuration; using System.Web.Hosting; using System.Web.Security; +using Umbraco.Core.Logging; namespace Umbraco.Core.Security { + + public abstract class UmbracoMembershipProviderBase : MembershipProviderBase + { + protected UmbracoMembershipProviderBase() + { + //Set the defaults! + DefaultMemberTypeAlias = "Member"; + LockPropertyTypeAlias = Constants.Conventions.Member.IsLockedOut; + LastLockedOutPropertyTypeAlias = Constants.Conventions.Member.LastLockoutDate; + FailedPasswordAttemptsPropertyTypeAlias = Constants.Conventions.Member.FailedPasswordAttempts; + ApprovedPropertyTypeAlias = Constants.Conventions.Member.IsApproved; + CommentPropertyTypeAlias = Constants.Conventions.Member.Comments; + LastLoginPropertyTypeAlias = Constants.Conventions.Member.LastLoginDate; + LastPasswordChangedPropertyTypeAlias = Constants.Conventions.Member.LastPasswordChangeDate; + PasswordRetrievalQuestionPropertyTypeAlias = Constants.Conventions.Member.PasswordQuestion; + PasswordRetrievalAnswerPropertyTypeAlias = Constants.Conventions.Member.PasswordAnswer; + } + + public string DefaultMemberTypeAlias { get; protected set; } + public string LockPropertyTypeAlias { get; protected set; } + public string LastLockedOutPropertyTypeAlias { get; protected set; } + public string FailedPasswordAttemptsPropertyTypeAlias { get; protected set; } + public string ApprovedPropertyTypeAlias { get; protected set; } + public string CommentPropertyTypeAlias { get; protected set; } + public string LastLoginPropertyTypeAlias { get; protected set; } + public string LastPasswordChangedPropertyTypeAlias { get; protected set; } + public string PasswordRetrievalQuestionPropertyTypeAlias { get; protected set; } + public string PasswordRetrievalAnswerPropertyTypeAlias { get; protected set; } + + public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) + { + base.ChangePasswordQuestionAndAnswer(username, password, newPasswordQuestion, newPasswordAnswer); + + if (string.IsNullOrEmpty(PasswordRetrievalQuestionPropertyTypeAlias) || string.IsNullOrEmpty(PasswordRetrievalAnswerPropertyTypeAlias)) + { + throw new NotSupportedException("Updating the password Question and Answer is not valid if the properties aren't set in the config file"); + } + + return PerformChangePasswordQuestionAndAnswer(username, password, newPasswordQuestion, newPasswordAnswer); + } + + /// + /// Adds a new membership user to the data source. + /// + /// The user name for the new user. + /// The password for the new user. + /// The e-mail address for the new user. + /// The password question for the new user. + /// The password answer for the new user + /// Whether or not the new user is approved to be validated. + /// The unique identifier from the membership data source for the user. + /// A enumeration value indicating whether the user was created successfully. + /// + /// A object populated with the information for the newly created user. + /// + protected sealed override MembershipUser PerformCreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) + { + return PerformCreateUser(DefaultMemberTypeAlias, username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); + } + + /// + /// Adds a new membership user to the data source. + /// + /// The member type alias to use when creating the member + /// The user name for the new user. + /// The password for the new user. + /// The e-mail address for the new user. + /// The password question for the new user. + /// The password answer for the new user + /// Whether or not the new user is approved to be validated. + /// The unique identifier from the membership data source for the user. + /// A enumeration value indicating whether the user was created successfully. + /// + /// A object populated with the information for the newly created user. + /// + public MembershipUser CreateUser(string memberTypeAlias, string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) + { + //do the base validation first + base.CreateUser(username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); + + return PerformCreateUser(memberTypeAlias, username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); + } + + /// + /// Adds a new membership user to the data source. + /// + /// The member type alias to use when creating the member + /// The user name for the new user. + /// The password for the new user. + /// The e-mail address for the new user. + /// The password question for the new user. + /// The password answer for the new user + /// Whether or not the new user is approved to be validated. + /// The unique identifier from the membership data source for the user. + /// A enumeration value indicating whether the user was created successfully. + /// + /// A object populated with the information for the newly created user. + /// + protected abstract MembershipUser PerformCreateUser(string memberTypeAlias, string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status); + } /// /// A base membership provider class offering much of the underlying functionality for initializing and password encryption/hashing. /// @@ -18,7 +119,7 @@ namespace Umbraco.Core.Security /// /// Providers can override this setting, default is 7 /// - protected virtual int DefaultMinPasswordLength + public virtual int DefaultMinPasswordLength { get { return 7; } } @@ -26,7 +127,7 @@ namespace Umbraco.Core.Security /// /// Providers can override this setting, default is 1 /// - protected virtual int DefaultMinNonAlphanumericChars + public virtual int DefaultMinNonAlphanumericChars { get { return 1; } } @@ -34,7 +135,7 @@ namespace Umbraco.Core.Security /// /// Providers can override this setting, default is false to use better security /// - protected virtual bool DefaultUseLegacyEncoding + public virtual bool DefaultUseLegacyEncoding { get { return false; } } @@ -44,7 +145,7 @@ namespace Umbraco.Core.Security /// authenticate the username + password when ChangePassword is called. This property exists purely for /// backwards compatibility. ///
    - internal virtual bool AllowManuallyChangingPassword + public virtual bool AllowManuallyChangingPassword { get { return false; } } @@ -60,7 +161,8 @@ namespace Umbraco.Core.Security private string _passwordStrengthRegularExpression; private bool _requiresQuestionAndAnswer; private bool _requiresUniqueEmail; - private bool _useLegacyEncoding; + + internal bool UseLegacyEncoding; #region Properties @@ -208,7 +310,7 @@ namespace Umbraco.Core.Security _enablePasswordRetrieval = config.GetValue("enablePasswordRetrieval", false); _enablePasswordReset = config.GetValue("enablePasswordReset", false); _requiresQuestionAndAnswer = config.GetValue("requiresQuestionAndAnswer", false); - _requiresUniqueEmail = config.GetValue("requiresUniqueEmail", false); + _requiresUniqueEmail = config.GetValue("requiresUniqueEmail", true); _maxInvalidPasswordAttempts = GetIntValue(config, "maxInvalidPasswordAttempts", 5, false, 0); _passwordAttemptWindow = GetIntValue(config, "passwordAttemptWindow", 10, false, 0); _minRequiredPasswordLength = GetIntValue(config, "minRequiredPasswordLength", DefaultMinPasswordLength, true, 0x80); @@ -220,10 +322,10 @@ namespace Umbraco.Core.Security _applicationName = GetDefaultAppName(); //by default we will continue using the legacy encoding. - _useLegacyEncoding = config.GetValue("useLegacyEncoding", DefaultUseLegacyEncoding); + UseLegacyEncoding = config.GetValue("useLegacyEncoding", DefaultUseLegacyEncoding); - // make sure password format is clear by default. - string str = config["passwordFormat"] ?? "Clear"; + // make sure password format is Hashed by default. + string str = config["passwordFormat"] ?? "Hashed"; switch (str.ToLower()) { @@ -244,7 +346,11 @@ namespace Umbraco.Core.Security } if ((PasswordFormat == MembershipPasswordFormat.Hashed) && EnablePasswordRetrieval) - throw new ProviderException("Provider can not retrieve hashed password"); + { + var ex = new ProviderException("Provider can not retrieve a hashed password"); + LogHelper.Error("Cannot specify a Hashed password format with the enabledPasswordRetrieval option set to true", ex); + throw ex; + } } @@ -271,14 +377,19 @@ namespace Umbraco.Core.Security AlphanumericChars, Strength } - + /// - /// Checks to ensure the AllowManuallyChangingPassword rule is adhered to + /// Processes a request to update the password for a membership user. /// - /// - /// - /// - /// + /// The user to update the password for. + /// This property is ignore for this provider + /// The new password for the specified user. + /// + /// true if the password was updated successfully; otherwise, false. + /// + /// + /// Checks to ensure the AllowManuallyChangingPassword rule is adhered to + /// public sealed override bool ChangePassword(string username, string oldPassword, string newPassword) { if (oldPassword.IsNullOrWhiteSpace() && AllowManuallyChangingPassword == false) @@ -290,8 +401,139 @@ namespace Umbraco.Core.Security return PerformChangePassword(username, oldPassword, newPassword); } + /// + /// Processes a request to update the password for a membership user. + /// + /// The user to update the password for. + /// This property is ignore for this provider + /// The new password for the specified user. + /// + /// true if the password was updated successfully; otherwise, false. + /// protected abstract bool PerformChangePassword(string username, string oldPassword, string newPassword); + /// + /// Processes a request to update the password question and answer for a membership user. + /// + /// The user to change the password question and answer for. + /// The password for the specified user. + /// The new password question for the specified user. + /// The new password answer for the specified user. + /// + /// true if the password question and answer are updated successfully; otherwise, false. + /// + /// + /// Performs the basic validation before passing off to PerformChangePasswordQuestionAndAnswer + /// + public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) + { + if (RequiresQuestionAndAnswer == false) + { + throw new NotSupportedException("Updating the password Question and Answer is not available if requiresQuestionAndAnswer is not set in web.config"); + } + + if (ValidateUser(username, password) == false) + { + return false; + } + + return PerformChangePasswordQuestionAndAnswer(username, password, newPasswordQuestion, newPasswordAnswer); + } + + /// + /// Processes a request to update the password question and answer for a membership user. + /// + /// The user to change the password question and answer for. + /// The password for the specified user. + /// The new password question for the specified user. + /// The new password answer for the specified user. + /// + /// true if the password question and answer are updated successfully; otherwise, false. + /// + protected abstract bool PerformChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer); + + /// + /// Adds a new membership user to the data source. + /// + /// The user name for the new user. + /// The password for the new user. + /// The e-mail address for the new user. + /// The password question for the new user. + /// The password answer for the new user + /// Whether or not the new user is approved to be validated. + /// The unique identifier from the membership data source for the user. + /// A enumeration value indicating whether the user was created successfully. + /// + /// A object populated with the information for the newly created user. + /// + /// + /// Ensures the ValidatingPassword event is executed before executing PerformCreateUser and performs basic membership provider validation of values. + /// + public sealed override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) + { + var args = new ValidatePasswordEventArgs(username, password, true); + OnValidatingPassword(args); + if (args.Cancel) + { + status = MembershipCreateStatus.InvalidPassword; + return null; + } + + // Validate password + var passwordValidAttempt = IsPasswordValid(password, MinRequiredNonAlphanumericCharacters, PasswordStrengthRegularExpression, MinRequiredPasswordLength); + if (passwordValidAttempt.Success == false) + { + status = MembershipCreateStatus.InvalidPassword; + return null; + } + + // Validate email + if (IsEmailValid(email) == false) + { + status = MembershipCreateStatus.InvalidEmail; + return null; + } + + // Make sure username isn't all whitespace + if (string.IsNullOrWhiteSpace(username.Trim())) + { + status = MembershipCreateStatus.InvalidUserName; + return null; + } + + // Check password question + if (string.IsNullOrWhiteSpace(passwordQuestion) && RequiresQuestionAndAnswer) + { + status = MembershipCreateStatus.InvalidQuestion; + return null; + } + + // Check password answer + if (string.IsNullOrWhiteSpace(passwordAnswer) && RequiresQuestionAndAnswer) + { + status = MembershipCreateStatus.InvalidAnswer; + return null; + } + + return PerformCreateUser(username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); + } + + /// + /// Adds a new membership user to the data source. + /// + /// The user name for the new user. + /// The password for the new user. + /// The e-mail address for the new user. + /// The password question for the new user. + /// The password answer for the new user + /// Whether or not the new user is approved to be validated. + /// The unique identifier from the membership data source for the user. + /// A enumeration value indicating whether the user was created successfully. + /// + /// A object populated with the information for the newly created user. + /// + protected abstract MembershipUser PerformCreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status); + protected internal static Attempt IsPasswordValid(string password, int minRequiredNonAlphanumericChars, string strengthRegex, int minLength) { if (minRequiredNonAlphanumericChars > 0) @@ -374,7 +616,7 @@ namespace Umbraco.Core.Security protected string FormatPasswordForStorage(string pass, string salt) { - if (_useLegacyEncoding) + if (UseLegacyEncoding) { return pass; } @@ -383,11 +625,20 @@ namespace Umbraco.Core.Security return salt + pass; } + protected bool IsEmailValid(string email) + { + const string pattern = @"^(?!\.)(""([^""\r\\]|\\[""\r\\])*""|" + + @"([-a-z0-9!#$%&'*+/=?^_`{|}~]|(? protected string EncryptOrHashExistingPassword(string storedPassword) { - if (_useLegacyEncoding) + if (UseLegacyEncoding) { return EncryptOrHashPassword(storedPassword, storedPassword); } @@ -481,7 +732,7 @@ namespace Umbraco.Core.Security { //if we are doing it the old way - if (_useLegacyEncoding) + if (UseLegacyEncoding) { return LegacyUnEncodePassword(pass); } @@ -533,7 +784,7 @@ namespace Umbraco.Core.Security protected HashAlgorithm GetHashAlgorithm(string password) { - if (_useLegacyEncoding) + if (UseLegacyEncoding) { //before we were never checking for an algorithm type so we were always using HMACSHA1 // for any SHA specified algorithm :( so we'll need to keep doing that for backwards compat support. @@ -601,5 +852,24 @@ namespace Umbraco.Core.Security return password; } + public override string ToString() + { + var result = base.ToString(); + var sb = new StringBuilder(result); + sb.AppendLine("Name =" + Name); + sb.AppendLine("_applicationName =" + _applicationName); + sb.AppendLine("_enablePasswordReset=" + _enablePasswordReset); + sb.AppendLine("_enablePasswordRetrieval=" + _enablePasswordRetrieval); + sb.AppendLine("_maxInvalidPasswordAttempts=" + _maxInvalidPasswordAttempts); + sb.AppendLine("_minRequiredNonAlphanumericCharacters=" + _minRequiredNonAlphanumericCharacters); + sb.AppendLine("_minRequiredPasswordLength=" + _minRequiredPasswordLength); + sb.AppendLine("_passwordAttemptWindow=" + _passwordAttemptWindow); + sb.AppendLine("_passwordFormat=" + _passwordFormat); + sb.AppendLine("_passwordStrengthRegularExpression=" + _passwordStrengthRegularExpression); + sb.AppendLine("_requiresQuestionAndAnswer=" + _requiresQuestionAndAnswer); + sb.AppendLine("_requiresUniqueEmail=" + _requiresUniqueEmail); + return sb.ToString(); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index 0f4cd7c242..a84734d1f7 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -206,6 +206,9 @@ namespace Umbraco.Core.Services case StringPropertyMatchType.EndsWith: query.Where(member => member.Email.EndsWith(emailStringToMatch)); break; + case StringPropertyMatchType.Wildcard: + query.Where(member => member.Email.SqlWildcard(emailStringToMatch, TextColumnType.NVarchar)); + break; default: throw new ArgumentOutOfRangeException("matchType"); } @@ -235,6 +238,9 @@ namespace Umbraco.Core.Services case StringPropertyMatchType.EndsWith: query.Where(member => member.Username.EndsWith(login)); break; + case StringPropertyMatchType.Wildcard: + query.Where(member => member.Email.SqlWildcard(login, TextColumnType.NVarchar)); + break; default: throw new ArgumentOutOfRangeException("matchType"); } diff --git a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs index 500b33501c..06e44be215 100644 --- a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs +++ b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; +using System.Configuration.Provider; using System.Diagnostics; using System.Linq; using System.Text; @@ -14,6 +16,48 @@ namespace Umbraco.Tests.Membership [TestFixture] public class MembershipProviderBaseTests { + //[Test] + //public void Change_Password_Base_Validation() + + //[Test] + //public void ChangePasswordQuestionAndAnswer_Base_Validation() + + //[Test] + //public void CreateUser_Base_Validation() + + [Test] + public void Sets_Defaults() + { + var provider = new TestProvider(); + provider.Initialize("test", new NameValueCollection()); + + Assert.AreEqual("test", provider.Name); + Assert.AreEqual(MembershipProviderBase.GetDefaultAppName(), provider.ApplicationName); + Assert.AreEqual(false, provider.EnablePasswordRetrieval); + Assert.AreEqual(false, provider.EnablePasswordReset); + Assert.AreEqual(false, provider.RequiresQuestionAndAnswer); + Assert.AreEqual(true, provider.RequiresUniqueEmail); + Assert.AreEqual(5, provider.MaxInvalidPasswordAttempts); + Assert.AreEqual(10, provider.PasswordAttemptWindow); + Assert.AreEqual(provider.DefaultMinPasswordLength, provider.MinRequiredPasswordLength); + Assert.AreEqual(provider.DefaultMinNonAlphanumericChars, provider.MinRequiredNonAlphanumericCharacters); + Assert.AreEqual(null, provider.PasswordStrengthRegularExpression); + Assert.AreEqual(provider.DefaultUseLegacyEncoding, provider.UseLegacyEncoding); + Assert.AreEqual(MembershipPasswordFormat.Hashed, provider.PasswordFormat); + } + + [Test] + public void Throws_Exception_With_Hashed_Password_And_Password_Retrieval() + { + var provider = new TestProvider(); + + Assert.Throws(() => provider.Initialize("test", new NameValueCollection() + { + {"enablePasswordRetrieval", "true"}, + {"passwordFormat", "Hashed"} + })); + } + [TestCase("hello", 0, "", 5, true)] [TestCase("hello", 0, "", 4, true)] [TestCase("hello", 0, "", 6, false)] @@ -65,5 +109,88 @@ namespace Umbraco.Tests.Membership Assert.AreEqual(salt, initSalt); } + private class TestProvider : MembershipProviderBase + { + public override string GetPassword(string username, string answer) + { + throw new NotImplementedException(); + } + + public override string ResetPassword(string username, string answer) + { + throw new NotImplementedException(); + } + + public override void UpdateUser(MembershipUser user) + { + throw new NotImplementedException(); + } + + public override bool ValidateUser(string username, string password) + { + throw new NotImplementedException(); + } + + public override bool UnlockUser(string userName) + { + throw new NotImplementedException(); + } + + public override MembershipUser GetUser(object providerUserKey, bool userIsOnline) + { + throw new NotImplementedException(); + } + + public override MembershipUser GetUser(string username, bool userIsOnline) + { + throw new NotImplementedException(); + } + + public override string GetUserNameByEmail(string email) + { + throw new NotImplementedException(); + } + + public override bool DeleteUser(string username, bool deleteAllRelatedData) + { + throw new NotImplementedException(); + } + + public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords) + { + throw new NotImplementedException(); + } + + public override int GetNumberOfUsersOnline() + { + throw new NotImplementedException(); + } + + public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) + { + throw new NotImplementedException(); + } + + public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) + { + throw new NotImplementedException(); + } + + protected override bool PerformChangePassword(string username, string oldPassword, string newPassword) + { + throw new NotImplementedException(); + } + + protected override bool PerformChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) + { + throw new NotImplementedException(); + } + + protected override MembershipUser PerformCreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) + { + throw new NotImplementedException(); + } + } + } } diff --git a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs index f423d95fbd..23ee46fabc 100644 --- a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs @@ -11,6 +11,8 @@ using System.Web.Security; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Core.Models.Membership; @@ -19,25 +21,8 @@ namespace Umbraco.Web.Security.Providers /// /// Custom Membership Provider for Umbraco Members (User authentication for Frontend applications NOT umbraco CMS) /// - internal class MembersMembershipProvider : MembershipProvider + internal class MembersMembershipProvider : UmbracoMembershipProviderBase { - #region Fields - private string _applicationName; - private bool _enablePasswordReset; - private bool _enablePasswordRetrieval; - private int _maxInvalidPasswordAttempts; - private int _minRequiredNonAlphanumericCharacters; - private int _minRequiredPasswordLength; - private int _passwordAttemptWindow; - - private MembershipPasswordFormat _passwordFormat; - - private string _passwordStrengthRegularExpression; - private bool _requiresQuestionAndAnswer; - private bool _requiresUniqueEmail; - - #endregion - private IMembershipMemberService _memberService; protected IMembershipMemberService MemberService @@ -45,125 +30,8 @@ namespace Umbraco.Web.Security.Providers get { return _memberService ?? (_memberService = ApplicationContext.Current.Services.MemberService); } } - /// - /// The name of the application using the custom membership provider. - /// - /// - /// The name of the application using the custom membership provider. - public override string ApplicationName + public string ProviderName { - get { return _applicationName; } - set { _applicationName = value; } - } - - /// - /// Indicates whether the membership provider is configured to allow users to reset their passwords. - /// - /// - /// true if the membership provider supports password reset; otherwise, false. The default is true. - public override bool EnablePasswordReset - { - get { return _enablePasswordReset; } - } - - /// - /// Indicates whether the membership provider is configured to allow users to retrieve their passwords. - /// - /// - /// true if the membership provider is configured to support password retrieval; otherwise, false. The default is false. - public override bool EnablePasswordRetrieval - { - get { return _enablePasswordRetrieval; } - } - - /// - /// Gets the number of invalid password or password-answer attempts allowed before the membership user is locked out. - /// - /// - /// The number of invalid password or password-answer attempts allowed before the membership user is locked out. - public override int MaxInvalidPasswordAttempts - { - get { return _maxInvalidPasswordAttempts; } - } - - /// - /// Gets the minimum number of special characters that must be present in a valid password. - /// - /// - /// The minimum number of special characters that must be present in a valid password. - public override int MinRequiredNonAlphanumericCharacters - { - get { return _minRequiredNonAlphanumericCharacters; } - } - - /// - /// Gets the minimum length required for a password. - /// - /// - /// The minimum length required for a password. - public override int MinRequiredPasswordLength - { - get { return _minRequiredPasswordLength; } - } - - /// - /// Gets the number of minutes in which a maximum number of invalid password or password-answer attempts are allowed before the membership user is locked out. - /// - /// - /// The number of minutes in which a maximum number of invalid password or password-answer attempts are allowed before the membership user is locked out. - public override int PasswordAttemptWindow - { - get { return _passwordAttemptWindow; } - } - - /// - /// Gets a value indicating the format for storing passwords in the membership data store. - /// - /// - /// One of the values indicating the format for storing passwords in the data store. - public override MembershipPasswordFormat PasswordFormat - { - get { return _passwordFormat; } - } - - /// - /// Gets the regular expression used to evaluate a password. - /// - /// - /// A regular expression used to evaluate a password. - public override string PasswordStrengthRegularExpression - { - get { return _passwordStrengthRegularExpression; } - } - - /// - /// Gets a value indicating whether the membership provider is configured to require the user to answer a password question for password reset and retrieval. - /// - /// - /// true if a password answer is required for password reset and retrieval; otherwise, false. The default is true. - public override bool RequiresQuestionAndAnswer - { - get { return _requiresQuestionAndAnswer; } - } - - /// - /// Gets a value indicating whether the membership provider is configured to require a unique e-mail address for each user name. - /// - /// - /// true if the membership provider requires a unique e-mail address; otherwise, false. The default is true. - public override bool RequiresUniqueEmail - { - get { return _requiresUniqueEmail; } - } - - /// - /// The default Umbraco member type alias to create when registration is performed using .net Membership - /// - /// - /// Member type alias - public string DefaultMemberTypeAlias { get; private set; } - - public string ProviderName { get { return "MembersMembershipProvider"; } } @@ -186,80 +54,127 @@ namespace Umbraco.Web.Security.Providers // Initialize base provider class base.Initialize(name, config); - _applicationName = string.IsNullOrEmpty(config["applicationName"]) ? GetDefaultAppName() : config["applicationName"]; + //// test for membertype (if not specified, choose the first member type available) + //if (config["defaultMemberTypeAlias"] != null) + // _defaultMemberTypeAlias = config["defaultMemberTypeAlias"]; + //else if (MemberType.GetAll.Length == 1) + // _defaultMemberTypeAlias = MemberType.GetAll[0].Alias; + //else + // throw new ProviderException("No default MemberType alias is specified in the web.config string. Please add a 'defaultMemberTypeAlias' to the add element in the provider declaration in web.config"); - _enablePasswordRetrieval = GetBooleanValue(config, "enablePasswordRetrieval", false); - _enablePasswordReset = GetBooleanValue(config, "enablePasswordReset", false); - _requiresQuestionAndAnswer = GetBooleanValue(config, "requiresQuestionAndAnswer", false); - _requiresUniqueEmail = GetBooleanValue(config, "requiresUniqueEmail", true); - _maxInvalidPasswordAttempts = GetIntValue(config, "maxInvalidPasswordAttempts", 5, false, 0); - _passwordAttemptWindow = GetIntValue(config, "passwordAttemptWindow", 10, false, 0); - _minRequiredPasswordLength = GetIntValue(config, "minRequiredPasswordLength", 7, true, 0x80); - _minRequiredNonAlphanumericCharacters = GetIntValue(config, "minRequiredNonalphanumericCharacters", 1, true, 0x80); - _passwordStrengthRegularExpression = config["passwordStrengthRegularExpression"]; - - // make sure password format is Hashed by default. - var str = config["passwordFormat"] ?? "Hashed"; - - LogHelper.Debug("Loaded membership provider properties"); - LogHelper.Debug(ToString()); - - switch (str.ToLower()) + // test for approve status + if (config["umbracoApprovePropertyTypeAlias"] != null) { - case "clear": - _passwordFormat = MembershipPasswordFormat.Clear; - break; - case "encrypted": - _passwordFormat = MembershipPasswordFormat.Encrypted; - break; - case "hashed": - _passwordFormat = MembershipPasswordFormat.Hashed; - break; - default: - var e = new ProviderException("Provider bad password format"); - LogHelper.Error(e.Message, e); - throw e; + ApprovedPropertyTypeAlias = config["umbracoApprovePropertyTypeAlias"]; } - - if ((PasswordFormat == MembershipPasswordFormat.Hashed) && EnablePasswordRetrieval) + // test for lock attempts + if (config["umbracoLockPropertyTypeAlias"] != null) { - var e = new ProviderException("Provider can not retrieve hashed password"); - LogHelper.Error(e.Message, e); - throw e; + LockPropertyTypeAlias = config["umbracoLockPropertyTypeAlias"]; } - - // TODO: rationalise what happens when no member alias is specified.... - DefaultMemberTypeAlias = config["defaultMemberTypeAlias"]; - - LogHelper.Debug("Finished initialising member ship provider " + GetType().FullName); + if (config["umbracoLastLockedPropertyTypeAlias"] != null) + { + LastLockedOutPropertyTypeAlias = config["umbracoLastLockedPropertyTypeAlias"]; + } + if (config["umbracoLastPasswordChangedPropertyTypeAlias"] != null) + { + LastPasswordChangedPropertyTypeAlias = config["umbracoLastPasswordChangedPropertyTypeAlias"]; + } + if (config["umbracoFailedPasswordAttemptsPropertyTypeAlias"] != null) + { + FailedPasswordAttemptsPropertyTypeAlias = config["umbracoFailedPasswordAttemptsPropertyTypeAlias"]; + } + // comment property + if (config["umbracoCommentPropertyTypeAlias"] != null) + { + CommentPropertyTypeAlias = config["umbracoCommentPropertyTypeAlias"]; + } + // last login date + if (config["umbracoLastLoginPropertyTypeAlias"] != null) + { + LastLoginPropertyTypeAlias = config["umbracoLastLoginPropertyTypeAlias"]; + } + // password retrieval + if (config["umbracoPasswordRetrievalQuestionPropertyTypeAlias"] != null) + { + PasswordRetrievalQuestionPropertyTypeAlias = config["umbracoPasswordRetrievalQuestionPropertyTypeAlias"]; + } + if (config["umbracoPasswordRetrievalAnswerPropertyTypeAlias"] != null) + { + PasswordRetrievalAnswerPropertyTypeAlias = config["umbracoPasswordRetrievalAnswerPropertyTypeAlias"]; + } } - public override string ToString() + /// + /// Processes a request to update the password for a membership user. + /// + /// The user to update the password for. + /// This property is ignore for this provider + /// The new password for the specified user. + /// + /// true if the password was updated successfully; otherwise, false. + /// + protected override bool PerformChangePassword(string username, string oldPassword, string newPassword) { - var result = base.ToString(); + //NOTE: due to backwards compatibilty reasons (and UX reasons), this provider doesn't care about the old password and + // allows simply setting the password manually so we don't really care about the old password. + // This is allowed based on the overridden AllowManuallyChangingPassword option. - result += "_applicationName =" + _applicationName + Environment.NewLine; - result += "_enablePasswordReset=" + _enablePasswordReset + Environment.NewLine; - result += "_enablePasswordRetrieval=" + _enablePasswordRetrieval + Environment.NewLine; - result += "_maxInvalidPasswordAttempts=" + _maxInvalidPasswordAttempts + Environment.NewLine; - result += "_minRequiredNonAlphanumericCharacters=" + _minRequiredNonAlphanumericCharacters + Environment.NewLine; - result += "_minRequiredPasswordLength=" + _minRequiredPasswordLength + Environment.NewLine; - result += "_passwordAttemptWindow=" + _passwordAttemptWindow + Environment.NewLine; + // in order to support updating passwords from the umbraco core, we can't validate the old password + var m = _memberService.GetByUsername(username); + if (m == null) return false; - result += "_passwordFormat=" + _passwordFormat + Environment.NewLine; + var args = new ValidatePasswordEventArgs(username, newPassword, false); + OnValidatingPassword(args); - result += "_passwordStrengthRegularExpression=" + _passwordStrengthRegularExpression + Environment.NewLine; - result += "_requiresQuestionAndAnswer=" + _requiresQuestionAndAnswer + Environment.NewLine; - result += "_requiresUniqueEmail=" + _requiresUniqueEmail + Environment.NewLine; - result += "DefaultMemberTypeAlias=" + DefaultMemberTypeAlias + Environment.NewLine; + if (args.Cancel) + { + if (args.FailureInformation != null) + throw args.FailureInformation; + throw new MembershipPasswordException("Change password canceled due to password validation failure."); + } - return result; + string salt; + var encodedPassword = EncryptOrHashNewPassword(newPassword, out salt); + + m.Password = FormatPasswordForStorage(encodedPassword, salt); + m.LastPasswordChangeDate = DateTime.Now; + + _memberService.Save(m); + + return true; + } + + /// + /// Processes a request to update the password question and answer for a membership user. + /// + /// The user to change the password question and answer for. + /// The password for the specified user. + /// The new password question for the specified user. + /// The new password answer for the specified user. + /// + /// true if the password question and answer are updated successfully; otherwise, false. + /// + protected override bool PerformChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) + { + var member = MemberService.GetByUsername(username); + if (member == null) + { + return false; + } + + member.PasswordQuestion = newPasswordQuestion; + member.PasswordAnswer = newPasswordAnswer; + + MemberService.Save(member); + + return true; } /// /// Adds a new membership user to the data source with the specified member type /// - /// A specific member type to create the member for + /// A specific member type to create the member for /// The user name for the new user. /// The password for the new user. /// The e-mail address for the new user. @@ -271,48 +186,11 @@ namespace Umbraco.Web.Security.Providers /// /// A object populated with the information for the newly created user. /// - public MembershipUser CreateUser(string memberType, string username, string password, string email, string passwordQuestion, string passwordAnswer, - bool isApproved, object providerUserKey, out MembershipCreateStatus status) + protected override MembershipUser PerformCreateUser(string memberTypeAlias, string username, string password, string email, string passwordQuestion, + string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) { - LogHelper.Debug("Member signup requested: username -> " + username + ". email -> " + email); - - // Validate password - if (IsPasswordValid(password) == false) - { - status = MembershipCreateStatus.InvalidPassword; - return null; - } - - // Validate email - if (IsEmaiValid(email) == false) - { - status = MembershipCreateStatus.InvalidEmail; - return null; - } - - // Make sure username isn't all whitespace - if (string.IsNullOrWhiteSpace(username.Trim())) - { - status = MembershipCreateStatus.InvalidUserName; - return null; - } - - // Check password question - if (string.IsNullOrWhiteSpace(passwordQuestion) && _requiresQuestionAndAnswer) - { - status = MembershipCreateStatus.InvalidQuestion; - return null; - } - - // Check password answer - if (string.IsNullOrWhiteSpace(passwordAnswer) && _requiresQuestionAndAnswer) - { - status = MembershipCreateStatus.InvalidAnswer; - return null; - } - // See if the user already exists - if (MemberService.GetByUsername(username) != null) + if (MemberService.Exists(username)) { status = MembershipCreateStatus.DuplicateUserName; LogHelper.Warn("Cannot create member as username already exists: " + username); @@ -328,14 +206,16 @@ namespace Umbraco.Web.Security.Providers return null; } - var member = MemberService.CreateMember(email, username, password, memberType); + string salt; + var encodedPassword = EncryptOrHashNewPassword(password, out salt); - member.IsApproved = isApproved; + var member = MemberService.CreateMember(email, username, encodedPassword, memberTypeAlias); + member.PasswordQuestion = passwordQuestion; member.PasswordAnswer = passwordAnswer; - - //encrypts/hashes the password depending on the settings - member.Password = EncryptOrHashPassword(member.Password); + member.IsApproved = isApproved; + member.LastLoginDate = DateTime.Now; + member.LastPasswordChangeDate = DateTime.Now; MemberService.Save(member); @@ -344,68 +224,72 @@ namespace Umbraco.Web.Security.Providers } /// - /// Adds a new membership user to the data source. + /// Removes a user from the membership data source. /// - /// The user name for the new user. - /// The password for the new user. - /// The e-mail address for the new user. - /// The password question for the new user. - /// The password answer for the new user - /// Whether or not the new user is approved to be validated. - /// The unique identifier from the membership data source for the user. - /// A enumeration value indicating whether the user was created successfully. + /// The name of the user to delete. + /// + /// TODO: This setting currently has no effect + /// /// - /// A object populated with the information for the newly created user. + /// true if the user was successfully deleted; otherwise, false. /// - public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, - bool isApproved, object providerUserKey, out MembershipCreateStatus status) + public override bool DeleteUser(string username, bool deleteAllRelatedData) { - return CreateUser(DefaultMemberTypeAlias, username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); + var member = MemberService.GetByUsername(username); + if (member == null) return false; + + MemberService.Delete(member); + return true; } /// - /// Processes a request to update the password question and answer for a membership user. + /// Gets a collection of membership users where the e-mail address contains the specified e-mail address to match. /// - /// The user to change the password question and answer for. - /// The password for the specified user. - /// The new password question for the specified user. - /// The new password answer for the specified user. + /// The e-mail address to search for. + /// The index of the page of results to return. pageIndex is zero-based. + /// The size of the page of results to return. + /// The total number of matched users. /// - /// true if the password question and answer are updated successfully; otherwise, false. + /// A collection that contains a page of pageSize objects beginning at the page specified by pageIndex. /// - public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, - string newPasswordAnswer) + public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) { - if (_requiresQuestionAndAnswer == false) + var byEmail = MemberService.FindMembersByEmail(emailToMatch, StringPropertyMatchType.Wildcard).ToArray(); + totalRecords = byEmail.Length; + var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); + + var collection = new MembershipUserCollection(); + foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) { - throw new NotSupportedException("Updating the password Question and Answer is not available if requiresQuestionAndAnswer is not set in web.config"); + collection.Add(m.AsConcreteMembershipUser()); } - - if (ValidateUser(username, password) == false) - { - throw new MembershipPasswordException("Invalid username and password combinatio"); - } - - var member = MemberService.GetByUsername(username); - var encodedPassword = EncryptOrHashPassword(password); - - if (member.Password == encodedPassword) - { - member.PasswordQuestion = newPasswordQuestion; - member.PasswordAnswer = newPasswordAnswer; - - MemberService.Save(member); - - return true; - } - else - { - //TODO: Throw here? or just return false; - } - - return false; + return collection; } + /// + /// Gets a collection of membership users where the user name contains the specified user name to match. + /// + /// The user name to search for. + /// The index of the page of results to return. pageIndex is zero-based. + /// The size of the page of results to return. + /// The total number of matched users. + /// + /// A collection that contains a page of pageSize objects beginning at the page specified by pageIndex. + /// + public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) + { + var byEmail = MemberService.FindMembersByUsername(usernameToMatch, StringPropertyMatchType.Wildcard).ToArray(); + totalRecords = byEmail.Length; + var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); + + var collection = new MembershipUserCollection(); + foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) + { + collection.Add(m.AsConcreteMembershipUser()); + } + return collection; + } + /// /// Gets the password for the specified user name from the data source. /// @@ -416,59 +300,22 @@ namespace Umbraco.Web.Security.Providers /// public override string GetPassword(string username, string answer) { - if (_enablePasswordRetrieval == false) + if (EnablePasswordRetrieval == false) throw new ProviderException("Password Retrieval Not Enabled."); - if (_passwordFormat == MembershipPasswordFormat.Hashed) + if (PasswordFormat == MembershipPasswordFormat.Hashed) throw new ProviderException("Cannot retrieve Hashed passwords."); var member = MemberService.GetByUsername(username); - if (_requiresQuestionAndAnswer && member.PasswordAnswer != answer) + if (RequiresQuestionAndAnswer && member.PasswordAnswer != answer) { throw new ProviderException("Password retrieval answer doesn't match"); } return member.Password; } - - /// - /// Processes a request to update the password for a membership user. - /// - /// The user to update the password for. - /// The current password for the specified user. - /// The new password for the specified user. - /// - /// true if the password was updated successfully; otherwise, false. - /// - public override bool ChangePassword(string username, string oldPassword, string newPassword) - { - // Validate new password - if (IsPasswordValid(newPassword) == false) - { - var e = new MembershipPasswordException("Change password canceled due to new password validation failure."); - LogHelper.WarnWithException(e.Message, e); - throw e; - } - - var member = MemberService.GetByUsername(username); - if (member == null) return false; - - var encodedPassword = EncryptOrHashPassword(oldPassword); - - if (member.Password == encodedPassword) - { - - member.Password = EncryptOrHashPassword(newPassword); - MemberService.Save(member); - - return true; - } - - LogHelper.Warn("Can't change password as old password was incorrect"); - return false; - } - + /// /// Resets a user's password to a new, automatically generated password. /// @@ -477,30 +324,34 @@ namespace Umbraco.Web.Security.Providers /// The new password for the specified user. public override string ResetPassword(string username, string answer) { - if (_enablePasswordReset == false) - throw new ProviderException("Password reset is Not Enabled."); + //TODO: Get logic from other provider - var member = MemberService.GetByUsername(username); + throw new NotImplementedException(); - if (member == null) - throw new ProviderException("The supplied user is not found"); + //if (EnablePasswordReset == false) + // throw new ProviderException("Password reset is Not Enabled."); - if(member.IsLockedOut) - throw new ProviderException("The member is locked out."); + //var member = MemberService.GetByUsername(username); - if (_requiresQuestionAndAnswer == false || (_requiresQuestionAndAnswer && answer == member.PasswordAnswer)) - { - member.Password = - EncryptOrHashPassword(Membership.GeneratePassword(_minRequiredPasswordLength, - _minRequiredNonAlphanumericCharacters)); - MemberService.Save(member); - } - else - { - throw new MembershipPasswordException("Incorrect password answer"); - } + //if (member == null) + // throw new ProviderException("The supplied user is not found"); + + //if(member.IsLockedOut) + // throw new ProviderException("The member is locked out."); + + //if (RequiresQuestionAndAnswer == false || (RequiresQuestionAndAnswer && answer == member.PasswordAnswer)) + //{ + // member.Password = + // EncryptOrHashPassword(Membership.GeneratePassword(MinRequiredPasswordLength, + // MinRequiredNonAlphanumericCharacters)); + // MemberService.Save(member); + //} + //else + //{ + // throw new MembershipPasswordException("Incorrect password answer"); + //} - return null; + //return null; } /// @@ -511,8 +362,8 @@ namespace Umbraco.Web.Security.Providers /// A object that represents the user to update and the updated information for the user. public override void UpdateUser(MembershipUser user) { - var member = user.AsIMember(); - MemberService.Save(member); + //var member = user.AsIMember(); + //MemberService.Save(member); } /// @@ -532,7 +383,8 @@ namespace Umbraco.Web.Security.Providers if (member.IsLockedOut) throw new ProviderException("The member is locked out."); - var encodedPassword = EncryptOrHashPassword(password); + string salt; + var encodedPassword = EncryptOrHashNewPassword(password, out salt); var authenticated = (encodedPassword == member.Password); @@ -645,22 +497,7 @@ namespace Umbraco.Web.Security.Providers return member == null ? null : member.Username; } - /// - /// Removes a user from the membership data source. - /// - /// The name of the user to delete. - /// true to delete data related to the user from the database; false to leave data related to the user in the database. - /// - /// true if the user was successfully deleted; otherwise, false. - /// - public override bool DeleteUser(string username, bool deleteAllRelatedData) - { - var member = MemberService.GetByUsername(username); - if (member == null) return false; - - MemberService.Delete(member); - return true; - } + /// /// Gets a collection of all the users in the data source in pages of data. @@ -687,182 +524,14 @@ namespace Umbraco.Web.Security.Providers throw new System.NotImplementedException(); } - /// - /// Gets a collection of membership users where the user name contains the specified user name to match. - /// - /// The user name to search for. - /// The index of the page of results to return. pageIndex is zero-based. - /// The size of the page of results to return. - /// The total number of matched users. - /// - /// A collection that contains a page of pageSize objects beginning at the page specified by pageIndex. - /// - public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) + + public override string ToString() { - throw new System.NotImplementedException(); + var result = base.ToString(); + var sb = new StringBuilder(result); + sb.AppendLine("DefaultMemberTypeAlias=" + DefaultMemberTypeAlias); + return sb.ToString(); } - /// - /// Gets a collection of membership users where the e-mail address contains the specified e-mail address to match. - /// - /// The e-mail address to search for. - /// The index of the page of results to return. pageIndex is zero-based. - /// The size of the page of results to return. - /// The total number of matched users. - /// - /// A collection that contains a page of pageSize objects beginning at the page specified by pageIndex. - /// - public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) - { - var byEmail = MemberService.FindMembersByEmail(emailToMatch).ToArray(); - totalRecords = byEmail.Length; - var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); - - var collection = new MembershipUserCollection(); - foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) - { - collection.Add(m.AsConcreteMembershipUser()); - } - return collection; - } - - #region Private methods - - private bool IsPasswordValid(string password) - { - if (_minRequiredNonAlphanumericCharacters > 0) - { - var nonAlphaNumeric = Regex.Replace(password, "[a-zA-Z0-9]", "", RegexOptions.Multiline | RegexOptions.IgnoreCase); - if (nonAlphaNumeric.Length < _minRequiredNonAlphanumericCharacters) - { - return false; - } - } - - var valid = true; - if(string.IsNullOrEmpty(_passwordStrengthRegularExpression) == false) - { - valid = Regex.IsMatch(password, _passwordStrengthRegularExpression, RegexOptions.Compiled); - } - - return valid && password.Length >= _minRequiredPasswordLength; - } - - private bool IsEmaiValid(string email) - { - const string pattern = @"^(?!\.)(""([^""\r\\]|\\[""\r\\])*""|" - + @"([-a-z0-9!#$%&'*+/=?^_`{|}~]|(? - /// Encodes the password. - /// - /// The password. - /// The encoded password. - private string EncryptOrHashPassword(string password) - { - var encodedPassword = password; - switch (PasswordFormat) - { - case MembershipPasswordFormat.Clear: - break; - case MembershipPasswordFormat.Encrypted: - encodedPassword = - Convert.ToBase64String(EncryptPassword(Encoding.Unicode.GetBytes(password))); - break; - case MembershipPasswordFormat.Hashed: - var hash = new HMACSHA1 {Key = Encoding.Unicode.GetBytes(password)}; - encodedPassword = - Convert.ToBase64String(hash.ComputeHash(Encoding.Unicode.GetBytes(password))); - break; - default: - throw new ProviderException("Unsupported password format."); - } - return encodedPassword; - } - - /// - /// Gets the boolean value. - /// - /// The config. - /// Name of the value. - /// if set to true [default value]. - /// - private bool GetBooleanValue(NameValueCollection config, string valueName, bool defaultValue) - { - bool flag; - var str = config[valueName]; - if (str == null) - return defaultValue; - - if (bool.TryParse(str, out flag) == false) - { - throw new ProviderException("Value must be boolean."); - } - return flag; - } - - /// - /// Gets the int value. - /// - /// The config. - /// Name of the value. - /// The default value. - /// if set to true [zero allowed]. - /// The max value allowed. - /// - private int GetIntValue(NameValueCollection config, string valueName, int defaultValue, bool zeroAllowed, int maxValueAllowed) - { - int num; - var s = config[valueName]; - if (s == null) - { - return defaultValue; - } - if (int.TryParse(s, out num) == false) - { - if (zeroAllowed) - { - throw new ProviderException("Value must be non negative integer"); - } - throw new ProviderException("Value must be positive integer"); - } - if (zeroAllowed && (num < 0)) - { - throw new ProviderException("Value must be non negativeinteger"); - } - if (zeroAllowed == false && (num <= 0)) - { - throw new ProviderException("Value must be positive integer"); - } - if ((maxValueAllowed > 0) && (num > maxValueAllowed)) - { - throw new ProviderException("Value too big"); - } - return num; - } - - - /// - /// Gets the name of the default app. - /// - /// - private string GetDefaultAppName() - { - try - { - var applicationVirtualPath = HostingEnvironment.ApplicationVirtualPath; - return string.IsNullOrEmpty(applicationVirtualPath) ? "/" : applicationVirtualPath; - } - catch - { - return "/"; - } - } - - #endregion } } \ No newline at end of file diff --git a/src/umbraco.providers/UsersMembershipProvider.cs b/src/umbraco.providers/UsersMembershipProvider.cs index 278aff9871..e862bf5b19 100644 --- a/src/umbraco.providers/UsersMembershipProvider.cs +++ b/src/umbraco.providers/UsersMembershipProvider.cs @@ -22,7 +22,7 @@ namespace umbraco.providers /// /// Override to maintain backwards compatibility with 0 required non-alphanumeric chars /// - protected override int DefaultMinNonAlphanumericChars + public override int DefaultMinNonAlphanumericChars { get { return 0; } } @@ -30,7 +30,7 @@ namespace umbraco.providers /// /// Override to maintain backwards compatibility with only 4 required length /// - protected override int DefaultMinPasswordLength + public override int DefaultMinPasswordLength { get { return 4; } } @@ -38,7 +38,7 @@ namespace umbraco.providers /// /// Override to maintain backwards compatibility /// - protected override bool DefaultUseLegacyEncoding + public override bool DefaultUseLegacyEncoding { get { return true; } } @@ -110,7 +110,7 @@ namespace umbraco.providers /// /// true if the password question and answer are updated successfully; otherwise, false. /// - public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) + protected override bool PerformChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) { throw new Exception("The method or operation is not implemented."); } @@ -129,7 +129,7 @@ namespace umbraco.providers /// /// A object populated with the information for the newly created user. /// - public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) + protected override MembershipUser PerformCreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) { var args = new ValidatePasswordEventArgs(username, password, true); OnValidatingPassword(args); diff --git a/src/umbraco.providers/members/MembersMembershipProvider.cs b/src/umbraco.providers/members/UmbracoMembershipProvider.cs similarity index 71% rename from src/umbraco.providers/members/MembersMembershipProvider.cs rename to src/umbraco.providers/members/UmbracoMembershipProvider.cs index a14b47d84d..62562a85b0 100644 --- a/src/umbraco.providers/members/MembersMembershipProvider.cs +++ b/src/umbraco.providers/members/UmbracoMembershipProvider.cs @@ -1,1031 +1,958 @@ -#region namespace -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Web.Security; -using System.Configuration; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Security; -using umbraco.BusinessLogic; -using System.Security.Cryptography; -using System.Web.Util; -using System.Collections.Specialized; -using System.Configuration.Provider; -using System.Security; -using System.Security.Permissions; -using System.Runtime.CompilerServices; -using Member = umbraco.cms.businesslogic.member.Member; -using MemberType = umbraco.cms.businesslogic.member.MemberType; - -#endregion - -namespace umbraco.providers.members -{ - /// - /// Custom Membership Provider for Umbraco Members (User authentication for Frontend applications NOT umbraco CMS) - /// - - public class UmbracoMembershipProvider : MembershipProviderBase - { - #region Fields - - //Set the defaults! - private string _defaultMemberTypeAlias = "Member"; - private string _lockPropertyTypeAlias = Constants.Conventions.Member.IsLockedOut; - private string _lastLockedOutPropertyTypeAlias = Constants.Conventions.Member.LastLockoutDate; - private string _failedPasswordAttemptsPropertyTypeAlias = Constants.Conventions.Member.FailedPasswordAttempts; - private string _approvedPropertyTypeAlias = Constants.Conventions.Member.IsApproved; - private string _commentPropertyTypeAlias = Constants.Conventions.Member.Comments; - private string _lastLoginPropertyTypeAlias = Constants.Conventions.Member.LastLoginDate; - private string _lastPasswordChangedPropertyTypeAlias = Constants.Conventions.Member.LastPasswordChangeDate; - private string _passwordRetrievalQuestionPropertyTypeAlias = Constants.Conventions.Member.PasswordQuestion; - private string _passwordRetrievalAnswerPropertyTypeAlias = Constants.Conventions.Member.PasswordAnswer; - - private string _providerName = Member.UmbracoMemberProviderName; - - //Need to expose these publicly so we know what field aliases to use in the editor, we only care about these 3 fields - // because they are the only 'settable' provider properties that are not stored against the IMember directly (i.e. they are - // property type properties). - - public string LockPropertyTypeAlias - { - get { return _lockPropertyTypeAlias; } - } - - public string ApprovedPropertyTypeAlias - { - get { return _approvedPropertyTypeAlias; } - } - - public string CommentPropertyTypeAlias - { - get { return _commentPropertyTypeAlias; } - } - - #endregion - - /// - /// Override to maintain backwards compatibility with 0 required non-alphanumeric chars - /// - protected override int DefaultMinNonAlphanumericChars - { - get { return 0; } - } - - /// - /// Override to maintain backwards compatibility with only 4 required length - /// - protected override int DefaultMinPasswordLength - { - get { return 4; } - } - - /// - /// Override to maintain backwards compatibility - /// - protected override bool DefaultUseLegacyEncoding - { - get { return true; } - } - - /// - /// For backwards compatibility, this provider supports this option - /// - internal override bool AllowManuallyChangingPassword - { - get { return true; } - } - - #region Initialization Method - /// - /// Initializes the provider. - /// - /// The friendly name of the provider. - /// A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider. - /// The name of the provider is null. - /// An attempt is made to call - /// on a provider after the provider - /// has already been initialized. - /// The name of the provider has a length of zero. - public override void Initialize(string name, NameValueCollection config) - { - // Intialize values from web.config - if (config == null) throw new ArgumentNullException("config"); - - if (string.IsNullOrEmpty(name)) name = "UmbracoMembershipProvider"; - - base.Initialize(name, config); - - _providerName = name; - - // test for membertype (if not specified, choose the first member type available) - if (config["defaultMemberTypeAlias"] != null) - _defaultMemberTypeAlias = config["defaultMemberTypeAlias"]; - else if (MemberType.GetAll.Length == 1) - _defaultMemberTypeAlias = MemberType.GetAll[0].Alias; - else - throw new ProviderException("No default MemberType alias is specified in the web.config string. Please add a 'defaultMemberTypeAlias' to the add element in the provider declaration in web.config"); - - // test for approve status - if (config["umbracoApprovePropertyTypeAlias"] != null) - { - _approvedPropertyTypeAlias = config["umbracoApprovePropertyTypeAlias"]; - } - // test for lock attempts - if (config["umbracoLockPropertyTypeAlias"] != null) - { - _lockPropertyTypeAlias = config["umbracoLockPropertyTypeAlias"]; - } - if (config["umbracoLastLockedPropertyTypeAlias"] != null) - { - _lastLockedOutPropertyTypeAlias = config["umbracoLastLockedPropertyTypeAlias"]; - } - if (config["umbracoLastPasswordChangedPropertyTypeAlias"] != null) - { - _lastPasswordChangedPropertyTypeAlias = config["umbracoLastPasswordChangedPropertyTypeAlias"]; - } - if (config["umbracoFailedPasswordAttemptsPropertyTypeAlias"] != null) - { - _failedPasswordAttemptsPropertyTypeAlias = config["umbracoFailedPasswordAttemptsPropertyTypeAlias"]; - } - // comment property - if (config["umbracoCommentPropertyTypeAlias"] != null) - { - _commentPropertyTypeAlias = config["umbracoCommentPropertyTypeAlias"]; - } - // last login date - if (config["umbracoLastLoginPropertyTypeAlias"] != null) - { - _lastLoginPropertyTypeAlias = config["umbracoLastLoginPropertyTypeAlias"]; - } - // password retrieval - if (config["umbracoPasswordRetrievalQuestionPropertyTypeAlias"] != null) - { - _passwordRetrievalQuestionPropertyTypeAlias = config["umbracoPasswordRetrievalQuestionPropertyTypeAlias"]; - } - if (config["umbracoPasswordRetrievalAnswerPropertyTypeAlias"] != null) - { - _passwordRetrievalAnswerPropertyTypeAlias = config["umbracoPasswordRetrievalAnswerPropertyTypeAlias"]; - } - - } - #endregion - - #region Methods - - /// - /// Processes a request to update the password for a membership user. - /// - /// The user to update the password for. - /// This property is ignore for this provider - /// The new password for the specified user. - /// - /// true if the password was updated successfully; otherwise, false. - /// - protected override bool PerformChangePassword(string username, string oldPassword, string newPassword) - { - //NOTE: due to backwards compatibilty reasons, this provider doesn't care about the old password and - // allows simply setting the password manually so we don't really care about the old password. - // This is allowed based on the overridden AllowManuallyChangingPassword option. - - // in order to support updating passwords from the umbraco core, we can't validate the old password - var m = Member.GetMemberFromLoginName(username); - if (m == null) return false; - - var args = new ValidatePasswordEventArgs(username, newPassword, false); - OnValidatingPassword(args); - - if (args.Cancel) - { - if (args.FailureInformation != null) - throw args.FailureInformation; - throw new MembershipPasswordException("Change password canceled due to password validation failure."); - } - - string salt; - var encodedPassword = EncryptOrHashNewPassword(newPassword, out salt); - m.ChangePassword( - FormatPasswordForStorage(encodedPassword, salt)); - - UpdateMemberProperty(m, _lastPasswordChangedPropertyTypeAlias, DateTime.Now); - - m.Save(); - - return true; - } - - /// - /// Processes a request to update the password question and answer for a membership user. - /// - /// The user to change the password question and answer for. - /// The password for the specified user. - /// The new password question for the specified user. - /// The new password answer for the specified user. - /// - /// true if the password question and answer are updated successfully; otherwise, false. - /// - public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) - { - if (!String.IsNullOrEmpty(_passwordRetrievalQuestionPropertyTypeAlias) && !String.IsNullOrEmpty(_passwordRetrievalAnswerPropertyTypeAlias)) - { - if (ValidateUser(username, password)) - { - Member m = Member.GetMemberFromLoginName(username); - if (m != null) - { - UpdateMemberProperty(m, _passwordRetrievalQuestionPropertyTypeAlias, newPasswordQuestion); - UpdateMemberProperty(m, _passwordRetrievalAnswerPropertyTypeAlias, newPasswordAnswer); - m.Save(); - return true; - } - else - { - throw new MembershipPasswordException("The supplied user is not found!"); - } - } - else { - throw new MembershipPasswordException("Invalid user/password combo"); - } - - } - else - { - throw new NotSupportedException("Updating the password Question and Answer is not valid if the properties aren't set in the config file"); - } - } - - /// - /// Adds a new membership user to the data source. - /// - /// - /// The user name for the new user. - /// The password for the new user. - /// The e-mail address for the new user. - /// The password question for the new user. - /// The password answer for the new user - /// Whether or not the new user is approved to be validated. - /// The unique identifier from the membership data source for the user. - /// A enumeration value indicating whether the user was created successfully. - /// - /// A object populated with the information for the newly created user. - /// - public MembershipUser CreateUser(string memberTypeAlias, string username, string password, string email, string passwordQuestion, - string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) - { - - var args = new ValidatePasswordEventArgs(username, password, true); - OnValidatingPassword(args); - if (args.Cancel) - { - status = MembershipCreateStatus.InvalidPassword; - return null; - } - - if (Member.GetMemberFromLoginName(username) != null) - { - status = MembershipCreateStatus.DuplicateUserName; - } - else if (Member.GetMemberFromEmail(email) != null && RequiresUniqueEmail) - { - status = MembershipCreateStatus.DuplicateEmail; - } - else - { - var memberType = MemberType.GetByAlias(memberTypeAlias); - if (memberType == null) - { - throw new InvalidOperationException("Could not find a member type with alias " + memberTypeAlias + ". Ensure your membership provider configuration is up to date and that the default member type exists."); - } - - var m = Member.MakeNew(username, email, memberType, User.GetUser(0)); - - string salt; - var encodedPassword = EncryptOrHashNewPassword(password, out salt); - //set the password on the member - m.ChangePassword( - FormatPasswordForStorage(encodedPassword, salt)); - - // custom fields - if (string.IsNullOrEmpty(_passwordRetrievalQuestionPropertyTypeAlias) == false) - { - UpdateMemberProperty(m, _passwordRetrievalQuestionPropertyTypeAlias, passwordQuestion); - } - - if (string.IsNullOrEmpty(_passwordRetrievalAnswerPropertyTypeAlias) == false) - { - UpdateMemberProperty(m, _passwordRetrievalAnswerPropertyTypeAlias, passwordAnswer); - } - - if (string.IsNullOrEmpty(ApprovedPropertyTypeAlias) == false) - { - UpdateMemberProperty(m, ApprovedPropertyTypeAlias, isApproved ? 1 : 0); - } - - if (string.IsNullOrEmpty(_lastLoginPropertyTypeAlias) == false) - { - UpdateMemberProperty(m, _lastLoginPropertyTypeAlias, DateTime.Now); - } - - if (string.IsNullOrEmpty(_lastPasswordChangedPropertyTypeAlias) == false) - { - UpdateMemberProperty(m, _lastPasswordChangedPropertyTypeAlias, DateTime.Now); - } - - var mUser = ConvertToMembershipUser(m); - - // save - m.Save(); - - status = MembershipCreateStatus.Success; - - return mUser; - } - return null; - } - - /// - /// Adds a new membership user to the data source. - /// - /// The user name for the new user. - /// The password for the new user. - /// The e-mail address for the new user. - /// The password question for the new user. - /// The password answer for the new user - /// Whether or not the new user is approved to be validated. - /// The unique identifier from the membership data source for the user. - /// A enumeration value indicating whether the user was created successfully. - /// - /// A object populated with the information for the newly created user. - /// - public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, - string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) - { - return CreateUser(_defaultMemberTypeAlias, username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); - } - - /// - /// Removes a user from the membership data source. - /// - /// The name of the user to delete. - /// true to delete data related to the user from the database; false to leave data related to the user in the database. - /// - /// true if the user was successfully deleted; otherwise, false. - /// - public override bool DeleteUser(string username, bool deleteAllRelatedData) - { - var m = Member.GetMemberFromLoginName(username); - if (m == null) return false; - m.delete(); - return true; - } - - /// - /// Gets a collection of membership users where the e-mail address contains the specified e-mail address to match. - /// - /// The e-mail address to search for. - /// The index of the page of results to return. pageIndex is zero-based. - /// The size of the page of results to return. - /// The total number of matched users. - /// - /// A collection that contains a page of pageSize objects beginning at the page specified by pageIndex. - /// - public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) - { - var byEmail = ApplicationContext.Current.Services.MemberService.FindMembersByEmail(emailToMatch).ToArray(); - totalRecords = byEmail.Length; - var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); - - var collection = new MembershipUserCollection(); - foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) - { - collection.Add(ConvertToMembershipUser(m)); - } - return collection; - } - - /// - /// Gets a collection of membership users where the user name contains the specified user name to match. - /// - /// The user name to search for. - /// The index of the page of results to return. pageIndex is zero-based. - /// The size of the page of results to return. - /// The total number of matched users. - /// - /// A collection that contains a page of pageSize objects beginning at the page specified by pageIndex. - /// - public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) - { - var counter = 0; - var startIndex = pageSize * pageIndex; - var endIndex = startIndex + pageSize - 1; - var membersList = new MembershipUserCollection(); - var memberArray = Member.GetMemberByName(usernameToMatch, false); - totalRecords = memberArray.Length; - - foreach (var m in memberArray) - { - if (counter >= startIndex) - membersList.Add(ConvertToMembershipUser(m)); - if (counter >= endIndex) break; - counter++; - } - return membersList; - } - - /// - /// Gets a collection of all the users in the data source in pages of data. - /// - /// The index of the page of results to return. pageIndex is zero-based. - /// The size of the page of results to return. - /// The total number of matched users. - /// - /// A collection that contains a page of pageSize objects beginning at the page specified by pageIndex. - /// - public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords) - { - var counter = 0; - var startIndex = pageSize * pageIndex; - var endIndex = startIndex + pageSize - 1; - var membersList = new MembershipUserCollection(); - var memberArray = Member.GetAll; - totalRecords = memberArray.Length; - - foreach (var m in memberArray) - { - if (counter >= startIndex) - membersList.Add(ConvertToMembershipUser(m)); - if (counter >= endIndex) break; - counter++; - } - return membersList; - - } - - /// - /// Gets the number of users currently accessing the application. - /// - /// - /// The number of users currently accessing the application. - /// - public override int GetNumberOfUsersOnline() - { - return Member.CachedMembers().Count; - } - - /// - /// Gets the password for the specified user name from the data source. - /// - /// The user to retrieve the password for. - /// The password answer for the user. - /// - /// The password for the specified user name. - /// - public override string GetPassword(string username, string answer) - { - if (EnablePasswordRetrieval == false) - throw new ProviderException("Password Retrieval Not Enabled."); - - if (PasswordFormat == MembershipPasswordFormat.Hashed) - throw new ProviderException("Cannot retrieve Hashed passwords."); - - var m = Member.GetMemberFromLoginName(username); - if (m != null) - { - if (RequiresQuestionAndAnswer) - { - // check if password answer property alias is set - if (string.IsNullOrEmpty(_passwordRetrievalAnswerPropertyTypeAlias) == false) - { - // check if user is locked out - if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) - { - var isLockedOut = false; - bool.TryParse(GetMemberProperty(m, LockPropertyTypeAlias, true), out isLockedOut); - if (isLockedOut) - { - throw new MembershipPasswordException("The supplied user is locked out"); - } - } - - // match password answer - if (GetMemberProperty(m, _passwordRetrievalAnswerPropertyTypeAlias, false) != answer) - { - throw new MembershipPasswordException("Incorrect password answer"); - } - } - else - { - throw new ProviderException("Password retrieval answer property alias is not set! To automatically support password question/answers you'll need to add references to the membertype properties in the 'Member' element in web.config by adding their aliases to the 'umbracoPasswordRetrievalQuestionPropertyTypeAlias' and 'umbracoPasswordRetrievalAnswerPropertyTypeAlias' attributes"); - } - } - } - if (m == null) - { - throw new MembershipPasswordException("The supplied user is not found"); - } - return m.GetPassword(); - } - - /// - /// Gets information from the data source for a user. Provides an option to update the last-activity date/time stamp for the user. - /// - /// The name of the user to get information for. - /// true to update the last-activity date/time stamp for the user; false to return user information without updating the last-activity date/time stamp for the user. - /// - /// A object populated with the specified user's information from the data source. - /// - public override MembershipUser GetUser(string username, bool userIsOnline) - { - if (String.IsNullOrEmpty(username)) - return null; - Member m = Member.GetMemberFromLoginName(username); - if (m == null) return null; - else return ConvertToMembershipUser(m); - } - - /// - /// Gets information from the data source for a user based on the unique identifier for the membership user. Provides an option to update the last-activity date/time stamp for the user. - /// - /// The unique identifier for the membership user to get information for. - /// true to update the last-activity date/time stamp for the user; false to return user information without updating the last-activity date/time stamp for the user. - /// - /// A object populated with the specified user's information from the data source. - /// - public override MembershipUser GetUser(object providerUserKey, bool userIsOnline) - { - var asGuid = providerUserKey.TryConvertTo(); - if (asGuid.Success) - { - var m = new Member(asGuid.Result); - return ConvertToMembershipUser(m); - } - var asInt = providerUserKey.TryConvertTo(); - if (asInt.Success) - { - var m = new Member(asInt.Result); - return ConvertToMembershipUser(m); - } - throw new InvalidOperationException("The " + GetType() + " provider only supports GUID or Int as a providerUserKey"); - - } - - - /// - /// Gets the user name associated with the specified e-mail address. - /// - /// The e-mail address to search for. - /// - /// The user name associated with the specified e-mail address. If no match is found, return null. - /// - public override string GetUserNameByEmail(string email) - { - Member m = Member.GetMemberFromEmail(email); - return m == null ? null : m.LoginName; - } - - /// - /// Resets a user's password to a new, automatically generated password. - /// - /// The user to reset the password for. - /// The password answer for the specified user (not used with Umbraco). - /// The new password for the specified user. - public override string ResetPassword(string username, string answer) - { - if (EnablePasswordReset == false) - { - throw new NotSupportedException("Password reset is not supported"); - } - - //TODO: This should be here - but how do we update failure count in this provider?? - //if (answer == null && RequiresQuestionAndAnswer) - //{ - // UpdateFailureCount(username, "passwordAnswer"); - - // throw new ProviderException("Password answer required for password reset."); - //} - - var newPassword = Membership.GeneratePassword(MinRequiredPasswordLength, MinRequiredNonAlphanumericCharacters); - - var args = new ValidatePasswordEventArgs(username, newPassword, true); - OnValidatingPassword(args); - if (args.Cancel) - { - if (args.FailureInformation != null) - throw args.FailureInformation; - throw new MembershipPasswordException("Reset password canceled due to password validation failure."); - } - - var m = Member.GetMemberFromLoginName(username); - if (m == null) - throw new MembershipPasswordException("The supplied user is not found"); - - if (RequiresQuestionAndAnswer) - { - // check if password answer property alias is set - if (string.IsNullOrEmpty(_passwordRetrievalAnswerPropertyTypeAlias) == false) - { - // check if user is locked out - if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) - { - var isLockedOut = false; - bool.TryParse(GetMemberProperty(m, LockPropertyTypeAlias, true), out isLockedOut); - if (isLockedOut) - { - throw new MembershipPasswordException("The supplied user is locked out"); - } - } - - // match password answer - if (GetMemberProperty(m, _passwordRetrievalAnswerPropertyTypeAlias, false) != answer) - { - throw new MembershipPasswordException("Incorrect password answer"); - } - } - else - { - throw new ProviderException("Password retrieval answer property alias is not set! To automatically support password question/answers you'll need to add references to the membertype properties in the 'Member' element in web.config by adding their aliases to the 'umbracoPasswordRetrievalQuestionPropertyTypeAlias' and 'umbracoPasswordRetrievalAnswerPropertyTypeAlias' attributes"); - } - } - - string salt; - var encodedPassword = EncryptOrHashNewPassword(newPassword, out salt); - //set the password on the member - m.ChangePassword( - FormatPasswordForStorage(encodedPassword, salt)); - - if (string.IsNullOrEmpty(_lastPasswordChangedPropertyTypeAlias) == false) - { - UpdateMemberProperty(m, _lastPasswordChangedPropertyTypeAlias, DateTime.Now); - } - - m.Save(); - - return newPassword; - } - - /// - /// Clears a lock so that the membership user can be validated. - /// - /// The membership user to clear the lock status for. - /// - /// true if the membership user was successfully unlocked; otherwise, false. - /// - public override bool UnlockUser(string userName) - { - if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) - { - var m = Member.GetMemberFromLoginName(userName); - if (m != null) - { - UpdateMemberProperty(m, LockPropertyTypeAlias, 0); - m.Save(); - return true; - } - throw new Exception(String.Format("No member with the username '{0}' found", userName)); - } - throw new ProviderException("To enable lock/unlocking, you need to add a 'bool' property on your membertype and add the alias of the property in the 'umbracoLockPropertyTypeAlias' attribute of the membership element in the web.config."); - } - - /// - /// Updates e-mail and potentially approved status, lock status and comment on a user. - /// - /// A object that represents the user to update and the updated information for the user. - public override void UpdateUser(MembershipUser user) - { - var m = Member.GetMemberFromLoginName(user.UserName); - m.Email = user.Email; - - // if supported, update approve status - UpdateMemberProperty(m, ApprovedPropertyTypeAlias, user.IsApproved ? 1 : 0); - - // if supported, update lock status - UpdateMemberProperty(m, LockPropertyTypeAlias, user.IsLockedOut ? 1 : 0); - if (user.IsLockedOut) - { - UpdateMemberProperty(m, _lastLockedOutPropertyTypeAlias, DateTime.Now); - } - - // if supported, update comment - UpdateMemberProperty(m, CommentPropertyTypeAlias, user.Comment); - - m.Save(); - } - - private static void UpdateMemberProperty(Member m, string propertyAlias, object propertyValue) - { - if (string.IsNullOrEmpty(propertyAlias) == false) - { - if (m.getProperty(propertyAlias) != null) - { - m.getProperty(propertyAlias).Value = propertyValue; - } - } - } - - private static string GetMemberProperty(Member m, string propertyAlias, bool isBool) - { - if (string.IsNullOrEmpty(propertyAlias) == false) - { - if (m.getProperty(propertyAlias) != null && - m.getProperty(propertyAlias).Value != null) - { - if (isBool) - { - // Umbraco stored true as 1, which means it can be bool.tryParse'd - return m.getProperty(propertyAlias).Value.ToString().Replace("1", "true").Replace("0", "false"); - } - return m.getProperty(propertyAlias).Value.ToString(); - } - } - - return null; - } - - private static string GetMemberProperty(IMember m, string propertyAlias, bool isBool) - { - if (string.IsNullOrEmpty(propertyAlias) == false) - { - if (m.Properties[propertyAlias] != null && - m.Properties[propertyAlias].Value != null) - { - if (isBool) - { - // Umbraco stored true as 1, which means it can be bool.tryParse'd - return m.Properties[propertyAlias].Value.ToString().Replace("1", "true").Replace("0", "false"); - } - return m.Properties[propertyAlias].Value.ToString(); - } - } - - return null; - } - - /// - /// Verifies that the specified user name and password exist in the data source. - /// - /// The name of the user to validate. - /// The password for the specified user. - /// - /// true if the specified username and password are valid; otherwise, false. - /// - public override bool ValidateUser(string username, string password) - { - var m = Member.GetMemberFromLoginAndEncodedPassword(username, EncryptOrHashExistingPassword(password)); - if (m != null) - { - // check for lock status. If locked, then set the member property to null - if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) - { - string lockedStatus = GetMemberProperty(m, LockPropertyTypeAlias, true); - if (string.IsNullOrEmpty(lockedStatus) == false) - { - var isLocked = false; - if (bool.TryParse(lockedStatus, out isLocked)) - { - if (isLocked) - { - m = null; - } - } - } - } - - //check for approve status. If not approved, then set the member property to null - if (m != null && !CheckApproveStatus(m)) { - m = null; - } - - // maybe update login date - if (m != null && string.IsNullOrEmpty(_lastLoginPropertyTypeAlias) == false) - { - UpdateMemberProperty(m, _lastLoginPropertyTypeAlias, DateTime.Now); - } - - // maybe reset password attempts - if (m != null && string.IsNullOrEmpty(_failedPasswordAttemptsPropertyTypeAlias) == false) - { - UpdateMemberProperty(m, _failedPasswordAttemptsPropertyTypeAlias, 0); - } - - // persist data - if (m != null) - m.Save(); - } - else if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false - && string.IsNullOrEmpty(_failedPasswordAttemptsPropertyTypeAlias) == false) - { - var updateMemberDataObject = Member.GetMemberFromLoginName(username); - // update fail rate if it's approved - if (updateMemberDataObject != null && CheckApproveStatus(updateMemberDataObject)) - { - int failedAttempts = 0; - int.TryParse(GetMemberProperty(updateMemberDataObject, _failedPasswordAttemptsPropertyTypeAlias, false), out failedAttempts); - failedAttempts = failedAttempts+1; - UpdateMemberProperty(updateMemberDataObject, _failedPasswordAttemptsPropertyTypeAlias, failedAttempts); - - // lock user? - if (failedAttempts >= MaxInvalidPasswordAttempts) - { - UpdateMemberProperty(updateMemberDataObject, LockPropertyTypeAlias, 1); - UpdateMemberProperty(updateMemberDataObject, _lastLockedOutPropertyTypeAlias, DateTime.Now); - } - updateMemberDataObject.Save(); - } - - } - return (m != null); - } - - private bool CheckApproveStatus(Member m) - { - var isApproved = false; - if (string.IsNullOrEmpty(ApprovedPropertyTypeAlias) == false) - { - if (m != null) - { - var approveStatus = GetMemberProperty(m, ApprovedPropertyTypeAlias, true); - if (string.IsNullOrEmpty(approveStatus) == false) - { - //try parsing as bool first (just in case) - if (bool.TryParse(approveStatus, out isApproved) == false) - { - int intStatus; - //if that fails, try parsing as int (since its normally stored as 0 or 1) - if (int.TryParse(approveStatus, out intStatus)) - { - isApproved = intStatus != 0; - } - } - } - else - { - //There is no property so we shouldn't use the approve status - isApproved = true; - } - } - } - else { - // if we don't use approve statuses - isApproved = true; - } - return isApproved; - } - #endregion - - #region Helper Methods - - /// - /// Checks the password. - /// - /// The password. - /// The dbPassword. - /// - internal bool CheckPassword(string password, string dbPassword) - { - string pass1 = password; - string pass2 = dbPassword; - - switch (PasswordFormat) - { - case MembershipPasswordFormat.Encrypted: - pass2 = DecodePassword(dbPassword); - break; - case MembershipPasswordFormat.Hashed: - pass1 = EncryptOrHashExistingPassword(password); - break; - default: - break; - } - return (pass1 == pass2) ? true : false; - } - - - /// - /// Encodes the password. - /// - /// The password. - /// The encoded password. - [Obsolete("Do not use this, it is the legacy way to encode a password")] - public string EncodePassword(string password) - { - return LegacyEncodePassword(password); - } - - /// - /// Unencode password. - /// - /// The encoded password. - /// The unencoded password. - [Obsolete("Do not use this, it is the legacy way to decode a password")] - public string UnEncodePassword(string encodedPassword) - { - return LegacyUnEncodePassword(encodedPassword); - } - - /// - /// Converts to membership user. - /// - /// The m. - /// - private MembershipUser ConvertToMembershipUser(Member m) - { - if (m == null) return null; - - var lastLogin = DateTime.Now; - var lastLocked = DateTime.MinValue; - var isApproved = true; - var isLocked = false; - var comment = ""; - var passwordQuestion = ""; - - // last login - if (string.IsNullOrEmpty(_lastLoginPropertyTypeAlias) == false) - { - DateTime.TryParse(GetMemberProperty(m, _lastLoginPropertyTypeAlias, false), out lastLogin); - } - // approved - if (string.IsNullOrEmpty(ApprovedPropertyTypeAlias) == false) - { - bool.TryParse(GetMemberProperty(m, ApprovedPropertyTypeAlias, true), out isApproved); - } - // locked - if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) - { - bool.TryParse(GetMemberProperty(m, LockPropertyTypeAlias, true), out isLocked); - } - // last locked - if (string.IsNullOrEmpty(_lastLockedOutPropertyTypeAlias) == false) - { - DateTime.TryParse(GetMemberProperty(m, _lastLockedOutPropertyTypeAlias, false), out lastLocked); - } - // comment - if (string.IsNullOrEmpty(CommentPropertyTypeAlias) == false) - { - comment = GetMemberProperty(m, CommentPropertyTypeAlias, false); - } - // password question - if (string.IsNullOrEmpty(_passwordRetrievalQuestionPropertyTypeAlias) == false) - { - passwordQuestion = GetMemberProperty(m, _passwordRetrievalQuestionPropertyTypeAlias, false); - } - - return new MembershipUser(_providerName, m.LoginName, m.Id, m.Email, passwordQuestion, comment, isApproved, isLocked, m.CreateDateTime, lastLogin, - DateTime.Now, DateTime.Now, lastLocked); - } - - /// - /// Converts to membership user. - /// - /// The m. - /// - private MembershipUser ConvertToMembershipUser(IMember m) - { - if (m == null) return null; - - var lastLogin = DateTime.Now; - var lastLocked = DateTime.MinValue; - var isApproved = true; - var isLocked = false; - var comment = ""; - var passwordQuestion = ""; - - // last login - if (string.IsNullOrEmpty(_lastLoginPropertyTypeAlias) == false) - { - DateTime.TryParse(GetMemberProperty(m, _lastLoginPropertyTypeAlias, false), out lastLogin); - } - // approved - if (string.IsNullOrEmpty(ApprovedPropertyTypeAlias) == false) - { - bool.TryParse(GetMemberProperty(m, ApprovedPropertyTypeAlias, true), out isApproved); - } - // locked - if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) - { - bool.TryParse(GetMemberProperty(m, LockPropertyTypeAlias, true), out isLocked); - } - // last locked - if (string.IsNullOrEmpty(_lastLockedOutPropertyTypeAlias) == false) - { - DateTime.TryParse(GetMemberProperty(m, _lastLockedOutPropertyTypeAlias, false), out lastLocked); - } - // comment - if (string.IsNullOrEmpty(CommentPropertyTypeAlias) == false) - { - comment = GetMemberProperty(m, CommentPropertyTypeAlias, false); - } - // password question - if (string.IsNullOrEmpty(_passwordRetrievalQuestionPropertyTypeAlias) == false) - { - passwordQuestion = GetMemberProperty(m, _passwordRetrievalQuestionPropertyTypeAlias, false); - } - - return new MembershipUser(_providerName, m.Username, m.Id, m.Email, passwordQuestion, comment, isApproved, isLocked, m.CreateDate, lastLogin, - DateTime.Now, DateTime.Now, lastLocked); - } - - #endregion - } -} +#region namespace +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Web.Security; +using System.Configuration; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Security; +using umbraco.BusinessLogic; +using System.Security.Cryptography; +using System.Web.Util; +using System.Collections.Specialized; +using System.Configuration.Provider; +using System.Security; +using System.Security.Permissions; +using System.Runtime.CompilerServices; +using Member = umbraco.cms.businesslogic.member.Member; +using MemberType = umbraco.cms.businesslogic.member.MemberType; + +#endregion + +namespace umbraco.providers.members +{ + /// + /// Custom Membership Provider for Umbraco Members (User authentication for Frontend applications NOT umbraco CMS) + /// + + public class UmbracoMembershipProvider : UmbracoMembershipProviderBase + { + #region Fields + + private string _providerName = Member.UmbracoMemberProviderName; + + #endregion + + /// + /// Override to maintain backwards compatibility with 0 required non-alphanumeric chars + /// + public override int DefaultMinNonAlphanumericChars + { + get { return 0; } + } + + /// + /// Override to maintain backwards compatibility with only 4 required length + /// + public override int DefaultMinPasswordLength + { + get { return 4; } + } + + /// + /// Override to maintain backwards compatibility + /// + public override bool DefaultUseLegacyEncoding + { + get { return true; } + } + + /// + /// For backwards compatibility, this provider supports this option + /// + public override bool AllowManuallyChangingPassword + { + get { return true; } + } + + #region Initialization Method + /// + /// Initializes the provider. + /// + /// The friendly name of the provider. + /// A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider. + /// The name of the provider is null. + /// An attempt is made to call + /// on a provider after the provider + /// has already been initialized. + /// The name of the provider has a length of zero. + public override void Initialize(string name, NameValueCollection config) + { + // Intialize values from web.config + if (config == null) throw new ArgumentNullException("config"); + + if (string.IsNullOrEmpty(name)) name = "UmbracoMembershipProvider"; + + base.Initialize(name, config); + + _providerName = name; + + // test for membertype (if not specified, choose the first member type available) + if (config["defaultMemberTypeAlias"] != null) + DefaultMemberTypeAlias = config["defaultMemberTypeAlias"]; + else if (MemberType.GetAll.Length == 1) + DefaultMemberTypeAlias = MemberType.GetAll[0].Alias; + else + throw new ProviderException("No default MemberType alias is specified in the web.config string. Please add a 'defaultMemberTypeAlias' to the add element in the provider declaration in web.config"); + + // test for approve status + if (config["umbracoApprovePropertyTypeAlias"] != null) + { + ApprovedPropertyTypeAlias = config["umbracoApprovePropertyTypeAlias"]; + } + // test for lock attempts + if (config["umbracoLockPropertyTypeAlias"] != null) + { + LockPropertyTypeAlias = config["umbracoLockPropertyTypeAlias"]; + } + if (config["umbracoLastLockedPropertyTypeAlias"] != null) + { + LastLockedOutPropertyTypeAlias = config["umbracoLastLockedPropertyTypeAlias"]; + } + if (config["umbracoLastPasswordChangedPropertyTypeAlias"] != null) + { + LastPasswordChangedPropertyTypeAlias = config["umbracoLastPasswordChangedPropertyTypeAlias"]; + } + if (config["umbracoFailedPasswordAttemptsPropertyTypeAlias"] != null) + { + FailedPasswordAttemptsPropertyTypeAlias = config["umbracoFailedPasswordAttemptsPropertyTypeAlias"]; + } + // comment property + if (config["umbracoCommentPropertyTypeAlias"] != null) + { + CommentPropertyTypeAlias = config["umbracoCommentPropertyTypeAlias"]; + } + // last login date + if (config["umbracoLastLoginPropertyTypeAlias"] != null) + { + LastLoginPropertyTypeAlias = config["umbracoLastLoginPropertyTypeAlias"]; + } + // password retrieval + if (config["umbracoPasswordRetrievalQuestionPropertyTypeAlias"] != null) + { + PasswordRetrievalQuestionPropertyTypeAlias = config["umbracoPasswordRetrievalQuestionPropertyTypeAlias"]; + } + if (config["umbracoPasswordRetrievalAnswerPropertyTypeAlias"] != null) + { + PasswordRetrievalAnswerPropertyTypeAlias = config["umbracoPasswordRetrievalAnswerPropertyTypeAlias"]; + } + + } + #endregion + + #region Methods + + /// + /// Processes a request to update the password for a membership user. + /// + /// The user to update the password for. + /// This property is ignore for this provider + /// The new password for the specified user. + /// + /// true if the password was updated successfully; otherwise, false. + /// + protected override bool PerformChangePassword(string username, string oldPassword, string newPassword) + { + //NOTE: due to backwards compatibilty reasons, this provider doesn't care about the old password and + // allows simply setting the password manually so we don't really care about the old password. + // This is allowed based on the overridden AllowManuallyChangingPassword option. + + // in order to support updating passwords from the umbraco core, we can't validate the old password + var m = Member.GetMemberFromLoginName(username); + if (m == null) return false; + + var args = new ValidatePasswordEventArgs(username, newPassword, false); + OnValidatingPassword(args); + + if (args.Cancel) + { + if (args.FailureInformation != null) + throw args.FailureInformation; + throw new MembershipPasswordException("Change password canceled due to password validation failure."); + } + + string salt; + var encodedPassword = EncryptOrHashNewPassword(newPassword, out salt); + m.ChangePassword( + FormatPasswordForStorage(encodedPassword, salt)); + + UpdateMemberProperty(m, LastPasswordChangedPropertyTypeAlias, DateTime.Now); + + m.Save(); + + return true; + } + + /// + /// Processes a request to update the password question and answer for a membership user. + /// + /// The user to change the password question and answer for. + /// The password for the specified user. + /// The new password question for the specified user. + /// The new password answer for the specified user. + /// + /// true if the password question and answer are updated successfully; otherwise, false. + /// + protected override bool PerformChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) + { + var m = Member.GetMemberFromLoginName(username); + if (m == null) + { + return false; + } + + UpdateMemberProperty(m, PasswordRetrievalQuestionPropertyTypeAlias, newPasswordQuestion); + UpdateMemberProperty(m, PasswordRetrievalAnswerPropertyTypeAlias, newPasswordAnswer); + m.Save(); + return true; + } + + /// + /// Adds a new membership user to the data source. + /// + /// + /// The user name for the new user. + /// The password for the new user. + /// The e-mail address for the new user. + /// The password question for the new user. + /// The password answer for the new user + /// Whether or not the new user is approved to be validated. + /// The unique identifier from the membership data source for the user. + /// A enumeration value indicating whether the user was created successfully. + /// + /// A object populated with the information for the newly created user. + /// + protected override MembershipUser PerformCreateUser(string memberTypeAlias, string username, string password, string email, string passwordQuestion, + string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) + { + if (Member.GetMemberFromLoginName(username) != null) + { + status = MembershipCreateStatus.DuplicateUserName; + LogHelper.Warn("Cannot create member as username already exists: " + username); + return null; + } + + if (Member.GetMemberFromEmail(email) != null && RequiresUniqueEmail) + { + status = MembershipCreateStatus.DuplicateEmail; + LogHelper.Warn( + "Cannot create member as a member with the same email address exists: " + email); + return null; + } + + var memberType = MemberType.GetByAlias(memberTypeAlias); + if (memberType == null) + { + throw new InvalidOperationException("Could not find a member type with alias " + memberTypeAlias + ". Ensure your membership provider configuration is up to date and that the default member type exists."); + } + + var m = Member.MakeNew(username, email, memberType, User.GetUser(0)); + + string salt; + var encodedPassword = EncryptOrHashNewPassword(password, out salt); + + //set the password on the member + m.ChangePassword(FormatPasswordForStorage(encodedPassword, salt)); + + // custom fields + if (string.IsNullOrEmpty(PasswordRetrievalQuestionPropertyTypeAlias) == false) + { + UpdateMemberProperty(m, PasswordRetrievalQuestionPropertyTypeAlias, passwordQuestion); + } + + if (string.IsNullOrEmpty(PasswordRetrievalAnswerPropertyTypeAlias) == false) + { + UpdateMemberProperty(m, PasswordRetrievalAnswerPropertyTypeAlias, passwordAnswer); + } + + if (string.IsNullOrEmpty(ApprovedPropertyTypeAlias) == false) + { + UpdateMemberProperty(m, ApprovedPropertyTypeAlias, isApproved ? 1 : 0); + } + + if (string.IsNullOrEmpty(LastLoginPropertyTypeAlias) == false) + { + UpdateMemberProperty(m, LastLoginPropertyTypeAlias, DateTime.Now); + } + + if (string.IsNullOrEmpty(LastPasswordChangedPropertyTypeAlias) == false) + { + UpdateMemberProperty(m, LastPasswordChangedPropertyTypeAlias, DateTime.Now); + } + + var mUser = ConvertToMembershipUser(m); + + // save + m.Save(); + + status = MembershipCreateStatus.Success; + + return mUser; + } + + + /// + /// Removes a user from the membership data source. + /// + /// The name of the user to delete. + /// + /// TODO: This setting currently has no effect + /// + /// + /// true if the user was successfully deleted; otherwise, false. + /// + public override bool DeleteUser(string username, bool deleteAllRelatedData) + { + var m = Member.GetMemberFromLoginName(username); + if (m == null) return false; + m.delete(); + return true; + } + + /// + /// Gets a collection of membership users where the e-mail address contains the specified e-mail address to match. + /// + /// The e-mail address to search for. + /// The index of the page of results to return. pageIndex is zero-based. + /// The size of the page of results to return. + /// The total number of matched users. + /// + /// A collection that contains a page of pageSize objects beginning at the page specified by pageIndex. + /// + public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) + { + var byEmail = ApplicationContext.Current.Services.MemberService.FindMembersByEmail(emailToMatch, StringPropertyMatchType.Wildcard).ToArray(); + totalRecords = byEmail.Length; + var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); + + var collection = new MembershipUserCollection(); + foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) + { + collection.Add(ConvertToMembershipUser(m)); + } + return collection; + } + + /// + /// Gets a collection of membership users where the user name contains the specified user name to match. + /// + /// The user name to search for. + /// The index of the page of results to return. pageIndex is zero-based. + /// The size of the page of results to return. + /// The total number of matched users. + /// + /// A collection that contains a page of pageSize objects beginning at the page specified by pageIndex. + /// + public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) + { + var byEmail = ApplicationContext.Current.Services.MemberService.FindMembersByUsername(usernameToMatch, StringPropertyMatchType.Wildcard).ToArray(); + totalRecords = byEmail.Length; + var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); + + var collection = new MembershipUserCollection(); + foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) + { + collection.Add(ConvertToMembershipUser(m)); + } + return collection; + } + + /// + /// Gets a collection of all the users in the data source in pages of data. + /// + /// The index of the page of results to return. pageIndex is zero-based. + /// The size of the page of results to return. + /// The total number of matched users. + /// + /// A collection that contains a page of pageSize objects beginning at the page specified by pageIndex. + /// + public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords) + { + var counter = 0; + var startIndex = pageSize * pageIndex; + var endIndex = startIndex + pageSize - 1; + var membersList = new MembershipUserCollection(); + var memberArray = Member.GetAll; + totalRecords = memberArray.Length; + + foreach (var m in memberArray) + { + if (counter >= startIndex) + membersList.Add(ConvertToMembershipUser(m)); + if (counter >= endIndex) break; + counter++; + } + return membersList; + + } + + /// + /// Gets the number of users currently accessing the application. + /// + /// + /// The number of users currently accessing the application. + /// + public override int GetNumberOfUsersOnline() + { + return Member.CachedMembers().Count; + } + + /// + /// Gets the password for the specified user name from the data source. + /// + /// The user to retrieve the password for. + /// The password answer for the user. + /// + /// The password for the specified user name. + /// + public override string GetPassword(string username, string answer) + { + if (EnablePasswordRetrieval == false) + throw new ProviderException("Password Retrieval Not Enabled."); + + if (PasswordFormat == MembershipPasswordFormat.Hashed) + throw new ProviderException("Cannot retrieve Hashed passwords."); + + var m = Member.GetMemberFromLoginName(username); + if (m != null) + { + if (RequiresQuestionAndAnswer) + { + // check if password answer property alias is set + if (string.IsNullOrEmpty(PasswordRetrievalAnswerPropertyTypeAlias) == false) + { + // check if user is locked out + if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) + { + var isLockedOut = false; + bool.TryParse(GetMemberProperty(m, LockPropertyTypeAlias, true), out isLockedOut); + if (isLockedOut) + { + throw new MembershipPasswordException("The supplied user is locked out"); + } + } + + // match password answer + if (GetMemberProperty(m, PasswordRetrievalAnswerPropertyTypeAlias, false) != answer) + { + throw new MembershipPasswordException("Incorrect password answer"); + } + } + else + { + throw new ProviderException("Password retrieval answer property alias is not set! To automatically support password question/answers you'll need to add references to the membertype properties in the 'Member' element in web.config by adding their aliases to the 'umbracoPasswordRetrievalQuestionPropertyTypeAlias' and 'umbracoPasswordRetrievalAnswerPropertyTypeAlias' attributes"); + } + } + } + if (m == null) + { + throw new MembershipPasswordException("The supplied user is not found"); + } + return m.GetPassword(); + } + + /// + /// Gets information from the data source for a user. Provides an option to update the last-activity date/time stamp for the user. + /// + /// The name of the user to get information for. + /// true to update the last-activity date/time stamp for the user; false to return user information without updating the last-activity date/time stamp for the user. + /// + /// A object populated with the specified user's information from the data source. + /// + public override MembershipUser GetUser(string username, bool userIsOnline) + { + if (String.IsNullOrEmpty(username)) + return null; + Member m = Member.GetMemberFromLoginName(username); + if (m == null) return null; + else return ConvertToMembershipUser(m); + } + + /// + /// Gets information from the data source for a user based on the unique identifier for the membership user. Provides an option to update the last-activity date/time stamp for the user. + /// + /// The unique identifier for the membership user to get information for. + /// true to update the last-activity date/time stamp for the user; false to return user information without updating the last-activity date/time stamp for the user. + /// + /// A object populated with the specified user's information from the data source. + /// + public override MembershipUser GetUser(object providerUserKey, bool userIsOnline) + { + var asGuid = providerUserKey.TryConvertTo(); + if (asGuid.Success) + { + var m = new Member(asGuid.Result); + return ConvertToMembershipUser(m); + } + var asInt = providerUserKey.TryConvertTo(); + if (asInt.Success) + { + var m = new Member(asInt.Result); + return ConvertToMembershipUser(m); + } + throw new InvalidOperationException("The " + GetType() + " provider only supports GUID or Int as a providerUserKey"); + + } + + + /// + /// Gets the user name associated with the specified e-mail address. + /// + /// The e-mail address to search for. + /// + /// The user name associated with the specified e-mail address. If no match is found, return null. + /// + public override string GetUserNameByEmail(string email) + { + Member m = Member.GetMemberFromEmail(email); + return m == null ? null : m.LoginName; + } + + /// + /// Resets a user's password to a new, automatically generated password. + /// + /// The user to reset the password for. + /// The password answer for the specified user (not used with Umbraco). + /// The new password for the specified user. + public override string ResetPassword(string username, string answer) + { + if (EnablePasswordReset == false) + { + throw new NotSupportedException("Password reset is not supported"); + } + + //TODO: This should be here - but how do we update failure count in this provider?? + //if (answer == null && RequiresQuestionAndAnswer) + //{ + // UpdateFailureCount(username, "passwordAnswer"); + + // throw new ProviderException("Password answer required for password reset."); + //} + + var newPassword = Membership.GeneratePassword(MinRequiredPasswordLength, MinRequiredNonAlphanumericCharacters); + + var args = new ValidatePasswordEventArgs(username, newPassword, true); + OnValidatingPassword(args); + if (args.Cancel) + { + if (args.FailureInformation != null) + throw args.FailureInformation; + throw new MembershipPasswordException("Reset password canceled due to password validation failure."); + } + + var m = Member.GetMemberFromLoginName(username); + if (m == null) + throw new MembershipPasswordException("The supplied user is not found"); + + if (RequiresQuestionAndAnswer) + { + // check if password answer property alias is set + if (string.IsNullOrEmpty(PasswordRetrievalAnswerPropertyTypeAlias) == false) + { + // check if user is locked out + if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) + { + var isLockedOut = false; + bool.TryParse(GetMemberProperty(m, LockPropertyTypeAlias, true), out isLockedOut); + if (isLockedOut) + { + throw new MembershipPasswordException("The supplied user is locked out"); + } + } + + // match password answer + if (GetMemberProperty(m, PasswordRetrievalAnswerPropertyTypeAlias, false) != answer) + { + throw new MembershipPasswordException("Incorrect password answer"); + } + } + else + { + throw new ProviderException("Password retrieval answer property alias is not set! To automatically support password question/answers you'll need to add references to the membertype properties in the 'Member' element in web.config by adding their aliases to the 'umbracoPasswordRetrievalQuestionPropertyTypeAlias' and 'umbracoPasswordRetrievalAnswerPropertyTypeAlias' attributes"); + } + } + + string salt; + var encodedPassword = EncryptOrHashNewPassword(newPassword, out salt); + //set the password on the member + m.ChangePassword( + FormatPasswordForStorage(encodedPassword, salt)); + + if (string.IsNullOrEmpty(LastPasswordChangedPropertyTypeAlias) == false) + { + UpdateMemberProperty(m, LastPasswordChangedPropertyTypeAlias, DateTime.Now); + } + + m.Save(); + + return newPassword; + } + + /// + /// Clears a lock so that the membership user can be validated. + /// + /// The membership user to clear the lock status for. + /// + /// true if the membership user was successfully unlocked; otherwise, false. + /// + public override bool UnlockUser(string userName) + { + if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) + { + var m = Member.GetMemberFromLoginName(userName); + if (m != null) + { + UpdateMemberProperty(m, LockPropertyTypeAlias, 0); + m.Save(); + return true; + } + throw new Exception(String.Format("No member with the username '{0}' found", userName)); + } + throw new ProviderException("To enable lock/unlocking, you need to add a 'bool' property on your membertype and add the alias of the property in the 'umbracoLockPropertyTypeAlias' attribute of the membership element in the web.config."); + } + + /// + /// Updates e-mail and potentially approved status, lock status and comment on a user. + /// + /// A object that represents the user to update and the updated information for the user. + public override void UpdateUser(MembershipUser user) + { + var m = Member.GetMemberFromLoginName(user.UserName); + m.Email = user.Email; + + // if supported, update approve status + UpdateMemberProperty(m, ApprovedPropertyTypeAlias, user.IsApproved ? 1 : 0); + + // if supported, update lock status + UpdateMemberProperty(m, LockPropertyTypeAlias, user.IsLockedOut ? 1 : 0); + if (user.IsLockedOut) + { + UpdateMemberProperty(m, LastLockedOutPropertyTypeAlias, DateTime.Now); + } + + // if supported, update comment + UpdateMemberProperty(m, CommentPropertyTypeAlias, user.Comment); + + m.Save(); + } + + private static void UpdateMemberProperty(Member m, string propertyTypeAlias, object propertyValue) + { + if (string.IsNullOrEmpty(propertyTypeAlias) == false) + { + if (m.getProperty(propertyTypeAlias) != null) + { + m.getProperty(propertyTypeAlias).Value = propertyValue; + } + } + } + + private static string GetMemberProperty(Member m, string propertyTypeAlias, bool isBool) + { + if (string.IsNullOrEmpty(propertyTypeAlias) == false) + { + if (m.getProperty(propertyTypeAlias) != null && + m.getProperty(propertyTypeAlias).Value != null) + { + if (isBool) + { + // Umbraco stored true as 1, which means it can be bool.tryParse'd + return m.getProperty(propertyTypeAlias).Value.ToString().Replace("1", "true").Replace("0", "false"); + } + return m.getProperty(propertyTypeAlias).Value.ToString(); + } + } + + return null; + } + + private static string GetMemberProperty(IMember m, string propertyTypeAlias, bool isBool) + { + if (string.IsNullOrEmpty(propertyTypeAlias) == false) + { + if (m.Properties[propertyTypeAlias] != null && + m.Properties[propertyTypeAlias].Value != null) + { + if (isBool) + { + // Umbraco stored true as 1, which means it can be bool.tryParse'd + return m.Properties[propertyTypeAlias].Value.ToString().Replace("1", "true").Replace("0", "false"); + } + return m.Properties[propertyTypeAlias].Value.ToString(); + } + } + + return null; + } + + /// + /// Verifies that the specified user name and password exist in the data source. + /// + /// The name of the user to validate. + /// The password for the specified user. + /// + /// true if the specified username and password are valid; otherwise, false. + /// + public override bool ValidateUser(string username, string password) + { + var m = Member.GetMemberFromLoginAndEncodedPassword(username, EncryptOrHashExistingPassword(password)); + if (m != null) + { + // check for lock status. If locked, then set the member property to null + if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) + { + string lockedStatus = GetMemberProperty(m, LockPropertyTypeAlias, true); + if (string.IsNullOrEmpty(lockedStatus) == false) + { + var isLocked = false; + if (bool.TryParse(lockedStatus, out isLocked)) + { + if (isLocked) + { + m = null; + } + } + } + } + + //check for approve status. If not approved, then set the member property to null + if (m != null && !CheckApproveStatus(m)) { + m = null; + } + + // maybe update login date + if (m != null && string.IsNullOrEmpty(LastLoginPropertyTypeAlias) == false) + { + UpdateMemberProperty(m, LastLoginPropertyTypeAlias, DateTime.Now); + } + + // maybe reset password attempts + if (m != null && string.IsNullOrEmpty(FailedPasswordAttemptsPropertyTypeAlias) == false) + { + UpdateMemberProperty(m, FailedPasswordAttemptsPropertyTypeAlias, 0); + } + + // persist data + if (m != null) + m.Save(); + } + else if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false + && string.IsNullOrEmpty(FailedPasswordAttemptsPropertyTypeAlias) == false) + { + var updateMemberDataObject = Member.GetMemberFromLoginName(username); + // update fail rate if it's approved + if (updateMemberDataObject != null && CheckApproveStatus(updateMemberDataObject)) + { + int failedAttempts = 0; + int.TryParse(GetMemberProperty(updateMemberDataObject, FailedPasswordAttemptsPropertyTypeAlias, false), out failedAttempts); + failedAttempts = failedAttempts+1; + UpdateMemberProperty(updateMemberDataObject, FailedPasswordAttemptsPropertyTypeAlias, failedAttempts); + + // lock user? + if (failedAttempts >= MaxInvalidPasswordAttempts) + { + UpdateMemberProperty(updateMemberDataObject, LockPropertyTypeAlias, 1); + UpdateMemberProperty(updateMemberDataObject, LastLockedOutPropertyTypeAlias, DateTime.Now); + } + updateMemberDataObject.Save(); + } + + } + return (m != null); + } + + private bool CheckApproveStatus(Member m) + { + var isApproved = false; + if (string.IsNullOrEmpty(ApprovedPropertyTypeAlias) == false) + { + if (m != null) + { + var approveStatus = GetMemberProperty(m, ApprovedPropertyTypeAlias, true); + if (string.IsNullOrEmpty(approveStatus) == false) + { + //try parsing as bool first (just in case) + if (bool.TryParse(approveStatus, out isApproved) == false) + { + int intStatus; + //if that fails, try parsing as int (since its normally stored as 0 or 1) + if (int.TryParse(approveStatus, out intStatus)) + { + isApproved = intStatus != 0; + } + } + } + else + { + //There is no property so we shouldn't use the approve status + isApproved = true; + } + } + } + else { + // if we don't use approve statuses + isApproved = true; + } + return isApproved; + } + #endregion + + #region Helper Methods + + /// + /// Checks the password. + /// + /// The password. + /// The dbPassword. + /// + internal bool CheckPassword(string password, string dbPassword) + { + string pass1 = password; + string pass2 = dbPassword; + + switch (PasswordFormat) + { + case MembershipPasswordFormat.Encrypted: + pass2 = DecodePassword(dbPassword); + break; + case MembershipPasswordFormat.Hashed: + pass1 = EncryptOrHashExistingPassword(password); + break; + default: + break; + } + return (pass1 == pass2) ? true : false; + } + + + /// + /// Encodes the password. + /// + /// The password. + /// The encoded password. + [Obsolete("Do not use this, it is the legacy way to encode a password")] + public string EncodePassword(string password) + { + return LegacyEncodePassword(password); + } + + /// + /// Unencode password. + /// + /// The encoded password. + /// The unencoded password. + [Obsolete("Do not use this, it is the legacy way to decode a password")] + public string UnEncodePassword(string encodedPassword) + { + return LegacyUnEncodePassword(encodedPassword); + } + + /// + /// Converts to membership user. + /// + /// The m. + /// + private MembershipUser ConvertToMembershipUser(Member m) + { + if (m == null) return null; + + var lastLogin = DateTime.Now; + var lastLocked = DateTime.MinValue; + var isApproved = true; + var isLocked = false; + var comment = ""; + var passwordQuestion = ""; + + // last login + if (string.IsNullOrEmpty(LastLoginPropertyTypeAlias) == false) + { + DateTime.TryParse(GetMemberProperty(m, LastLoginPropertyTypeAlias, false), out lastLogin); + } + // approved + if (string.IsNullOrEmpty(ApprovedPropertyTypeAlias) == false) + { + bool.TryParse(GetMemberProperty(m, ApprovedPropertyTypeAlias, true), out isApproved); + } + // locked + if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) + { + bool.TryParse(GetMemberProperty(m, LockPropertyTypeAlias, true), out isLocked); + } + // last locked + if (string.IsNullOrEmpty(LastLockedOutPropertyTypeAlias) == false) + { + DateTime.TryParse(GetMemberProperty(m, LastLockedOutPropertyTypeAlias, false), out lastLocked); + } + // comment + if (string.IsNullOrEmpty(CommentPropertyTypeAlias) == false) + { + comment = GetMemberProperty(m, CommentPropertyTypeAlias, false); + } + // password question + if (string.IsNullOrEmpty(PasswordRetrievalQuestionPropertyTypeAlias) == false) + { + passwordQuestion = GetMemberProperty(m, PasswordRetrievalQuestionPropertyTypeAlias, false); + } + + return new MembershipUser(_providerName, m.LoginName, m.Id, m.Email, passwordQuestion, comment, isApproved, isLocked, m.CreateDateTime, lastLogin, + DateTime.Now, DateTime.Now, lastLocked); + } + + /// + /// Converts to membership user. + /// + /// The m. + /// + private MembershipUser ConvertToMembershipUser(IMember m) + { + if (m == null) return null; + + var lastLogin = DateTime.Now; + var lastLocked = DateTime.MinValue; + var isApproved = true; + var isLocked = false; + var comment = ""; + var passwordQuestion = ""; + + // last login + if (string.IsNullOrEmpty(LastLoginPropertyTypeAlias) == false) + { + DateTime.TryParse(GetMemberProperty(m, LastLoginPropertyTypeAlias, false), out lastLogin); + } + // approved + if (string.IsNullOrEmpty(ApprovedPropertyTypeAlias) == false) + { + bool.TryParse(GetMemberProperty(m, ApprovedPropertyTypeAlias, true), out isApproved); + } + // locked + if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) + { + bool.TryParse(GetMemberProperty(m, LockPropertyTypeAlias, true), out isLocked); + } + // last locked + if (string.IsNullOrEmpty(LastLockedOutPropertyTypeAlias) == false) + { + DateTime.TryParse(GetMemberProperty(m, LastLockedOutPropertyTypeAlias, false), out lastLocked); + } + // comment + if (string.IsNullOrEmpty(CommentPropertyTypeAlias) == false) + { + comment = GetMemberProperty(m, CommentPropertyTypeAlias, false); + } + // password question + if (string.IsNullOrEmpty(PasswordRetrievalQuestionPropertyTypeAlias) == false) + { + passwordQuestion = GetMemberProperty(m, PasswordRetrievalQuestionPropertyTypeAlias, false); + } + + return new MembershipUser(_providerName, m.Username, m.Id, m.Email, passwordQuestion, comment, isApproved, isLocked, m.CreateDate, lastLogin, + DateTime.Now, DateTime.Now, lastLocked); + } + + #endregion + } +} diff --git a/src/umbraco.providers/members/MembersProfileProvider.cs b/src/umbraco.providers/members/UmbracoProfileProvider.cs similarity index 69% rename from src/umbraco.providers/members/MembersProfileProvider.cs rename to src/umbraco.providers/members/UmbracoProfileProvider.cs index bd8f6a5ce7..4d1ac06ea3 100644 --- a/src/umbraco.providers/members/MembersProfileProvider.cs +++ b/src/umbraco.providers/members/UmbracoProfileProvider.cs @@ -1,159 +1,187 @@ -#region namespace -using System; -using System.Collections.Generic; -using System.Text; -using System.Web.Security; -using System.Configuration; -using umbraco.BusinessLogic; -using System.Security.Cryptography; -using System.Web.Util; -using System.Collections.Specialized; -using System.Configuration.Provider; -using umbraco.cms.businesslogic; -using umbraco.cms.businesslogic.member; -using System.Collections; -using System.Web.Profile; -#endregion - -namespace umbraco.providers.members { - public class UmbracoProfileProvider : ProfileProvider { - - private string m_ApplicationName = ""; - - public override string ApplicationName { - get { - return m_ApplicationName; - } - set { - m_ApplicationName = value; - } - } - public override string Description { - get { - return "Profile Provider for umbraco member profile data"; - } - } - public override string Name { - get { - return base.Name; - } - } - public override void Initialize(string name, NameValueCollection config) { - - if (config == null) - throw new ArgumentNullException("Null configuration parameters"); - - if (String.IsNullOrEmpty(name)) - name = "UmbracoProfileProvider"; - - base.Initialize(name, config); - - m_ApplicationName = config["applicationName"]; - if (String.IsNullOrEmpty(m_ApplicationName)) - m_ApplicationName = System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath; - config.Remove("applicationName"); - - // if the config element contains unused parameters we should throw an exception - if (config.Count > 0) { - string attrib = config.GetKey(0); - if (!String.IsNullOrEmpty(attrib)) - throw new ProviderException(String.Format("Unrecognized attribute: {0}", attrib)); - } - - - } - - public override int DeleteInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate) { - throw new NotSupportedException(); - } - - public override int DeleteProfiles(string[] usernames) { - throw new NotSupportedException(); - } - - public override int DeleteProfiles(ProfileInfoCollection profiles) { - throw new NotSupportedException(); - } - - public override ProfileInfoCollection FindInactiveProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords) { - throw new NotSupportedException(); - } - - public override ProfileInfoCollection FindProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) { - throw new NotSupportedException(); - } - - public override ProfileInfoCollection GetAllInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords) { - throw new NotSupportedException(); - } - public override ProfileInfoCollection GetAllProfiles(ProfileAuthenticationOption authenticationOption, int pageIndex, int pageSize, out int totalRecords) { - throw new NotSupportedException(); - } - public override int GetNumberOfInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate) { - throw new NotSupportedException(); - } - /// - /// Returns the collection of settings property values for the current umbraco member. - /// - /// A describing the current application use. - /// A containing the settings property group whose values are to be retrieved. - /// - /// A containing the values for the specified settings property group. - /// - public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection) { - SettingsPropertyValueCollection settings = new SettingsPropertyValueCollection(); - - if (collection.Count == 0) - return settings; - - foreach(SettingsProperty property in collection){ - SettingsPropertyValue pv = new SettingsPropertyValue(property); - settings.Add(pv); - } - - // get the current user - string username = (string)context["UserName"]; - Member m = Member.GetMemberFromLoginName(username); - if (m == null) - throw new ProviderException(String.Format("No member with username '{0}' exists", username)); - - foreach (SettingsPropertyValue spv in settings) { - if (m.getProperty(spv.Name) != null) { - spv.Deserialized = true; - spv.PropertyValue = m.getProperty(spv.Name).Value; - } - } - - return settings; - - } - - /// - /// Sets the values of the specified group of property settings for the current umbraco member. - /// - /// A describing the current application usage. - /// A representing the group of property settings to set. - public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection) { - - string username = (string)context["UserName"]; - bool authenticated = (bool)context["IsAuthenticated"]; - - if (String.IsNullOrEmpty(username) || collection.Count == 0) - return; - - Member m = Member.GetMemberFromLoginName(username); - if (m == null) - throw new ProviderException(String.Format("No member with username '{0}' exists", username)); - - - foreach (SettingsPropertyValue spv in collection) { - if (!authenticated && !(bool)spv.Property.Attributes["AllowAnonymous"]) - continue; - - if (m.getProperty(spv.Name) != null) - m.getProperty(spv.Name).Value = spv.PropertyValue; - } - m.Save(); - } - } -} +#region namespace +using System; +using System.Collections.Generic; +using System.Text; +using System.Web.Security; +using System.Configuration; +using umbraco.BusinessLogic; +using System.Security.Cryptography; +using System.Web.Util; +using System.Collections.Specialized; +using System.Configuration.Provider; +using umbraco.cms.businesslogic; +using umbraco.cms.businesslogic.member; +using System.Collections; +using System.Web.Profile; +#endregion + +namespace umbraco.providers.members +{ + public class UmbracoProfileProvider : ProfileProvider + { + + private string _applicationName = ""; + + public override string ApplicationName + { + get + { + return _applicationName; + } + set + { + _applicationName = value; + } + } + public override string Description + { + get + { + return "Profile Provider for umbraco member profile data"; + } + } + public override string Name + { + get + { + return base.Name; + } + } + public override void Initialize(string name, NameValueCollection config) + { + + if (config == null) + throw new ArgumentNullException("Null configuration parameters"); + + if (String.IsNullOrEmpty(name)) + name = "UmbracoProfileProvider"; + + base.Initialize(name, config); + + _applicationName = config["applicationName"]; + if (String.IsNullOrEmpty(_applicationName)) + _applicationName = System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath; + config.Remove("applicationName"); + + // if the config element contains unused parameters we should throw an exception + if (config.Count > 0) + { + var attrib = config.GetKey(0); + if (string.IsNullOrEmpty(attrib) == false) + { + throw new ProviderException(String.Format("Unrecognized attribute: {0}", attrib)); + } + + } + + + } + + public override int DeleteInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate) + { + throw new NotSupportedException(); + } + + public override int DeleteProfiles(string[] usernames) + { + throw new NotSupportedException(); + } + + public override int DeleteProfiles(ProfileInfoCollection profiles) + { + throw new NotSupportedException(); + } + + public override ProfileInfoCollection FindInactiveProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords) + { + throw new NotSupportedException(); + } + + public override ProfileInfoCollection FindProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) + { + throw new NotSupportedException(); + } + + public override ProfileInfoCollection GetAllInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords) + { + throw new NotSupportedException(); + } + public override ProfileInfoCollection GetAllProfiles(ProfileAuthenticationOption authenticationOption, int pageIndex, int pageSize, out int totalRecords) + { + throw new NotSupportedException(); + } + public override int GetNumberOfInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate) + { + throw new NotSupportedException(); + } + /// + /// Returns the collection of settings property values for the current umbraco member. + /// + /// A describing the current application use. + /// A containing the settings property group whose values are to be retrieved. + /// + /// A containing the values for the specified settings property group. + /// + public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection) + { + var settings = new SettingsPropertyValueCollection(); + + if (collection.Count == 0) + return settings; + + foreach (SettingsProperty property in collection) + { + var pv = new SettingsPropertyValue(property); + settings.Add(pv); + } + + // get the current user + var username = (string)context["UserName"]; + var m = Member.GetMemberFromLoginName(username); + if (m == null) + throw new ProviderException(String.Format("No member with username '{0}' exists", username)); + + foreach (SettingsPropertyValue spv in settings) + { + if (m.getProperty(spv.Name) != null) + { + spv.Deserialized = true; + spv.PropertyValue = m.getProperty(spv.Name).Value; + } + } + + return settings; + + } + + /// + /// Sets the values of the specified group of property settings for the current umbraco member. + /// + /// A describing the current application usage. + /// A representing the group of property settings to set. + public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection) + { + + var username = (string)context["UserName"]; + var authenticated = (bool)context["IsAuthenticated"]; + + if (string.IsNullOrEmpty(username) || collection.Count == 0) + return; + + var m = Member.GetMemberFromLoginName(username); + if (m == null) + throw new ProviderException(String.Format("No member with username '{0}' exists", username)); + + + foreach (SettingsPropertyValue spv in collection) + { + if (authenticated == false && (bool)spv.Property.Attributes["AllowAnonymous"] == false) + continue; + + if (m.getProperty(spv.Name) != null) + m.getProperty(spv.Name).Value = spv.PropertyValue; + } + m.Save(); + } + } +} diff --git a/src/umbraco.providers/members/MembersRoleProvider.cs b/src/umbraco.providers/members/UmbracoRoleProvider.cs similarity index 97% rename from src/umbraco.providers/members/MembersRoleProvider.cs rename to src/umbraco.providers/members/UmbracoRoleProvider.cs index 41a6da6568..bbae8aabfa 100644 --- a/src/umbraco.providers/members/MembersRoleProvider.cs +++ b/src/umbraco.providers/members/UmbracoRoleProvider.cs @@ -1,253 +1,256 @@ -#region namespace -using System; -using System.Collections.Generic; -using System.Text; -using System.Web.Security; -using System.Configuration; -using umbraco.BusinessLogic; -using System.Security.Cryptography; -using System.Web.Util; -using System.Collections.Specialized; -using System.Configuration.Provider; -using umbraco.cms.businesslogic; -using umbraco.cms.businesslogic.member; -using System.Collections; -#endregion - -namespace umbraco.providers.members { - public class UmbracoRoleProvider : RoleProvider { - #region - private string _ApplicationName = Member.UmbracoRoleProviderName; - #endregion - - #region Properties - /// - /// Gets or sets the name of the application to store and retrieve role information for. - /// - /// - /// The name of the application to store and retrieve role information for. - public override string ApplicationName { - get { - return _ApplicationName; - } - set { - if (string.IsNullOrEmpty(value)) - throw new ProviderException("ApplicationName cannot be empty."); - - if (value.Length > 0x100) - throw new ProviderException("Provider application name too long."); - - _ApplicationName = value; - } - } - #endregion - - #region Initialization Method - /// - /// Initializes the provider. - /// - /// The friendly name of the provider. - /// A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider. - /// The name of the provider is null. - /// An attempt is made to call - /// on a provider - /// after the provider has already been initialized. - /// The name of the provider has a length of zero. - public override void Initialize(string name, NameValueCollection config) { - // Initialize values from web.config - if (config == null) throw new ArgumentNullException("config"); - - if (name == null || name.Length == 0) name = "UmbracoMemberRoleProvider"; - - if (String.IsNullOrEmpty(config["description"])) { - config.Remove("description"); - config.Add("description", "Umbraco Member Role provider"); - } - - // Initialize the abstract base class. - base.Initialize(name, config); - - this._ApplicationName = config["applicationName"]; - if (string.IsNullOrEmpty(this._ApplicationName)) - this._ApplicationName = SecUtility.GetDefaultAppName(); - } - #endregion - - #region Methods - /// - /// Adds the specified user names to the specified roles for the configured applicationName. - /// - /// A string array of user names to be added to the specified roles. - /// A string array of the role names to add the specified user names to. - public override void AddUsersToRoles(string[] usernames, string[] roleNames) { - ArrayList roles = new ArrayList(); - foreach (string role in roleNames) - try { - roles.Add(MemberGroup.GetByName(role).Id); - } catch { - throw new ProviderException(String.Format("No role with name '{0}' exists", role)); - } - foreach (string username in usernames) { - Member m = Member.GetMemberFromLoginName(username); - foreach (int roleId in roles) - m.AddGroup(roleId); - } - } - - /// - /// Adds a new role to the data source for the configured applicationName. - /// - /// The name of the role to create. - public override void CreateRole(string roleName) { - MemberGroup.MakeNew(roleName, User.GetUser(0)); - } - - /// - /// Removes a role from the data source for the configured applicationName. - /// - /// The name of the role to delete. - /// If true, throw an exception if roleName has one or more members and do not delete roleName. - /// - /// true if the role was successfully deleted; otherwise, false. - /// - public override bool DeleteRole(string roleName, bool throwOnPopulatedRole) { - MemberGroup group = MemberGroup.GetByName(roleName); - if (group == null) - throw new ProviderException(String.Format("No role with name '{0}' exists", roleName)); - else if (throwOnPopulatedRole && group.GetMembersAsIds().Length > 0) - throw new ProviderException(String.Format("Can't delete role '{0}', there are members assigned to the role", roleName)); - else { - foreach (Member m in group.GetMembers()) - m.RemoveGroup(group.Id); - group.delete(); - return true; - } - } - - /// - /// Gets an array of user names in a role where the user name contains the specified user name to match. - /// - /// The role to search in. - /// The user name to search for. - /// - /// A string array containing the names of all the users where the user name matches usernameToMatch and the user is a member of the specified role. - /// - public override string[] FindUsersInRole(string roleName, string usernameToMatch) { - ArrayList members = new ArrayList(); - MemberGroup group = MemberGroup.GetByName(roleName); - if (group == null) - throw new ProviderException(String.Format("No role with name '{0}' exists", roleName)); - else { - foreach (Member m in group.GetMembers(usernameToMatch)) - members.Add(m.LoginName); - return (string[])members.ToArray(typeof(string)); - } - } - - /// - /// Gets a list of all the roles for the configured applicationName. - /// - /// - /// A string array containing the names of all the roles stored in the data source for the configured applicationName. - /// - public override string[] GetAllRoles() { - ArrayList roles = new ArrayList(); - foreach (MemberGroup mg in MemberGroup.GetAll) - roles.Add(mg.Text); - return (string[])roles.ToArray(typeof(string)); - } - - /// - /// Gets a list of the roles that a specified user is in for the configured applicationName. - /// - /// The user to return a list of roles for. - /// - /// A string array containing the names of all the roles that the specified user is in for the configured applicationName. - /// - public override string[] GetRolesForUser(string username) { - ArrayList roles = new ArrayList(); - Member m = Member.GetMemberFromLoginName(username); - if (m != null) { - IDictionaryEnumerator ide = m.Groups.GetEnumerator(); - while (ide.MoveNext()) - roles.Add(((MemberGroup)ide.Value).Text); - return (string[])roles.ToArray(typeof(string)); - } else - throw new ProviderException(String.Format("No member with username '{0}' exists", username)); - } - - /// - /// Gets a list of users in the specified role for the configured applicationName. - /// - /// The name of the role to get the list of users for. - /// - /// A string array containing the names of all the users who are members of the specified role for the configured applicationName. - /// - public override string[] GetUsersInRole(string roleName) { - ArrayList members = new ArrayList(); - MemberGroup group = MemberGroup.GetByName(roleName); - if (group == null) - throw new ProviderException(String.Format("No role with name '{0}' exists", roleName)); - else { - foreach (Member m in group.GetMembers()) - members.Add(m.LoginName); - return (string[])members.ToArray(typeof(string)); - } - } - - /// - /// Gets a value indicating whether the specified user is in the specified role for the configured applicationName. - /// - /// The user name to search for. - /// The role to search in. - /// - /// true if the specified user is in the specified role for the configured applicationName; otherwise, false. - /// - public override bool IsUserInRole(string username, string roleName) { - Member m = Member.GetMemberFromLoginName(username); - if (m == null) - throw new ProviderException(String.Format("No user with name '{0}' exists", username)); - else { - MemberGroup mg = MemberGroup.GetByName(roleName); - if (mg == null) - throw new ProviderException(String.Format("No Membergroup with name '{0}' exists", roleName)); - else - return mg.HasMember(m.Id); - } - } - - /// - /// Removes the specified user names from the specified roles for the configured applicationName. - /// - /// A string array of user names to be removed from the specified roles. - /// A string array of role names to remove the specified user names from. - public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames) { - ArrayList roles = new ArrayList(); - foreach (string role in roleNames) - try { - roles.Add(MemberGroup.GetByName(role).Id); - } catch { - throw new ProviderException(String.Format("No role with name '{0}' exists", role)); - } - foreach (string username in usernames) { - Member m = Member.GetMemberFromLoginName(username); - foreach (int roleId in roles) - m.RemoveGroup(roleId); - } - } - - /// - /// Gets a value indicating whether the specified role name already exists in the role data source for the configured applicationName. - /// - /// The name of the role to search for in the data source. - /// - /// true if the role name already exists in the data source for the configured applicationName; otherwise, false. - /// - public override bool RoleExists(string roleName) { - MemberGroup mg = MemberGroup.GetByName(roleName); - return mg != null; - } - #endregion - - } -} +#region namespace +using System; +using System.Collections.Generic; +using System.Text; +using System.Web.Security; +using System.Configuration; +using umbraco.BusinessLogic; +using System.Security.Cryptography; +using System.Web.Util; +using System.Collections.Specialized; +using System.Configuration.Provider; +using umbraco.cms.businesslogic; +using umbraco.cms.businesslogic.member; +using System.Collections; +#endregion + +namespace umbraco.providers.members +{ + public class UmbracoRoleProvider : RoleProvider + { + + #region + private string _ApplicationName = Member.UmbracoRoleProviderName; + #endregion + + #region Properties + /// + /// Gets or sets the name of the application to store and retrieve role information for. + /// + /// + /// The name of the application to store and retrieve role information for. + public override string ApplicationName { + get { + return _ApplicationName; + } + set { + if (string.IsNullOrEmpty(value)) + throw new ProviderException("ApplicationName cannot be empty."); + + if (value.Length > 0x100) + throw new ProviderException("Provider application name too long."); + + _ApplicationName = value; + } + } + #endregion + + #region Initialization Method + /// + /// Initializes the provider. + /// + /// The friendly name of the provider. + /// A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider. + /// The name of the provider is null. + /// An attempt is made to call + /// on a provider + /// after the provider has already been initialized. + /// The name of the provider has a length of zero. + public override void Initialize(string name, NameValueCollection config) { + // Initialize values from web.config + if (config == null) throw new ArgumentNullException("config"); + + if (name == null || name.Length == 0) name = "UmbracoMemberRoleProvider"; + + if (String.IsNullOrEmpty(config["description"])) { + config.Remove("description"); + config.Add("description", "Umbraco Member Role provider"); + } + + // Initialize the abstract base class. + base.Initialize(name, config); + + this._ApplicationName = config["applicationName"]; + if (string.IsNullOrEmpty(this._ApplicationName)) + this._ApplicationName = SecUtility.GetDefaultAppName(); + } + #endregion + + #region Methods + /// + /// Adds the specified user names to the specified roles for the configured applicationName. + /// + /// A string array of user names to be added to the specified roles. + /// A string array of the role names to add the specified user names to. + public override void AddUsersToRoles(string[] usernames, string[] roleNames) { + ArrayList roles = new ArrayList(); + foreach (string role in roleNames) + try { + roles.Add(MemberGroup.GetByName(role).Id); + } catch { + throw new ProviderException(String.Format("No role with name '{0}' exists", role)); + } + foreach (string username in usernames) { + Member m = Member.GetMemberFromLoginName(username); + foreach (int roleId in roles) + m.AddGroup(roleId); + } + } + + /// + /// Adds a new role to the data source for the configured applicationName. + /// + /// The name of the role to create. + public override void CreateRole(string roleName) { + MemberGroup.MakeNew(roleName, User.GetUser(0)); + } + + /// + /// Removes a role from the data source for the configured applicationName. + /// + /// The name of the role to delete. + /// If true, throw an exception if roleName has one or more members and do not delete roleName. + /// + /// true if the role was successfully deleted; otherwise, false. + /// + public override bool DeleteRole(string roleName, bool throwOnPopulatedRole) { + MemberGroup group = MemberGroup.GetByName(roleName); + if (group == null) + throw new ProviderException(String.Format("No role with name '{0}' exists", roleName)); + else if (throwOnPopulatedRole && group.GetMembersAsIds().Length > 0) + throw new ProviderException(String.Format("Can't delete role '{0}', there are members assigned to the role", roleName)); + else { + foreach (Member m in group.GetMembers()) + m.RemoveGroup(group.Id); + group.delete(); + return true; + } + } + + /// + /// Gets an array of user names in a role where the user name contains the specified user name to match. + /// + /// The role to search in. + /// The user name to search for. + /// + /// A string array containing the names of all the users where the user name matches usernameToMatch and the user is a member of the specified role. + /// + public override string[] FindUsersInRole(string roleName, string usernameToMatch) { + ArrayList members = new ArrayList(); + MemberGroup group = MemberGroup.GetByName(roleName); + if (group == null) + throw new ProviderException(String.Format("No role with name '{0}' exists", roleName)); + else { + foreach (Member m in group.GetMembers(usernameToMatch)) + members.Add(m.LoginName); + return (string[])members.ToArray(typeof(string)); + } + } + + /// + /// Gets a list of all the roles for the configured applicationName. + /// + /// + /// A string array containing the names of all the roles stored in the data source for the configured applicationName. + /// + public override string[] GetAllRoles() { + ArrayList roles = new ArrayList(); + foreach (MemberGroup mg in MemberGroup.GetAll) + roles.Add(mg.Text); + return (string[])roles.ToArray(typeof(string)); + } + + /// + /// Gets a list of the roles that a specified user is in for the configured applicationName. + /// + /// The user to return a list of roles for. + /// + /// A string array containing the names of all the roles that the specified user is in for the configured applicationName. + /// + public override string[] GetRolesForUser(string username) { + ArrayList roles = new ArrayList(); + Member m = Member.GetMemberFromLoginName(username); + if (m != null) { + IDictionaryEnumerator ide = m.Groups.GetEnumerator(); + while (ide.MoveNext()) + roles.Add(((MemberGroup)ide.Value).Text); + return (string[])roles.ToArray(typeof(string)); + } else + throw new ProviderException(String.Format("No member with username '{0}' exists", username)); + } + + /// + /// Gets a list of users in the specified role for the configured applicationName. + /// + /// The name of the role to get the list of users for. + /// + /// A string array containing the names of all the users who are members of the specified role for the configured applicationName. + /// + public override string[] GetUsersInRole(string roleName) { + ArrayList members = new ArrayList(); + MemberGroup group = MemberGroup.GetByName(roleName); + if (group == null) + throw new ProviderException(String.Format("No role with name '{0}' exists", roleName)); + else { + foreach (Member m in group.GetMembers()) + members.Add(m.LoginName); + return (string[])members.ToArray(typeof(string)); + } + } + + /// + /// Gets a value indicating whether the specified user is in the specified role for the configured applicationName. + /// + /// The user name to search for. + /// The role to search in. + /// + /// true if the specified user is in the specified role for the configured applicationName; otherwise, false. + /// + public override bool IsUserInRole(string username, string roleName) { + Member m = Member.GetMemberFromLoginName(username); + if (m == null) + throw new ProviderException(String.Format("No user with name '{0}' exists", username)); + else { + MemberGroup mg = MemberGroup.GetByName(roleName); + if (mg == null) + throw new ProviderException(String.Format("No Membergroup with name '{0}' exists", roleName)); + else + return mg.HasMember(m.Id); + } + } + + /// + /// Removes the specified user names from the specified roles for the configured applicationName. + /// + /// A string array of user names to be removed from the specified roles. + /// A string array of role names to remove the specified user names from. + public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames) { + ArrayList roles = new ArrayList(); + foreach (string role in roleNames) + try { + roles.Add(MemberGroup.GetByName(role).Id); + } catch { + throw new ProviderException(String.Format("No role with name '{0}' exists", role)); + } + foreach (string username in usernames) { + Member m = Member.GetMemberFromLoginName(username); + foreach (int roleId in roles) + m.RemoveGroup(roleId); + } + } + + /// + /// Gets a value indicating whether the specified role name already exists in the role data source for the configured applicationName. + /// + /// The name of the role to search for in the data source. + /// + /// true if the role name already exists in the data source for the configured applicationName; otherwise, false. + /// + public override bool RoleExists(string roleName) { + MemberGroup mg = MemberGroup.GetByName(roleName); + return mg != null; + } + #endregion + + } +} diff --git a/src/umbraco.providers/umbraco.providers.csproj b/src/umbraco.providers/umbraco.providers.csproj index de33276993..e7e5e28b12 100644 --- a/src/umbraco.providers/umbraco.providers.csproj +++ b/src/umbraco.providers/umbraco.providers.csproj @@ -80,9 +80,9 @@ - - - + + + From dc727ba0de257092478b120c04b2327801d9ba27 Mon Sep 17 00:00:00 2001 From: Marc Date: Thu, 19 Dec 2013 10:41:44 +0100 Subject: [PATCH 0121/1046] Fix for U4-3908 YSOD on frontend after being logged out --- src/Umbraco.Web/UmbracoContext.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index 801a554996..198b3c85f3 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -425,7 +425,8 @@ namespace Umbraco.Web { get { - return new User(Security.CurrentUser); + var user = Security.CurrentUser; + return user == null ? null : new User(user); } } From 22d6981ab0d57d64dba7a955844382abdb411d25 Mon Sep 17 00:00:00 2001 From: perploug Date: Thu, 19 Dec 2013 11:06:10 +0100 Subject: [PATCH 0122/1046] Fixes small issue with language loading --- .../umbraco/js/language.aspx.cs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/js/language.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/js/language.aspx.cs index f6fd2f200b..315cfc6f22 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/js/language.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/js/language.aspx.cs @@ -22,8 +22,7 @@ namespace umbraco.js return; StringBuilder sb = new StringBuilder(); - - sb.Append("{"); + foreach(XmlNode x in all.DocumentElement.ChildNodes) { if(x == null) @@ -50,16 +49,10 @@ namespace umbraco.js sb.Append("\"" + _tempKey + "\": \"" + tmpStr + "\","); - - } - - } - sb.Remove(sb.Length - 1, 1); - sb.Append("}"); - - Response.Write(sb.ToString()); + var f = "{" + sb.ToString().Trim().Trim(',').Trim() + "}"; + Response.Write(f); } #region Web Form Designer generated code From f2c2b7590b370b3479db5c081b2a273ec3ce04a9 Mon Sep 17 00:00:00 2001 From: perploug Date: Thu, 19 Dec 2013 11:31:55 +0100 Subject: [PATCH 0123/1046] Get Entity by Query WIP --- .../src/common/resources/entity.resource.js | 10 +++++ .../contentpicker/contentpicker.controller.js | 22 ++++++++-- src/Umbraco.Web/Editors/EntityController.cs | 40 +++++++++++++++++++ 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 75d2f47a28..182b487f11 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -99,6 +99,16 @@ function entityResource($q, $http, umbRequestHelper) { 'Failed to retreive entity data for id ' + id); }, + getByQuery: function (query, rootNodeId, type) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetByQuery", + [{query: query},{ rootNodeId: rootNodeId}, {type: type }])), + 'Failed to retreive entity data for query ' + query); + }, + /** * @ngdoc method * @name umbraco.resources.entityResource#getByIds diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index ab1aeb06e2..70de53bf24 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -3,7 +3,7 @@ angular.module('umbraco') .controller("Umbraco.PropertyEditors.ContentPickerController", - function($scope, dialogService, entityResource, $log, iconHelper){ + function($scope, dialogService, entityResource, editorState, $log, iconHelper){ $scope.renderModel = []; $scope.ids = $scope.model.value ? $scope.model.value.split(',') : []; @@ -11,6 +11,8 @@ angular.module('umbraco') $scope.cfg = { multiPicker: "0", entityType: "Document", + filterCssClass: "not-allowed not-published", + startNode:{ type: "content", id: -1 @@ -31,11 +33,23 @@ angular.module('umbraco') $scope.cfg.entityType = "Media"; } + //if we have a query for the startnode, we will use that. + if($scope.cfg.startNode.query){ + var rootId = -1; + if($scope.cfg.startNode.scope === "current"){ + rootId = editorState.current.id; + } + + entityResource.getByQuery($scope.cfg.startNode.query, rootId, "Document").then(function(ent){ + $scope.cfg.startNodeId = ent.id; + }); + }else{ + $scope.cfg.startNodeId = $scope.cfg.startNode.id; + } + $scope.cfg.callback = populate; $scope.cfg.treeAlias = $scope.cfg.startNode.type; - $scope.cfg.section = $scope.cfg.startNode.type; - $scope.cfg.startNodeId = $scope.cfg.startNode.id; - $scope.cfg.filterCssClass = "not-allowed not-published"; + $scope.cfg.section = $scope.cfg.startNode.type; //load current data entityResource.getByIds($scope.ids, $scope.cfg.entityType).then(function(data){ diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index b63ec39229..475ac6824c 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -25,6 +25,7 @@ using Examine; using Examine.LuceneEngine.SearchCriteria; using Examine.SearchCriteria; using Umbraco.Web.Dynamics; +using umbraco; namespace Umbraco.Web.Editors { @@ -128,6 +129,45 @@ namespace Umbraco.Web.Editors return GetResultForKey(id, type); } + /// + /// Gets an entity by a xpath or css-like query + /// + /// + /// + /// + public EntityBasic GetByQuery(string query, int rootNodeId, UmbracoEntityTypes type) + { + + //this is css (commented out for now, due to external dependency) + //if (!query.Contains("::") && !query.Contains('/')) + // query = css2xpath.Converter.CSSToXPath(query, ""); + + + if(rootNodeId < 0){ + var nodes = global::umbraco.library.GetXmlNodeByXPath(query); + var node = uQuery.GetNodesByXPath(query).FirstOrDefault(); + + if(node == null) + return null; + + return GetById(node.Id, UmbracoEntityTypes.Document); + }else{ + var node = global::umbraco.library.GetXmlNodeById(rootNodeId.ToString()); + if (node.MoveNext()) + { + if (node.Current != null) + { + var result = node.Current.Select(query); + //set it to the first node found (if there is one), otherwise to -1 + if (result.Current != null) + return GetById(int.Parse(result.Current.GetAttribute("id", string.Empty)), UmbracoEntityTypes.Document); + } + } + } + + return null; + } + public EntityBasic GetById(int id, UmbracoEntityTypes type) { return GetResultForId(id, type); From 391c778b8000deaeceda06340c070031b05cb706 Mon Sep 17 00:00:00 2001 From: perploug Date: Thu, 19 Dec 2013 11:32:09 +0100 Subject: [PATCH 0124/1046] Minor refactor of treesource internal model --- .../prevalueeditors/treesource.controller.js | 28 +++++++------------ .../src/views/prevalueeditors/treesource.html | 21 ++++++++++++-- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.controller.js b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.controller.js index 3f353a9d4f..87fa2c8724 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.controller.js @@ -5,20 +5,13 @@ angular.module('umbraco') function($scope, dialogService, entityResource, $log, iconHelper){ - if(angular.isObject($scope.model.value)){ - $scope.model.type = $scope.model.value.type; - $scope.model.id = $scope.model.value.id; - }else{ - $scope.model.type = "content"; - } - - if($scope.model.id && $scope.model.type !== "member"){ + if($scope.model.value.id && $scope.model.value.type !== "member"){ var ent = "Document"; - if($scope.model.type === "media"){ + if($scope.model.value.type === "media"){ ent = "Media"; } - entityResource.getById($scope.model.id, ent).then(function(item){ + entityResource.getById($scope.model.value.id, ent).then(function(item){ item.icon = iconHelper.convertFromLegacyIcon(item.icon); $scope.node = item; }); @@ -27,30 +20,29 @@ angular.module('umbraco') $scope.openContentPicker =function(){ var d = dialogService.treePicker({ - section: $scope.model.type, - treeAlias: $scope.model.type, + section: $scope.model.value.type, + treeAlias: $scope.model.value.type, scope: $scope, multiPicker: false, callback: populate}); }; $scope.clear = function() { - $scope.model.id = undefined; + $scope.model.value.id = undefined; $scope.node = undefined; }; $scope.$on("formSubmitting", function (ev, args) { - if($scope.model.type === "member"){ - $scope.model.id = -1; + if($scope.model.value.type === "member"){ + $scope.model.value.id = -1; + $scope.model.value.query = ""; } - - $scope.model.value = {type: $scope.model.type, id: $scope.model.id}; }); function populate(item){ $scope.clear(); item.icon = iconHelper.convertFromLegacyIcon(item.icon); $scope.node = item; - $scope.model.id = item.id; + $scope.model.value.id = item.id; } }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.html b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.html index 370a2c8cd6..bc5f5f8158 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.html +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.html @@ -1,6 +1,6 @@
    - @@ -8,7 +8,7 @@

    -
    +
    • @@ -18,12 +18,27 @@
    + + {{model | json}}
    \ No newline at end of file From 414b65b269d230fe67ac7e5df44d852510b56aa5 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 20 Dec 2013 14:01:10 +1100 Subject: [PATCH 0125/1046] More WIP on the membership providers - both new and old. They are now both working with the same up-to-date logic and base classes. Updated the interface definitions on the member services to support the membership provider queries - now to implement the logic. --- .../Querying/ValuePropertyMatchType.cs | 14 + .../Security/MembershipProviderBase.cs | 108 +++-- src/Umbraco.Core/Services/IMemberService.cs | 73 ++- .../Services/IMembershipMemberService.cs | 20 + src/Umbraco.Core/Services/MemberCountType.cs | 13 + src/Umbraco.Core/Services/MemberService.cs | 26 +- src/Umbraco.Core/Umbraco.Core.csproj | 2 + .../Membership/MembershipProviderBaseTests.cs | 30 +- .../Providers/MembersMembershipProvider.cs | 449 +++++++++--------- .../UsersMembershipProvider.cs | 28 +- .../members/UmbracoMembershipProvider.cs | 370 ++++++++------- 11 files changed, 668 insertions(+), 465 deletions(-) create mode 100644 src/Umbraco.Core/Persistence/Querying/ValuePropertyMatchType.cs create mode 100644 src/Umbraco.Core/Services/MemberCountType.cs diff --git a/src/Umbraco.Core/Persistence/Querying/ValuePropertyMatchType.cs b/src/Umbraco.Core/Persistence/Querying/ValuePropertyMatchType.cs new file mode 100644 index 0000000000..f0c70a069d --- /dev/null +++ b/src/Umbraco.Core/Persistence/Querying/ValuePropertyMatchType.cs @@ -0,0 +1,14 @@ +namespace Umbraco.Core.Persistence.Querying +{ + /// + /// Determine how to match a number or data value + /// + public enum ValuePropertyMatchType + { + Exact, + GreaterThan, + LessThan, + GreaterThanOrEqualTo, + LessThanOrEqualTo + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Security/MembershipProviderBase.cs b/src/Umbraco.Core/Security/MembershipProviderBase.cs index 24a0e29409..9d76c8e595 100644 --- a/src/Umbraco.Core/Security/MembershipProviderBase.cs +++ b/src/Umbraco.Core/Security/MembershipProviderBase.cs @@ -18,40 +18,10 @@ namespace Umbraco.Core.Security { //Set the defaults! DefaultMemberTypeAlias = "Member"; - LockPropertyTypeAlias = Constants.Conventions.Member.IsLockedOut; - LastLockedOutPropertyTypeAlias = Constants.Conventions.Member.LastLockoutDate; - FailedPasswordAttemptsPropertyTypeAlias = Constants.Conventions.Member.FailedPasswordAttempts; - ApprovedPropertyTypeAlias = Constants.Conventions.Member.IsApproved; - CommentPropertyTypeAlias = Constants.Conventions.Member.Comments; - LastLoginPropertyTypeAlias = Constants.Conventions.Member.LastLoginDate; - LastPasswordChangedPropertyTypeAlias = Constants.Conventions.Member.LastPasswordChangeDate; - PasswordRetrievalQuestionPropertyTypeAlias = Constants.Conventions.Member.PasswordQuestion; - PasswordRetrievalAnswerPropertyTypeAlias = Constants.Conventions.Member.PasswordAnswer; } public string DefaultMemberTypeAlias { get; protected set; } - public string LockPropertyTypeAlias { get; protected set; } - public string LastLockedOutPropertyTypeAlias { get; protected set; } - public string FailedPasswordAttemptsPropertyTypeAlias { get; protected set; } - public string ApprovedPropertyTypeAlias { get; protected set; } - public string CommentPropertyTypeAlias { get; protected set; } - public string LastLoginPropertyTypeAlias { get; protected set; } - public string LastPasswordChangedPropertyTypeAlias { get; protected set; } - public string PasswordRetrievalQuestionPropertyTypeAlias { get; protected set; } - public string PasswordRetrievalAnswerPropertyTypeAlias { get; protected set; } - - public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) - { - base.ChangePasswordQuestionAndAnswer(username, password, newPasswordQuestion, newPasswordAnswer); - - if (string.IsNullOrEmpty(PasswordRetrievalQuestionPropertyTypeAlias) || string.IsNullOrEmpty(PasswordRetrievalAnswerPropertyTypeAlias)) - { - throw new NotSupportedException("Updating the password Question and Answer is not valid if the properties aren't set in the config file"); - } - - return PerformChangePasswordQuestionAndAnswer(username, password, newPasswordQuestion, newPasswordAnswer); - } - + /// /// Adds a new membership user to the data source. /// @@ -398,6 +368,21 @@ namespace Umbraco.Core.Security throw new NotSupportedException("This provider does not support manually changing the password"); } + var args = new ValidatePasswordEventArgs(username, newPassword, false); + OnValidatingPassword(args); + + if (args.Cancel) + { + if (args.FailureInformation != null) + throw args.FailureInformation; + throw new MembershipPasswordException("Change password canceled due to password validation failure."); + } + + if (AllowManuallyChangingPassword == false) + { + if (ValidateUser(username, oldPassword) == false) return false; + } + return PerformChangePassword(username, oldPassword, newPassword); } @@ -431,10 +416,13 @@ namespace Umbraco.Core.Security { throw new NotSupportedException("Updating the password Question and Answer is not available if requiresQuestionAndAnswer is not set in web.config"); } - - if (ValidateUser(username, password) == false) + + if (AllowManuallyChangingPassword == false) { - return false; + if (ValidateUser(username, password) == false) + { + return false; + } } return PerformChangePasswordQuestionAndAnswer(username, password, newPasswordQuestion, newPasswordAnswer); @@ -534,6 +522,56 @@ namespace Umbraco.Core.Security /// protected abstract MembershipUser PerformCreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status); + /// + /// Gets the members password if password retreival is enabled + /// + /// + /// + /// + public sealed override string GetPassword(string username, string answer) + { + if (EnablePasswordRetrieval == false) + throw new ProviderException("Password Retrieval Not Enabled."); + + if (PasswordFormat == MembershipPasswordFormat.Hashed) + throw new ProviderException("Cannot retrieve Hashed passwords."); + + return PerformGetPassword(username, answer); + } + + /// + /// Gets the members password if password retreival is enabled + /// + /// + /// + /// + protected abstract string PerformGetPassword(string username, string answer); + + public sealed override string ResetPassword(string username, string answer) + { + if (EnablePasswordReset == false) + { + throw new NotSupportedException("Password reset is not supported"); + } + + var newPassword = Membership.GeneratePassword(MinRequiredPasswordLength, MinRequiredNonAlphanumericCharacters); + + var args = new ValidatePasswordEventArgs(username, newPassword, true); + OnValidatingPassword(args); + if (args.Cancel) + { + if (args.FailureInformation != null) + { + throw args.FailureInformation; + } + throw new MembershipPasswordException("Reset password canceled due to password validation failure."); + } + + return PerformResetPassword(username, answer, newPassword); + } + + protected abstract string PerformResetPassword(string username, string answer, string generatedPassword); + protected internal static Attempt IsPasswordValid(string password, int minRequiredNonAlphanumericChars, string strengthRegex, int minLength) { if (minRequiredNonAlphanumericChars > 0) @@ -561,7 +599,7 @@ namespace Umbraco.Core.Security return Attempt.Succeed(PasswordValidityError.Ok); } - + /// /// Gets the name of the default app. /// diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs index 0be5211532..87c15573c6 100644 --- a/src/Umbraco.Core/Services/IMemberService.cs +++ b/src/Umbraco.Core/Services/IMemberService.cs @@ -17,18 +17,87 @@ namespace Umbraco.Core.Services /// bool Exists(int id); + /// + /// Get a member by its id + /// + /// + /// IMember GetById(int id); + + /// + /// Get a member by the unique key + /// + /// + /// IMember GetByKey(Guid id); + + /// + /// Get all members for the member type alias + /// + /// + /// IEnumerable GetMembersByMemberType(string memberTypeAlias); + + /// + /// Get all members for the member type id + /// + /// + /// IEnumerable GetMembersByMemberType(int memberTypeId); + + /// + /// Get all members in the member group name specified + /// + /// + /// IEnumerable GetMembersByGroup(string memberGroupName); + + /// + /// Get all members with the ids specified + /// + /// + /// IEnumerable GetAllMembers(params int[] ids); + /// + /// Delete members of the specified member type id + /// + /// void DeleteMembersOfType(int memberTypeId); + /// + /// Get members based on a property search + /// + /// + /// + /// + /// IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, string value, StringPropertyMatchType matchType = StringPropertyMatchType.Exact); - IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, int value); + + /// + /// Get members based on a property search + /// + /// + /// + /// + /// + IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, int value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact); + + /// + /// Get members based on a property search + /// + /// + /// + /// IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, bool value); - IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, DateTime value); + + /// + /// Get members based on a property search + /// + /// + /// + /// + /// + IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, DateTime value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IMembershipMemberService.cs b/src/Umbraco.Core/Services/IMembershipMemberService.cs index 18edae2bea..715f85f3fb 100644 --- a/src/Umbraco.Core/Services/IMembershipMemberService.cs +++ b/src/Umbraco.Core/Services/IMembershipMemberService.cs @@ -31,6 +31,11 @@ namespace Umbraco.Core.Services IMember GetById(object id); + /// + /// Get a member by email + /// + /// + /// IMember GetByEmail(string email); IMember GetByUsername(string login); @@ -44,5 +49,20 @@ namespace Umbraco.Core.Services IEnumerable FindMembersByEmail(string emailStringToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); IEnumerable FindMembersByUsername(string login, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); + + /// + /// Gets the total number of members based on the count type + /// + /// + int GetMemberCount(MemberCountType countType); + + /// + /// Gets a list of paged member data + /// + /// + /// + /// + /// + IEnumerable GetAllMembers(int pageIndex, int pageSize, out int totalRecords); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/MemberCountType.cs b/src/Umbraco.Core/Services/MemberCountType.cs new file mode 100644 index 0000000000..882d1515e2 --- /dev/null +++ b/src/Umbraco.Core/Services/MemberCountType.cs @@ -0,0 +1,13 @@ +namespace Umbraco.Core.Services +{ + /// + /// The types of members to count + /// + public enum MemberCountType + { + All, + Online, + LockedOut, + Approved + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index a84734d1f7..a5333cc503 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -310,8 +310,9 @@ namespace Umbraco.Core.Services ///
    /// /// + /// /// - public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, int value) + public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, int value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact) { using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) { @@ -352,8 +353,9 @@ namespace Umbraco.Core.Services ///
    /// /// + /// /// - public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, DateTime value) + public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, DateTime value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact) { using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) { @@ -372,6 +374,26 @@ namespace Umbraco.Core.Services #region IMembershipMemberService Implementation + /// + /// Returns the count of members based on the countType + /// + /// + /// + /// + /// The way the Online count is done is the same way that it is done in the MS SqlMembershipProvider - We query for any members + /// that have their last active date within the Membership.UserIsOnlineTimeWindow (which is in minutes). It isn't exact science + /// but that is how MS have made theirs so we'll follow that principal. + /// + public int GetMemberCount(MemberCountType countType) + { + throw new NotImplementedException(); + } + + public IEnumerable GetAllMembers(int pageIndex, int pageSize, out int totalRecords) + { + throw new NotImplementedException(); + } + /// /// Creates and persists a new Member /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 02b36c8e85..9f7bd325a1 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -203,6 +203,7 @@ + @@ -772,6 +773,7 @@ + diff --git a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs index 06e44be215..f1b8d66773 100644 --- a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs +++ b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs @@ -13,6 +13,16 @@ using Umbraco.Web.Security.Providers; namespace Umbraco.Tests.Membership { + [TestFixture] + public class MembersMembershipProviderTests + { + //[Test] + //public void Set_Default_Member_Type_On_Init() + + //[Test] + //public void Question_Answer_Is_Encrypted() + } + [TestFixture] public class MembershipProviderBaseTests { @@ -111,16 +121,6 @@ namespace Umbraco.Tests.Membership private class TestProvider : MembershipProviderBase { - public override string GetPassword(string username, string answer) - { - throw new NotImplementedException(); - } - - public override string ResetPassword(string username, string answer) - { - throw new NotImplementedException(); - } - public override void UpdateUser(MembershipUser user) { throw new NotImplementedException(); @@ -190,6 +190,16 @@ namespace Umbraco.Tests.Membership { throw new NotImplementedException(); } + + protected override string PerformGetPassword(string username, string answer) + { + throw new NotImplementedException(); + } + + protected override string PerformResetPassword(string username, string answer, string generatedPassword) + { + throw new NotImplementedException(); + } } } diff --git a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs index 23ee46fabc..133314d31a 100644 --- a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs @@ -61,48 +61,7 @@ namespace Umbraco.Web.Security.Providers // _defaultMemberTypeAlias = MemberType.GetAll[0].Alias; //else // throw new ProviderException("No default MemberType alias is specified in the web.config string. Please add a 'defaultMemberTypeAlias' to the add element in the provider declaration in web.config"); - - // test for approve status - if (config["umbracoApprovePropertyTypeAlias"] != null) - { - ApprovedPropertyTypeAlias = config["umbracoApprovePropertyTypeAlias"]; - } - // test for lock attempts - if (config["umbracoLockPropertyTypeAlias"] != null) - { - LockPropertyTypeAlias = config["umbracoLockPropertyTypeAlias"]; - } - if (config["umbracoLastLockedPropertyTypeAlias"] != null) - { - LastLockedOutPropertyTypeAlias = config["umbracoLastLockedPropertyTypeAlias"]; - } - if (config["umbracoLastPasswordChangedPropertyTypeAlias"] != null) - { - LastPasswordChangedPropertyTypeAlias = config["umbracoLastPasswordChangedPropertyTypeAlias"]; - } - if (config["umbracoFailedPasswordAttemptsPropertyTypeAlias"] != null) - { - FailedPasswordAttemptsPropertyTypeAlias = config["umbracoFailedPasswordAttemptsPropertyTypeAlias"]; - } - // comment property - if (config["umbracoCommentPropertyTypeAlias"] != null) - { - CommentPropertyTypeAlias = config["umbracoCommentPropertyTypeAlias"]; - } - // last login date - if (config["umbracoLastLoginPropertyTypeAlias"] != null) - { - LastLoginPropertyTypeAlias = config["umbracoLastLoginPropertyTypeAlias"]; - } - // password retrieval - if (config["umbracoPasswordRetrievalQuestionPropertyTypeAlias"] != null) - { - PasswordRetrievalQuestionPropertyTypeAlias = config["umbracoPasswordRetrievalQuestionPropertyTypeAlias"]; - } - if (config["umbracoPasswordRetrievalAnswerPropertyTypeAlias"] != null) - { - PasswordRetrievalAnswerPropertyTypeAlias = config["umbracoPasswordRetrievalAnswerPropertyTypeAlias"]; - } + } /// @@ -123,17 +82,7 @@ namespace Umbraco.Web.Security.Providers // in order to support updating passwords from the umbraco core, we can't validate the old password var m = _memberService.GetByUsername(username); if (m == null) return false; - - var args = new ValidatePasswordEventArgs(username, newPassword, false); - OnValidatingPassword(args); - - if (args.Cancel) - { - if (args.FailureInformation != null) - throw args.FailureInformation; - throw new MembershipPasswordException("Change password canceled due to password validation failure."); - } - + string salt; var encodedPassword = EncryptOrHashNewPassword(newPassword, out salt); @@ -289,7 +238,45 @@ namespace Umbraco.Web.Security.Providers } return collection; } - + + /// + /// Gets a collection of all the users in the data source in pages of data. + /// + /// The index of the page of results to return. pageIndex is zero-based. + /// The size of the page of results to return. + /// The total number of matched users. + /// + /// A collection that contains a page of pageSize objects beginning at the page specified by pageIndex. + /// + public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords) + { + var membersList = new MembershipUserCollection(); + + var pagedMembers = MemberService.GetAllMembers(pageIndex, pageSize, out totalRecords); + + foreach (var m in pagedMembers) + { + membersList.Add(m.AsConcreteMembershipUser()); + } + return membersList; + } + + /// + /// Gets the number of users currently accessing the application. + /// + /// + /// The number of users currently accessing the application. + /// + /// + /// The way this is done is the same way that it is done in the MS SqlMembershipProvider - We query for any members + /// that have their last active date within the Membership.UserIsOnlineTimeWindow (which is in minutes). It isn't exact science + /// but that is how MS have made theirs so we'll follow that principal. + /// + public override int GetNumberOfUsersOnline() + { + return MemberService.GetMemberCount(MemberCountType.Online); + } + /// /// Gets the password for the specified user name from the data source. /// @@ -298,168 +285,24 @@ namespace Umbraco.Web.Security.Providers /// /// The password for the specified user name. /// - public override string GetPassword(string username, string answer) - { - if (EnablePasswordRetrieval == false) - throw new ProviderException("Password Retrieval Not Enabled."); - - if (PasswordFormat == MembershipPasswordFormat.Hashed) - throw new ProviderException("Cannot retrieve Hashed passwords."); - - var member = MemberService.GetByUsername(username); - - if (RequiresQuestionAndAnswer && member.PasswordAnswer != answer) + protected override string PerformGetPassword(string username, string answer) + { + var m = MemberService.GetByUsername(username); + if (m == null) { - throw new ProviderException("Password retrieval answer doesn't match"); - } - - return member.Password; - } - - /// - /// Resets a user's password to a new, automatically generated password. - /// - /// The user to reset the password for. - /// The password answer for the specified user (not used with Umbraco). - /// The new password for the specified user. - public override string ResetPassword(string username, string answer) - { - //TODO: Get logic from other provider - - throw new NotImplementedException(); - - //if (EnablePasswordReset == false) - // throw new ProviderException("Password reset is Not Enabled."); - - //var member = MemberService.GetByUsername(username); - - //if (member == null) - // throw new ProviderException("The supplied user is not found"); - - //if(member.IsLockedOut) - // throw new ProviderException("The member is locked out."); - - //if (RequiresQuestionAndAnswer == false || (RequiresQuestionAndAnswer && answer == member.PasswordAnswer)) - //{ - // member.Password = - // EncryptOrHashPassword(Membership.GeneratePassword(MinRequiredPasswordLength, - // MinRequiredNonAlphanumericCharacters)); - // MemberService.Save(member); - //} - //else - //{ - // throw new MembershipPasswordException("Incorrect password answer"); - //} - - //return null; - } - - /// - /// Updates e-mail and potentially approved status, lock status and comment on a user. - /// Note: To automatically support lock, approve and comments you'll need to add references to the membertype properties in the - /// 'Member' element in web.config by adding their aliases to the 'umbracoApprovePropertyTypeAlias', 'umbracoLockPropertyTypeAlias' and 'umbracoCommentPropertyTypeAlias' attributes - /// - /// A object that represents the user to update and the updated information for the user. - public override void UpdateUser(MembershipUser user) - { - //var member = user.AsIMember(); - //MemberService.Save(member); - } - - /// - /// Verifies that the specified user name and password exist in the data source. - /// - /// The name of the user to validate. - /// The password for the specified user. - /// - /// true if the specified username and password are valid; otherwise, false. - /// - public override bool ValidateUser(string username, string password) - { - var member = MemberService.GetByUsername(username); - - if (member == null || member.IsApproved == false) return false; - - if (member.IsLockedOut) - throw new ProviderException("The member is locked out."); - - string salt; - var encodedPassword = EncryptOrHashNewPassword(password, out salt); - - var authenticated = (encodedPassword == member.Password); - - if (authenticated == false) - { - // TODO: Increment login attempts - lock if too many. - - //var count = member.GetValue("loginAttempts"); - //count++; - - //if (count >= _maxInvalidPasswordAttempts) - //{ - // member.SetValue("loginAttempts", 0); - // member.IsLockedOut = true; - // throw new ProviderException("The member " + member.Username + " is locked out."); - //} - //else - //{ - // member.SetValue("loginAttempts", count); - //} - } - else - { - // add this later - member.SetValue("loginAttempts", 0); - member.LastLoginDate = DateTime.Now; + throw new ProviderException("The supplied user is not found"); } - MemberService.Save(member); - return authenticated; - } + //TODO: We need to encrypt the answer here to match against the encrypted answer in the database - /// - /// Clears a lock so that the membership user can be validated. - /// - /// The membership user to clear the lock status for. - /// - /// true if the membership user was successfully unlocked; otherwise, false. - /// - public override bool UnlockUser(string username) - { - var member = MemberService.GetByUsername(username); - - if(member == null) - throw new ProviderException("Cannot find member " + username); - - // Non need to update - if (member.IsLockedOut == false) return true; - - member.IsLockedOut = false; - // TODO: add this later - member.SetValue("loginAttempts", 0); - - MemberService.Save(member); - - return true; - } - - /// - /// Gets information from the data source for a user based on the unique identifier for the membership user. Provides an option to update the last-activity date/time stamp for the user. - /// - /// The unique identifier for the membership user to get information for. - /// true to update the last-activity date/time stamp for the user; false to return user information without updating the last-activity date/time stamp for the user. - /// - /// A object populated with the specified user's information from the data source. - /// - public override MembershipUser GetUser(object providerUserKey, bool userIsOnline) - { - var member = MemberService.GetById(providerUserKey); - - if (userIsOnline) + if (RequiresQuestionAndAnswer && m.PasswordAnswer != answer) { - member.UpdateDate = DateTime.Now; - MemberService.Save(member); + throw new ProviderException("Incorrect password answer"); } - return member.AsConcreteMembershipUser(); + var decodedPassword = DecodePassword(m.Password); + + return decodedPassword; } /// @@ -473,9 +316,40 @@ namespace Umbraco.Web.Security.Providers public override MembershipUser GetUser(string username, bool userIsOnline) { var member = MemberService.GetByUsername(username); + if (member == null) + { + return null; + } if (userIsOnline) { + member.LastLoginDate = DateTime.Now; + member.UpdateDate = DateTime.Now; + MemberService.Save(member); + } + + return member.AsConcreteMembershipUser(); + } + + /// + /// Gets information from the data source for a user based on the unique identifier for the membership user. Provides an option to update the last-activity date/time stamp for the user. + /// + /// The unique identifier for the membership user to get information for. + /// true to update the last-activity date/time stamp for the user; false to return user information without updating the last-activity date/time stamp for the user. + /// + /// A object populated with the specified user's information from the data source. + /// + public override MembershipUser GetUser(object providerUserKey, bool userIsOnline) + { + var member = MemberService.GetById(providerUserKey); + if (member == null) + { + return null; + } + + if (userIsOnline) + { + member.LastLoginDate = DateTime.Now; member.UpdateDate = DateTime.Now; MemberService.Save(member); } @@ -497,33 +371,158 @@ namespace Umbraco.Web.Security.Providers return member == null ? null : member.Username; } - - /// - /// Gets a collection of all the users in the data source in pages of data. + /// Resets a user's password to a new, automatically generated password. /// - /// The index of the page of results to return. pageIndex is zero-based. - /// The size of the page of results to return. - /// The total number of matched users. - /// - /// A collection that contains a page of pageSize objects beginning at the page specified by pageIndex. - /// - public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords) + /// The user to reset the password for. + /// The password answer for the specified user (not used with Umbraco). + /// + /// The new password for the specified user. + protected override string PerformResetPassword(string username, string answer, string generatedPassword) { - throw new System.NotImplementedException(); + //TODO: This should be here - but how do we update failure count in this provider?? + //if (answer == null && RequiresQuestionAndAnswer) + //{ + // UpdateFailureCount(username, "passwordAnswer"); + + // throw new ProviderException("Password answer required for password reset."); + //} + + var m = MemberService.GetByUsername(username); + if (m == null) + { + throw new ProviderException("The supplied user is not found"); + } + + if (m.IsLockedOut) + { + throw new ProviderException("The member is locked out."); + } + + //TODO: We need to encrypt the answer here to match against the encrypted answer in the database + + if (RequiresQuestionAndAnswer && m.PasswordAnswer != answer) + { + throw new ProviderException("Incorrect password answer"); + } + + string salt; + var encodedPassword = EncryptOrHashNewPassword(generatedPassword, out salt); + m.Password = encodedPassword; + m.LastPasswordChangeDate = DateTime.Now; + MemberService.Save(m); + + return generatedPassword; } /// - /// Gets the number of users currently accessing the application. + /// Clears a lock so that the membership user can be validated. /// + /// The membership user to clear the lock status for. /// - /// The number of users currently accessing the application. + /// true if the membership user was successfully unlocked; otherwise, false. /// - public override int GetNumberOfUsersOnline() + public override bool UnlockUser(string username) { - throw new System.NotImplementedException(); + var member = MemberService.GetByUsername(username); + + if (member == null) + { + throw new ProviderException(string.Format("No member with the username '{0}' found", username)); + } + + // Non need to update + if (member.IsLockedOut == false) return true; + + member.IsLockedOut = false; + member.FailedPasswordAttempts = 0; + + MemberService.Save(member); + + return true; } - + + /// + /// Updates e-mail approved status, lock status and comment on a user. + /// + /// A object that represents the user to update and the updated information for the user. + public override void UpdateUser(MembershipUser user) + { + var m = MemberService.GetByUsername(user.UserName); + + if (m == null) + { + throw new ProviderException(string.Format("No member with the username '{0}' found", user.UserName)); + } + + m.Email = user.Email; + m.IsApproved = user.IsApproved; + m.IsLockedOut = user.IsLockedOut; + if (user.IsLockedOut) + { + m.LastLockoutDate = DateTime.Now; + } + m.Comments = user.Comment; + + MemberService.Save(m); + } + + /// + /// Verifies that the specified user name and password exist in the data source. + /// + /// The name of the user to validate. + /// The password for the specified user. + /// + /// true if the specified username and password are valid; otherwise, false. + /// + public override bool ValidateUser(string username, string password) + { + var member = MemberService.GetByUsername(username); + + if (member == null) return false; + + if (member.IsApproved == false) + { + LogHelper.Info("Cannot validate member " + username + " because they are not approved"); + return false; + } + if (member.IsLockedOut) + { + LogHelper.Info("Cannot validate member " + username + " because they are currently locked out"); + return false; + } + + string salt; + var encodedPassword = EncryptOrHashNewPassword(password, out salt); + + var authenticated = (encodedPassword == member.Password); + + if (authenticated == false) + { + // TODO: Increment login attempts - lock if too many. + + var count = member.FailedPasswordAttempts; + count++; + member.FailedPasswordAttempts = count; + + if (count >= MaxInvalidPasswordAttempts) + { + member.IsLockedOut = true; + member.LastLockoutDate = DateTime.Now; + LogHelper.Info("Member " + username + " is now locked out, max invalid password attempts exceeded"); + } + } + else + { + member.FailedPasswordAttempts = 0; + member.LastLoginDate = DateTime.Now; + } + + MemberService.Save(member); + return authenticated; + } + + public override string ToString() { diff --git a/src/umbraco.providers/UsersMembershipProvider.cs b/src/umbraco.providers/UsersMembershipProvider.cs index e862bf5b19..64534abf3a 100644 --- a/src/umbraco.providers/UsersMembershipProvider.cs +++ b/src/umbraco.providers/UsersMembershipProvider.cs @@ -305,7 +305,7 @@ namespace umbraco.providers /// /// The password for the specified user name. /// - public override string GetPassword(string username, string answer) + protected override string PerformGetPassword(string username, string answer) { throw new ProviderException("Password Retrieval Not Enabled."); } @@ -355,13 +355,8 @@ namespace umbraco.providers /// The user to reset the password for. /// The password answer for the specified user. /// The new password for the specified user. - public override string ResetPassword(string username, string answer) - { - if (EnablePasswordReset == false) - { - throw new NotSupportedException("Password reset is not supported"); - } - + protected override string PerformResetPassword(string username, string answer, string generatedPassword) + { //TODO: This should be here - but how do we update failure count in this provider?? //if (answer == null && RequiresQuestionAndAnswer) //{ @@ -370,18 +365,7 @@ namespace umbraco.providers // throw new ProviderException("Password answer required for password reset."); //} - var newPassword = Membership.GeneratePassword(MinRequiredPasswordLength, MinRequiredNonAlphanumericCharacters); - - var args = new ValidatePasswordEventArgs(username, newPassword, true); - OnValidatingPassword(args); - if (args.Cancel) - { - if (args.FailureInformation != null) - throw args.FailureInformation; - throw new MembershipPasswordException("Reset password canceled due to password validation failure."); - } - - var found = User.GetAllByLoginName(username, false); + var found = User.GetAllByLoginName(username, false).ToArray(); if (found == null || found.Any() == false) throw new MembershipPasswordException("The supplied user is not found"); @@ -389,12 +373,12 @@ namespace umbraco.providers //Yes, it's true, this actually makes a db call to set the password string salt; - var encPass = EncryptOrHashNewPassword(newPassword, out salt); + var encPass = EncryptOrHashNewPassword(generatedPassword, out salt); user.Password = FormatPasswordForStorage(encPass, salt); //call this just for fun. user.Save(); - return newPassword; + return generatedPassword; } /// diff --git a/src/umbraco.providers/members/UmbracoMembershipProvider.cs b/src/umbraco.providers/members/UmbracoMembershipProvider.cs index 62562a85b0..500444b3fd 100644 --- a/src/umbraco.providers/members/UmbracoMembershipProvider.cs +++ b/src/umbraco.providers/members/UmbracoMembershipProvider.cs @@ -31,12 +31,35 @@ namespace umbraco.providers.members public class UmbracoMembershipProvider : UmbracoMembershipProviderBase { + public UmbracoMembershipProvider() + { + LockPropertyTypeAlias = Constants.Conventions.Member.IsLockedOut; + LastLockedOutPropertyTypeAlias = Constants.Conventions.Member.LastLockoutDate; + FailedPasswordAttemptsPropertyTypeAlias = Constants.Conventions.Member.FailedPasswordAttempts; + ApprovedPropertyTypeAlias = Constants.Conventions.Member.IsApproved; + CommentPropertyTypeAlias = Constants.Conventions.Member.Comments; + LastLoginPropertyTypeAlias = Constants.Conventions.Member.LastLoginDate; + LastPasswordChangedPropertyTypeAlias = Constants.Conventions.Member.LastPasswordChangeDate; + PasswordRetrievalQuestionPropertyTypeAlias = Constants.Conventions.Member.PasswordQuestion; + PasswordRetrievalAnswerPropertyTypeAlias = Constants.Conventions.Member.PasswordAnswer; + } + #region Fields private string _providerName = Member.UmbracoMemberProviderName; #endregion - + + public string LockPropertyTypeAlias { get; protected set; } + public string LastLockedOutPropertyTypeAlias { get; protected set; } + public string FailedPasswordAttemptsPropertyTypeAlias { get; protected set; } + public string ApprovedPropertyTypeAlias { get; protected set; } + public string CommentPropertyTypeAlias { get; protected set; } + public string LastLoginPropertyTypeAlias { get; protected set; } + public string LastPasswordChangedPropertyTypeAlias { get; protected set; } + public string PasswordRetrievalQuestionPropertyTypeAlias { get; protected set; } + public string PasswordRetrievalAnswerPropertyTypeAlias { get; protected set; } + /// /// Override to maintain backwards compatibility with 0 required non-alphanumeric chars /// @@ -164,17 +187,7 @@ namespace umbraco.providers.members // in order to support updating passwords from the umbraco core, we can't validate the old password var m = Member.GetMemberFromLoginName(username); if (m == null) return false; - - var args = new ValidatePasswordEventArgs(username, newPassword, false); - OnValidatingPassword(args); - - if (args.Cancel) - { - if (args.FailureInformation != null) - throw args.FailureInformation; - throw new MembershipPasswordException("Change password canceled due to password validation failure."); - } - + string salt; var encodedPassword = EncryptOrHashNewPassword(newPassword, out salt); m.ChangePassword( @@ -199,6 +212,11 @@ namespace umbraco.providers.members /// protected override bool PerformChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) { + if (string.IsNullOrEmpty(PasswordRetrievalQuestionPropertyTypeAlias) || string.IsNullOrEmpty(PasswordRetrievalAnswerPropertyTypeAlias)) + { + throw new NotSupportedException("Updating the password Question and Answer is not valid if the properties aren't set in the config file"); + } + var m = Member.GetMemberFromLoginName(username); if (m == null) { @@ -409,50 +427,45 @@ namespace umbraco.providers.members /// /// The password for the specified user name. /// - public override string GetPassword(string username, string answer) + protected override string PerformGetPassword(string username, string answer) { - if (EnablePasswordRetrieval == false) - throw new ProviderException("Password Retrieval Not Enabled."); - - if (PasswordFormat == MembershipPasswordFormat.Hashed) - throw new ProviderException("Cannot retrieve Hashed passwords."); - var m = Member.GetMemberFromLoginName(username); - if (m != null) - { - if (RequiresQuestionAndAnswer) - { - // check if password answer property alias is set - if (string.IsNullOrEmpty(PasswordRetrievalAnswerPropertyTypeAlias) == false) - { - // check if user is locked out - if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) - { - var isLockedOut = false; - bool.TryParse(GetMemberProperty(m, LockPropertyTypeAlias, true), out isLockedOut); - if (isLockedOut) - { - throw new MembershipPasswordException("The supplied user is locked out"); - } - } - - // match password answer - if (GetMemberProperty(m, PasswordRetrievalAnswerPropertyTypeAlias, false) != answer) - { - throw new MembershipPasswordException("Incorrect password answer"); - } - } - else - { - throw new ProviderException("Password retrieval answer property alias is not set! To automatically support password question/answers you'll need to add references to the membertype properties in the 'Member' element in web.config by adding their aliases to the 'umbracoPasswordRetrievalQuestionPropertyTypeAlias' and 'umbracoPasswordRetrievalAnswerPropertyTypeAlias' attributes"); - } - } - } if (m == null) { throw new MembershipPasswordException("The supplied user is not found"); } - return m.GetPassword(); + + // check if user is locked out + if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) + { + var isLockedOut = false; + bool.TryParse(GetMemberProperty(m, LockPropertyTypeAlias, true), out isLockedOut); + if (isLockedOut) + { + throw new MembershipPasswordException("The supplied user is locked out"); + } + } + + if (RequiresQuestionAndAnswer) + { + // check if password answer property alias is set + if (string.IsNullOrEmpty(PasswordRetrievalAnswerPropertyTypeAlias) == false) + { + // match password answer + if (GetMemberProperty(m, PasswordRetrievalAnswerPropertyTypeAlias, false) != answer) + { + throw new MembershipPasswordException("Incorrect password answer"); + } + } + else + { + throw new ProviderException("Password retrieval answer property alias is not set! To automatically support password question/answers you'll need to add references to the membertype properties in the 'Member' element in web.config by adding their aliases to the 'umbracoPasswordRetrievalQuestionPropertyTypeAlias' and 'umbracoPasswordRetrievalAnswerPropertyTypeAlias' attributes"); + } + } + + var decodedPassword = DecodePassword(m.GetPassword()); + + return decodedPassword; } /// @@ -465,11 +478,21 @@ namespace umbraco.providers.members /// public override MembershipUser GetUser(string username, bool userIsOnline) { - if (String.IsNullOrEmpty(username)) + if (string.IsNullOrEmpty(username)) return null; - Member m = Member.GetMemberFromLoginName(username); - if (m == null) return null; - else return ConvertToMembershipUser(m); + var m = Member.GetMemberFromLoginName(username); + + if (m == null) + { + return null; + } + + if (userIsOnline && LastLoginPropertyTypeAlias.IsNullOrWhiteSpace() == false) + { + UpdateMemberProperty(m, LastLoginPropertyTypeAlias, DateTime.Now); + } + + return ConvertToMembershipUser(m); } /// @@ -486,14 +509,23 @@ namespace umbraco.providers.members if (asGuid.Success) { var m = new Member(asGuid.Result); + if (userIsOnline && LastLoginPropertyTypeAlias.IsNullOrWhiteSpace() == false) + { + UpdateMemberProperty(m, LastLoginPropertyTypeAlias, DateTime.Now); + } return ConvertToMembershipUser(m); } var asInt = providerUserKey.TryConvertTo(); if (asInt.Success) { var m = new Member(asInt.Result); + if (userIsOnline && LastLoginPropertyTypeAlias.IsNullOrWhiteSpace() == false) + { + UpdateMemberProperty(m, LastLoginPropertyTypeAlias, DateTime.Now); + } return ConvertToMembershipUser(m); } + throw new InvalidOperationException("The " + GetType() + " provider only supports GUID or Int as a providerUserKey"); } @@ -508,7 +540,7 @@ namespace umbraco.providers.members /// public override string GetUserNameByEmail(string email) { - Member m = Member.GetMemberFromEmail(email); + var m = Member.GetMemberFromEmail(email); return m == null ? null : m.LoginName; } @@ -517,14 +549,10 @@ namespace umbraco.providers.members /// /// The user to reset the password for. /// The password answer for the specified user (not used with Umbraco). + /// /// The new password for the specified user. - public override string ResetPassword(string username, string answer) + protected override string PerformResetPassword(string username, string answer, string generatedPassword) { - if (EnablePasswordReset == false) - { - throw new NotSupportedException("Password reset is not supported"); - } - //TODO: This should be here - but how do we update failure count in this provider?? //if (answer == null && RequiresQuestionAndAnswer) //{ @@ -532,42 +560,33 @@ namespace umbraco.providers.members // throw new ProviderException("Password answer required for password reset."); //} - - var newPassword = Membership.GeneratePassword(MinRequiredPasswordLength, MinRequiredNonAlphanumericCharacters); - - var args = new ValidatePasswordEventArgs(username, newPassword, true); - OnValidatingPassword(args); - if (args.Cancel) - { - if (args.FailureInformation != null) - throw args.FailureInformation; - throw new MembershipPasswordException("Reset password canceled due to password validation failure."); - } var m = Member.GetMemberFromLoginName(username); if (m == null) - throw new MembershipPasswordException("The supplied user is not found"); + { + throw new ProviderException("The supplied user is not found"); + } + + // check if user is locked out + if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) + { + var isLockedOut = false; + bool.TryParse(GetMemberProperty(m, LockPropertyTypeAlias, true), out isLockedOut); + if (isLockedOut) + { + throw new ProviderException("The member is locked out."); + } + } if (RequiresQuestionAndAnswer) { // check if password answer property alias is set if (string.IsNullOrEmpty(PasswordRetrievalAnswerPropertyTypeAlias) == false) { - // check if user is locked out - if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) - { - var isLockedOut = false; - bool.TryParse(GetMemberProperty(m, LockPropertyTypeAlias, true), out isLockedOut); - if (isLockedOut) - { - throw new MembershipPasswordException("The supplied user is locked out"); - } - } - // match password answer if (GetMemberProperty(m, PasswordRetrievalAnswerPropertyTypeAlias, false) != answer) { - throw new MembershipPasswordException("Incorrect password answer"); + throw new ProviderException("Incorrect password answer"); } } else @@ -577,10 +596,9 @@ namespace umbraco.providers.members } string salt; - var encodedPassword = EncryptOrHashNewPassword(newPassword, out salt); + var encodedPassword = EncryptOrHashNewPassword(generatedPassword, out salt); //set the password on the member - m.ChangePassword( - FormatPasswordForStorage(encodedPassword, salt)); + m.ChangePassword(FormatPasswordForStorage(encodedPassword, salt)); if (string.IsNullOrEmpty(LastPasswordChangedPropertyTypeAlias) == false) { @@ -589,7 +607,7 @@ namespace umbraco.providers.members m.Save(); - return newPassword; + return generatedPassword; } /// @@ -607,10 +625,14 @@ namespace umbraco.providers.members if (m != null) { UpdateMemberProperty(m, LockPropertyTypeAlias, 0); + if (string.IsNullOrEmpty(FailedPasswordAttemptsPropertyTypeAlias) == false) + { + UpdateMemberProperty(m, FailedPasswordAttemptsPropertyTypeAlias, 0); + } m.Save(); return true; } - throw new Exception(String.Format("No member with the username '{0}' found", userName)); + throw new ProviderException(string.Format("No member with the username '{0}' found", userName)); } throw new ProviderException("To enable lock/unlocking, you need to add a 'bool' property on your membertype and add the alias of the property in the 'umbracoLockPropertyTypeAlias' attribute of the membership element in the web.config."); } @@ -622,6 +644,12 @@ namespace umbraco.providers.members public override void UpdateUser(MembershipUser user) { var m = Member.GetMemberFromLoginName(user.UserName); + + if (m == null) + { + throw new ProviderException(string.Format("No member with the username '{0}' found", user.UserName)); + } + m.Email = user.Email; // if supported, update approve status @@ -640,6 +668,86 @@ namespace umbraco.providers.members m.Save(); } + /// + /// Verifies that the specified user name and password exist in the data source. + /// + /// The name of the user to validate. + /// The password for the specified user. + /// + /// true if the specified username and password are valid; otherwise, false. + /// + public override bool ValidateUser(string username, string password) + { + var m = Member.GetMemberFromLoginAndEncodedPassword(username, EncryptOrHashExistingPassword(password)); + if (m != null) + { + // check for lock status. If locked, then set the member property to null + if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) + { + string lockedStatus = GetMemberProperty(m, LockPropertyTypeAlias, true); + if (string.IsNullOrEmpty(lockedStatus) == false) + { + var isLocked = false; + if (bool.TryParse(lockedStatus, out isLocked)) + { + if (isLocked) + { + LogHelper.Info("Cannot validate member " + username + " because they are currently locked out"); + m = null; + } + } + } + } + + //check for approve status. If not approved, then set the member property to null + if (m != null && CheckApproveStatus(m) == false) + { + LogHelper.Info("Cannot validate member " + username + " because they are not approved"); + m = null; + } + + // maybe update login date + if (m != null && string.IsNullOrEmpty(LastLoginPropertyTypeAlias) == false) + { + UpdateMemberProperty(m, LastLoginPropertyTypeAlias, DateTime.Now); + } + + // maybe reset password attempts + if (m != null && string.IsNullOrEmpty(FailedPasswordAttemptsPropertyTypeAlias) == false) + { + UpdateMemberProperty(m, FailedPasswordAttemptsPropertyTypeAlias, 0); + } + + // persist data + if (m != null) + m.Save(); + } + else if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false + && string.IsNullOrEmpty(FailedPasswordAttemptsPropertyTypeAlias) == false) + { + var updateMemberDataObject = Member.GetMemberFromLoginName(username); + // update fail rate if it's approved + if (updateMemberDataObject != null && CheckApproveStatus(updateMemberDataObject)) + { + int failedAttempts = 0; + int.TryParse(GetMemberProperty(updateMemberDataObject, FailedPasswordAttemptsPropertyTypeAlias, false), out failedAttempts); + failedAttempts = failedAttempts + 1; + UpdateMemberProperty(updateMemberDataObject, FailedPasswordAttemptsPropertyTypeAlias, failedAttempts); + + // lock user? + if (failedAttempts >= MaxInvalidPasswordAttempts) + { + UpdateMemberProperty(updateMemberDataObject, LockPropertyTypeAlias, 1); + UpdateMemberProperty(updateMemberDataObject, LastLockedOutPropertyTypeAlias, DateTime.Now); + LogHelper.Info("Member " + username + " is now locked out, max invalid password attempts exceeded"); + } + updateMemberDataObject.Save(); + } + + } + return (m != null); + } + private static void UpdateMemberProperty(Member m, string propertyTypeAlias, object propertyValue) { if (string.IsNullOrEmpty(propertyTypeAlias) == false) @@ -688,83 +796,7 @@ namespace umbraco.providers.members return null; } - - /// - /// Verifies that the specified user name and password exist in the data source. - /// - /// The name of the user to validate. - /// The password for the specified user. - /// - /// true if the specified username and password are valid; otherwise, false. - /// - public override bool ValidateUser(string username, string password) - { - var m = Member.GetMemberFromLoginAndEncodedPassword(username, EncryptOrHashExistingPassword(password)); - if (m != null) - { - // check for lock status. If locked, then set the member property to null - if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) - { - string lockedStatus = GetMemberProperty(m, LockPropertyTypeAlias, true); - if (string.IsNullOrEmpty(lockedStatus) == false) - { - var isLocked = false; - if (bool.TryParse(lockedStatus, out isLocked)) - { - if (isLocked) - { - m = null; - } - } - } - } - - //check for approve status. If not approved, then set the member property to null - if (m != null && !CheckApproveStatus(m)) { - m = null; - } - - // maybe update login date - if (m != null && string.IsNullOrEmpty(LastLoginPropertyTypeAlias) == false) - { - UpdateMemberProperty(m, LastLoginPropertyTypeAlias, DateTime.Now); - } - - // maybe reset password attempts - if (m != null && string.IsNullOrEmpty(FailedPasswordAttemptsPropertyTypeAlias) == false) - { - UpdateMemberProperty(m, FailedPasswordAttemptsPropertyTypeAlias, 0); - } - - // persist data - if (m != null) - m.Save(); - } - else if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false - && string.IsNullOrEmpty(FailedPasswordAttemptsPropertyTypeAlias) == false) - { - var updateMemberDataObject = Member.GetMemberFromLoginName(username); - // update fail rate if it's approved - if (updateMemberDataObject != null && CheckApproveStatus(updateMemberDataObject)) - { - int failedAttempts = 0; - int.TryParse(GetMemberProperty(updateMemberDataObject, FailedPasswordAttemptsPropertyTypeAlias, false), out failedAttempts); - failedAttempts = failedAttempts+1; - UpdateMemberProperty(updateMemberDataObject, FailedPasswordAttemptsPropertyTypeAlias, failedAttempts); - - // lock user? - if (failedAttempts >= MaxInvalidPasswordAttempts) - { - UpdateMemberProperty(updateMemberDataObject, LockPropertyTypeAlias, 1); - UpdateMemberProperty(updateMemberDataObject, LastLockedOutPropertyTypeAlias, DateTime.Now); - } - updateMemberDataObject.Save(); - } - - } - return (m != null); - } - + private bool CheckApproveStatus(Member m) { var isApproved = false; @@ -834,7 +866,7 @@ namespace umbraco.providers.members /// /// The password. /// The encoded password. - [Obsolete("Do not use this, it is the legacy way to encode a password")] + [Obsolete("Do not use this, it is the legacy way to encode a password - use the base class EncryptOrHashExistingPassword instead")] public string EncodePassword(string password) { return LegacyEncodePassword(password); @@ -845,7 +877,7 @@ namespace umbraco.providers.members /// /// The encoded password. /// The unencoded password. - [Obsolete("Do not use this, it is the legacy way to decode a password")] + [Obsolete("Do not use this, it is the legacy way to decode a password - use the base class DecodePassword instead")] public string UnEncodePassword(string encodedPassword) { return LegacyUnEncodePassword(encodedPassword); From 0f68818a1924072b25e1d2123bdd727ad47ed51e Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 20 Dec 2013 15:44:53 +1100 Subject: [PATCH 0126/1046] Updated member service with the newer property value queries and added unit tests for all of them --- src/Umbraco.Core/Services/MemberService.cs | 94 ++++++- .../Membership/MembershipProviderBaseTests.cs | 6 + .../Services/MemberServiceTests.cs | 258 +++++++++++++++++- .../TestHelpers/Entities/MockedMember.cs | 9 +- 4 files changed, 351 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index a5333cc503..d732953863 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -316,12 +316,49 @@ namespace Umbraco.Core.Services { using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) { - var query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).IntegerropertyValue == value); + IQuery query; + switch (matchType) + { + case ValuePropertyMatchType.Exact: + query = + Query.Builder.Where( + x => + ((Member) x).PropertyTypeAlias == propertyTypeAlias && + ((Member) x).IntegerropertyValue == value); + break; + case ValuePropertyMatchType.GreaterThan: + query = + Query.Builder.Where( + x => + ((Member) x).PropertyTypeAlias == propertyTypeAlias && + ((Member) x).IntegerropertyValue > value); + break; + case ValuePropertyMatchType.LessThan: + query = + Query.Builder.Where( + x => + ((Member) x).PropertyTypeAlias == propertyTypeAlias && + ((Member) x).IntegerropertyValue < value); + break; + case ValuePropertyMatchType.GreaterThanOrEqualTo: + query = + Query.Builder.Where( + x => + ((Member) x).PropertyTypeAlias == propertyTypeAlias && + ((Member) x).IntegerropertyValue >= value); + break; + case ValuePropertyMatchType.LessThanOrEqualTo: + query = + Query.Builder.Where( + x => + ((Member) x).PropertyTypeAlias == propertyTypeAlias && + ((Member) x).IntegerropertyValue <= value); + break; + default: + throw new ArgumentOutOfRangeException("matchType"); + } + var members = repository.GetByQuery(query); return members; } @@ -359,11 +396,48 @@ namespace Umbraco.Core.Services { using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) { - var query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).DateTimePropertyValue == value); + IQuery query; + + switch (matchType) + { + case ValuePropertyMatchType.Exact: + query = + Query.Builder.Where( + x => + ((Member) x).PropertyTypeAlias == propertyTypeAlias && + ((Member) x).DateTimePropertyValue == value); + break; + case ValuePropertyMatchType.GreaterThan: + query = + Query.Builder.Where( + x => + ((Member) x).PropertyTypeAlias == propertyTypeAlias && + ((Member) x).DateTimePropertyValue > value); + break; + case ValuePropertyMatchType.LessThan: + query = + Query.Builder.Where( + x => + ((Member) x).PropertyTypeAlias == propertyTypeAlias && + ((Member) x).DateTimePropertyValue < value); + break; + case ValuePropertyMatchType.GreaterThanOrEqualTo: + query = + Query.Builder.Where( + x => + ((Member) x).PropertyTypeAlias == propertyTypeAlias && + ((Member) x).DateTimePropertyValue >= value); + break; + case ValuePropertyMatchType.LessThanOrEqualTo: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).DateTimePropertyValue <= value); + break; + default: + throw new ArgumentOutOfRangeException("matchType"); + } var members = repository.GetByQuery(query); return members; diff --git a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs index f1b8d66773..cf29e71eab 100644 --- a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs +++ b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs @@ -35,6 +35,12 @@ namespace Umbraco.Tests.Membership //[Test] //public void CreateUser_Base_Validation() + //[Test] + //public void GetPassword_Base_Validation() + + //[Test] + //public void ResetPassword_Base_Validation() + [Test] public void Sets_Defaults() { diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs index 54fa355ce5..3fe0940b42 100644 --- a/src/Umbraco.Tests/Services/MemberServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using NUnit.Framework; using Umbraco.Core.Models; @@ -239,7 +240,7 @@ namespace Umbraco.Tests.Services } [Test] - public void Get_By_Property_Value_Exact() + public void Get_By_Property_String_Value_Exact() { IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); ServiceContext.MemberTypeService.Save(memberType); @@ -255,7 +256,7 @@ namespace Umbraco.Tests.Services } [Test] - public void Get_By_Property_Value_Contains() + public void Get_By_Property_String_Value_Contains() { IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); ServiceContext.MemberTypeService.Save(memberType); @@ -271,7 +272,7 @@ namespace Umbraco.Tests.Services } [Test] - public void Get_By_Property_Value_Starts_With() + public void Get_By_Property_String_Value_Starts_With() { IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); ServiceContext.MemberTypeService.Save(memberType); @@ -287,7 +288,7 @@ namespace Umbraco.Tests.Services } [Test] - public void Get_By_Property_Value_Ends_With() + public void Get_By_Property_String_Value_Ends_With() { IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); ServiceContext.MemberTypeService.Save(memberType); @@ -303,6 +304,255 @@ namespace Umbraco.Tests.Services Assert.AreEqual(1, found.Count()); } + [Test] + public void Get_By_Property_Int_Value_Exact() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + memberType.AddPropertyType(new PropertyType(new Guid(), DataTypeDatabaseType.Integer) + { + Alias = "number", + Name = "Number", + //NOTE: This is what really determines the db type - the above definition doesn't really do anything + DataTypeDefinitionId = -36 + }, "Content"); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10, (i, member) => member.SetValue("number", i)); + ServiceContext.MemberService.Save(members); + + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + customMember.SetValue("number", 2); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.GetMembersByPropertyValue( + "number", 2, ValuePropertyMatchType.Exact); + + Assert.AreEqual(2, found.Count()); + } + + [Test] + public void Get_By_Property_Int_Value_Greater_Than() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + memberType.AddPropertyType(new PropertyType(new Guid(), DataTypeDatabaseType.Integer) + { + Alias = "number", + Name = "Number", + //NOTE: This is what really determines the db type - the above definition doesn't really do anything + DataTypeDefinitionId = -36 + }, "Content"); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10, (i, member) => member.SetValue("number", i)); + ServiceContext.MemberService.Save(members); + + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + customMember.SetValue("number", 10); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.GetMembersByPropertyValue( + "number", 3, ValuePropertyMatchType.GreaterThan); + + Assert.AreEqual(7, found.Count()); + } + + [Test] + public void Get_By_Property_Int_Value_Greater_Than_Equal_To() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + memberType.AddPropertyType(new PropertyType(new Guid(), DataTypeDatabaseType.Integer) + { + Alias = "number", + Name = "Number", + //NOTE: This is what really determines the db type - the above definition doesn't really do anything + DataTypeDefinitionId = -36 + }, "Content"); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10, (i, member) => member.SetValue("number", i)); + ServiceContext.MemberService.Save(members); + + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + customMember.SetValue("number", 10); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.GetMembersByPropertyValue( + "number", 3, ValuePropertyMatchType.GreaterThanOrEqualTo); + + Assert.AreEqual(8, found.Count()); + } + + [Test] + public void Get_By_Property_Int_Value_Less_Than() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + memberType.AddPropertyType(new PropertyType(new Guid(), DataTypeDatabaseType.Date) + { + Alias = "date", + Name = "Date", + //NOTE: This is what really determines the db type - the above definition doesn't really do anything + DataTypeDefinitionId = -36 + }, "Content"); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10, (i, member) => member.SetValue("number", i)); + ServiceContext.MemberService.Save(members); + + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + customMember.SetValue("number", 1); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.GetMembersByPropertyValue( + "number", 5, ValuePropertyMatchType.LessThan); + + Assert.AreEqual(6, found.Count()); + } + + [Test] + public void Get_By_Property_Int_Value_Less_Than_Or_Equal() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + memberType.AddPropertyType(new PropertyType(new Guid(), DataTypeDatabaseType.Integer) + { + Alias = "number", + Name = "Number", + //NOTE: This is what really determines the db type - the above definition doesn't really do anything + DataTypeDefinitionId = -36 + }, "Content"); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10, (i, member) => member.SetValue("number", i)); + ServiceContext.MemberService.Save(members); + + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + customMember.SetValue("number", 1); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.GetMembersByPropertyValue( + "number", 5, ValuePropertyMatchType.LessThanOrEqualTo); + + Assert.AreEqual(7, found.Count()); + } + + [Test] + public void Get_By_Property_Date_Value_Exact() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + memberType.AddPropertyType(new PropertyType(new Guid(), DataTypeDatabaseType.Integer) + { + Alias = "date", + Name = "Date", + //NOTE: This is what really determines the db type - the above definition doesn't really do anything + DataTypeDefinitionId = -36 + }, "Content"); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10, (i, member) => member.SetValue("date", new DateTime(2013, 12, 20, 1, i, 0))); + ServiceContext.MemberService.Save(members); + + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + customMember.SetValue("date", new DateTime(2013, 12, 20, 1, 2, 0)); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.GetMembersByPropertyValue( + "date", new DateTime(2013, 12, 20, 1, 2, 0), ValuePropertyMatchType.Exact); + + Assert.AreEqual(2, found.Count()); + } + + [Test] + public void Get_By_Property_Date_Value_Greater_Than() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + memberType.AddPropertyType(new PropertyType(new Guid(), DataTypeDatabaseType.Integer) + { + Alias = "date", + Name = "Date", + //NOTE: This is what really determines the db type - the above definition doesn't really do anything + DataTypeDefinitionId = -36 + }, "Content"); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10, (i, member) => member.SetValue("date", new DateTime(2013, 12, 20, 1, i, 0))); + ServiceContext.MemberService.Save(members); + + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + customMember.SetValue("date", new DateTime(2013, 12, 20, 1, 10, 0)); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.GetMembersByPropertyValue( + "date", new DateTime(2013, 12, 20, 1, 3, 0), ValuePropertyMatchType.GreaterThan); + + Assert.AreEqual(7, found.Count()); + } + + [Test] + public void Get_By_Property_Date_Value_Greater_Than_Equal_To() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + memberType.AddPropertyType(new PropertyType(new Guid(), DataTypeDatabaseType.Integer) + { + Alias = "date", + Name = "Date", + //NOTE: This is what really determines the db type - the above definition doesn't really do anything + DataTypeDefinitionId = -36 + }, "Content"); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10, (i, member) => member.SetValue("date", new DateTime(2013, 12, 20, 1, i, 0))); + ServiceContext.MemberService.Save(members); + + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + customMember.SetValue("date", new DateTime(2013, 12, 20, 1, 10, 0)); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.GetMembersByPropertyValue( + "date", new DateTime(2013, 12, 20, 1, 3, 0), ValuePropertyMatchType.GreaterThanOrEqualTo); + + Assert.AreEqual(8, found.Count()); + } + + [Test] + public void Get_By_Property_Date_Value_Less_Than() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + memberType.AddPropertyType(new PropertyType(new Guid(), DataTypeDatabaseType.Integer) + { + Alias = "date", + Name = "Date", + //NOTE: This is what really determines the db type - the above definition doesn't really do anything + DataTypeDefinitionId = -36 + }, "Content"); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10, (i, member) => member.SetValue("date", new DateTime(2013, 12, 20, 1, i, 0))); + ServiceContext.MemberService.Save(members); + + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + customMember.SetValue("date", new DateTime(2013, 12, 20, 1, 1, 0)); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.GetMembersByPropertyValue( + "date", new DateTime(2013, 12, 20, 1, 5, 0), ValuePropertyMatchType.LessThan); + + Assert.AreEqual(6, found.Count()); + } + + [Test] + public void Get_By_Property_Date_Value_Less_Than_Or_Equal() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + memberType.AddPropertyType(new PropertyType(new Guid(), DataTypeDatabaseType.Integer) + { + Alias = "date", + Name = "Date", + //NOTE: This is what really determines the db type - the above definition doesn't really do anything + DataTypeDefinitionId = -36 + }, "Content"); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10, (i, member) => member.SetValue("date", new DateTime(2013, 12, 20, 1, i, 0))); + ServiceContext.MemberService.Save(members); + + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + customMember.SetValue("date", new DateTime(2013, 12, 20, 1, 1, 0)); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.GetMembersByPropertyValue( + "date", new DateTime(2013, 12, 20, 1, 5, 0), ValuePropertyMatchType.LessThanOrEqualTo); + + Assert.AreEqual(7, found.Count()); + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedMember.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedMember.cs index 0aa224d635..2bf06ec87a 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedMember.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedMember.cs @@ -31,7 +31,7 @@ namespace Umbraco.Tests.TestHelpers.Entities return member; } - public static IEnumerable CreateSimpleMember(IMemberType memberType, int amount) + public static IEnumerable CreateSimpleMember(IMemberType memberType, int amount, Action onCreating = null) { var list = new List(); @@ -43,8 +43,13 @@ namespace Umbraco.Tests.TestHelpers.Entities member.SetValue("bodyText", "This is a subpage" + i); member.SetValue("author", "John Doe" + i); - member.ResetDirtyProperties(false); + if (onCreating != null) + { + onCreating(i, member); + } + member.ResetDirtyProperties(false); + list.Add(member); } From a4dde1948f5a5ee7c69844488d97b18d936e2c61 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 20 Dec 2013 16:20:32 +1100 Subject: [PATCH 0127/1046] Implements the member count methods on the member service and adds unit tests. --- .../Interfaces/IMemberRepository.cs | 8 ++ .../Repositories/MemberRepository.cs | 12 +++ src/Umbraco.Core/Services/MemberService.cs | 90 +++++++++++++------ .../Services/MemberServiceTests.cs | 67 ++++++++++++++ 4 files changed, 150 insertions(+), 27 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs index ed98d81551..c37997769e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { @@ -19,5 +20,12 @@ namespace Umbraco.Core.Persistence.Repositories /// bool Exists(string username); + /// + /// Gets the count of items based on a complex query + /// + /// + /// + int GetCountByQuery(IQuery query); + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index ba28e68f02..6fb99d0d13 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -448,6 +448,18 @@ namespace Umbraco.Core.Persistence.Repositories return Database.ExecuteScalar(sql) > 0; } + public int GetCountByQuery(IQuery query) + { + var sqlSubquery = GetSubquery(); + var translator = new SqlTranslator(sqlSubquery, query); + var subquery = translator.Translate(); + //get the COUNT base query + var sql = GetBaseQuery(true) + .Append(new Sql("WHERE umbracoNode.id IN (" + subquery.SQL + ")", subquery.Arguments)); + + return Database.ExecuteScalar(sql); + } + private IMember BuildFromDto(List dtos) { if (dtos == null || dtos.Any() == false) diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index d732953863..f5474288ff 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading; +using System.Web.Security; using System.Xml.Linq; using Umbraco.Core.Events; using Umbraco.Core.Models; @@ -75,7 +76,7 @@ namespace Umbraco.Core.Services public IMember GetById(int id) { using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) - { + { return repository.Get(id); } } @@ -268,7 +269,7 @@ namespace Umbraco.Core.Services query = Query.Builder.Where( x => - ((Member) x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).PropertyTypeAlias == propertyTypeAlias && (((Member)x).LongStringPropertyValue.SqlEquals(value, TextColumnType.NText) || ((Member)x).ShortStringPropertyValue.SqlEquals(value, TextColumnType.NVarchar))); break; @@ -276,7 +277,7 @@ namespace Umbraco.Core.Services query = Query.Builder.Where( x => - ((Member) x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).PropertyTypeAlias == propertyTypeAlias && (((Member)x).LongStringPropertyValue.SqlContains(value, TextColumnType.NText) || ((Member)x).ShortStringPropertyValue.SqlContains(value, TextColumnType.NVarchar))); break; @@ -284,7 +285,7 @@ namespace Umbraco.Core.Services query = Query.Builder.Where( x => - ((Member) x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).PropertyTypeAlias == propertyTypeAlias && (((Member)x).LongStringPropertyValue.SqlStartsWith(value, TextColumnType.NText) || ((Member)x).ShortStringPropertyValue.SqlStartsWith(value, TextColumnType.NVarchar))); break; @@ -292,7 +293,7 @@ namespace Umbraco.Core.Services query = Query.Builder.Where( x => - ((Member) x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).PropertyTypeAlias == propertyTypeAlias && (((Member)x).LongStringPropertyValue.SqlEndsWith(value, TextColumnType.NText) || ((Member)x).ShortStringPropertyValue.SqlEndsWith(value, TextColumnType.NVarchar))); break; @@ -324,41 +325,41 @@ namespace Umbraco.Core.Services query = Query.Builder.Where( x => - ((Member) x).PropertyTypeAlias == propertyTypeAlias && - ((Member) x).IntegerropertyValue == value); + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).IntegerropertyValue == value); break; case ValuePropertyMatchType.GreaterThan: query = Query.Builder.Where( x => - ((Member) x).PropertyTypeAlias == propertyTypeAlias && - ((Member) x).IntegerropertyValue > value); + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).IntegerropertyValue > value); break; case ValuePropertyMatchType.LessThan: query = Query.Builder.Where( x => - ((Member) x).PropertyTypeAlias == propertyTypeAlias && - ((Member) x).IntegerropertyValue < value); + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).IntegerropertyValue < value); break; case ValuePropertyMatchType.GreaterThanOrEqualTo: query = Query.Builder.Where( x => - ((Member) x).PropertyTypeAlias == propertyTypeAlias && - ((Member) x).IntegerropertyValue >= value); + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).IntegerropertyValue >= value); break; case ValuePropertyMatchType.LessThanOrEqualTo: query = Query.Builder.Where( x => - ((Member) x).PropertyTypeAlias == propertyTypeAlias && - ((Member) x).IntegerropertyValue <= value); + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).IntegerropertyValue <= value); break; default: throw new ArgumentOutOfRangeException("matchType"); } - + var members = repository.GetByQuery(query); return members; } @@ -404,29 +405,29 @@ namespace Umbraco.Core.Services query = Query.Builder.Where( x => - ((Member) x).PropertyTypeAlias == propertyTypeAlias && - ((Member) x).DateTimePropertyValue == value); + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).DateTimePropertyValue == value); break; case ValuePropertyMatchType.GreaterThan: query = Query.Builder.Where( x => - ((Member) x).PropertyTypeAlias == propertyTypeAlias && - ((Member) x).DateTimePropertyValue > value); + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).DateTimePropertyValue > value); break; case ValuePropertyMatchType.LessThan: query = Query.Builder.Where( x => - ((Member) x).PropertyTypeAlias == propertyTypeAlias && - ((Member) x).DateTimePropertyValue < value); + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).DateTimePropertyValue < value); break; case ValuePropertyMatchType.GreaterThanOrEqualTo: query = Query.Builder.Where( x => - ((Member) x).PropertyTypeAlias == propertyTypeAlias && - ((Member) x).DateTimePropertyValue >= value); + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).DateTimePropertyValue >= value); break; case ValuePropertyMatchType.LessThanOrEqualTo: query = @@ -443,7 +444,7 @@ namespace Umbraco.Core.Services return members; } } - + #endregion #region IMembershipMemberService Implementation @@ -460,7 +461,42 @@ namespace Umbraco.Core.Services /// public int GetMemberCount(MemberCountType countType) { - throw new NotImplementedException(); + using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork())) + { + IQuery query; + + switch (countType) + { + case MemberCountType.All: + query = new Query(); + return repository.Count(query); + case MemberCountType.Online: + var fromDate = DateTime.Now.AddMinutes(-Membership.UserIsOnlineTimeWindow); + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == Constants.Conventions.Member.LastLoginDate && + ((Member)x).DateTimePropertyValue > fromDate); + return repository.GetCountByQuery(query); + case MemberCountType.LockedOut: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == Constants.Conventions.Member.IsLockedOut && + ((Member)x).BoolPropertyValue == true); + return repository.GetCountByQuery(query); + case MemberCountType.Approved: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == Constants.Conventions.Member.IsApproved && + ((Member)x).BoolPropertyValue == true); + return repository.GetCountByQuery(query); + default: + throw new ArgumentOutOfRangeException("countType"); + } + } + } public IEnumerable GetAllMembers(int pageIndex, int pageSize, out int totalRecords) @@ -610,7 +646,7 @@ namespace Umbraco.Core.Services return; } using (new WriteLock(Locker)) - { + { var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateMemberRepository(uow)) { diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs index 3fe0940b42..873e5c189d 100644 --- a/src/Umbraco.Tests/Services/MemberServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.Querying; @@ -554,5 +555,71 @@ namespace Umbraco.Tests.Services Assert.AreEqual(7, found.Count()); } + [Test] + public void Count_All_Members() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10); + ServiceContext.MemberService.Save(members); + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.GetMemberCount(MemberCountType.All); + + Assert.AreEqual(11, found); + } + + [Test] + public void Count_All_Online_Members() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10, (i, member) => member.LastLoginDate = DateTime.Now.AddMinutes(i * -2)); + ServiceContext.MemberService.Save(members); + + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + customMember.SetValue(Constants.Conventions.Member.LastLoginDate, DateTime.Now); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.GetMemberCount(MemberCountType.Online); + + Assert.AreEqual(9, found); + } + + [Test] + public void Count_All_Locked_Members() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10, (i, member) => member.IsLockedOut = i % 2 == 0); + ServiceContext.MemberService.Save(members); + + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + customMember.SetValue(Constants.Conventions.Member.IsLockedOut, true); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.GetMemberCount(MemberCountType.LockedOut); + + Assert.AreEqual(6, found); + } + + [Test] + public void Count_All_Approved_Members() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + var members = MockedMember.CreateSimpleMember(memberType, 10, (i, member) => member.IsApproved = i % 2 == 0); + ServiceContext.MemberService.Save(members); + + var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); + customMember.SetValue(Constants.Conventions.Member.IsApproved, false); + ServiceContext.MemberService.Save(customMember); + + var found = ServiceContext.MemberService.GetMemberCount(MemberCountType.Approved); + + Assert.AreEqual(5, found); + } + } } \ No newline at end of file From 7a8b3dc9bcf24dcc7394c3c28fadbef829d0d7d0 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 20 Dec 2013 18:11:12 +1100 Subject: [PATCH 0128/1046] Updated the member service/repo to have paged queries and results which use db paging for maximum efficiency (of course only works for things like email, username but those are 2 specific things that are needed by the membership provider) --- .../Interfaces/IMemberRepository.cs | 16 ++++++- .../Repositories/MemberRepository.cs | 47 +++++++++++++++++++ .../Services/IMembershipMemberService.cs | 4 +- src/Umbraco.Core/Services/MemberService.cs | 15 ++++-- .../Services/MemberServiceTests.cs | 24 ++++++---- .../Providers/MembersMembershipProvider.cs | 18 +++---- .../members/UmbracoMembershipProvider.cs | 18 +++---- 7 files changed, 111 insertions(+), 31 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs index c37997769e..1045182cf5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs @@ -1,5 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq.Expressions; using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories @@ -27,5 +30,16 @@ namespace Umbraco.Core.Persistence.Repositories /// int GetCountByQuery(IQuery query); + /// + /// Gets paged member results + /// + /// + /// + /// + /// + /// + /// + IEnumerable GetPagedResultsByQuery(IQuery query, int pageIndex, int pageSize, out int totalRecords, Expression> orderBy); + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index 6fb99d0d13..58996209fa 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Linq.Expressions; using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Models.EntityBase; @@ -9,6 +10,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.Caching; using Umbraco.Core.Persistence.Factories; +using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Relators; using Umbraco.Core.Persistence.UnitOfWork; @@ -460,6 +462,51 @@ namespace Umbraco.Core.Persistence.Repositories return Database.ExecuteScalar(sql); } + /// + /// Gets paged member results + /// + /// + /// + /// + /// + /// + /// + /// + /// The query supplied will ONLY work with data specifically on the cmsMember table because we are using PetaPoco paging (SQL paging) + /// + public IEnumerable GetPagedResultsByQuery(IQuery query, int pageIndex, int pageSize, out int totalRecords, Expression> orderBy) + { + if (orderBy == null) throw new ArgumentNullException("orderBy"); + + var sql = new Sql(); + sql.Select("*").From(); + + var translator = new SqlTranslator(sql, query); + var resultQuery = translator.Translate(); + + //get the referenced column name + var expressionMember = ExpressionHelper.GetMemberInfo(orderBy); + //now find the mapped column name + var mapper = MappingResolver.Current.ResolveMapperByType(typeof(IMember)); + var mappedField = mapper.Map(expressionMember.Name); + if (mappedField.IsNullOrWhiteSpace()) + { + throw new ArgumentException("Could not find a mapping for the column specified in the orderBy clause"); + } + //need to ensure the order by is in brackets, see: https://github.com/toptensoftware/PetaPoco/issues/177 + resultQuery.OrderBy(string.Format("({0})", mappedField)); + + var pagedResult = Database.Page(pageIndex + 1, pageSize, resultQuery); + + totalRecords = Convert.ToInt32(pagedResult.TotalItems); + + //now that we have the member dto's we need to construct true members from the list. + var result = GetAll(pagedResult.Items.Select(x => x.NodeId).ToArray()); + + //now we need to ensure this result is also ordered by the same order by clause + return result.OrderBy(orderBy.Compile()); + } + private IMember BuildFromDto(List dtos) { if (dtos == null || dtos.Any() == false) diff --git a/src/Umbraco.Core/Services/IMembershipMemberService.cs b/src/Umbraco.Core/Services/IMembershipMemberService.cs index 715f85f3fb..6c3027d21c 100644 --- a/src/Umbraco.Core/Services/IMembershipMemberService.cs +++ b/src/Umbraco.Core/Services/IMembershipMemberService.cs @@ -46,9 +46,9 @@ namespace Umbraco.Core.Services void Save(IEnumerable members, bool raiseEvents = true); - IEnumerable FindMembersByEmail(string emailStringToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); + IEnumerable FindMembersByEmail(string emailStringToMatch, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); - IEnumerable FindMembersByUsername(string login, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); + IEnumerable FindMembersByUsername(string login, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); /// /// Gets the total number of members based on the count type diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index f5474288ff..d180d13e82 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -184,9 +184,12 @@ namespace Umbraco.Core.Services /// Does a search for members that contain the specified string in their email address /// /// + /// /// + /// + /// /// - public IEnumerable FindMembersByEmail(string emailStringToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + public IEnumerable FindMembersByEmail(string emailStringToMatch, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) { var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateMemberRepository(uow)) @@ -214,11 +217,13 @@ namespace Umbraco.Core.Services throw new ArgumentOutOfRangeException("matchType"); } - return repository.GetByQuery(query); + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, dto => dto.Email); + + //return repository.GetByQuery(query); } } - public IEnumerable FindMembersByUsername(string login, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + public IEnumerable FindMembersByUsername(string login, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) { var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateMemberRepository(uow)) @@ -246,7 +251,9 @@ namespace Umbraco.Core.Services throw new ArgumentOutOfRangeException("matchType"); } - return repository.GetByQuery(query); + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, dto => dto.Username); + + //return repository.GetByQuery(query); } } diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs index 873e5c189d..76f565223e 100644 --- a/src/Umbraco.Tests/Services/MemberServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs @@ -123,7 +123,8 @@ namespace Umbraco.Tests.Services var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello","hello"); ServiceContext.MemberService.Save(customMember); - var found = ServiceContext.MemberService.FindMembersByEmail("tes", StringPropertyMatchType.StartsWith); + int totalRecs; + var found = ServiceContext.MemberService.FindMembersByEmail("tes", 0, 100, out totalRecs, StringPropertyMatchType.StartsWith); Assert.AreEqual(10, found.Count()); } @@ -139,7 +140,8 @@ namespace Umbraco.Tests.Services var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); ServiceContext.MemberService.Save(customMember); - var found = ServiceContext.MemberService.FindMembersByEmail("test.com", StringPropertyMatchType.EndsWith); + int totalRecs; + var found = ServiceContext.MemberService.FindMembersByEmail("test.com", 0, 100, out totalRecs, StringPropertyMatchType.EndsWith); Assert.AreEqual(11, found.Count()); } @@ -155,7 +157,8 @@ namespace Umbraco.Tests.Services var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); ServiceContext.MemberService.Save(customMember); - var found = ServiceContext.MemberService.FindMembersByEmail("test", StringPropertyMatchType.Contains); + int totalRecs; + var found = ServiceContext.MemberService.FindMembersByEmail("test", 0, 100, out totalRecs, StringPropertyMatchType.Contains); Assert.AreEqual(11, found.Count()); } @@ -171,7 +174,8 @@ namespace Umbraco.Tests.Services var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); ServiceContext.MemberService.Save(customMember); - var found = ServiceContext.MemberService.FindMembersByEmail("hello@test.com", StringPropertyMatchType.Exact); + int totalRecs; + var found = ServiceContext.MemberService.FindMembersByEmail("hello@test.com", 0, 100, out totalRecs, StringPropertyMatchType.Exact); Assert.AreEqual(1, found.Count()); } @@ -187,7 +191,8 @@ namespace Umbraco.Tests.Services var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); ServiceContext.MemberService.Save(customMember); - var found = ServiceContext.MemberService.FindMembersByUsername("tes", StringPropertyMatchType.StartsWith); + int totalRecs; + var found = ServiceContext.MemberService.FindMembersByUsername("tes", 0, 100, out totalRecs, StringPropertyMatchType.StartsWith); Assert.AreEqual(10, found.Count()); } @@ -203,7 +208,8 @@ namespace Umbraco.Tests.Services var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); ServiceContext.MemberService.Save(customMember); - var found = ServiceContext.MemberService.FindMembersByUsername("llo", StringPropertyMatchType.EndsWith); + int totalRecs; + var found = ServiceContext.MemberService.FindMembersByUsername("llo", 0, 100, out totalRecs, StringPropertyMatchType.EndsWith); Assert.AreEqual(1, found.Count()); } @@ -219,7 +225,8 @@ namespace Umbraco.Tests.Services var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hellotest"); ServiceContext.MemberService.Save(customMember); - var found = ServiceContext.MemberService.FindMembersByUsername("test", StringPropertyMatchType.Contains); + int totalRecs; + var found = ServiceContext.MemberService.FindMembersByUsername("test", 0, 100, out totalRecs, StringPropertyMatchType.Contains); Assert.AreEqual(11, found.Count()); } @@ -235,7 +242,8 @@ namespace Umbraco.Tests.Services var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); ServiceContext.MemberService.Save(customMember); - var found = ServiceContext.MemberService.FindMembersByUsername("hello", StringPropertyMatchType.Exact); + int totalRecs; + var found = ServiceContext.MemberService.FindMembersByUsername("hello", 0, 100, out totalRecs, StringPropertyMatchType.Exact); Assert.AreEqual(1, found.Count()); } diff --git a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs index 133314d31a..d1b9c9a14e 100644 --- a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs @@ -203,12 +203,13 @@ namespace Umbraco.Web.Security.Providers /// public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) { - var byEmail = MemberService.FindMembersByEmail(emailToMatch, StringPropertyMatchType.Wildcard).ToArray(); - totalRecords = byEmail.Length; - var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); + var byEmail = MemberService.FindMembersByEmail(emailToMatch, pageIndex, pageSize, out totalRecords, StringPropertyMatchType.Wildcard).ToArray(); + //totalRecords = byEmail.Length; + //var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); var collection = new MembershipUserCollection(); - foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) + //foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) + foreach (var m in byEmail) { collection.Add(m.AsConcreteMembershipUser()); } @@ -227,12 +228,13 @@ namespace Umbraco.Web.Security.Providers /// public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) { - var byEmail = MemberService.FindMembersByUsername(usernameToMatch, StringPropertyMatchType.Wildcard).ToArray(); - totalRecords = byEmail.Length; - var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); + var byEmail = MemberService.FindMembersByUsername(usernameToMatch, pageIndex, pageSize, out totalRecords, StringPropertyMatchType.Wildcard).ToArray(); + //totalRecords = byEmail.Length; + //var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); var collection = new MembershipUserCollection(); - foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) + //foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) + foreach (var m in byEmail) { collection.Add(m.AsConcreteMembershipUser()); } diff --git a/src/umbraco.providers/members/UmbracoMembershipProvider.cs b/src/umbraco.providers/members/UmbracoMembershipProvider.cs index 500444b3fd..6bac76be81 100644 --- a/src/umbraco.providers/members/UmbracoMembershipProvider.cs +++ b/src/umbraco.providers/members/UmbracoMembershipProvider.cs @@ -343,12 +343,13 @@ namespace umbraco.providers.members /// public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) { - var byEmail = ApplicationContext.Current.Services.MemberService.FindMembersByEmail(emailToMatch, StringPropertyMatchType.Wildcard).ToArray(); - totalRecords = byEmail.Length; - var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); + var byEmail = ApplicationContext.Current.Services.MemberService.FindMembersByEmail(emailToMatch, pageIndex, pageSize, out totalRecords, StringPropertyMatchType.Wildcard).ToArray(); + //totalRecords = byEmail.Length; + //var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); var collection = new MembershipUserCollection(); - foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) + //foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) + foreach (var m in byEmail) { collection.Add(ConvertToMembershipUser(m)); } @@ -367,12 +368,13 @@ namespace umbraco.providers.members /// public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) { - var byEmail = ApplicationContext.Current.Services.MemberService.FindMembersByUsername(usernameToMatch, StringPropertyMatchType.Wildcard).ToArray(); - totalRecords = byEmail.Length; - var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); + var byEmail = ApplicationContext.Current.Services.MemberService.FindMembersByUsername(usernameToMatch, pageIndex, pageSize, out totalRecords, StringPropertyMatchType.Wildcard).ToArray(); + //totalRecords = byEmail.Length; + //var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); var collection = new MembershipUserCollection(); - foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) + //foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) + foreach (var m in byEmail) { collection.Add(ConvertToMembershipUser(m)); } From 1c139b685af5480820eea0a451a2ea3599215d47 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 20 Dec 2013 18:12:54 +1100 Subject: [PATCH 0129/1046] updates legacy membership provider to use efficient paging. --- src/Umbraco.Tests/Views/web.config | 55 ------------------- .../Providers/MembersMembershipProvider.cs | 8 +-- .../members/UmbracoMembershipProvider.cs | 29 +++------- 3 files changed, 9 insertions(+), 83 deletions(-) delete mode 100644 src/Umbraco.Tests/Views/web.config diff --git a/src/Umbraco.Tests/Views/web.config b/src/Umbraco.Tests/Views/web.config deleted file mode 100644 index aa5e0eb2e3..0000000000 --- a/src/Umbraco.Tests/Views/web.config +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs index d1b9c9a14e..5e263f76a4 100644 --- a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs @@ -204,11 +204,8 @@ namespace Umbraco.Web.Security.Providers public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) { var byEmail = MemberService.FindMembersByEmail(emailToMatch, pageIndex, pageSize, out totalRecords, StringPropertyMatchType.Wildcard).ToArray(); - //totalRecords = byEmail.Length; - //var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); - + var collection = new MembershipUserCollection(); - //foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) foreach (var m in byEmail) { collection.Add(m.AsConcreteMembershipUser()); @@ -229,11 +226,8 @@ namespace Umbraco.Web.Security.Providers public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) { var byEmail = MemberService.FindMembersByUsername(usernameToMatch, pageIndex, pageSize, out totalRecords, StringPropertyMatchType.Wildcard).ToArray(); - //totalRecords = byEmail.Length; - //var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); var collection = new MembershipUserCollection(); - //foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) foreach (var m in byEmail) { collection.Add(m.AsConcreteMembershipUser()); diff --git a/src/umbraco.providers/members/UmbracoMembershipProvider.cs b/src/umbraco.providers/members/UmbracoMembershipProvider.cs index 6bac76be81..fa3a70804e 100644 --- a/src/umbraco.providers/members/UmbracoMembershipProvider.cs +++ b/src/umbraco.providers/members/UmbracoMembershipProvider.cs @@ -344,11 +344,8 @@ namespace umbraco.providers.members public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) { var byEmail = ApplicationContext.Current.Services.MemberService.FindMembersByEmail(emailToMatch, pageIndex, pageSize, out totalRecords, StringPropertyMatchType.Wildcard).ToArray(); - //totalRecords = byEmail.Length; - //var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); - - var collection = new MembershipUserCollection(); - //foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) + + var collection = new MembershipUserCollection(); foreach (var m in byEmail) { collection.Add(ConvertToMembershipUser(m)); @@ -369,11 +366,8 @@ namespace umbraco.providers.members public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) { var byEmail = ApplicationContext.Current.Services.MemberService.FindMembersByUsername(usernameToMatch, pageIndex, pageSize, out totalRecords, StringPropertyMatchType.Wildcard).ToArray(); - //totalRecords = byEmail.Length; - //var pagedResult = new PagedResult(totalRecords, pageIndex, pageSize); - - var collection = new MembershipUserCollection(); - //foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize)) + + var collection = new MembershipUserCollection(); foreach (var m in byEmail) { collection.Add(ConvertToMembershipUser(m)); @@ -392,22 +386,15 @@ namespace umbraco.providers.members /// public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords) { - var counter = 0; - var startIndex = pageSize * pageIndex; - var endIndex = startIndex + pageSize - 1; var membersList = new MembershipUserCollection(); - var memberArray = Member.GetAll; - totalRecords = memberArray.Length; - foreach (var m in memberArray) + var pagedMembers = ApplicationContext.Current.Services.MemberService.GetAllMembers(pageIndex, pageSize, out totalRecords); + + foreach (var m in pagedMembers) { - if (counter >= startIndex) - membersList.Add(ConvertToMembershipUser(m)); - if (counter >= endIndex) break; - counter++; + membersList.Add(ConvertToMembershipUser(m)); } return membersList; - } /// From 63ca9f210b95d11764686d25c244ac1f51aa16a3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 24 Dec 2013 10:48:14 +1100 Subject: [PATCH 0130/1046] updates version # --- build/Build.bat | 2 +- src/Umbraco.Core/Configuration/UmbracoVersion.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/Build.bat b/build/Build.bat index af15cd5f07..ead7df0abc 100644 --- a/build/Build.bat +++ b/build/Build.bat @@ -1,5 +1,5 @@ @ECHO OFF -SET release=7.0.1 +SET release=7.0.2 SET comment= SET version=%release% diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index 86b2f17836..f4517608a4 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -5,7 +5,7 @@ namespace Umbraco.Core.Configuration { public class UmbracoVersion { - private static readonly Version Version = new Version("7.0.1"); + private static readonly Version Version = new Version("7.0.2"); /// /// Gets the current version of Umbraco. From 154ee3975579f3c883d74970a638de3432e6e22b Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 24 Dec 2013 10:48:32 +1100 Subject: [PATCH 0131/1046] Fixes: U4-3907 Token cookie issues in 7.0.1 --- src/Umbraco.Web/HttpCookieExtensions.cs | 27 +++++++++++++++++- .../ClearCsrfCookiesAfterUpgrade.cs | 28 +++++++++++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 1 + .../SetAngularAntiForgeryTokensAttribute.cs | 13 +++++++-- 4 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 src/Umbraco.Web/Strategies/Migrations/ClearCsrfCookiesAfterUpgrade.cs diff --git a/src/Umbraco.Web/HttpCookieExtensions.cs b/src/Umbraco.Web/HttpCookieExtensions.cs index 8a17d440c4..dc6d087fb3 100644 --- a/src/Umbraco.Web/HttpCookieExtensions.cs +++ b/src/Umbraco.Web/HttpCookieExtensions.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Web; @@ -14,6 +15,30 @@ namespace Umbraco.Web /// internal static class HttpCookieExtensions { + /// + /// Removes the cookie from the request and the response if it exists + /// + /// + /// + public static void ExpireCookie(this HttpContextBase http, string cookieName) + { + //remove from the request + http.Request.Cookies.Remove(cookieName); + + //expire from the response + var angularCookie = http.Response.Cookies[cookieName]; + if (angularCookie != null) + { + //this will expire immediately and be removed from the browser + angularCookie.Expires = DateTime.Now.AddYears(-1); + } + else + { + //ensure there's def an expired cookie + http.Response.Cookies.Add(new HttpCookie(cookieName) { Expires = DateTime.Now.AddYears(-1) }); + } + } + public static string GetPreviewCookieValue(this HttpRequestMessage request) { var cookie = request.Headers.GetCookies(Constants.Web.PreviewCookieName).FirstOrDefault(); diff --git a/src/Umbraco.Web/Strategies/Migrations/ClearCsrfCookiesAfterUpgrade.cs b/src/Umbraco.Web/Strategies/Migrations/ClearCsrfCookiesAfterUpgrade.cs new file mode 100644 index 0000000000..8e76053a09 --- /dev/null +++ b/src/Umbraco.Web/Strategies/Migrations/ClearCsrfCookiesAfterUpgrade.cs @@ -0,0 +1,28 @@ +using System.Web; +using Umbraco.Core.Persistence.Migrations; +using Umbraco.Web.WebApi.Filters; +using umbraco.interfaces; + +namespace Umbraco.Web.Strategies.Migrations +{ + /// + /// After upgrade we clear out the csrf tokens + /// + public class ClearCsrfCookiesAfterUpgrade : IApplicationStartupHandler + { + public ClearCsrfCookiesAfterUpgrade() + { + MigrationRunner.Migrated += MigrationRunner_Migrated; + } + + void MigrationRunner_Migrated(MigrationRunner sender, Core.Events.MigrationEventArgs e) + { + if (HttpContext.Current == null) return; + + var http = new HttpContextWrapper(HttpContext.Current); + + http.ExpireCookie(AngularAntiForgeryHelper.AngularCookieName); + http.ExpireCookie(AngularAntiForgeryHelper.CsrfValidationCookieName); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 7e1d051c5f..fa5cb23dac 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -405,6 +405,7 @@ + diff --git a/src/Umbraco.Web/WebApi/Filters/SetAngularAntiForgeryTokensAttribute.cs b/src/Umbraco.Web/WebApi/Filters/SetAngularAntiForgeryTokensAttribute.cs index 9ea42f8a39..a0ed584ef8 100644 --- a/src/Umbraco.Web/WebApi/Filters/SetAngularAntiForgeryTokensAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/SetAngularAntiForgeryTokensAttribute.cs @@ -1,4 +1,5 @@ using System.Linq; +using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Web.Helpers; @@ -16,11 +17,19 @@ namespace Umbraco.Web.WebApi.Filters { if (context.Response == null) return; - //don't need to set the cookie if they already exist + //DO not set the token cookies if the request has failed!! + if (context.Response.StatusCode != HttpStatusCode.OK) return; + + //don't need to set the cookie if they already exist and they are valid if (context.Request.Headers.GetCookies(AngularAntiForgeryHelper.AngularCookieName).Any() && context.Request.Headers.GetCookies(AngularAntiForgeryHelper.CsrfValidationCookieName).Any()) { - return; + //if they are not valid for some strange reason - we need to continue setting valid ones + string failedReason; + if (AngularAntiForgeryHelper.ValidateHeaders(context.Request.Headers, out failedReason)) + { + return; + } } string cookieToken, headerToken; From 62e3ab27fe095ae779d0e7f182baeb66338f0a52 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 24 Dec 2013 10:58:11 +1100 Subject: [PATCH 0132/1046] Adds comments to EntityController code to use UmbracoHelper --- src/Umbraco.Web/Editors/EntityController.cs | 24 ++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 475ac6824c..4debbc8f57 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -132,7 +132,8 @@ namespace Umbraco.Web.Editors /// /// Gets an entity by a xpath or css-like query /// - /// + /// + /// /// /// public EntityBasic GetByQuery(string query, int rootNodeId, UmbracoEntityTypes type) @@ -143,15 +144,28 @@ namespace Umbraco.Web.Editors // query = css2xpath.Converter.CSSToXPath(query, ""); - if(rootNodeId < 0){ - var nodes = global::umbraco.library.GetXmlNodeByXPath(query); - var node = uQuery.GetNodesByXPath(query).FirstOrDefault(); + if(rootNodeId < 0) + { + var node = Umbraco.TypedContentSingleAtXPath(query); if(node == null) return null; return GetById(node.Id, UmbracoEntityTypes.Document); - }else{ + } + else + { + //SD: This should be done using UmbracoHelper + + //var node = Umbraco.TypedContent(rootNodeId); + //if (node != null) + //{ + // //TODO: Build an Xpath query based on this node ID and the rest of the query + // // var subQuery = [@id=rootNodeId]/query + // // and then get that node with: + // // var result = Umbraco.TypedContentSingleAtXPath(subQuery); + //} + var node = global::umbraco.library.GetXmlNodeById(rootNodeId.ToString()); if (node.MoveNext()) { From d474481cba9c2c26fedd8073c9d65d21ed2f36dd Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 24 Dec 2013 11:03:20 +1100 Subject: [PATCH 0133/1046] publicizes MemberService so that devs can use the events on nightly builds for now. --- src/Umbraco.Core/Services/IMemberService.cs | 2 +- src/Umbraco.Core/Services/IMembershipMemberService.cs | 2 +- src/Umbraco.Core/Services/MemberService.cs | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs index 44fce3eba2..e394c3bffd 100644 --- a/src/Umbraco.Core/Services/IMemberService.cs +++ b/src/Umbraco.Core/Services/IMemberService.cs @@ -8,7 +8,7 @@ namespace Umbraco.Core.Services /// /// Defines the MemberService, which is an easy access to operations involving (umbraco) members. /// - internal interface IMemberService : IMembershipMemberService + public interface IMemberService : IMembershipMemberService { /// /// Checks if a member with the id exists diff --git a/src/Umbraco.Core/Services/IMembershipMemberService.cs b/src/Umbraco.Core/Services/IMembershipMemberService.cs index 18edae2bea..f309eea445 100644 --- a/src/Umbraco.Core/Services/IMembershipMemberService.cs +++ b/src/Umbraco.Core/Services/IMembershipMemberService.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core.Services /// /// Idea is to have this is an isolated interface so that it can be easily 'replaced' in the membership provider impl. /// - internal interface IMembershipMemberService : IService + public interface IMembershipMemberService : IService { /// /// Checks if a member with the username exists diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index 7dcbbe2ff6..c9734d09bd 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -18,24 +18,24 @@ namespace Umbraco.Core.Services /// /// Represents the MemberService. /// - internal class MemberService : IMemberService + public class MemberService : IMemberService { private readonly RepositoryFactory _repositoryFactory; private readonly IDatabaseUnitOfWorkProvider _uowProvider; private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); - public MemberService(RepositoryFactory repositoryFactory) + internal MemberService(RepositoryFactory repositoryFactory) : this(new PetaPocoUnitOfWorkProvider(), repositoryFactory) { } - public MemberService(IDatabaseUnitOfWorkProvider provider) + internal MemberService(IDatabaseUnitOfWorkProvider provider) : this(provider, new RepositoryFactory()) { } - public MemberService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory) + internal MemberService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory) { _repositoryFactory = repositoryFactory; _uowProvider = provider; From edaa5ebd4336137db64d327ffd8c3d4deba28201 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 24 Dec 2013 14:03:18 +1100 Subject: [PATCH 0134/1046] Lots more unit tests for the membership providers and some other fixups - have a new failing test as i need to sort out salting issue with encrypted passwords. --- .../Security/MembershipProviderBase.cs | 72 +--- .../Security/UmbracoMembershipProviderBase.cs | 78 +++++ src/Umbraco.Core/Services/IMemberService.cs | 2 +- .../Services/IMembershipMemberService.cs | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + src/Umbraco.Tests/App.config | 1 + .../Membership/MembershipProviderBaseTests.cs | 327 +++++++++++++----- .../Providers/MembersMembershipProvider.cs | 45 ++- 8 files changed, 348 insertions(+), 180 deletions(-) create mode 100644 src/Umbraco.Core/Security/UmbracoMembershipProviderBase.cs diff --git a/src/Umbraco.Core/Security/MembershipProviderBase.cs b/src/Umbraco.Core/Security/MembershipProviderBase.cs index 9d76c8e595..3794327781 100644 --- a/src/Umbraco.Core/Security/MembershipProviderBase.cs +++ b/src/Umbraco.Core/Security/MembershipProviderBase.cs @@ -11,76 +11,6 @@ using Umbraco.Core.Logging; namespace Umbraco.Core.Security { - - public abstract class UmbracoMembershipProviderBase : MembershipProviderBase - { - protected UmbracoMembershipProviderBase() - { - //Set the defaults! - DefaultMemberTypeAlias = "Member"; - } - - public string DefaultMemberTypeAlias { get; protected set; } - - /// - /// Adds a new membership user to the data source. - /// - /// The user name for the new user. - /// The password for the new user. - /// The e-mail address for the new user. - /// The password question for the new user. - /// The password answer for the new user - /// Whether or not the new user is approved to be validated. - /// The unique identifier from the membership data source for the user. - /// A enumeration value indicating whether the user was created successfully. - /// - /// A object populated with the information for the newly created user. - /// - protected sealed override MembershipUser PerformCreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) - { - return PerformCreateUser(DefaultMemberTypeAlias, username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); - } - - /// - /// Adds a new membership user to the data source. - /// - /// The member type alias to use when creating the member - /// The user name for the new user. - /// The password for the new user. - /// The e-mail address for the new user. - /// The password question for the new user. - /// The password answer for the new user - /// Whether or not the new user is approved to be validated. - /// The unique identifier from the membership data source for the user. - /// A enumeration value indicating whether the user was created successfully. - /// - /// A object populated with the information for the newly created user. - /// - public MembershipUser CreateUser(string memberTypeAlias, string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) - { - //do the base validation first - base.CreateUser(username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); - - return PerformCreateUser(memberTypeAlias, username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); - } - - /// - /// Adds a new membership user to the data source. - /// - /// The member type alias to use when creating the member - /// The user name for the new user. - /// The password for the new user. - /// The e-mail address for the new user. - /// The password question for the new user. - /// The password answer for the new user - /// Whether or not the new user is approved to be validated. - /// The unique identifier from the membership data source for the user. - /// A enumeration value indicating whether the user was created successfully. - /// - /// A object populated with the information for the newly created user. - /// - protected abstract MembershipUser PerformCreateUser(string memberTypeAlias, string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status); - } /// /// A base membership provider class offering much of the underlying functionality for initializing and password encryption/hashing. /// @@ -741,7 +671,7 @@ namespace Umbraco.Core.Security /// /// /// - protected string EncryptOrHashNewPassword(string newPassword, out string salt) + protected internal string EncryptOrHashNewPassword(string newPassword, out string salt) { salt = GenerateSalt(); return EncryptOrHashPassword(newPassword, salt); diff --git a/src/Umbraco.Core/Security/UmbracoMembershipProviderBase.cs b/src/Umbraco.Core/Security/UmbracoMembershipProviderBase.cs new file mode 100644 index 0000000000..6ecb6eedce --- /dev/null +++ b/src/Umbraco.Core/Security/UmbracoMembershipProviderBase.cs @@ -0,0 +1,78 @@ +using System.Web.Security; + +namespace Umbraco.Core.Security +{ + + /// + /// A base membership provider class for umbraco members (not users) + /// + public abstract class UmbracoMembershipProviderBase : MembershipProviderBase + { + protected UmbracoMembershipProviderBase() + { + //Set the defaults! + DefaultMemberTypeAlias = "Member"; + } + + public string DefaultMemberTypeAlias { get; protected set; } + + /// + /// Adds a new membership user to the data source. + /// + /// The user name for the new user. + /// The password for the new user. + /// The e-mail address for the new user. + /// The password question for the new user. + /// The password answer for the new user + /// Whether or not the new user is approved to be validated. + /// The unique identifier from the membership data source for the user. + /// A enumeration value indicating whether the user was created successfully. + /// + /// A object populated with the information for the newly created user. + /// + protected sealed override MembershipUser PerformCreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) + { + return PerformCreateUser(DefaultMemberTypeAlias, username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); + } + + /// + /// Adds a new membership user to the data source. + /// + /// The member type alias to use when creating the member + /// The user name for the new user. + /// The password for the new user. + /// The e-mail address for the new user. + /// The password question for the new user. + /// The password answer for the new user + /// Whether or not the new user is approved to be validated. + /// The unique identifier from the membership data source for the user. + /// A enumeration value indicating whether the user was created successfully. + /// + /// A object populated with the information for the newly created user. + /// + public MembershipUser CreateUser(string memberTypeAlias, string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) + { + //do the base validation first + base.CreateUser(username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); + + return PerformCreateUser(memberTypeAlias, username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); + } + + /// + /// Adds a new membership user to the data source. + /// + /// The member type alias to use when creating the member + /// The user name for the new user. + /// The password for the new user. + /// The e-mail address for the new user. + /// The password question for the new user. + /// The password answer for the new user + /// Whether or not the new user is approved to be validated. + /// The unique identifier from the membership data source for the user. + /// A enumeration value indicating whether the user was created successfully. + /// + /// A object populated with the information for the newly created user. + /// + protected abstract MembershipUser PerformCreateUser(string memberTypeAlias, string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs index 87c15573c6..05bcafba38 100644 --- a/src/Umbraco.Core/Services/IMemberService.cs +++ b/src/Umbraco.Core/Services/IMemberService.cs @@ -8,7 +8,7 @@ namespace Umbraco.Core.Services /// /// Defines the MemberService, which is an easy access to operations involving (umbraco) members. /// - internal interface IMemberService : IMembershipMemberService + public interface IMemberService : IMembershipMemberService { /// /// Checks if a member with the id exists diff --git a/src/Umbraco.Core/Services/IMembershipMemberService.cs b/src/Umbraco.Core/Services/IMembershipMemberService.cs index 6c3027d21c..0b9ff79665 100644 --- a/src/Umbraco.Core/Services/IMembershipMemberService.cs +++ b/src/Umbraco.Core/Services/IMembershipMemberService.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core.Services /// /// Idea is to have this is an isolated interface so that it can be easily 'replaced' in the membership provider impl. /// - internal interface IMembershipMemberService : IService + public interface IMembershipMemberService : IService { /// /// Checks if a member with the username exists diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 9f7bd325a1..3119ae7cf0 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -741,6 +741,7 @@ + diff --git a/src/Umbraco.Tests/App.config b/src/Umbraco.Tests/App.config index d86051cdee..0af67e158b 100644 --- a/src/Umbraco.Tests/App.config +++ b/src/Umbraco.Tests/App.config @@ -82,6 +82,7 @@ + diff --git a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs index cf29e71eab..2af97b49e2 100644 --- a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs +++ b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs @@ -7,8 +7,13 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using System.Web.Security; +using Moq; using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; using Umbraco.Core.Security; +using Umbraco.Core.Services; +using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Web.Security.Providers; namespace Umbraco.Tests.Membership @@ -20,31 +25,243 @@ namespace Umbraco.Tests.Membership //public void Set_Default_Member_Type_On_Init() //[Test] - //public void Question_Answer_Is_Encrypted() + //public void Create_User_Already_Exists() + //{ + + //} + + //[Test] + //public void Create_User_Requires_Unique_Email() + //{ + + //} + + [Test] + public void Answer_Is_Encrypted() + { + IMember createdMember = null; + var memberType = MockedContentTypes.CreateSimpleMemberType(); + foreach (var p in Constants.Conventions.Member.GetStandardPropertyTypeStubs()) + { + memberType.AddPropertyType(p.Value); + } + var mServiceMock = new Mock(); + mServiceMock.Setup(service => service.Exists("test")).Returns(false); + mServiceMock.Setup(service => service.GetByEmail("test@test.com")).Returns(() => null); + mServiceMock.Setup( + service => service.CreateMember(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string u, string e, string p, string m) => + { + createdMember = new Member("test", e, u, p, memberType); + }) + .Returns(() => createdMember); + var provider = new MembersMembershipProvider(mServiceMock.Object); + + MembershipCreateStatus status; + provider.CreateUser("test", "test", "test", "test@test.com", "test", "test", true, "test", out status); + + Assert.AreNotEqual("test", createdMember.PasswordAnswer); + Assert.AreEqual(provider.EncryptString("test"), createdMember.PasswordAnswer); + } + + [Test] + public void Password_Encrypted_With_Salt() + { + IMember createdMember = null; + var memberType = MockedContentTypes.CreateSimpleMemberType(); + foreach (var p in Constants.Conventions.Member.GetStandardPropertyTypeStubs()) + { + memberType.AddPropertyType(p.Value); + } + var mServiceMock = new Mock(); + mServiceMock.Setup(service => service.Exists("test")).Returns(false); + mServiceMock.Setup(service => service.GetByEmail("test@test.com")).Returns(() => null); + mServiceMock.Setup( + service => service.CreateMember(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string u, string e, string p, string m) => + { + createdMember = new Member("test", e, u, p, memberType); + }) + .Returns(() => createdMember); + + var provider = new MembersMembershipProvider(mServiceMock.Object); + provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Encrypted" } }); + MembershipCreateStatus status; + provider.CreateUser("test", "test", "test", "test@test.com", "test", "test", true, "test", out status); + + Assert.AreNotEqual("test", createdMember.Password); + //Assert.AreNotEqual(provider.EncryptString("test"), createdMember.PasswordAnswer); + string salt; + var encodedPassword = provider.EncryptOrHashNewPassword("test", out salt); + Assert.AreEqual(encodedPassword, createdMember.Password); + } + + //[Test] + //public void Password_Hashed_With_Salt() + //{ + // IMember createdMember = null; + // var memberType = MockedContentTypes.CreateSimpleMemberType(); + // foreach (var p in Constants.Conventions.Member.GetStandardPropertyTypeStubs()) + // { + // memberType.AddPropertyType(p.Value); + // } + // var mServiceMock = new Mock(); + // mServiceMock.Setup(service => service.Exists("test")).Returns(false); + // mServiceMock.Setup(service => service.GetByEmail("test@test.com")).Returns(() => null); + // mServiceMock.Setup( + // service => service.CreateMember(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + // .Callback((string u, string e, string p, string m) => + // { + // createdMember = new Member("test", e, u, p, memberType); + // }) + // .Returns(() => createdMember); + + // var provider = new MembersMembershipProvider(mServiceMock.Object); + // provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Hashed" } }); + // MembershipCreateStatus status; + // provider.CreateUser("test", "test", "test", "test@test.com", "test", "test", true, "test", out status); + + // Assert.AreNotEqual("test", createdMember.Password); + // Assert.AreNotEqual(provider.EncryptString("test"), createdMember.PasswordAnswer); + // string salt; + // var encodedPassword = provider.EncryptOrHashNewPassword("test", out salt); + // Assert.AreEqual(encodedPassword, createdMember.Password); + //} + + //[Test] + //public void Password_Encrypted_Validated_With_Salt() + + //[Test] + //public void Password_Encrypted_Validated_With_Salt() + } [TestFixture] public class MembershipProviderBaseTests { - //[Test] - //public void Change_Password_Base_Validation() - //[Test] - //public void ChangePasswordQuestionAndAnswer_Base_Validation() - //[Test] - //public void CreateUser_Base_Validation() + [Test] + public void Change_Password_Without_AllowManuallyChangingPassword_And_No_Pass_Validation() + { + var providerMock = new Mock() { CallBase = true }; + providerMock.Setup(@base => @base.AllowManuallyChangingPassword).Returns(false); + var provider = providerMock.Object; - //[Test] - //public void GetPassword_Base_Validation() + Assert.Throws(() => provider.ChangePassword("test", "", "test")); + } - //[Test] - //public void ResetPassword_Base_Validation() + [Test] + public void Change_Password_With_AllowManuallyChangingPassword_And_Invalid_Creds() + { + var providerMock = new Mock() { CallBase = true }; + providerMock.Setup(@base => @base.AllowManuallyChangingPassword).Returns(false); + providerMock.Setup(@base => @base.ValidateUser("test", "test")).Returns(false); + var provider = providerMock.Object; + + Assert.IsFalse(provider.ChangePassword("test", "test", "test")); + + } + + [Test] + public void ChangePasswordQuestionAndAnswer_Without_RequiresQuestionAndAnswer() + { + var providerMock = new Mock() { CallBase = true }; + providerMock.Setup(@base => @base.RequiresQuestionAndAnswer).Returns(false); + var provider = providerMock.Object; + + Assert.Throws(() => provider.ChangePasswordQuestionAndAnswer("test", "test", "test", "test")); + } + + [Test] + public void ChangePasswordQuestionAndAnswer_Without_AllowManuallyChangingPassword_And_Invalid_Creds() + { + var providerMock = new Mock() { CallBase = true }; + providerMock.Setup(@base => @base.RequiresQuestionAndAnswer).Returns(true); + providerMock.Setup(@base => @base.AllowManuallyChangingPassword).Returns(false); + providerMock.Setup(@base => @base.ValidateUser("test", "test")).Returns(false); + var provider = providerMock.Object; + + Assert.IsFalse(provider.ChangePasswordQuestionAndAnswer("test", "test", "test", "test")); + } + + [Test] + public void CreateUser_Not_Whitespace() + { + var providerMock = new Mock() {CallBase = true}; + var provider = providerMock.Object; + + MembershipCreateStatus status; + var result = provider.CreateUser("", "", "test@test.com", "", "", true, "", out status); + + Assert.IsNull(result); + Assert.AreEqual(MembershipCreateStatus.InvalidUserName, status); + } + + [Test] + public void CreateUser_Invalid_Question() + { + var providerMock = new Mock() { CallBase = true }; + providerMock.Setup(@base => @base.RequiresQuestionAndAnswer).Returns(true); + var provider = providerMock.Object; + + MembershipCreateStatus status; + var result = provider.CreateUser("test", "test", "test@test.com", "", "", true, "", out status); + + Assert.IsNull(result); + Assert.AreEqual(MembershipCreateStatus.InvalidQuestion, status); + } + + [Test] + public void CreateUser_Invalid_Answer() + { + var providerMock = new Mock() { CallBase = true }; + providerMock.Setup(@base => @base.RequiresQuestionAndAnswer).Returns(true); + var provider = providerMock.Object; + + MembershipCreateStatus status; + var result = provider.CreateUser("test", "test", "test@test.com", "test", "", true, "", out status); + + Assert.IsNull(result); + Assert.AreEqual(MembershipCreateStatus.InvalidAnswer, status); + } + + [Test] + public void GetPassword_Without_EnablePasswordRetrieval() + { + var providerMock = new Mock() { CallBase = true }; + providerMock.Setup(@base => @base.EnablePasswordRetrieval).Returns(false); + var provider = providerMock.Object; + + Assert.Throws(() => provider.GetPassword("test", "test")); + } + + [Test] + public void GetPassword_With_Hashed() + { + var providerMock = new Mock() { CallBase = true }; + providerMock.Setup(@base => @base.EnablePasswordRetrieval).Returns(true); + providerMock.Setup(@base => @base.PasswordFormat).Returns(MembershipPasswordFormat.Hashed); + var provider = providerMock.Object; + + Assert.Throws(() => provider.GetPassword("test", "test")); + } + + [Test] + public void ResetPassword_Without_EnablePasswordReset() + { + var providerMock = new Mock() { CallBase = true }; + providerMock.Setup(@base => @base.EnablePasswordReset).Returns(false); + var provider = providerMock.Object; + + Assert.Throws(() => provider.ResetPassword("test", "test")); + } [Test] public void Sets_Defaults() { - var provider = new TestProvider(); + var providerMock = new Mock() { CallBase = true }; + var provider = providerMock.Object; provider.Initialize("test", new NameValueCollection()); Assert.AreEqual("test", provider.Name); @@ -65,7 +282,8 @@ namespace Umbraco.Tests.Membership [Test] public void Throws_Exception_With_Hashed_Password_And_Password_Retrieval() { - var provider = new TestProvider(); + var providerMock = new Mock() { CallBase = true }; + var provider = providerMock.Object; Assert.Throws(() => provider.Initialize("test", new NameValueCollection() { @@ -125,88 +343,5 @@ namespace Umbraco.Tests.Membership Assert.AreEqual(salt, initSalt); } - private class TestProvider : MembershipProviderBase - { - public override void UpdateUser(MembershipUser user) - { - throw new NotImplementedException(); - } - - public override bool ValidateUser(string username, string password) - { - throw new NotImplementedException(); - } - - public override bool UnlockUser(string userName) - { - throw new NotImplementedException(); - } - - public override MembershipUser GetUser(object providerUserKey, bool userIsOnline) - { - throw new NotImplementedException(); - } - - public override MembershipUser GetUser(string username, bool userIsOnline) - { - throw new NotImplementedException(); - } - - public override string GetUserNameByEmail(string email) - { - throw new NotImplementedException(); - } - - public override bool DeleteUser(string username, bool deleteAllRelatedData) - { - throw new NotImplementedException(); - } - - public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords) - { - throw new NotImplementedException(); - } - - public override int GetNumberOfUsersOnline() - { - throw new NotImplementedException(); - } - - public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) - { - throw new NotImplementedException(); - } - - public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) - { - throw new NotImplementedException(); - } - - protected override bool PerformChangePassword(string username, string oldPassword, string newPassword) - { - throw new NotImplementedException(); - } - - protected override bool PerformChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) - { - throw new NotImplementedException(); - } - - protected override MembershipUser PerformCreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) - { - throw new NotImplementedException(); - } - - protected override string PerformGetPassword(string username, string answer) - { - throw new NotImplementedException(); - } - - protected override string PerformResetPassword(string username, string answer, string generatedPassword) - { - throw new NotImplementedException(); - } - } - } } diff --git a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs index 5e263f76a4..e4fbda1d08 100644 --- a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; +using System.Web.Configuration; using System.Web.Hosting; using System.Web.Security; using Umbraco.Core; @@ -30,6 +31,15 @@ namespace Umbraco.Web.Security.Providers get { return _memberService ?? (_memberService = ApplicationContext.Current.Services.MemberService); } } + public MembersMembershipProvider() + { + } + + internal MembersMembershipProvider(IMemberService memberService) + { + _memberService = memberService; + } + public string ProviderName { get { return "MembersMembershipProvider"; } @@ -80,7 +90,7 @@ namespace Umbraco.Web.Security.Providers // This is allowed based on the overridden AllowManuallyChangingPassword option. // in order to support updating passwords from the umbraco core, we can't validate the old password - var m = _memberService.GetByUsername(username); + var m = MemberService.GetByUsername(username); if (m == null) return false; string salt; @@ -88,8 +98,8 @@ namespace Umbraco.Web.Security.Providers m.Password = FormatPasswordForStorage(encodedPassword, salt); m.LastPasswordChangeDate = DateTime.Now; - - _memberService.Save(m); + + MemberService.Save(m); return true; } @@ -113,7 +123,7 @@ namespace Umbraco.Web.Security.Providers } member.PasswordQuestion = newPasswordQuestion; - member.PasswordAnswer = newPasswordAnswer; + member.PasswordAnswer = EncryptString(newPasswordAnswer); MemberService.Save(member); @@ -158,10 +168,14 @@ namespace Umbraco.Web.Security.Providers string salt; var encodedPassword = EncryptOrHashNewPassword(password, out salt); - var member = MemberService.CreateMember(email, username, encodedPassword, memberTypeAlias); + var member = MemberService.CreateMember( + email, + username, + FormatPasswordForStorage(encodedPassword, salt), + memberTypeAlias); member.PasswordQuestion = passwordQuestion; - member.PasswordAnswer = passwordAnswer; + member.PasswordAnswer = EncryptString(passwordAnswer); member.IsApproved = isApproved; member.LastLoginDate = DateTime.Now; member.LastPasswordChangeDate = DateTime.Now; @@ -289,9 +303,9 @@ namespace Umbraco.Web.Security.Providers throw new ProviderException("The supplied user is not found"); } - //TODO: We need to encrypt the answer here to match against the encrypted answer in the database + var encAnswer = EncryptString(answer); - if (RequiresQuestionAndAnswer && m.PasswordAnswer != answer) + if (RequiresQuestionAndAnswer && m.PasswordAnswer != encAnswer) { throw new ProviderException("Incorrect password answer"); } @@ -301,6 +315,15 @@ namespace Umbraco.Web.Security.Providers return decodedPassword; } + internal string EncryptString(string str) + { + var bytes = Encoding.Unicode.GetBytes(str); + var password = new byte[bytes.Length]; + Buffer.BlockCopy(bytes, 0, password, 0, bytes.Length); + var encBytes = EncryptPassword(password, MembershipPasswordCompatibilityMode.Framework40); + return Convert.ToBase64String(encBytes); + } + /// /// Gets information from the data source for a user. Provides an option to update the last-activity date/time stamp for the user. /// @@ -395,16 +418,16 @@ namespace Umbraco.Web.Security.Providers throw new ProviderException("The member is locked out."); } - //TODO: We need to encrypt the answer here to match against the encrypted answer in the database + var encAnswer = EncryptString(answer); - if (RequiresQuestionAndAnswer && m.PasswordAnswer != answer) + if (RequiresQuestionAndAnswer && m.PasswordAnswer != encAnswer) { throw new ProviderException("Incorrect password answer"); } string salt; var encodedPassword = EncryptOrHashNewPassword(generatedPassword, out salt); - m.Password = encodedPassword; + m.Password = FormatPasswordForStorage(encodedPassword, salt); m.LastPasswordChangeDate = DateTime.Now; MemberService.Save(m); From 71a07060a9eadbd97bcbe2361b689e83d87967f5 Mon Sep 17 00:00:00 2001 From: perploug Date: Fri, 27 Dec 2013 21:55:59 +0100 Subject: [PATCH 0135/1046] removes debugging output from treesource preval editor --- .../src/views/prevalueeditors/treesource.html | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.html b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.html index bc5f5f8158..080be5cc58 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.html +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.html @@ -39,6 +39,4 @@ - - {{model | json}} \ No newline at end of file From fb029861cf4cbd9aa0840d8d9f1a76c4796adfe0 Mon Sep 17 00:00:00 2001 From: Shannon Date: Sat, 28 Dec 2013 13:57:20 +1100 Subject: [PATCH 0136/1046] Fixes: U4-3942 Cannot configure the request if there is not content assigned - exception --- .../PublishedContentRequestEngineTests.cs | 110 ++++++++++++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + .../Routing/PublishedContentRequestEngine.cs | 32 +++-- 3 files changed, 132 insertions(+), 11 deletions(-) create mode 100644 src/Umbraco.Tests/PublishedContent/PublishedContentRequestEngineTests.cs diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentRequestEngineTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentRequestEngineTests.cs new file mode 100644 index 0000000000..5bfc94fd05 --- /dev/null +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentRequestEngineTests.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections; +using System.Collections.ObjectModel; +using System.Globalization; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Tests.TestHelpers; +using Umbraco.Web.Routing; + +namespace Umbraco.Tests.PublishedContent +{ + [TestFixture] + public class PublishedContentRequestEngineTests : BaseRoutingTest + { + + protected override DatabaseBehavior DatabaseTestBehavior + { + get { return DatabaseBehavior.NoDatabasePerFixture; } + } + + [Test] + public void Ctor_Throws_On_Null_PCR() + { + Assert.Throws(() => new PublishedContentRequestEngine(null)); + } + + [Test] + public void ConfigureRequest_Returns_False_Without_HasPublishedContent() + { + var routeCtx = GetRoutingContext("/test"); + + var pcre = new PublishedContentRequestEngine( + new PublishedContentRequest( + routeCtx.UmbracoContext.CleanedUmbracoUrl, routeCtx)); + + var result = pcre.ConfigureRequest(); + Assert.IsFalse(result); + } + + [Test] + public void ConfigureRequest_Returns_False_When_IsRedirect() + { + var routeCtx = GetRoutingContext("/test"); + + var pcr = new PublishedContentRequest(routeCtx.UmbracoContext.CleanedUmbracoUrl, routeCtx); + var pc = GetPublishedContentMock(); + pcr.PublishedContent = pc.Object; + pcr.Culture = new CultureInfo("en-AU"); + pcr.SetRedirect("/hello"); + var pcre = new PublishedContentRequestEngine(pcr); + + var result = pcre.ConfigureRequest(); + Assert.IsFalse(result); + } + + [Test] + public void ConfigureRequest_Adds_HttpContext_Items_When_Published_Content_Assigned() + { + var routeCtx = GetRoutingContext("/test"); + + var pcr = new PublishedContentRequest(routeCtx.UmbracoContext.CleanedUmbracoUrl, routeCtx); + var pc = GetPublishedContentMock(); + pcr.PublishedContent = pc.Object; + pcr.Culture = new CultureInfo("en-AU"); + var pcre = new PublishedContentRequestEngine(pcr); + + pcre.ConfigureRequest(); + + Assert.AreEqual(1, routeCtx.UmbracoContext.HttpContext.Items["pageID"]); + Assert.AreEqual(pcr.UmbracoPage.Elements.Count, ((Hashtable)routeCtx.UmbracoContext.HttpContext.Items["pageElements"]).Count); + } + + [Test] + public void ConfigureRequest_Sets_UmbracoPage_When_Published_Content_Assigned() + { + var routeCtx = GetRoutingContext("/test"); + + var pcr = new PublishedContentRequest(routeCtx.UmbracoContext.CleanedUmbracoUrl, routeCtx); + var pc = GetPublishedContentMock(); + pcr.Culture = new CultureInfo("en-AU"); + pcr.PublishedContent = pc.Object; + var pcre = new PublishedContentRequestEngine(pcr); + + pcre.ConfigureRequest(); + + Assert.IsNotNull(pcr.UmbracoPage); + } + + private Mock GetPublishedContentMock() + { + var pc = new Mock(); + pc.Setup(content => content.Id).Returns(1); + pc.Setup(content => content.Name).Returns("test"); + pc.Setup(content => content.DocumentTypeId).Returns(2); + pc.Setup(content => content.DocumentTypeAlias).Returns("testAlias"); + pc.Setup(content => content.WriterName).Returns("admin"); + pc.Setup(content => content.CreatorName).Returns("admin"); + pc.Setup(content => content.CreateDate).Returns(DateTime.Now); + pc.Setup(content => content.UpdateDate).Returns(DateTime.Now); + pc.Setup(content => content.Path).Returns("-1,1"); + pc.Setup(content => content.Version).Returns(Guid.NewGuid); + pc.Setup(content => content.Parent).Returns(() => null); + pc.Setup(content => content.Version).Returns(Guid.NewGuid); + pc.Setup(content => content.Properties).Returns(new Collection()); + return pc; + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 945e4f8624..026b5ddee0 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -277,6 +277,7 @@ + diff --git a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs index 63aff23e34..9fe1957ccf 100644 --- a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs +++ b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs @@ -46,7 +46,10 @@ namespace Umbraco.Web.Routing /// /// Prepares the request. /// - public void PrepareRequest() + /// + /// Returns false if the request was not successfully prepared + /// + public bool PrepareRequest() { // note - at that point the original legacy module did something do handle IIS custom 404 errors // ie pages looking like /anything.aspx?404;/path/to/document - I guess the reason was to support @@ -62,10 +65,12 @@ namespace Umbraco.Web.Routing // if request has been flagged to redirect then return // whoever called us is in charge of actually redirecting - if (_pcr.IsRedirect) - return; + if (_pcr.IsRedirect) + { + return false; + } - // set the culture on the thread - once, so it's set when running document lookups + // set the culture on the thread - once, so it's set when running document lookups Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = _pcr.Culture; // find the document & template @@ -85,24 +90,25 @@ namespace Umbraco.Web.Routing // to find out the appropriate template //complete the PCR and assign the remaining values - ConfigureRequest(); + return ConfigureRequest(); } /// /// Called by PrepareRequest once everything has been discovered, resolved and assigned to the PCR. This method /// finalizes the PCR with the values assigned. /// + /// + /// Returns false if the request was not successfully configured + /// /// /// This method logic has been put into it's own method in case developers have created a custom PCR or are assigning their own values /// but need to finalize it themselves. - /// - /// This method will throw an exception if no content has been assigned /// - public void ConfigureRequest() + public bool ConfigureRequest() { if (_pcr.HasPublishedContent == false) { - throw new InvalidOperationException("Cannot configure the request if there is not content assigned"); + return false; } // set the culture on the thread -- again, 'cos it might have changed in the event handler @@ -110,8 +116,10 @@ namespace Umbraco.Web.Routing // if request has been flagged to redirect, or has no content to display, // then return - whoever called us is in charge of actually redirecting - if (_pcr.IsRedirect || !_pcr.HasPublishedContent) - return; + if (_pcr.IsRedirect || _pcr.HasPublishedContent == false) + { + return false; + } // we may be 404 _and_ have a content @@ -132,6 +140,8 @@ namespace Umbraco.Web.Routing // used by many legacy objects _routingContext.UmbracoContext.HttpContext.Items["pageID"] = _pcr.PublishedContent.Id; _routingContext.UmbracoContext.HttpContext.Items["pageElements"] = _pcr.UmbracoPage.Elements; + + return true; } /// From fbdb1d5d6cf612d5d0bf1d4417e1f2608196e11f Mon Sep 17 00:00:00 2001 From: Shannon Date: Sat, 28 Dec 2013 14:01:08 +1100 Subject: [PATCH 0137/1046] Fixes: U4-3942 Cannot configure the request if there is not content assigned - exception Conflicts: src/Umbraco.Tests/Umbraco.Tests.csproj --- .../PublishedContentRequestEngineTests.cs | 110 ++++++++++++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + .../Routing/PublishedContentRequestEngine.cs | 32 +++-- 3 files changed, 132 insertions(+), 11 deletions(-) create mode 100644 src/Umbraco.Tests/PublishedContent/PublishedContentRequestEngineTests.cs diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentRequestEngineTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentRequestEngineTests.cs new file mode 100644 index 0000000000..5bfc94fd05 --- /dev/null +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentRequestEngineTests.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections; +using System.Collections.ObjectModel; +using System.Globalization; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Tests.TestHelpers; +using Umbraco.Web.Routing; + +namespace Umbraco.Tests.PublishedContent +{ + [TestFixture] + public class PublishedContentRequestEngineTests : BaseRoutingTest + { + + protected override DatabaseBehavior DatabaseTestBehavior + { + get { return DatabaseBehavior.NoDatabasePerFixture; } + } + + [Test] + public void Ctor_Throws_On_Null_PCR() + { + Assert.Throws(() => new PublishedContentRequestEngine(null)); + } + + [Test] + public void ConfigureRequest_Returns_False_Without_HasPublishedContent() + { + var routeCtx = GetRoutingContext("/test"); + + var pcre = new PublishedContentRequestEngine( + new PublishedContentRequest( + routeCtx.UmbracoContext.CleanedUmbracoUrl, routeCtx)); + + var result = pcre.ConfigureRequest(); + Assert.IsFalse(result); + } + + [Test] + public void ConfigureRequest_Returns_False_When_IsRedirect() + { + var routeCtx = GetRoutingContext("/test"); + + var pcr = new PublishedContentRequest(routeCtx.UmbracoContext.CleanedUmbracoUrl, routeCtx); + var pc = GetPublishedContentMock(); + pcr.PublishedContent = pc.Object; + pcr.Culture = new CultureInfo("en-AU"); + pcr.SetRedirect("/hello"); + var pcre = new PublishedContentRequestEngine(pcr); + + var result = pcre.ConfigureRequest(); + Assert.IsFalse(result); + } + + [Test] + public void ConfigureRequest_Adds_HttpContext_Items_When_Published_Content_Assigned() + { + var routeCtx = GetRoutingContext("/test"); + + var pcr = new PublishedContentRequest(routeCtx.UmbracoContext.CleanedUmbracoUrl, routeCtx); + var pc = GetPublishedContentMock(); + pcr.PublishedContent = pc.Object; + pcr.Culture = new CultureInfo("en-AU"); + var pcre = new PublishedContentRequestEngine(pcr); + + pcre.ConfigureRequest(); + + Assert.AreEqual(1, routeCtx.UmbracoContext.HttpContext.Items["pageID"]); + Assert.AreEqual(pcr.UmbracoPage.Elements.Count, ((Hashtable)routeCtx.UmbracoContext.HttpContext.Items["pageElements"]).Count); + } + + [Test] + public void ConfigureRequest_Sets_UmbracoPage_When_Published_Content_Assigned() + { + var routeCtx = GetRoutingContext("/test"); + + var pcr = new PublishedContentRequest(routeCtx.UmbracoContext.CleanedUmbracoUrl, routeCtx); + var pc = GetPublishedContentMock(); + pcr.Culture = new CultureInfo("en-AU"); + pcr.PublishedContent = pc.Object; + var pcre = new PublishedContentRequestEngine(pcr); + + pcre.ConfigureRequest(); + + Assert.IsNotNull(pcr.UmbracoPage); + } + + private Mock GetPublishedContentMock() + { + var pc = new Mock(); + pc.Setup(content => content.Id).Returns(1); + pc.Setup(content => content.Name).Returns("test"); + pc.Setup(content => content.DocumentTypeId).Returns(2); + pc.Setup(content => content.DocumentTypeAlias).Returns("testAlias"); + pc.Setup(content => content.WriterName).Returns("admin"); + pc.Setup(content => content.CreatorName).Returns("admin"); + pc.Setup(content => content.CreateDate).Returns(DateTime.Now); + pc.Setup(content => content.UpdateDate).Returns(DateTime.Now); + pc.Setup(content => content.Path).Returns("-1,1"); + pc.Setup(content => content.Version).Returns(Guid.NewGuid); + pc.Setup(content => content.Parent).Returns(() => null); + pc.Setup(content => content.Version).Returns(Guid.NewGuid); + pc.Setup(content => content.Properties).Returns(new Collection()); + return pc; + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 22048799ac..b3dc588345 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -213,6 +213,7 @@ + diff --git a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs index 45de9c2f8b..40fa86b67b 100644 --- a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs +++ b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs @@ -46,7 +46,10 @@ namespace Umbraco.Web.Routing /// /// Prepares the request. /// - public void PrepareRequest() + /// + /// Returns false if the request was not successfully prepared + /// + public bool PrepareRequest() { // note - at that point the original legacy module did something do handle IIS custom 404 errors // ie pages looking like /anything.aspx?404;/path/to/document - I guess the reason was to support @@ -62,10 +65,12 @@ namespace Umbraco.Web.Routing // if request has been flagged to redirect then return // whoever called us is in charge of actually redirecting - if (_pcr.IsRedirect) - return; + if (_pcr.IsRedirect) + { + return false; + } - // set the culture on the thread - once, so it's set when running document lookups + // set the culture on the thread - once, so it's set when running document lookups Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = _pcr.Culture; // find the document & template @@ -85,24 +90,25 @@ namespace Umbraco.Web.Routing // to find out the appropriate template //complete the PCR and assign the remaining values - ConfigureRequest(); + return ConfigureRequest(); } /// /// Called by PrepareRequest once everything has been discovered, resolved and assigned to the PCR. This method /// finalizes the PCR with the values assigned. /// + /// + /// Returns false if the request was not successfully configured + /// /// /// This method logic has been put into it's own method in case developers have created a custom PCR or are assigning their own values /// but need to finalize it themselves. - /// - /// This method will throw an exception if no content has been assigned /// - public void ConfigureRequest() + public bool ConfigureRequest() { if (_pcr.HasPublishedContent == false) { - throw new InvalidOperationException("Cannot configure the request if there is not content assigned"); + return false; } // set the culture on the thread -- again, 'cos it might have changed in the event handler @@ -110,8 +116,10 @@ namespace Umbraco.Web.Routing // if request has been flagged to redirect, or has no content to display, // then return - whoever called us is in charge of actually redirecting - if (_pcr.IsRedirect || !_pcr.HasPublishedContent) - return; + if (_pcr.IsRedirect || _pcr.HasPublishedContent == false) + { + return false; + } // we may be 404 _and_ have a content @@ -132,6 +140,8 @@ namespace Umbraco.Web.Routing // used by many legacy objects _routingContext.UmbracoContext.HttpContext.Items["pageID"] = _pcr.PublishedContent.Id; _routingContext.UmbracoContext.HttpContext.Items["pageElements"] = _pcr.UmbracoPage.Elements; + + return true; } /// From 57d1ff20fd55fb20c4ab8bac59d25e8d290ea81f Mon Sep 17 00:00:00 2001 From: Shannon Date: Sat, 28 Dec 2013 14:35:43 +1100 Subject: [PATCH 0138/1046] Improves test suite performance - this should shed a few minutes off of the test runs. --- .../PublishedContentCacheTests.cs | 4 ++-- .../DynamicDocumentTestsBase.cs | 8 ++++++- .../LegacyExamineBackedMediaTests.cs | 2 +- .../Routing/NiceUrlProviderTests.cs | 22 ++++++++++++++----- .../NiceUrlsProviderWithDomainsTests.cs | 19 +++++++++++----- .../Routing/UrlsWithNestedDomains.cs | 9 +++++++- .../Routing/uQueryGetNodeIdByUrlTests.cs | 18 +++++++++++---- .../BaseUmbracoConfigurationTest.cs | 13 +---------- .../TestHelpers/SettingsForTests.cs | 16 +++++++++----- src/Umbraco.Tests/UriUtilityTests.cs | 2 +- 10 files changed, 75 insertions(+), 38 deletions(-) diff --git a/src/Umbraco.Tests/PublishedCache/PublishedContentCacheTests.cs b/src/Umbraco.Tests/PublishedCache/PublishedContentCacheTests.cs index 20f2604c02..864bf491eb 100644 --- a/src/Umbraco.Tests/PublishedCache/PublishedContentCacheTests.cs +++ b/src/Umbraco.Tests/PublishedCache/PublishedContentCacheTests.cs @@ -78,7 +78,7 @@ namespace Umbraco.Tests.PublishedCache //ensure the StateHelper is using our custom context StateHelper.HttpContext = _httpContextFactory.HttpContext; - var settings = SettingsForTests.GetMockSettings(); + var settings = SettingsForTests.GenerateMockSettings(); var contentMock = Mock.Get(settings.Content); contentMock.Setup(x => x.UseLegacyXmlSchema).Returns(false); SettingsForTests.ConfigureSettings(settings); @@ -100,7 +100,7 @@ namespace Umbraco.Tests.PublishedCache private void SetupForLegacy() { - var settings = SettingsForTests.GetMockSettings(); + var settings = SettingsForTests.GenerateMockSettings(); var contentMock = Mock.Get(settings.Content); contentMock.Setup(x => x.UseLegacyXmlSchema).Returns(true); SettingsForTests.ConfigureSettings(settings); diff --git a/src/Umbraco.Tests/PublishedContent/DynamicDocumentTestsBase.cs b/src/Umbraco.Tests/PublishedContent/DynamicDocumentTestsBase.cs index 85dc2ab269..2bbd383041 100644 --- a/src/Umbraco.Tests/PublishedContent/DynamicDocumentTestsBase.cs +++ b/src/Umbraco.Tests/PublishedContent/DynamicDocumentTestsBase.cs @@ -22,6 +22,8 @@ namespace Umbraco.Tests.PublishedContent get { return DatabaseBehavior.NoDatabasePerFixture; } } + private IUmbracoSettingsSection _umbracoSettings; + public override void Initialize() { // required so we can access property.Value @@ -29,7 +31,11 @@ namespace Umbraco.Tests.PublishedContent base.Initialize(); - var scriptingMock = Mock.Get(UmbracoSettings.Scripting); + //generate new mock settings and assign so we can configure in individual tests + _umbracoSettings = SettingsForTests.GenerateMockSettings(); + SettingsForTests.ConfigureSettings(_umbracoSettings); + + var scriptingMock = Mock.Get(_umbracoSettings.Scripting); scriptingMock.Setup(x => x.DataTypeModelStaticMappings).Returns(new List()); // need to specify a custom callback for unit tests diff --git a/src/Umbraco.Tests/PublishedContent/LegacyExamineBackedMediaTests.cs b/src/Umbraco.Tests/PublishedContent/LegacyExamineBackedMediaTests.cs index 3e2739bc9a..7b0ffb41d7 100644 --- a/src/Umbraco.Tests/PublishedContent/LegacyExamineBackedMediaTests.cs +++ b/src/Umbraco.Tests/PublishedContent/LegacyExamineBackedMediaTests.cs @@ -16,7 +16,7 @@ namespace Umbraco.Tests.PublishedContent { base.TestSetup(); - var settings = SettingsForTests.GetMockSettings(); + var settings = SettingsForTests.GenerateMockSettings(); var contentMock = Mock.Get(settings.Content); contentMock.Setup(x => x.ForceSafeAliases).Returns(true); contentMock.Setup(x => x.UmbracoLibraryCacheDuration).Returns(1800); diff --git a/src/Umbraco.Tests/Routing/NiceUrlProviderTests.cs b/src/Umbraco.Tests/Routing/NiceUrlProviderTests.cs index 08c96c9207..a2ac098cad 100644 --- a/src/Umbraco.Tests/Routing/NiceUrlProviderTests.cs +++ b/src/Umbraco.Tests/Routing/NiceUrlProviderTests.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using Moq; using NUnit.Framework; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Tests.TestHelpers; using Umbraco.Web.PublishedCache.XmlPublishedCache; using Umbraco.Web.Routing; @@ -18,6 +19,17 @@ namespace Umbraco.Tests.Routing base.FreezeResolution(); } + private IUmbracoSettingsSection _umbracoSettings; + + public override void Initialize() + { + base.Initialize(); + + //generate new mock settings and assign so we can configure in individual tests + _umbracoSettings = SettingsForTests.GenerateMockSettings(); + SettingsForTests.ConfigureSettings(_umbracoSettings); + } + /// /// This checks that when we retreive a NiceUrl for multiple items that there are no issues with cache overlap /// and that they are all cached correctly. @@ -29,7 +41,7 @@ namespace Umbraco.Tests.Routing SettingsForTests.UseDirectoryUrls = true; SettingsForTests.HideTopLevelNodeFromPath = false; - var requestHandlerMock = Mock.Get(UmbracoSettings.RequestHandler); + var requestHandlerMock = Mock.Get(_umbracoSettings.RequestHandler); requestHandlerMock.Setup(x => x.AddTrailingSlash).Returns(false);// (cached routes have none) var samples = new Dictionary { @@ -93,7 +105,7 @@ namespace Umbraco.Tests.Routing SettingsForTests.UseDirectoryUrls = true; SettingsForTests.HideTopLevelNodeFromPath = false; - var requestMock = Mock.Get(UmbracoSettings.RequestHandler); + var requestMock = Mock.Get(_umbracoSettings.RequestHandler); requestMock.Setup(x => x.UseDomainPrefixes).Returns(false); var result = routingContext.UrlProvider.GetUrl(nodeId); @@ -117,7 +129,7 @@ namespace Umbraco.Tests.Routing SettingsForTests.UseDirectoryUrls = true; SettingsForTests.HideTopLevelNodeFromPath = true; - var requestMock = Mock.Get(UmbracoSettings.RequestHandler); + var requestMock = Mock.Get(_umbracoSettings.RequestHandler); requestMock.Setup(x => x.UseDomainPrefixes).Returns(false); var result = routingContext.UrlProvider.GetUrl(nodeId); @@ -131,7 +143,7 @@ namespace Umbraco.Tests.Routing SettingsForTests.UseDirectoryUrls = true; SettingsForTests.HideTopLevelNodeFromPath = false; - var requestMock = Mock.Get(UmbracoSettings.RequestHandler); + var requestMock = Mock.Get(_umbracoSettings.RequestHandler); requestMock.Setup(x => x.UseDomainPrefixes).Returns(false); Assert.AreEqual("/home/sub1/custom-sub-1/", routingContext.UrlProvider.GetUrl(1177)); @@ -153,7 +165,7 @@ namespace Umbraco.Tests.Routing SettingsForTests.HideTopLevelNodeFromPath = false; //mock the Umbraco settings that we need - var requestMock = Mock.Get(UmbracoSettings.RequestHandler); + var requestMock = Mock.Get(_umbracoSettings.RequestHandler); requestMock.Setup(x => x.UseDomainPrefixes).Returns(false); Assert.AreEqual("#", routingContext.UrlProvider.GetUrl(999999)); diff --git a/src/Umbraco.Tests/Routing/NiceUrlsProviderWithDomainsTests.cs b/src/Umbraco.Tests/Routing/NiceUrlsProviderWithDomainsTests.cs index fdc395e0cf..a64f397c2c 100644 --- a/src/Umbraco.Tests/Routing/NiceUrlsProviderWithDomainsTests.cs +++ b/src/Umbraco.Tests/Routing/NiceUrlsProviderWithDomainsTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Moq; using NUnit.Framework; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Tests.TestHelpers; using Umbraco.Web.PublishedCache.XmlPublishedCache; using Umbraco.Web.Routing; @@ -18,10 +19,16 @@ namespace Umbraco.Tests.Routing { base.Initialize(); + //generate new mock settings and assign so we can configure in individual tests + _umbracoSettings = SettingsForTests.GenerateMockSettings(); + SettingsForTests.ConfigureSettings(_umbracoSettings); + // ensure we can create them although the content is not in the database TestHelper.DropForeignKeys("umbracoDomains"); } + private IUmbracoSettingsSection _umbracoSettings; + protected override DatabaseBehavior DatabaseTestBehavior { get { return DatabaseBehavior.NewSchemaPerFixture; } @@ -202,7 +209,7 @@ namespace Umbraco.Tests.Routing SettingsForTests.UseDirectoryUrls = true; SettingsForTests.HideTopLevelNodeFromPath = false; // ignored w/domains - var requestMock = Mock.Get(UmbracoSettings.RequestHandler); + var requestMock = Mock.Get(_umbracoSettings.RequestHandler); requestMock.Setup(x => x.UseDomainPrefixes).Returns(false); InitializeLanguagesAndDomains(); @@ -232,7 +239,7 @@ namespace Umbraco.Tests.Routing SettingsForTests.UseDirectoryUrls = true; SettingsForTests.HideTopLevelNodeFromPath = false; // ignored w/domains - var requestMock = Mock.Get(UmbracoSettings.RequestHandler); + var requestMock = Mock.Get(_umbracoSettings.RequestHandler); requestMock.Setup(x => x.UseDomainPrefixes).Returns(false); InitializeLanguagesAndDomains(); @@ -254,7 +261,7 @@ namespace Umbraco.Tests.Routing SettingsForTests.UseDirectoryUrls = true; SettingsForTests.HideTopLevelNodeFromPath = false; // ignored w/domains - var requestMock = Mock.Get(UmbracoSettings.RequestHandler); + var requestMock = Mock.Get(_umbracoSettings.RequestHandler); requestMock.Setup(x => x.UseDomainPrefixes).Returns(false); InitializeLanguagesAndDomains(); @@ -282,7 +289,7 @@ namespace Umbraco.Tests.Routing SettingsForTests.UseDirectoryUrls = true; SettingsForTests.HideTopLevelNodeFromPath = false; // ignored w/domains - var requestMock = Mock.Get(UmbracoSettings.RequestHandler); + var requestMock = Mock.Get(_umbracoSettings.RequestHandler); requestMock.Setup(x => x.UseDomainPrefixes).Returns(false); InitializeLanguagesAndDomains(); @@ -300,7 +307,7 @@ namespace Umbraco.Tests.Routing SettingsForTests.UseDirectoryUrls = true; SettingsForTests.HideTopLevelNodeFromPath = false; // ignored w/domains - var requestMock = Mock.Get(UmbracoSettings.RequestHandler); + var requestMock = Mock.Get(_umbracoSettings.RequestHandler); requestMock.Setup(x => x.UseDomainPrefixes).Returns(false); InitializeLanguagesAndDomains(); @@ -367,7 +374,7 @@ namespace Umbraco.Tests.Routing SetDomains4(); //mock the Umbraco settings that we need - var requestMock = Mock.Get(UmbracoSettings.RequestHandler); + var requestMock = Mock.Get(_umbracoSettings.RequestHandler); requestMock.Setup(x => x.UseDomainPrefixes).Returns(false); Assert.AreEqual("/en/1001-1-1/", routingContext.UrlProvider.GetUrl(100111)); diff --git a/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs b/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs index dafb27a2b5..9e45371de2 100644 --- a/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs +++ b/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs @@ -2,6 +2,7 @@ using System.Linq; using Moq; using NUnit.Framework; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Tests.TestHelpers; using Umbraco.Web.PublishedCache.XmlPublishedCache; using umbraco.cms.businesslogic.web; @@ -23,7 +24,7 @@ namespace Umbraco.Tests.Routing { SettingsForTests.UseDirectoryUrls = true; SettingsForTests.HideTopLevelNodeFromPath = false; // ignored w/domains - var requestMock = Mock.Get(UmbracoSettings.RequestHandler); + var requestMock = Mock.Get(_umbracoSettings.RequestHandler); requestMock.Setup(x => x.UseDomainPrefixes).Returns(true); InitializeLanguagesAndDomains(); @@ -65,12 +66,18 @@ namespace Umbraco.Tests.Routing //Assert.AreEqual("http://domain1.com/1001-1/1001-1-1", routingContext.NiceUrlProvider.GetNiceUrl(100111, true)); // bad } + private IUmbracoSettingsSection _umbracoSettings; + public override void Initialize() { base.Initialize(); // ensure we can create them although the content is not in the database TestHelper.DropForeignKeys("umbracoDomains"); + + //generate new mock settings and assign so we can configure in individual tests + _umbracoSettings = SettingsForTests.GenerateMockSettings(); + SettingsForTests.ConfigureSettings(_umbracoSettings); } protected override void FreezeResolution() diff --git a/src/Umbraco.Tests/Routing/uQueryGetNodeIdByUrlTests.cs b/src/Umbraco.Tests/Routing/uQueryGetNodeIdByUrlTests.cs index 375964c00d..3d6d402642 100644 --- a/src/Umbraco.Tests/Routing/uQueryGetNodeIdByUrlTests.cs +++ b/src/Umbraco.Tests/Routing/uQueryGetNodeIdByUrlTests.cs @@ -1,5 +1,6 @@ using Moq; using NUnit.Framework; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Web.Routing; @@ -11,6 +12,9 @@ namespace Umbraco.Tests.Routing [TestFixture] public class uQueryGetNodeIdByUrlTests : BaseRoutingTest { + + private IUmbracoSettingsSection _umbracoSettings; + public override void Initialize() { base.Initialize(); @@ -36,6 +40,10 @@ namespace Umbraco.Tests.Routing ////assign the routing context back to the umbraco context //umbracoContext.RoutingContext = routingContext; Umbraco.Web.UmbracoContext.Current = routingContext.UmbracoContext; + + //generate new mock settings and assign so we can configure in individual tests + _umbracoSettings = SettingsForTests.GenerateMockSettings(); + SettingsForTests.ConfigureSettings(_umbracoSettings); } public override void TearDown() @@ -57,9 +65,10 @@ namespace Umbraco.Tests.Routing { SettingsForTests.UseDirectoryUrls = true; SettingsForTests.HideTopLevelNodeFromPath = false; - var requestMock = Mock.Get(UmbracoSettings.RequestHandler); - requestMock.Setup(x => x.UseDomainPrefixes).Returns(false); + var requestMock = Mock.Get(_umbracoSettings.RequestHandler); + requestMock.Setup(x => x.UseDomainPrefixes).Returns(false); + Assert.AreEqual(nodeId, global::umbraco.uQuery.GetNodeIdByUrl("http://example.com" + url)); } @@ -76,9 +85,10 @@ namespace Umbraco.Tests.Routing { SettingsForTests.UseDirectoryUrls = true; SettingsForTests.HideTopLevelNodeFromPath = false; - var requestMock = Mock.Get(UmbracoSettings.RequestHandler); - requestMock.Setup(x => x.UseDomainPrefixes).Returns(false); + var requestMock = Mock.Get(_umbracoSettings.RequestHandler); + requestMock.Setup(x => x.UseDomainPrefixes).Returns(false); + Assert.AreEqual(nodeId, global::umbraco.uQuery.GetNodeIdByUrl(url)); } } diff --git a/src/Umbraco.Tests/TestHelpers/BaseUmbracoConfigurationTest.cs b/src/Umbraco.Tests/TestHelpers/BaseUmbracoConfigurationTest.cs index 64769511b0..d1596e8ff3 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUmbracoConfigurationTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUmbracoConfigurationTest.cs @@ -19,16 +19,7 @@ namespace Umbraco.Tests.TestHelpers [SetUp] public virtual void Initialize() { - using (DisposableTimer.TraceDuration < BaseUmbracoConfigurationTest>("init")) - { - //mock the Umbraco settings that we need - var settings = SettingsForTests.GetMockSettings(); - //sets the global singleton to use the mocked format - SettingsForTests.ConfigureSettings(settings); - //set our local variable for tests to use (preferably) - UmbracoSettings = settings; - } - + SettingsForTests.Reset(); } [TearDown] @@ -37,7 +28,5 @@ namespace Umbraco.Tests.TestHelpers //reset settings SettingsForTests.Reset(); } - - protected virtual IUmbracoSettingsSection UmbracoSettings { get; private set; } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs b/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs index 4e5249b194..cdfc28c55c 100644 --- a/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs +++ b/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs @@ -25,7 +25,7 @@ namespace Umbraco.Tests.TestHelpers /// Returns generated settings which can be stubbed to return whatever values necessary /// /// - public static IUmbracoSettingsSection GetMockSettings() + public static IUmbracoSettingsSection GenerateMockSettings() { var settings = new Mock(); @@ -157,14 +157,20 @@ namespace Umbraco.Tests.TestHelpers ConfigureSettings(GetDefault()); } + private static IUmbracoSettingsSection _defaultSettings; + internal static IUmbracoSettingsSection GetDefault() { - var config = new FileInfo(TestHelper.MapPathForTest("~/Configurations/UmbracoSettings/web.config")); + if (_defaultSettings == null) + { + var config = new FileInfo(TestHelper.MapPathForTest("~/Configurations/UmbracoSettings/web.config")); - var fileMap = new ExeConfigurationFileMap() { ExeConfigFilename = config.FullName }; - var configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None); + var fileMap = new ExeConfigurationFileMap() { ExeConfigFilename = config.FullName }; + var configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None); + _defaultSettings = configuration.GetSection("umbracoConfiguration/defaultSettings") as UmbracoSettingsSection; + } - return configuration.GetSection("umbracoConfiguration/defaultSettings") as UmbracoSettingsSection; + return _defaultSettings; } } } diff --git a/src/Umbraco.Tests/UriUtilityTests.cs b/src/Umbraco.Tests/UriUtilityTests.cs index 895ee247e8..835f004948 100644 --- a/src/Umbraco.Tests/UriUtilityTests.cs +++ b/src/Umbraco.Tests/UriUtilityTests.cs @@ -85,7 +85,7 @@ namespace Umbraco.Tests { ConfigurationManager.AppSettings.Set("umbracoUseDirectoryUrls", directoryUrls ? "true" : "false"); - var settings = SettingsForTests.GetMockSettings(); + var settings = SettingsForTests.GenerateMockSettings(); var requestMock = Mock.Get(settings.RequestHandler); requestMock.Setup(x => x.AddTrailingSlash).Returns(trailingSlash); SettingsForTests.ConfigureSettings(settings); From 0955b0c4741e44333990594fe1f1fb4b297567c3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Sat, 28 Dec 2013 14:37:53 +1100 Subject: [PATCH 0139/1046] fixes proj ref --- src/Umbraco.Web/Umbraco.Web.csproj | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index fa5cb23dac..080495bada 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1820,9 +1820,6 @@ ASPXCodeBehind - - ASPXCodeBehind - From 07f3de8b678bee4a07521550f32b17419af1933d Mon Sep 17 00:00:00 2001 From: Shannon Date: Sat, 28 Dec 2013 14:42:34 +1100 Subject: [PATCH 0140/1046] updates port number --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index edc25e7cac..253deb9365 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -2625,9 +2625,9 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.0\x86\*.* "$(TargetDir)x86\" True True - 7000 + 7020 / - http://localhost:7000 + http://localhost:7020 False False From 8a70c440f26ddd4c8b29412191bafb336e2d8d6c Mon Sep 17 00:00:00 2001 From: Shannon Date: Sat, 28 Dec 2013 18:55:43 +1100 Subject: [PATCH 0141/1046] Added many more unit tests for the membership provider base class and fixed some logic inconsistencies. --- .../NameValueCollectionExtensions.cs | 36 +- .../Security/MembershipProviderBase.cs | 105 ++++-- .../MembersMembershipProviderTests.cs | 129 +++++++ .../Membership/MembershipProviderBaseTests.cs | 318 +++++++++++------- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + .../Providers/MembersMembershipProvider.cs | 7 +- src/umbraco.businesslogic/User.cs | 3 + .../businesslogic/member/Member.cs | 4 +- .../UsersMembershipProvider.cs | 29 +- .../members/UmbracoMembershipProvider.cs | 35 +- 10 files changed, 422 insertions(+), 245 deletions(-) create mode 100644 src/Umbraco.Tests/Membership/MembersMembershipProviderTests.cs diff --git a/src/Umbraco.Core/NameValueCollectionExtensions.cs b/src/Umbraco.Core/NameValueCollectionExtensions.cs index 0bfa485438..d47add61ed 100644 --- a/src/Umbraco.Core/NameValueCollectionExtensions.cs +++ b/src/Umbraco.Core/NameValueCollectionExtensions.cs @@ -13,23 +13,23 @@ namespace Umbraco.Core { return collection.Keys.Cast().Any(k => (string) k == key); } - - public static T GetValue(this NameValueCollection collection, string key, T defaultIfNotFound) - { - if (collection.ContainsKey(key) == false) - { - return defaultIfNotFound; - } - - var val = collection[key]; - if (val == null) - { - return defaultIfNotFound; - } - - var result = val.TryConvertTo(); - - return result.Success ? result.Result : defaultIfNotFound; - } + + public static T GetValue(this NameValueCollection collection, string key, T defaultIfNotFound) + { + if (collection.ContainsKey(key) == false) + { + return defaultIfNotFound; + } + + var val = collection[key]; + if (val == null) + { + return defaultIfNotFound; + } + + var result = val.TryConvertTo(); + + return result.Success ? result.Result : defaultIfNotFound; + } } } diff --git a/src/Umbraco.Core/Security/MembershipProviderBase.cs b/src/Umbraco.Core/Security/MembershipProviderBase.cs index 3794327781..5480b8220c 100644 --- a/src/Umbraco.Core/Security/MembershipProviderBase.cs +++ b/src/Umbraco.Core/Security/MembershipProviderBase.cs @@ -61,7 +61,7 @@ namespace Umbraco.Core.Security private string _passwordStrengthRegularExpression; private bool _requiresQuestionAndAnswer; private bool _requiresUniqueEmail; - + private string _customHashAlgorithmType ; internal bool UseLegacyEncoding; #region Properties @@ -251,7 +251,8 @@ namespace Umbraco.Core.Security LogHelper.Error("Cannot specify a Hashed password format with the enabledPasswordRetrieval option set to true", ex); throw ex; } - + + _customHashAlgorithmType = config.GetValue("hashAlgorithmType", string.Empty); } /// @@ -582,15 +583,26 @@ namespace Umbraco.Core.Security return num; } - protected string FormatPasswordForStorage(string pass, string salt) + /// + /// If the password format is a hashed keyed algorithm then we will pre-pend the salt used to hash the password + /// to the hashed password itself. + /// + /// + /// + /// + protected internal string FormatPasswordForStorage(string pass, string salt) { if (UseLegacyEncoding) { return pass; } - - //the better way, we use salt per member - return salt + pass; + + if (PasswordFormat == MembershipPasswordFormat.Hashed) + { + //the better way, we use salt per member + return salt + pass; + } + return pass; } protected bool IsEmailValid(string email) @@ -602,7 +614,7 @@ namespace Umbraco.Core.Security return Regex.IsMatch(email, pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled); } - protected string EncryptOrHashPassword(string pass, string salt) + protected internal string EncryptOrHashPassword(string pass, string salt) { //if we are doing it the old way @@ -613,12 +625,13 @@ namespace Umbraco.Core.Security //This is the correct way to implement this (as per the sql membership provider) - if ((int)PasswordFormat == 0) + if (PasswordFormat == MembershipPasswordFormat.Clear) return pass; var bytes = Encoding.Unicode.GetBytes(pass); var numArray1 = Convert.FromBase64String(salt); byte[] inArray; - if ((int)PasswordFormat == 1) + + if (PasswordFormat == MembershipPasswordFormat.Hashed) { var hashAlgorithm = GetHashAlgorithm(pass); var algorithm = hashAlgorithm as KeyedHashAlgorithm; @@ -657,6 +670,8 @@ namespace Umbraco.Core.Security } else { + //this code is copied from the sql membership provider - pretty sure this could be nicely re-written to completely + // ignore the salt stuff since we are not salting the password when encrypting. var password = new byte[numArray1.Length + bytes.Length]; Buffer.BlockCopy(numArray1, 0, password, 0, numArray1.Length); Buffer.BlockCopy(bytes, 0, password, numArray1.Length, bytes.Length); @@ -665,6 +680,31 @@ namespace Umbraco.Core.Security return Convert.ToBase64String(inArray); } + /// + /// Checks the password. + /// + /// The password. + /// The dbPassword. + /// + protected internal bool CheckPassword(string password, string dbPassword) + { + switch (PasswordFormat) + { + case MembershipPasswordFormat.Encrypted: + var decrypted = DecryptPassword(dbPassword); + return decrypted == password; + case MembershipPasswordFormat.Hashed: + string salt; + var storedHashedPass = StoredPassword(dbPassword, out salt); + var hashed = EncryptOrHashPassword(password, salt); + return storedHashedPass == hashed; + case MembershipPasswordFormat.Clear: + return password == dbPassword; + default: + throw new ArgumentOutOfRangeException(); + } + } + /// /// Encrypt/hash a new password with a new salt /// @@ -677,26 +717,7 @@ namespace Umbraco.Core.Security return EncryptOrHashPassword(newPassword, salt); } - /// - /// Gets the encrypted or hashed string of an existing password for an existing user - /// - /// The stored string for the password - /// - protected string EncryptOrHashExistingPassword(string storedPassword) - { - if (UseLegacyEncoding) - { - return EncryptOrHashPassword(storedPassword, storedPassword); - } - else - { - string salt; - var pass = StoredPassword(storedPassword, PasswordFormat, out salt); - return EncryptOrHashPassword(pass, salt); - } - } - - protected string DecodePassword(string pass) + protected internal string DecryptPassword(string pass) { //if we are doing it the old way @@ -712,7 +733,7 @@ namespace Umbraco.Core.Security case 0: return pass; case 1: - throw new ProviderException("Provider can not decode hashed password"); + throw new ProviderException("Provider can not decrypt hashed password"); default: var bytes = DecryptPassword(Convert.FromBase64String(pass)); return bytes == null ? null : Encoding.Unicode.GetString(bytes, 16, bytes.Length - 16); @@ -723,17 +744,16 @@ namespace Umbraco.Core.Security /// Returns the hashed password without the salt if it is hashed /// /// - /// /// returns the salt /// - internal static string StoredPassword(string storedString, MembershipPasswordFormat format, out string salt) + internal string StoredPassword(string storedString, out string salt) { - switch (format) + switch (PasswordFormat) { case MembershipPasswordFormat.Hashed: var saltLen = GenerateSalt(); salt = storedString.Substring(0, saltLen.Length); - return storedString.Substring(saltLen.Length); + return storedString.Substring(saltLen.Length); case MembershipPasswordFormat.Clear: case MembershipPasswordFormat.Encrypted: default: @@ -750,7 +770,7 @@ namespace Umbraco.Core.Security return Convert.ToBase64String(numArray); } - protected HashAlgorithm GetHashAlgorithm(string password) + protected internal HashAlgorithm GetHashAlgorithm(string password) { if (UseLegacyEncoding) { @@ -765,10 +785,21 @@ namespace Umbraco.Core.Security }; } } - + //get the algorithm by name - return HashAlgorithm.Create(Membership.HashAlgorithmType); + if (_customHashAlgorithmType.IsNullOrWhiteSpace()) + { + _customHashAlgorithmType = Membership.HashAlgorithmType; + } + + var alg = HashAlgorithm.Create(_customHashAlgorithmType); + if (alg == null) + { + throw new InvalidOperationException("The hash algorithm specified " + Membership.HashAlgorithmType + " cannot be resolved"); + } + + return alg; } /// diff --git a/src/Umbraco.Tests/Membership/MembersMembershipProviderTests.cs b/src/Umbraco.Tests/Membership/MembersMembershipProviderTests.cs new file mode 100644 index 0000000000..411c22f750 --- /dev/null +++ b/src/Umbraco.Tests/Membership/MembersMembershipProviderTests.cs @@ -0,0 +1,129 @@ +using System.Collections.Specialized; +using System.Web.Security; +using Moq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Tests.TestHelpers.Entities; +using Umbraco.Web.Security.Providers; + +namespace Umbraco.Tests.Membership +{ + [TestFixture] + public class MembersMembershipProviderTests + { + //[Test] + //public void Set_Default_Member_Type_On_Init() + + //[Test] + //public void Create_User_Already_Exists() + //{ + + //} + + //[Test] + //public void Create_User_Requires_Unique_Email() + //{ + + //} + + [Test] + public void Answer_Is_Encrypted() + { + IMember createdMember = null; + var memberType = MockedContentTypes.CreateSimpleMemberType(); + foreach (var p in Constants.Conventions.Member.GetStandardPropertyTypeStubs()) + { + memberType.AddPropertyType(p.Value); + } + var mServiceMock = new Mock(); + mServiceMock.Setup(service => service.Exists("test")).Returns(false); + mServiceMock.Setup(service => service.GetByEmail("test@test.com")).Returns(() => null); + mServiceMock.Setup( + service => service.CreateMember(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string u, string e, string p, string m) => + { + createdMember = new Member("test", e, u, p, memberType); + }) + .Returns(() => createdMember); + var provider = new MembersMembershipProvider(mServiceMock.Object); + + MembershipCreateStatus status; + provider.CreateUser("test", "test", "test", "test@test.com", "test", "test", true, "test", out status); + + Assert.AreNotEqual("test", createdMember.PasswordAnswer); + Assert.AreEqual(provider.EncryptString("test"), createdMember.PasswordAnswer); + } + + [Test] + public void Password_Encrypted() + { + IMember createdMember = null; + var memberType = MockedContentTypes.CreateSimpleMemberType(); + foreach (var p in Constants.Conventions.Member.GetStandardPropertyTypeStubs()) + { + memberType.AddPropertyType(p.Value); + } + var mServiceMock = new Mock(); + mServiceMock.Setup(service => service.Exists("test")).Returns(false); + mServiceMock.Setup(service => service.GetByEmail("test@test.com")).Returns(() => null); + mServiceMock.Setup( + service => service.CreateMember(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string u, string e, string p, string m) => + { + createdMember = new Member("test", e, u, p, memberType); + }) + .Returns(() => createdMember); + + var provider = new MembersMembershipProvider(mServiceMock.Object); + provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Encrypted" } }); + MembershipCreateStatus status; + provider.CreateUser("test", "test", "test", "test@test.com", "test", "test", true, "test", out status); + + Assert.AreNotEqual("test", createdMember.Password); + var decrypted = provider.DecryptPassword(createdMember.Password); + Assert.AreEqual("test", decrypted); + } + + [Test] + public void Password_Hashed_With_Salt() + { + IMember createdMember = null; + var memberType = MockedContentTypes.CreateSimpleMemberType(); + foreach (var p in Constants.Conventions.Member.GetStandardPropertyTypeStubs()) + { + memberType.AddPropertyType(p.Value); + } + var mServiceMock = new Mock(); + mServiceMock.Setup(service => service.Exists("test")).Returns(false); + mServiceMock.Setup(service => service.GetByEmail("test@test.com")).Returns(() => null); + mServiceMock.Setup( + service => service.CreateMember(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string u, string e, string p, string m) => + { + createdMember = new Member("test", e, u, p, memberType); + }) + .Returns(() => createdMember); + + var provider = new MembersMembershipProvider(mServiceMock.Object); + provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Hashed" }, { "hashAlgorithmType", "HMACSHA256" } }); + MembershipCreateStatus status; + provider.CreateUser("test", "test", "test", "test@test.com", "test", "test", true, "test", out status); + + Assert.AreNotEqual("test", createdMember.Password); + + string salt; + var storedPassword = provider.StoredPassword(createdMember.Password, out salt); + var hashedPassword = provider.EncryptOrHashPassword("test", salt); + Assert.AreEqual(hashedPassword, storedPassword); + } + + //[Test] + //public void Password_Encrypted_Validated_With_Salt() + + //[Test] + //public void Password_Encrypted_Validated_With_Salt() + + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs index 2af97b49e2..defa6fb4cd 100644 --- a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs +++ b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs @@ -4,138 +4,16 @@ using System.Collections.Specialized; using System.Configuration.Provider; using System.Diagnostics; using System.Linq; +using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using System.Web.Security; using Moq; using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Models; using Umbraco.Core.Security; -using Umbraco.Core.Services; -using Umbraco.Tests.TestHelpers.Entities; -using Umbraco.Web.Security.Providers; namespace Umbraco.Tests.Membership { - [TestFixture] - public class MembersMembershipProviderTests - { - //[Test] - //public void Set_Default_Member_Type_On_Init() - - //[Test] - //public void Create_User_Already_Exists() - //{ - - //} - - //[Test] - //public void Create_User_Requires_Unique_Email() - //{ - - //} - - [Test] - public void Answer_Is_Encrypted() - { - IMember createdMember = null; - var memberType = MockedContentTypes.CreateSimpleMemberType(); - foreach (var p in Constants.Conventions.Member.GetStandardPropertyTypeStubs()) - { - memberType.AddPropertyType(p.Value); - } - var mServiceMock = new Mock(); - mServiceMock.Setup(service => service.Exists("test")).Returns(false); - mServiceMock.Setup(service => service.GetByEmail("test@test.com")).Returns(() => null); - mServiceMock.Setup( - service => service.CreateMember(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((string u, string e, string p, string m) => - { - createdMember = new Member("test", e, u, p, memberType); - }) - .Returns(() => createdMember); - var provider = new MembersMembershipProvider(mServiceMock.Object); - - MembershipCreateStatus status; - provider.CreateUser("test", "test", "test", "test@test.com", "test", "test", true, "test", out status); - - Assert.AreNotEqual("test", createdMember.PasswordAnswer); - Assert.AreEqual(provider.EncryptString("test"), createdMember.PasswordAnswer); - } - - [Test] - public void Password_Encrypted_With_Salt() - { - IMember createdMember = null; - var memberType = MockedContentTypes.CreateSimpleMemberType(); - foreach (var p in Constants.Conventions.Member.GetStandardPropertyTypeStubs()) - { - memberType.AddPropertyType(p.Value); - } - var mServiceMock = new Mock(); - mServiceMock.Setup(service => service.Exists("test")).Returns(false); - mServiceMock.Setup(service => service.GetByEmail("test@test.com")).Returns(() => null); - mServiceMock.Setup( - service => service.CreateMember(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((string u, string e, string p, string m) => - { - createdMember = new Member("test", e, u, p, memberType); - }) - .Returns(() => createdMember); - - var provider = new MembersMembershipProvider(mServiceMock.Object); - provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Encrypted" } }); - MembershipCreateStatus status; - provider.CreateUser("test", "test", "test", "test@test.com", "test", "test", true, "test", out status); - - Assert.AreNotEqual("test", createdMember.Password); - //Assert.AreNotEqual(provider.EncryptString("test"), createdMember.PasswordAnswer); - string salt; - var encodedPassword = provider.EncryptOrHashNewPassword("test", out salt); - Assert.AreEqual(encodedPassword, createdMember.Password); - } - - //[Test] - //public void Password_Hashed_With_Salt() - //{ - // IMember createdMember = null; - // var memberType = MockedContentTypes.CreateSimpleMemberType(); - // foreach (var p in Constants.Conventions.Member.GetStandardPropertyTypeStubs()) - // { - // memberType.AddPropertyType(p.Value); - // } - // var mServiceMock = new Mock(); - // mServiceMock.Setup(service => service.Exists("test")).Returns(false); - // mServiceMock.Setup(service => service.GetByEmail("test@test.com")).Returns(() => null); - // mServiceMock.Setup( - // service => service.CreateMember(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - // .Callback((string u, string e, string p, string m) => - // { - // createdMember = new Member("test", e, u, p, memberType); - // }) - // .Returns(() => createdMember); - - // var provider = new MembersMembershipProvider(mServiceMock.Object); - // provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Hashed" } }); - // MembershipCreateStatus status; - // provider.CreateUser("test", "test", "test", "test@test.com", "test", "test", true, "test", out status); - - // Assert.AreNotEqual("test", createdMember.Password); - // Assert.AreNotEqual(provider.EncryptString("test"), createdMember.PasswordAnswer); - // string salt; - // var encodedPassword = provider.EncryptOrHashNewPassword("test", out salt); - // Assert.AreEqual(encodedPassword, createdMember.Password); - //} - - //[Test] - //public void Password_Encrypted_Validated_With_Salt() - - //[Test] - //public void Password_Encrypted_Validated_With_Salt() - - } - [TestFixture] public class MembershipProviderBaseTests { @@ -330,18 +208,204 @@ namespace Umbraco.Tests.Membership lastLength = result.Length; } } - + [Test] - public void Get_StoredPassword() + public void Get_Stored_Password_Hashed() { + var providerMock = new Mock() { CallBase = true }; + var provider = providerMock.Object; + provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Hashed" }, { "hashAlgorithmType", "HMACSHA256" } }); + var salt = MembershipProviderBase.GenerateSalt(); var stored = salt + "ThisIsAHashedPassword"; string initSalt; - var result = MembershipProviderBase.StoredPassword(stored, MembershipPasswordFormat.Hashed, out initSalt); + var result = provider.StoredPassword(stored, out initSalt); - Assert.AreEqual(salt, initSalt); + Assert.AreEqual("ThisIsAHashedPassword", result); } + [Test] + public void Get_Stored_Password_Encrypted() + { + var providerMock = new Mock() { CallBase = true }; + var provider = providerMock.Object; + provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Encrypted" } }); + + var stored = "ThisIsAnEncryptedPassword"; + + string initSalt; + var result = provider.StoredPassword(stored, out initSalt); + + Assert.AreEqual("ThisIsAnEncryptedPassword", result); + } + + [Test] + public void Get_Stored_Password_Clear() + { + var providerMock = new Mock() { CallBase = true }; + var provider = providerMock.Object; + provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Clear" } }); + + var salt = MembershipProviderBase.GenerateSalt(); + var stored = "ThisIsAClearPassword"; + + string initSalt; + var result = provider.StoredPassword(stored, out initSalt); + + Assert.AreEqual("ThisIsAClearPassword", result); + } + + [Test] + public void Format_Pass_For_Storage_Hashed() + { + var providerMock = new Mock() { CallBase = true }; + var provider = providerMock.Object; + provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Hashed" }, { "hashAlgorithmType", "HMACSHA256" } }); + + var salt = MembershipProviderBase.GenerateSalt(); + var stored = "ThisIsAHashedPassword"; + + var result = provider.FormatPasswordForStorage(stored, salt); + + Assert.AreEqual(salt + "ThisIsAHashedPassword", result); + } + + [Test] + public void Format_Pass_For_Storage_Encrypted() + { + var providerMock = new Mock() { CallBase = true }; + var provider = providerMock.Object; + provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Encrypted" } }); + + var salt = MembershipProviderBase.GenerateSalt(); + var stored = "ThisIsAnEncryptedPassword"; + + var result = provider.FormatPasswordForStorage(stored, salt); + + Assert.AreEqual("ThisIsAnEncryptedPassword", result); + } + + [Test] + public void Format_Pass_For_Storage_Clear() + { + var providerMock = new Mock() { CallBase = true }; + var provider = providerMock.Object; + provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Clear" } }); + + var salt = MembershipProviderBase.GenerateSalt(); + var stored = "ThisIsAClearPassword"; + + var result = provider.FormatPasswordForStorage(stored, salt); + + Assert.AreEqual("ThisIsAClearPassword", result); + } + + [Test] + public void Check_Password_Hashed_KeyedHashAlgorithm() + { + var providerMock = new Mock() { CallBase = true }; + var provider = providerMock.Object; + provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Hashed" }, { "hashAlgorithmType", "HMACSHA256" } }); + + string salt; + var pass = "ThisIsAHashedPassword"; + var hashed = provider.EncryptOrHashNewPassword(pass, out salt); + var storedPassword = provider.FormatPasswordForStorage(hashed, salt); + + var result = provider.CheckPassword("ThisIsAHashedPassword", storedPassword); + + Assert.IsTrue(result); + } + + [Test] + public void Check_Password_Hashed_Non_KeyedHashAlgorithm() + { + var providerMock = new Mock() { CallBase = true }; + var provider = providerMock.Object; + provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Hashed" } }); + + string salt; + var pass = "ThisIsAHashedPassword"; + var hashed = provider.EncryptOrHashNewPassword(pass, out salt); + var storedPassword = provider.FormatPasswordForStorage(hashed, salt); + + var result = provider.CheckPassword("ThisIsAHashedPassword", storedPassword); + + Assert.IsTrue(result); + } + + [Test] + public void Check_Password_Encrypted() + { + var providerMock = new Mock() { CallBase = true }; + var provider = providerMock.Object; + provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Encrypted" } }); + + string salt; + var pass = "ThisIsAnEncryptedPassword"; + var encrypted = provider.EncryptOrHashNewPassword(pass, out salt); + + var result = provider.CheckPassword("ThisIsAnEncryptedPassword", encrypted); + + Assert.IsTrue(result); + } + + [Test] + public void Check_Password_Clear() + { + var providerMock = new Mock() { CallBase = true }; + var provider = providerMock.Object; + provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Clear" } }); + + var pass = "ThisIsAClearPassword"; + + var result = provider.CheckPassword("ThisIsAClearPassword", pass); + + Assert.IsTrue(result); + } + + [Test] + public void Can_Decrypt_Password() + { + var providerMock = new Mock() { CallBase = true }; + var provider = providerMock.Object; + provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Encrypted" } }); + + string salt; + var pass = "ThisIsAnEncryptedPassword"; + var encrypted = provider.EncryptOrHashNewPassword(pass, out salt); + + var result = provider.DecryptPassword(encrypted); + + Assert.AreEqual(pass, result); + + } + + [Test] + public void Get_Hash_Algorithm_Legacy() + { + var providerMock = new Mock() { CallBase = true }; + var provider = providerMock.Object; + provider.Initialize("test", new NameValueCollection { {"useLegacyEncoding", "true"}, { "passwordFormat", "Hashed" }, { "hashAlgorithmType", "HMACSHA256" } }); + + var alg = provider.GetHashAlgorithm("blah"); + + Assert.IsTrue(alg is HMACSHA1); + } + + [Test] + public void Get_Hash_Algorithm() + { + var providerMock = new Mock() { CallBase = true }; + var provider = providerMock.Object; + provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Hashed" }, { "hashAlgorithmType", "HMACSHA256" } }); + + var alg = provider.GetHashAlgorithm("blah"); + + Assert.IsTrue(alg is HMACSHA256); + } + + } } diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index b3dc588345..6a3ded0fde 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -155,6 +155,7 @@ + diff --git a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs index e4fbda1d08..a735e3a9a2 100644 --- a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs @@ -310,7 +310,7 @@ namespace Umbraco.Web.Security.Providers throw new ProviderException("Incorrect password answer"); } - var decodedPassword = DecodePassword(m.Password); + var decodedPassword = DecryptPassword(m.Password); return decodedPassword; } @@ -511,10 +511,7 @@ namespace Umbraco.Web.Security.Providers return false; } - string salt; - var encodedPassword = EncryptOrHashNewPassword(password, out salt); - - var authenticated = (encodedPassword == member.Password); + var authenticated = CheckPassword(password, member.Password); if (authenticated == false) { diff --git a/src/umbraco.businesslogic/User.cs b/src/umbraco.businesslogic/User.cs index 5da83d2a6d..9584b2f1cd 100644 --- a/src/umbraco.businesslogic/User.cs +++ b/src/umbraco.businesslogic/User.cs @@ -212,6 +212,7 @@ namespace umbraco.BusinessLogic return UserType.Alias == "admin"; } + [Obsolete("Do not use this method to validate credentials, use the user's membership provider to do authentication. This method will not work if the password format is 'Encrypted'")] public bool ValidatePassword(string password) { string userLogin = @@ -310,6 +311,7 @@ namespace umbraco.BusinessLogic /// The login name. /// The password. /// + [Obsolete("Do not use this method to validate credentials, use the user's membership provider to do authentication. This method will not work if the password format is 'Encrypted'")] public static bool validateCredentials(string lname, string passw) { return validateCredentials(lname, passw, true); @@ -322,6 +324,7 @@ namespace umbraco.BusinessLogic /// The password. /// if set to true [check for umbraco console access]. /// + [Obsolete("Do not use this method to validate credentials, use the user's membership provider to do authentication. This method will not work if the password format is 'Encrypted'")] public static bool validateCredentials(string lname, string passw, bool checkForUmbracoConsoleAccess) { string consoleCheckSql = ""; diff --git a/src/umbraco.cms/businesslogic/member/Member.cs b/src/umbraco.cms/businesslogic/member/Member.cs index 9b051aea7b..40275672d1 100644 --- a/src/umbraco.cms/businesslogic/member/Member.cs +++ b/src/umbraco.cms/businesslogic/member/Member.cs @@ -353,7 +353,6 @@ namespace umbraco.cms.businesslogic.member /// Member login /// Member password /// The member with the credentials - null if none exists - public static Member GetMemberFromLoginNameAndPassword(string loginName, string password) { if (IsMember(loginName)) @@ -375,7 +374,8 @@ namespace umbraco.cms.businesslogic.member return null; } } - + + [Obsolete("This method will not work if the password format is encrypted since the encryption that is performed is not static and a new value will be created each time the same string is encrypted")] public static Member GetMemberFromLoginAndEncodedPassword(string loginName, string password) { var o = SqlHelper.ExecuteScalar( diff --git a/src/umbraco.providers/UsersMembershipProvider.cs b/src/umbraco.providers/UsersMembershipProvider.cs index 64534abf3a..2f625095ad 100644 --- a/src/umbraco.providers/UsersMembershipProvider.cs +++ b/src/umbraco.providers/UsersMembershipProvider.cs @@ -460,7 +460,7 @@ namespace umbraco.providers return true; } - return user.ValidatePassword(EncryptOrHashExistingPassword(password)); + return CheckPassword(password, user.Password); } } return false; @@ -468,32 +468,7 @@ namespace umbraco.providers #endregion #region Helper Methods - /// - /// Checks the password. - /// - /// The password. - /// The dbPassword. - /// - internal bool CheckPassword(string password, string dbPassword) - { - string pass1 = password; - string pass2 = dbPassword; - - switch (PasswordFormat) - { - case MembershipPasswordFormat.Encrypted: - pass2 = DecodePassword(dbPassword); - break; - case MembershipPasswordFormat.Hashed: - pass1 = EncryptOrHashExistingPassword(password); - break; - default: - break; - } - return (pass1 == pass2) ? true : false; - } - - + /// /// Encodes the password. /// diff --git a/src/umbraco.providers/members/UmbracoMembershipProvider.cs b/src/umbraco.providers/members/UmbracoMembershipProvider.cs index fa3a70804e..cf4714496d 100644 --- a/src/umbraco.providers/members/UmbracoMembershipProvider.cs +++ b/src/umbraco.providers/members/UmbracoMembershipProvider.cs @@ -452,7 +452,7 @@ namespace umbraco.providers.members } } - var decodedPassword = DecodePassword(m.GetPassword()); + var decodedPassword = DecryptPassword(m.GetPassword()); return decodedPassword; } @@ -667,8 +667,11 @@ namespace umbraco.providers.members /// public override bool ValidateUser(string username, string password) { - var m = Member.GetMemberFromLoginAndEncodedPassword(username, EncryptOrHashExistingPassword(password)); - if (m != null) + var m = Member.GetMemberFromLoginName(username); + if (m == null) return false; + var authenticated = CheckPassword(password, m.GetPassword()); + + if (authenticated) { // check for lock status. If locked, then set the member property to null if (string.IsNullOrEmpty(LockPropertyTypeAlias) == false) @@ -824,32 +827,6 @@ namespace umbraco.providers.members #region Helper Methods - /// - /// Checks the password. - /// - /// The password. - /// The dbPassword. - /// - internal bool CheckPassword(string password, string dbPassword) - { - string pass1 = password; - string pass2 = dbPassword; - - switch (PasswordFormat) - { - case MembershipPasswordFormat.Encrypted: - pass2 = DecodePassword(dbPassword); - break; - case MembershipPasswordFormat.Hashed: - pass1 = EncryptOrHashExistingPassword(password); - break; - default: - break; - } - return (pass1 == pass2) ? true : false; - } - - /// /// Encodes the password. /// From 4d3f67885a058120f34988b7338cf30f969c08f1 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Sun, 29 Dec 2013 14:12:23 +0100 Subject: [PATCH 0142/1046] Fixes U4-1076 Apostrophes in document type description are shown preceded/escaped by a slash in the Create Node dialog --- .../umbraco.presentation/umbraco/create/content.ascx.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/create/content.ascx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/content.ascx.cs index 91b2a8c519..7b5ade8b0e 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/content.ascx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/create/content.ascx.cs @@ -41,7 +41,8 @@ namespace umbraco.cms.presentation.create.controls { string docDescription = "No description available..."; if (string.IsNullOrEmpty(dt.Description) == false) - docDescription = dt.Description; + docDescription = System.Web.HttpUtility.HtmlEncode(dt.Description); + docDescription = "" + dt.Text + "
    " + docDescription.Replace(Environment.NewLine, "
    "); docDescription = docDescription.Replace("'", "\\'"); From 32bc2f1d69d292e1a79466529558013119414c3e Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Sun, 29 Dec 2013 14:34:54 +0100 Subject: [PATCH 0143/1046] merge ismailmayat-UK-Festival --- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 1 + .../DataTypes/LegacyUploadFieldWorkaround.cs | 2 +- .../umbraco/controls/ContentControl.cs | 340 ++++++++++-------- .../businesslogic/Property/Property.cs | 18 +- .../businesslogic/datatype/DefaultData.cs | 7 +- 5 files changed, 206 insertions(+), 162 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index c0f093b70b..9cd5f47a95 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -279,6 +279,7 @@ You cannot split a cell that hasn't been merged. Error in XSLT source The XSLT has not been saved, because it contained error(s) + There is a configuration error with the data type used for this property, please check the data type About diff --git a/src/Umbraco.Web/Strategies/DataTypes/LegacyUploadFieldWorkaround.cs b/src/Umbraco.Web/Strategies/DataTypes/LegacyUploadFieldWorkaround.cs index 65aebcce0b..1de385caff 100644 --- a/src/Umbraco.Web/Strategies/DataTypes/LegacyUploadFieldWorkaround.cs +++ b/src/Umbraco.Web/Strategies/DataTypes/LegacyUploadFieldWorkaround.cs @@ -29,7 +29,7 @@ namespace Umbraco.Web.Strategies.DataTypes { if (UmbracoSettings.ImageAutoFillImageProperties != null) { - var property = sender.GenericProperties.FirstOrDefault(x => x.PropertyType.DataTypeDefinition.DataType.Id == new Guid(Constants.PropertyEditors.UploadField)); + var property = sender.GenericProperties.FirstOrDefault(x =>x.PropertyType.DataTypeDefinition.DataType!=null && x.PropertyType.DataTypeDefinition.DataType.Id == new Guid(Constants.PropertyEditors.UploadField)); if (property == null) return; diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentControl.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentControl.cs index 589682a18c..4431a89e7e 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentControl.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentControl.cs @@ -436,196 +436,224 @@ namespace umbraco.controls private void AddControlNew(Property p, TabPage tp, string cap) { IDataType dt = p.PropertyType.DataTypeDefinition.DataType; - dt.DataEditor.Editor.ID = string.Format("prop_{0}", p.PropertyType.Alias); - dt.Data.PropertyId = p.Id; - //Add the DataType to an internal dictionary, which will be used to call the save method on the IDataEditor - //and to retrieve the value from IData in editContent.aspx.cs, so that it can be set on the legacy Document class. - DataTypes.Add(p.PropertyType.Alias, dt); - - // check for buttons - IDataFieldWithButtons df1 = dt.DataEditor.Editor as IDataFieldWithButtons; - if (df1 != null) + //check that property editor has been set for the data type used by this property + if (dt != null) { - ((Control)df1).ID = p.PropertyType.Alias; + dt.DataEditor.Editor.ID = string.Format("prop_{0}", p.PropertyType.Alias); + + dt.Data.PropertyId = p.Id; + + //Add the DataType to an internal dictionary, which will be used to call the save method on the IDataEditor + //and to retrieve the value from IData in editContent.aspx.cs, so that it can be set on the legacy Document class. + DataTypes.Add(p.PropertyType.Alias, dt); + + // check for buttons + IDataFieldWithButtons df1 = dt.DataEditor.Editor as IDataFieldWithButtons; + if (df1 != null) + { + ((Control)df1).ID = p.PropertyType.Alias; - if (df1.MenuIcons.Length > 0) + if (df1.MenuIcons.Length > 0) + tp.Menu.InsertSplitter(); + + + // Add buttons + int c = 0; + bool atEditHtml = false; + bool atSplitter = false; + foreach (object o in df1.MenuIcons) + { + try + { + MenuIconI m = (MenuIconI)o; + MenuIconI mi = tp.Menu.NewIcon(); + mi.ImageURL = m.ImageURL; + mi.OnClickCommand = m.OnClickCommand; + mi.AltText = m.AltText; + mi.ID = tp.ID + "_" + m.ID; + + if (m.ID == "html") + atEditHtml = true; + else + atEditHtml = false; + + atSplitter = false; + } + catch + { + tp.Menu.InsertSplitter(); + atSplitter = true; + } + + // Testing custom styles in editor + if (atSplitter && atEditHtml && dt.DataEditor.TreatAsRichTextEditor) + { + DropDownList ddl = tp.Menu.NewDropDownList(); + + ddl.Style.Add("margin-bottom", "5px"); + ddl.Items.Add(ui.Text("buttons", "styleChoose", null)); + ddl.ID = tp.ID + "_editorStyle"; + if (StyleSheet.GetAll().Length > 0) + { + foreach (StyleSheet s in StyleSheet.GetAll()) + { + foreach (StylesheetProperty sp in s.Properties) + { + ddl.Items.Add(new ListItem(sp.Text, sp.Alias)); + } + } + } + ddl.Attributes.Add("onChange", "addStyle(this, '" + p.PropertyType.Alias + "');"); + atEditHtml = false; + } + c++; + } + } + + // check for element additions + IMenuElement menuElement = dt.DataEditor.Editor as IMenuElement; + if (menuElement != null) + { + // add separator tp.Menu.InsertSplitter(); + // add the element + tp.Menu.NewElement(menuElement.ElementName, menuElement.ElementIdPreFix + p.Id.ToString(), + menuElement.ElementClass, menuElement.ExtraMenuWidth); + } - // Add buttons - int c = 0; - bool atEditHtml = false; - bool atSplitter = false; - foreach (object o in df1.MenuIcons) + Pane pp = new Pane(); + Control holder = new Control(); + holder.Controls.Add(dt.DataEditor.Editor); + if (p.PropertyType.DataTypeDefinition.DataType.DataEditor.ShowLabel) + { + string caption = p.PropertyType.Name; + if (p.PropertyType.Description != null && p.PropertyType.Description != String.Empty) + switch (UmbracoSettings.PropertyContextHelpOption) + { + case "icon": + caption += " \"""; + break; + case "text": + caption += "
    " + umbraco.library.ReplaceLineBreaks(p.PropertyType.Description) + ""; + break; + } + pp.addProperty(caption, holder); + } + else + pp.addProperty(holder); + + // Validation + if (p.PropertyType.Mandatory) { try { - MenuIconI m = (MenuIconI)o; - MenuIconI mi = tp.Menu.NewIcon(); - mi.ImageURL = m.ImageURL; - mi.OnClickCommand = m.OnClickCommand; - mi.AltText = m.AltText; - mi.ID = tp.ID + "_" + m.ID; - - if (m.ID == "html") - atEditHtml = true; - else - atEditHtml = false; - - atSplitter = false; - } - catch - { - tp.Menu.InsertSplitter(); - atSplitter = true; - } - - // Testing custom styles in editor - if (atSplitter && atEditHtml && dt.DataEditor.TreatAsRichTextEditor) - { - DropDownList ddl = tp.Menu.NewDropDownList(); - - ddl.Style.Add("margin-bottom", "5px"); - ddl.Items.Add(ui.Text("buttons", "styleChoose", null)); - ddl.ID = tp.ID + "_editorStyle"; - if (StyleSheet.GetAll().Length > 0) + var rq = new RequiredFieldValidator { - foreach (StyleSheet s in StyleSheet.GetAll()) - { - foreach (StylesheetProperty sp in s.Properties) - { - ddl.Items.Add(new ListItem(sp.Text, sp.Alias)); - } - } + ControlToValidate = dt.DataEditor.Editor.ID, + CssClass = "error" + }; + rq.Style.Add(HtmlTextWriterStyle.Display, "block"); + rq.Style.Add(HtmlTextWriterStyle.Padding, "2px"); + var component = dt.DataEditor.Editor; // holder.FindControl(rq.ControlToValidate); + var attribute = (ValidationPropertyAttribute)TypeDescriptor.GetAttributes(component)[typeof(ValidationPropertyAttribute)]; + PropertyDescriptor pd = null; + if (attribute != null) + { + pd = TypeDescriptor.GetProperties(component, null)[attribute.Name]; + } + if (pd != null) + { + rq.EnableClientScript = false; + rq.Display = ValidatorDisplay.Dynamic; + string[] errorVars = { p.PropertyType.Name, cap }; + rq.ErrorMessage = ui.Text("errorHandling", "errorMandatory", errorVars, null) + "
    "; + holder.Controls.AddAt(0, rq); } - ddl.Attributes.Add("onChange", "addStyle(this, '" + p.PropertyType.Alias + "');"); - atEditHtml = false; } - c++; - } - } - - // check for element additions - IMenuElement menuElement = dt.DataEditor.Editor as IMenuElement; - if (menuElement != null) - { - // add separator - tp.Menu.InsertSplitter(); - - // add the element - tp.Menu.NewElement(menuElement.ElementName, menuElement.ElementIdPreFix + p.Id.ToString(), - menuElement.ElementClass, menuElement.ExtraMenuWidth); - } - - Pane pp = new Pane(); - Control holder = new Control(); - holder.Controls.Add(dt.DataEditor.Editor); - if (p.PropertyType.DataTypeDefinition.DataType.DataEditor.ShowLabel) - { - string caption = p.PropertyType.Name; - if (p.PropertyType.Description != null && p.PropertyType.Description != String.Empty) - switch (UmbracoSettings.PropertyContextHelpOption) + catch (Exception valE) { - case "icon": - caption += " \"""; - break; - case "text": - caption += "
    " + umbraco.library.ReplaceLineBreaks(p.PropertyType.Description) + ""; - break; + HttpContext.Current.Trace.Warn("contentControl", + "EditorControl (" + dt.DataTypeName + ") does not support validation", + valE); } - pp.addProperty(caption, holder); - } - else - pp.addProperty(holder); + } - // Validation - if (p.PropertyType.Mandatory) - { - try + // RegExp Validation + if (p.PropertyType.ValidationRegExp != "") { - var rq = new RequiredFieldValidator + try + { + var rv = new RegularExpressionValidator { ControlToValidate = dt.DataEditor.Editor.ID, CssClass = "error" }; - rq.Style.Add(HtmlTextWriterStyle.Display, "block"); - rq.Style.Add(HtmlTextWriterStyle.Padding, "2px"); - var component = dt.DataEditor.Editor; // holder.FindControl(rq.ControlToValidate); - var attribute = (ValidationPropertyAttribute)TypeDescriptor.GetAttributes(component)[typeof(ValidationPropertyAttribute)]; - PropertyDescriptor pd = null; - if (attribute != null) - { - pd = TypeDescriptor.GetProperties(component, null)[attribute.Name]; - } - if (pd != null) - { - rq.EnableClientScript = false; - rq.Display = ValidatorDisplay.Dynamic; - string[] errorVars = { p.PropertyType.Name, cap }; - rq.ErrorMessage = ui.Text("errorHandling", "errorMandatory", errorVars, null) + "
    "; - holder.Controls.AddAt(0, rq); - } - } - catch (Exception valE) - { - HttpContext.Current.Trace.Warn("contentControl", - "EditorControl (" + dt.DataTypeName + ") does not support validation", - valE); - } - } - - // RegExp Validation - if (p.PropertyType.ValidationRegExp != "") - { - try - { - var rv = new RegularExpressionValidator + rv.Style.Add(HtmlTextWriterStyle.Display, "block"); + rv.Style.Add(HtmlTextWriterStyle.Padding, "2px"); + var component = dt.DataEditor.Editor; // holder.FindControl(rq.ControlToValidate); + var attribute = (ValidationPropertyAttribute)TypeDescriptor.GetAttributes(component)[typeof(ValidationPropertyAttribute)]; + PropertyDescriptor pd = null; + if (attribute != null) { - ControlToValidate = dt.DataEditor.Editor.ID, - CssClass = "error" - }; - rv.Style.Add(HtmlTextWriterStyle.Display, "block"); - rv.Style.Add(HtmlTextWriterStyle.Padding, "2px"); - var component = dt.DataEditor.Editor; // holder.FindControl(rq.ControlToValidate); - var attribute = (ValidationPropertyAttribute)TypeDescriptor.GetAttributes(component)[typeof(ValidationPropertyAttribute)]; - PropertyDescriptor pd = null; - if (attribute != null) - { - pd = TypeDescriptor.GetProperties(component, null)[attribute.Name]; + pd = TypeDescriptor.GetProperties(component, null)[attribute.Name]; + } + if (pd != null) + { + rv.ValidationExpression = p.PropertyType.ValidationRegExp; + rv.EnableClientScript = false; + rv.Display = ValidatorDisplay.Dynamic; + string[] errorVars = { p.PropertyType.Name, cap }; + rv.ErrorMessage = ui.Text("errorHandling", "errorRegExp", errorVars, null) + "
    "; + holder.Controls.AddAt(0, rv); + } } - if (pd != null) + catch (Exception valE) { - rv.ValidationExpression = p.PropertyType.ValidationRegExp; - rv.EnableClientScript = false; - rv.Display = ValidatorDisplay.Dynamic; - string[] errorVars = { p.PropertyType.Name, cap }; - rv.ErrorMessage = ui.Text("errorHandling", "errorRegExp", errorVars, null) + "
    "; - holder.Controls.AddAt(0, rv); + HttpContext.Current.Trace.Warn("contentControl", + "EditorControl (" + dt.DataTypeName + ") does not support validation", + valE); } } - catch (Exception valE) - { - HttpContext.Current.Trace.Warn("contentControl", - "EditorControl (" + dt.DataTypeName + ") does not support validation", - valE); - } - } - // This is once again a nasty nasty hack to fix gui when rendering wysiwygeditor - if (dt.DataEditor.TreatAsRichTextEditor) - { - tp.Controls.Add(dt.DataEditor.Editor); + // This is once again a nasty nasty hack to fix gui when rendering wysiwygeditor + if (dt.DataEditor.TreatAsRichTextEditor) + { + tp.Controls.Add(dt.DataEditor.Editor); + } + else + { + Panel ph = new Panel(); + ph.Attributes.Add("style", "padding: 0; position: relative;"); // NH 4.7.1, latest styles added to support CP item: 30363 + ph.Controls.Add(pp); + + tp.Controls.Add(ph); + } } else { - Panel ph = new Panel(); - ph.Attributes.Add("style", "padding: 0; position: relative;"); // NH 4.7.1, latest styles added to support CP item: 30363 + + var ph = new Panel(); + + var pp = new Pane(); + + var missingPropertyEditorLabel = new Literal + { + Text = ui.Text("errors", "missingPropertyEditorErrorMessage") + }; + + pp.addProperty(p.PropertyType.Name, missingPropertyEditorLabel); + + ph.Attributes.Add("style", "padding: 0; position: relative;"); + ph.Controls.Add(pp); tp.Controls.Add(ph); } + + } public enum publishModes diff --git a/src/umbraco.cms/businesslogic/Property/Property.cs b/src/umbraco.cms/businesslogic/Property/Property.cs index d34f234d5a..47e16b8769 100644 --- a/src/umbraco.cms/businesslogic/Property/Property.cs +++ b/src/umbraco.cms/businesslogic/Property/Property.cs @@ -1,5 +1,7 @@ using System; +using System.Reflection.Emit; using System.Runtime.CompilerServices; +using System.Web.UI; using System.Xml; using Umbraco.Core; using Umbraco.Core.Persistence; @@ -64,9 +66,19 @@ namespace umbraco.cms.businesslogic.property //Just to ensure that there is a PropertyType available _pt = PropertyType.GetPropertyType(property.PropertyTypeId); - if (_pt.DataTypeDefinition.DataType == null) - throw new Exception(string.Format("Could not load datatype '{0}'", _pt.DataTypeDefinition.Text)); - _data = _pt.DataTypeDefinition.DataType.Data; + + //ensure we have data property editor set + if (_pt.DataTypeDefinition.DataType != null) + { + _data = _pt.DataTypeDefinition.DataType.Data; + } + else + { + //send back null we will handle it in ContentControl AddControlNew + //and display to use message from the dictionary errors section + _data= new DefaultData(null); + } + _data.PropertyId = Id; //set the value so it doesn't need to go to the database diff --git a/src/umbraco.cms/businesslogic/datatype/DefaultData.cs b/src/umbraco.cms/businesslogic/datatype/DefaultData.cs index 16be25827c..feb7783187 100644 --- a/src/umbraco.cms/businesslogic/datatype/DefaultData.cs +++ b/src/umbraco.cms/businesslogic/datatype/DefaultData.cs @@ -81,7 +81,10 @@ namespace umbraco.cms.businesslogic.datatype //instead of making it query for itself. This is a peformance optimization enhancement. var dbType = BaseDataType.GetDBType(strDbType); var fieldName = BaseDataType.GetDataFieldName(dbType); - _dataType.SetDataTypeProperties(fieldName, dbType); + + //if misconfigured (datatype created in the tree, but save button never clicked), the datatype will be null + if(_dataType != null) + _dataType.SetDataTypeProperties(fieldName, dbType); //ensures that it doesn't go back to the db _valueLoaded = true; @@ -102,7 +105,7 @@ namespace umbraco.cms.businesslogic.datatype .Where(x => x.Id == _propertyId); var dto = Database.Fetch(sql).FirstOrDefault(); - if (dto != null) + if (dto != null && _dataType != null) { //the type stored in the cmsDataType table var strDbType = dto.PropertyTypeDto.DataTypeDto.DbType; From a49058fe00695c8c50e62c8821b989e0fc55fb66 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Sun, 29 Dec 2013 15:07:28 +0100 Subject: [PATCH 0144/1046] Fixes U4-510 Copy node makes multiple copies --- src/Umbraco.Web.UI/umbraco/dialogs/moveOrCopy.aspx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/umbraco/dialogs/moveOrCopy.aspx b/src/Umbraco.Web.UI/umbraco/dialogs/moveOrCopy.aspx index 43f17f096b..7323eb0ca1 100644 --- a/src/Umbraco.Web.UI/umbraco/dialogs/moveOrCopy.aspx +++ b/src/Umbraco.Web.UI/umbraco/dialogs/moveOrCopy.aspx @@ -83,7 +83,7 @@

    - +   <%=umbraco.ui.Text("general", "or", UmbracoUser)%>   <%=umbraco.ui.Text("general", "cancel", UmbracoUser)%> From fc0c6a1cd80cb22c661aa5c46754be18ef8f9d9d Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 31 Dec 2013 13:27:47 +1100 Subject: [PATCH 0145/1046] fixes stored password check with legacy encoding. --- src/Umbraco.Core/Security/MembershipProviderBase.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Umbraco.Core/Security/MembershipProviderBase.cs b/src/Umbraco.Core/Security/MembershipProviderBase.cs index 5480b8220c..901bec808b 100644 --- a/src/Umbraco.Core/Security/MembershipProviderBase.cs +++ b/src/Umbraco.Core/Security/MembershipProviderBase.cs @@ -748,6 +748,12 @@ namespace Umbraco.Core.Security /// internal string StoredPassword(string storedString, out string salt) { + if (UseLegacyEncoding) + { + salt = string.Empty; + return storedString; + } + switch (PasswordFormat) { case MembershipPasswordFormat.Hashed: From 17883358b6869bbf58e99cbb7fdbd2108cda214d Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 2 Jan 2014 14:13:43 +1100 Subject: [PATCH 0146/1046] Fixes some issues with the User membership provider, updates user create dialog to also have an email address (which is required by membership), adds validation to the member and user create dialogs to ensure that the email address is validated to be a correcty formatted address. --- .../Security/MembershipProviderBase.cs | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 8 + .../umbraco/config/create/UI.xml | 2 +- src/Umbraco.Web.UI/umbraco/create/User.ascx | 35 +++++ .../umbraco/create/User.ascx.cs | 84 +++++++++++ .../umbraco/create/User.ascx.designer.cs | 105 +++++++++++++ src/Umbraco.Web.UI/umbraco/create/member.ascx | 11 +- .../Application/UmbracoApplicationActions.js | 142 ++++++++++-------- .../umbraco/create/member.ascx.cs | 27 +--- .../umbraco/create/member.ascx.designer.cs | 11 +- .../umbraco/create/userTasks.cs | 19 ++- .../UsersMembershipProvider.cs | 16 +- 12 files changed, 358 insertions(+), 104 deletions(-) create mode 100644 src/Umbraco.Web.UI/umbraco/create/User.ascx create mode 100644 src/Umbraco.Web.UI/umbraco/create/User.ascx.cs create mode 100644 src/Umbraco.Web.UI/umbraco/create/User.ascx.designer.cs diff --git a/src/Umbraco.Core/Security/MembershipProviderBase.cs b/src/Umbraco.Core/Security/MembershipProviderBase.cs index 901bec808b..71fb729b78 100644 --- a/src/Umbraco.Core/Security/MembershipProviderBase.cs +++ b/src/Umbraco.Core/Security/MembershipProviderBase.cs @@ -605,7 +605,7 @@ namespace Umbraco.Core.Security return pass; } - protected bool IsEmailValid(string email) + internal static bool IsEmailValid(string email) { const string pattern = @"^(?!\.)(""([^""\r\\]|\\[""\r\\])*""|" + @"([-a-z0-9!#$%&'*+/=?^_`{|}~]|(? PartialViewMacro.ascx + + User.ascx + ASPXCodeBehind + + + User.ascx + ExamineManagement.ascx ASPXCodeBehind @@ -589,6 +596,7 @@ + diff --git a/src/Umbraco.Web.UI/umbraco/config/create/UI.xml b/src/Umbraco.Web.UI/umbraco/config/create/UI.xml index 88105d0ad2..3f063c134a 100644 --- a/src/Umbraco.Web.UI/umbraco/config/create/UI.xml +++ b/src/Umbraco.Web.UI/umbraco/config/create/UI.xml @@ -70,7 +70,7 @@

    User
    - /create/simple.ascx + /create/user.ascx diff --git a/src/Umbraco.Web.UI/umbraco/create/User.ascx b/src/Umbraco.Web.UI/umbraco/create/User.ascx new file mode 100644 index 0000000000..deb139c557 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco/create/User.ascx @@ -0,0 +1,35 @@ +<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="User.ascx.cs" Inherits="Umbraco.Web.UI.Umbraco.Create.User" %> +<%@ Import Namespace="umbraco" %> + +

    + Login Name: + * + +
    + +

    +

    + E-mail: + * + + +
    + +

    + +