From adae4325c9735c0b10c3b40f916ea2f42b7a574e Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 24 Oct 2018 19:07:35 +1100 Subject: [PATCH 01/10] Removes old legacy files --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 5 - .../Umbraco/webservices/CheckForUpgrade.asmx | 1 - .../Umbraco/webservices/Developer.asmx | 1 - .../Umbraco/webservices/ajax.js | 805 ------------------ .../Umbraco/webservices/codeEditorSave.asmx | 1 - .../Umbraco/webservices/legacyAjaxCalls.asmx | 1 - .../Umbraco/webservices/nodeSorter.asmx | 1 - .../Umbraco/webservices/templates.asmx | 1 - src/Umbraco.Web/Umbraco.Web.csproj | 19 - .../umbraco/webservices/CheckForUpgrade.asmx | 1 - .../webservices/CheckForUpgrade.asmx.cs | 109 --- .../UmbracoAuthorizedWebService.cs | 97 --- .../umbraco/webservices/ajaxHelpers.cs | 25 - .../umbraco/webservices/legacyAjaxCalls.asmx | 1 - .../webservices/legacyAjaxCalls.asmx.cs | 141 --- .../umbraco/webservices/nodeSorter.asmx | 1 - .../umbraco/webservices/nodeSorter.asmx.cs | 262 ------ 17 files changed, 1472 deletions(-) delete mode 100644 src/Umbraco.Web.UI/Umbraco/webservices/CheckForUpgrade.asmx delete mode 100644 src/Umbraco.Web.UI/Umbraco/webservices/Developer.asmx delete mode 100644 src/Umbraco.Web.UI/Umbraco/webservices/ajax.js delete mode 100644 src/Umbraco.Web.UI/Umbraco/webservices/codeEditorSave.asmx delete mode 100644 src/Umbraco.Web.UI/Umbraco/webservices/legacyAjaxCalls.asmx delete mode 100644 src/Umbraco.Web.UI/Umbraco/webservices/nodeSorter.asmx delete mode 100644 src/Umbraco.Web.UI/Umbraco/webservices/templates.asmx delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CheckForUpgrade.asmx delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CheckForUpgrade.asmx.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/webservices/UmbracoAuthorizedWebService.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/webservices/ajaxHelpers.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/webservices/legacyAjaxCalls.asmx delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/webservices/legacyAjaxCalls.asmx.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx.cs diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index de52021220..d95d8ca664 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -424,7 +424,6 @@ - @@ -434,10 +433,6 @@ Form - - - - Designer diff --git a/src/Umbraco.Web.UI/Umbraco/webservices/CheckForUpgrade.asmx b/src/Umbraco.Web.UI/Umbraco/webservices/CheckForUpgrade.asmx deleted file mode 100644 index 226022e0fd..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/webservices/CheckForUpgrade.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService Language="C#" CodeBehind="CheckForUpgrade.asmx.cs" Class="umbraco.presentation.webservices.CheckForUpgrade" %> diff --git a/src/Umbraco.Web.UI/Umbraco/webservices/Developer.asmx b/src/Umbraco.Web.UI/Umbraco/webservices/Developer.asmx deleted file mode 100644 index b98e33ef54..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/webservices/Developer.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService Language="c#" Codebehind="Developer.asmx.cs" Class="umbraco.webservices.Developer" %> diff --git a/src/Umbraco.Web.UI/Umbraco/webservices/ajax.js b/src/Umbraco.Web.UI/Umbraco/webservices/ajax.js deleted file mode 100644 index 868ccf6650..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/webservices/ajax.js +++ /dev/null @@ -1,805 +0,0 @@ -// ajax.js -// Common Javascript methods and global objects -// Ajax framework for Internet Explorer (6.0, ...) and Firefox (1.0, ...) -// Copyright by Matthias Hertel, http://www.mathertel.de -// This work is licensed under a Creative Commons Attribution 2.0 Germany License. -// See http://creativecommons.org/licenses/by/2.0/de/ -// More information on: http://ajaxaspects.blogspot.com/ and http://ajaxaspekte.blogspot.com/ -// ----- -// 05.06.2005 created by Matthias Hertel. -// 19.06.2005 minor corrections to webservices. -// 25.06.2005 ajax action queue and timing. -// 02.07.2005 queue up actions fixed. -// 10.07.2005 ajax.timeout -// 10.07.2005 a option object that is passed from ajax.Start() to prepare() is also queued. -// 10.07.2005 a option object that is passed from ajax.Start() to prepare(), finish() -// and onException() is also queued. -// 12.07.2005 correct xml encoding when CallSoap() -// 20.07.2005 more datatypes and XML Documents -// 20.07.2005 more datatypes and XML Documents fixed -// 06.08.2005 caching implemented. -// 07.08.2005 bugs fixed, when queuing without a delay time. -// 04.09.2005 bugs fixed, when entering non-multiple actions. -// 07.09.2005 proxies.IsActive added -// 27.09.2005 fixed error in handling bool as a datatype -// 13.12.2005 WebServices with arrays on strings, ints, floats and booleans - still undocumented -// 27.12.2005 fixed: empty string return values enabled. -// 27.12.2005 enable the late binding of proxy methods. -// 21.01.2006 void return bug fixed. -// 18.02.2006 typo: Finsh -> Finish. -// 25.02.2006 better xmlhttp request object retrieval, see http://blogs.msdn.com/ie/archive/2006/01/23/516393.aspx -// 22.04.2006 progress indicator added. -// 28.01.2006 void return bug fixed again? -// 09.03.2006 enable late binding of prepare and finish methods by using an expression. -// 14.07.2006 Safari Browser Version 2.03/Mac OS X 10.4. compatibility: xNode.textContent || xNode.innerText || xNode.text || xNode.childNodes[0].nodeValue -// 10.08.2006 date to xml format fixed by Kars Veling -// 16.09.2006 .postUrl - -// ----- global variable for the proxies to webservices. ----- - -/// The root object for the proxies to webservices. -var proxies = new Object(); - -proxies.current = null; // the current active webservice call. -proxies.xmlhttp = null; // The current active xmlhttp object. - - -// ----- global variable for the ajax engine. ----- - -/// The root object for the ajax engine. -var ajax = new Object(); - -ajax.current = null; /// The current active AJAX action. -ajax.option = null; /// The options for the current active AJAX action. - -ajax.queue = new Array(); /// The pending AJAX actions. -ajax.options = new Array(); /// The options for the pending AJAX actions. - -ajax.timer = null; /// The timer for delayed actions. - -ajax.progress = false; /// show a progress indicator -ajax.progressTimer = null; /// a timer-object that help displaying the progress indicator not too often. - -// ----- AJAX engine and actions implementation ----- - -///Start an AJAX action by entering it into the queue -ajax.Start = function (action, options) { - ajax.Add(action, options); - // check if the action should start - if ((ajax.current == null) && (ajax.timer == null)) - ajax._next(false); -} // ajax.Start - - -///Start an AJAX action by entering it into the queue -ajax.Add = function (action, options) { - if (action == null) { - alert("ajax.Start: Argument action must be set."); - return; - } // if - - // enable the late binding of the methods by using a string that is evaluated. - if (typeof(action.call) == "string") action.call = eval(action.call); - if (typeof(action.prepare) == "string") action.prepare = eval(action.prepare); - if (typeof(action.finish) == "string") action.finish = eval(action.finish); - - if ((action.queueClear != null) && (action.queueClear == true)) { - ajax.queue = new Array(); - ajax.options = new Array(); - - } else if ((ajax.queue.length > 0) && ((action.queueMultiple == null) || (action.queueMultiple == false))) { - // remove existing action entries from the queue and clear a running timer - if ((ajax.timer != null) && (ajax.queue[0] == action)) { - window.clearTimeout(ajax.timer); - ajax.timer = null; - } // if - - var n = 0; - while (n < ajax.queue.length) { - if (ajax.queue[n] == action) { - ajax.queue.splice(n, 1); - ajax.options.splice(n, 1); - } else { - n++; - } // if - } // while - } // if - - if ((action.queueTop == null) || (action.queueTop == false)) { - // to the end. - ajax.queue.push(action); - ajax.options.push(options); - - } else { - // to the top - ajax.queue.unshift(action); - ajax.options.unshift(options); - } // if -} // ajax.Add - - -///Check, if the next AJAX action can start. -///This is an internal method that should not be called from external. -///for private use only. -ajax._next = function (forceStart) { - var ca = null // current action - var co = null // current opptions - var data = null; - - if (ajax.current != null) - return; // a call is active: wait more time - - if (ajax.timer != null) - return; // a call is pendig: wait more time - - if (ajax.queue.length == 0) - return; // nothing to do. - - ca = ajax.queue[0]; - co = ajax.options[0]; - if ((forceStart == true) || (ca.delay == null) || (ca.delay == 0)) { - // start top action - ajax.current = ca; - ajax.queue.shift(); - ajax.option = co; - ajax.options.shift(); - - // get the data - if (ca.prepare != null) - try { - data = ca.prepare(co); - } catch (ex) { } - - if (ca.call != null) { - ajax.StartProgress(); - - // start the call - ca.call.func = ajax.Finish; - ca.call.onException = ajax.Exception; - ca.call(data); - // start timeout timer - if (ca.timeout != null) - ajax.timer = window.setTimeout(ajax.Cancel, ca.timeout * 1000); - - } else if (ca.postUrl != null) { - // post raw data to URL - - } else { - // no call - ajax.Finish(data); - } // if - - } else { - // start a timer and wait - ajax.timer = window.setTimeout(ajax.EndWait, ca.delay); - } // if -} // ajax._next - - -///The delay time of an action is over. -ajax.EndWait = function() { - ajax.timer = null; - ajax._next(true); -} // ajax.EndWait - - -///The current action timed out. -ajax.Cancel = function() { - proxies.cancel(false); // cancel the current webservice call. - ajax.timer = null; - ajax.current = null; - ajax.option = null; - ajax.EndProgress(); - window.setTimeout(ajax._next, 200); // give some to time to cancel the http connection. -} // ajax.Cancel - - -///Finish an AJAX Action the normal way -ajax.Finish = function (data) { - // clear timeout timer if set - if (ajax.timer != null) { - window.clearTimeout(ajax.timer); - ajax.timer = null; - } // if - - // use the data - try { - if ((ajax.current != null) && (ajax.current.finish != null)) - ajax.current.finish(data, ajax.option); - } catch (ex) { } - // reset the running action - ajax.current = null; - ajax.option = null; - ajax.EndProgress(); - ajax._next(false) -} // ajax.Finish - - -///Finish an AJAX Action with an exception -ajax.Exception = function (ex) { - // use the data - if (ajax.current.onException != null) - ajax.current.onException(ex, ajax.option); - - // reset the running action - ajax.current = null; - ajax.option = null; - ajax.EndProgress(); -} // ajax.Exception - - -///Clear the current and all pending AJAX actions. -ajax.CancelAll = function () { - ajax.Cancel(); - // clear all pending AJAX actions in the queue. - ajax.queue = new Array(); - ajax.options = new Array(); -} // ajax.CancelAll - - -// ----- show or hide a progress indicator ----- - -// show a progress indicator if it takes longer... -ajax.StartProgress = function() { - ajax.progress = true; - if (ajax.progressTimer != null) - window.clearTimeout(ajax.progressTimer); - ajax.progressTimer = window.setTimeout(ajax.ShowProgress, 220); -} // ajax.StartProgress - - -// hide any progress indicator soon. -ajax.EndProgress = function () { - ajax.progress = false; - if (ajax.progressTimer != null) - window.clearTimeout(ajax.progressTimer); - ajax.progressTimer = window.setTimeout(ajax.ShowProgress, 20); -} // ajax.EndProgress - - -// this function is called by a timer to show or hide a progress indicator -ajax.ShowProgress = function() { - ajax.progressTimer = null; - var a = document.getElementById("AjaxProgressIndicator"); - - if (ajax.progress && (a != null)) { - // just display the existing object - a.style.top = document.documentElement.scrollTop + 2 + "px"; - a.style.display = ""; - - } else if (ajax.progress) { - - // find a relative link to the ajaxcore folder containing ajax.js - var path = "../ajaxcore/" - for (var n in document.scripts) { - s = document.scripts[n].src; - if ((s != null) && (s.length >= 7) && (s.substr(s.length -7).toLowerCase() == "ajax.js")) - path = s.substr(0,s.length -7); - } // for - - // create new standard progress object - a = document.createElement("div"); - a.id = "AjaxProgressIndicator"; - a.style.position = "absolute"; - a.style.right = "2px"; - a.style.top = document.documentElement.scrollTop + 2 + "px"; - a.style.width = "98px"; - a.style.height = "16px" - a.style.padding = "2px"; - a.style.verticalAlign = "bottom"; - a.style.backgroundColor="#51c77d"; - - a.innerHTML = " please wait..."; - document.body.appendChild(a); - - } else if (a) { - a.style.display="none"; - } // if -} // ajax.ShowProgress - - -// ----- simple http-POST server call ----- - -ajax.postData = function (url, data, func) { - var x = proxies._getXHR(); - - // enable cookieless sessions: - var cs = document.location.href.match(/\/\(.*\)\//); - if (cs != null) { - url = url.split('/'); - url[3] += cs[0].substr(0, cs[0].length-1); - url = url.join('/'); - } // if - - x.open("POST", url, (func != null)); - - if (func != null) { - // async call with xmlhttp-object as parameter - x.onreadystatechange = func; - x.send(data); - - } else { - // sync call - x.send(soap); - return(x.responseText); - } // if -} // ajax.postData - - -///Execute a soap call. -///Build the xml for the call of a soap method of a webservice -///and post it to the server. -proxies.callSoap = function (args) { - var p = args.callee; - var x = null; - - // check for existing cache-entry - if (p._cache != null) { - if ((p.params.length == 1) && (args.length == 1) && (p._cache[args[0]] != null)) { - if (p.func != null) { - p.func(p._cache[args[0]]); - return(null); - } else { - return(p._cache[args[0]]); - } // if - } else { - p._cachekey = args[0]; - }// if - } // if - - proxies.current = p; - x = proxies._getXHR(); - proxies.xmlhttp = x; - - // envelope start - var soap = "" - + "" - + "" - + "<" + p.fname + " xmlns='" + p.service.ns + "'>"; - - // parameters - for (n = 0; (n < p.params.length) && (n < args.length); n++) { - var val = args[n]; - var typ = p.params[n].split(':'); - - if ((typ.length == 1) || (typ[1] == "string")) { - val = String(args[n]).replace(/&/g, "&").replace(//g, ">"); - - } else if (typ[1] == "int") { - val = parseInt(args[n]); - } else if (typ[1] == "float") { - val = parseFloat(args[n]); - - } else if ((typ[1] == "x") && (typeof(args[n]) == "string")) { - val = args[n]; - - } else if ((typ[1] == "x") && (typeof(XMLSerializer) != "undefined")) { - val = (new XMLSerializer()).serializeToString(args[n].firstChild); - - } else if (typ[1] == "x") { - val = args[n].xml; - - } else if ((typ[1] == "bool") && (typeof(args[n]) == "string")) { - val = args[n].toLowerCase(); - - } else if (typ[1] == "bool") { - val = String(args[n]).toLowerCase(); - - } else if (typ[1] == "date") { - // calculate the xml format for datetime objects from a javascript date object - var s, ret; - ret = String(val.getFullYear()); - ret += "-"; - s = String(val.getMonth() + 1); - ret += (s.length == 1 ? "0" + s : s); - ret += "-"; - s = String(val.getDate()); - ret += (s.length == 1 ? "0" + s : s); - ret += "T"; - s = String(val.getHours()); - ret += (s.length == 1 ? "0" + s : s); - ret += ":"; - s = String(val.getMinutes()); - ret += (s.length == 1 ? "0" + s : s); - ret += ":"; - s = String(val.getSeconds()); - ret += (s.length == 1 ? "0" + s : s); - val = ret; - - } else if (typ[1] == "s[]") { - val = "" + args[n].join("") + ""; - - } else if (typ[1] == "int[]") { - val = "" + args[n].join("") + ""; - - } else if (typ[1] == "float[]") { - val = "" + args[n].join("") + ""; - - } else if (typ[1] == "bool[]") { - val = "" + args[n].join("") + ""; - - } // if - soap += "<" + typ[0] + ">" + val + "" - } // for - - // envelope end - soap += "" - + "" - + ""; - - // enable cookieless sessions: - var u = p.service.url; - var cs = document.location.href.match(/\/\(.*\)\//); - if (cs != null) { - u = p.service.url.split('/'); - u[3] += cs[0].substr(0, cs[0].length-1); - u = u.join('/'); - } // if - - x.open("POST", u, (p.func != null)); - x.setRequestHeader("SOAPAction", p.action); - x.setRequestHeader("Content-Type", "text/xml; charset=utf-8"); - - if (p.corefunc != null) { - // async call with xmlhttp-object as parameter - x.onreadystatechange = p.corefunc; - x.send(soap); - - } else if (p.func != null) { - // async call - x.onreadystatechange = proxies._response; - x.send(soap); - - } else { - // sync call - x.send(soap); - return(proxies._response()); - } // if -} // proxies.callSoap - - -// cancel the running webservice call. -// raise: set raise to false to prevent raising an exception -proxies.cancel = function(raise) { - var cc = proxies.current; - var cx = proxies.xmlhttp; - - if (raise == null) raise == true; - - if (proxies.xmlhttp != null) { - proxies.xmlhttp.onreadystatechange = function() { }; - proxies.xmlhttp.abort(); - if (raise && (proxies.current.onException != null)) - proxies.current.onException("WebService call was canceled.") - proxies.current = null; - proxies.xmlhttp = null; - } // if -} // proxies.cancel - - -// px is a proxies.service.func object ! -proxies.EnableCache = function (px) { - // attach an empty _cache object. - px._cache = new Object(); -} // proxies.EnableCache - - -// check, if a call is currently waiting for a result -proxies.IsActive = function () { - return(proxies.xmlhttp != null); -} // proxies.IsActive - - -///Callback method for a webservice call that dispatches the response to servive.func or service.onException. -///for private use only. -proxies._response = function () { - var ret = null; - var x = proxies.xmlhttp; - var cc = proxies.current; - var rtype = null; - - if ((cc.rtype.length > 0) && (cc.rtype[0] != null)) - rtype = cc.rtype[0].split(':'); - - if ((x != null) && (x.readyState == 4)) { - if (x.status == 200) { - var xNode = null; - - if (rtype != null) - xNode = x.responseXML.getElementsByTagName(rtype[0])[0]; - - if (xNode == null) { - ret = null; - - } else if (xNode.firstChild == null) { // 27.12.2005: empty string return values - ret = ((rtype.length == 1) || (rtype[1] == "string") ? "" : null); - - } else if ((rtype.length == 1) || (rtype[1] == "string")) { - ret = xNode.textContent || xNode.innerText || xNode.text || xNode.childNodes[0].nodeValue; - - } else if (rtype[1] == "bool") { - ret = xNode.textContent || xNode.innerText || xNode.text || xNode.childNodes[0].nodeValue; - ret = (ret == "true"); - - } else if (rtype[1] == "int") { - ret = xNode.textContent || xNode.innerText || xNode.text || xNode.childNodes[0].nodeValue; - ret = parseInt(ret); - - } else if (rtype[1] == "float") { - ret = xNode.textContent || xNode.innerText || xNode.text || xNode.childNodes[0].nodeValue; - ret = parseFloat(ret); - - } else if ((rtype[1] == "x") && (typeof(XMLSerializer) != "undefined")) { - ret = (new XMLSerializer()).serializeToString(xNode.firstChild); - ret = ajax._getXMLDOM(ret); - - } else if ((rtype[1] == "ds") && (typeof(XMLSerializer) != "undefined")) { - // ret = (new XMLSerializer()).serializeToString(xNode.firstChild.nextSibling.firstChild); - ret = (new XMLSerializer()).serializeToString(xNode); - ret = ajax._getXMLDOM(ret); - - } else if (rtype[1] == "x") { - ret = xNode.firstChild.xml; - ret = ajax._getXMLDOM(ret); - - } else if (rtype[1] == "ds") { -// ret = xNode.firstChild.nextSibling.firstChild.xml; - ret = xNode.xml; - ret = ajax._getXMLDOM(ret); - - } else if (rtype[1] == "s[]") { - // Array of strings - ret = new Array(); - xNode = xNode.firstChild; - while (xNode != null) { - ret.push(xNode.textContent || xNode.innerText || xNode.text || xNode.childNodes[0].nodeValue); - xNode = xNode.nextSibling; - } // while - - } else if (rtype[1] == "int[]") { - // Array of int - ret = new Array(); - xNode = xNode.firstChild; - while (xNode != null) { - ret.push(parseInt(xNode.textContent || xNode.innerText || xNode.text || xNode.childNodes[0].nodeValue)); - xNode = xNode.nextSibling; - } // while - - } else if (rtype[1] == "float[]") { - // Array of float - ret = new Array(); - xNode = xNode.firstChild; - while (xNode != null) { - ret.push(parseFloat(xNode.textContent || xNode.innerText || xNode.text || xNode.childNodes[0].nodeValue)); - xNode = xNode.nextSibling; - } // while - - } else if (rtype[1] == "bool[]") { - // Array of bool - ret = new Array(); - xNode = xNode.firstChild; - while (xNode != null) { - ret.push((xNode.textContent || xNode.innerText || xNode.text || xNode.childNodes[0].nodeValue).toLowerCase() == "true"); - xNode = xNode.nextSibling; - } // while - - } else { - ret = xNode.textContent || xNode.innerText || xNode.text || xNode.childNodes[0].nodeValue; - } // if - - // store to _cache - if ((cc._cache != null) && (cc._cachekey != null)) { - cc._cache[cc._cachekey] = ret; - cc._cachekey = null; - } // if - - proxies.xmlhttp = null; - proxies.current = null; - - if (cc.func == null) { - return(ret); // sync - } else { - cc.func(ret); // async - return(null); - } // if - - } else if (proxies.current.onException == null) { - // no exception - - } else { - // raise an exception - ret = new Error(); - - if (x.status == 404) { - ret.message = "The webservice could not be found."; - - } else if (x.status == 500) { - ret.name = "SoapException"; - var n = x.responseXML.documentElement.firstChild.firstChild.firstChild; - while (n != null) { - if (n.nodeName == "faultcode") ret.message = n.firstChild.nodeValue; - if (n.nodeName == "faultstring") ret.description = n.firstChild.nodeValue; - n = n.nextSibling; - } // while - - } else if ((x.status == 502) || (x.status == 12031)) { - ret.message = "The server could not be found."; - - } else { - // no classified response. - ret.message = "Result-Status:" + x.status + "\n" + x.responseText; - } // if - proxies.current.onException(ret); - } // if - - proxies.xmlhttp = null; - proxies.current = null; - } // if -} // proxies._response - - -///Callback method to show the result of a soap call in an alert box. -///To set up a debug output in an alert box use: -///proxies.service.method.corefunc = proxies.alertResult; -proxies.alertResult = function () { - var x = proxies.xmlhttp; - - if (x.readyState == 4) { - if (x.status == 200) { - if (x.responseXML.documentElement.firstChild.firstChild.firstChild == null) - alert("(no result)"); - else - alert(x.responseXML.documentElement.firstChild.firstChild.firstChild.firstChild.nodeValue); - - } else if (x.status == 404) { alert("Error!\n\nThe webservice could not be found."); - - } else if (x.status == 500) { - // a SoapException - var ex = new Error(); - ex.name = "SoapException"; - var n = x.responseXML.documentElement.firstChild.firstChild.firstChild; - while (n != null) { - if (n.nodeName == "faultcode") ex.message = n.firstChild.nodeValue; - if (n.nodeName == "faultstring") ex.description = n.firstChild.nodeValue; - n = n.nextSibling; - } // while - alert("The server threw an exception.\n\n" + ex.message + "\n\n" + ex.description); - - } else if (x.status == 502) { alert("Error!\n\nThe server could not be found."); - - } else { - // no classified response. - alert("Result-Status:" + x.status + "\n" + x.responseText); - } // if - - proxies.xmlhttp = null; - proxies.current = null; - } // if -} // proxies.alertResult - - -///Show all the details of the returned data of a webservice call. -///Use this method for debugging transmission problems. -///To set up a debug output in an alert box use: -///proxies.service.method.corefunc = proxies.alertResponseText; -proxies.alertResponseText = function () { - if (proxies.xmlhttp.readyState == 4) - alert("Status:" + proxies.xmlhttp.status + "\nRESULT:" + proxies.xmlhttp.responseText); -} // proxies.alertResponseText - - -///show the details about an exception. -proxies.alertException = function(ex) { - var s = "Exception:\n\n"; - - if (ex.constructor == String) { - s = ex; - } else { - if ((ex.name != null) && (ex.name != "")) - s += "Type: " + ex.name + "\n\n"; - - if ((ex.message != null) && (ex.message != "")) - s += "Message:\n" + ex.message + "\n\n"; - - if ((ex.description != null) && (ex.description != "") && (ex.message != ex.description)) - s += "Description:\n" + ex.description + "\n\n"; - } // if - alert(s); -} // proxies.alertException - - -///Get a browser specific implementation of the XMLHttpRequest object. -// from http://blogs.msdn.com/ie/archive/2006/01/23/516393.aspx -proxies._getXHR = function () { - var x = null; - if (window.XMLHttpRequest) { - // if IE7, Mozilla, Safari, etc: Use native object - x = new XMLHttpRequest() - - } else if (window.ActiveXObject) { - // ...otherwise, use the ActiveX control for IE5.x and IE6 - try { x = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { } - if (x == null) - try { x = new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) { } - } // if - return(x); -} // proxies._getXHR - - -///Get a browser specific implementation of the XMLDOM object, containing a XML document. -///the xml document as string. -ajax._getXMLDOM = function (xmlText) { - var obj = null; - - if ((document.implementation != null) && (typeof document.implementation.createDocument == "function")) { - // Gecko / Mozilla / Firefox - var parser = new DOMParser(); - obj = parser.parseFromString(xmlText, "text/xml"); - - } else { - // IE - try { - obj = new ActiveXObject("MSXML2.DOMDocument"); - } catch (e) { } - - if (obj == null) { - try { - obj = new ActiveXObject("Microsoft.XMLDOM"); - } catch (e) { } - } // if - - if (obj != null) { - obj.async = false; - obj.validateOnParse = false; - } // if - obj.loadXML(xmlText); - } // if - return(obj); -} // _getXMLDOM - - -///show the details of a javascript object. -///This helps a lot while developing and debugging. -function inspectObj(obj) { - var s = "InspectObj:"; - - if (obj == null) { - s = "(null)"; alert(s); return; - } else if (obj.constructor == String) { - s = "\"" + obj + "\""; - } else if (obj.constructor == Array) { - s += " _ARRAY"; - } else if (typeof(obj) == "function") { - s += " [function]" + obj; - - } else if ((typeof(XMLSerializer) != "undefined") && (obj.constructor == XMLDocument)) { - s = "[XMLDocument]:\n" + (new XMLSerializer()).serializeToString(obj.firstChild); - alert(s); return; - - } else if ((obj.constructor == null) && (typeof(obj) == "object") && (obj.xml != null)) { - s = "[XML]:\n" + obj.xml; - alert(s); return; - } - - for (p in obj) { - try { - if (obj[p] == null) { - s += "\n" + String(p) + " (...)"; - - } else if (typeof(obj[p]) == "function") { - s += "\n" + String(p) + " [function]"; - - } else if (obj[p].constructor == Array) { - s += "\n" + String(p) + " [ARRAY]: " + obj[p]; - for (n = 0; n < obj[p].length; n++) - s += "\n " + n + ": " + obj[p][n]; - - } else { - s += "\n" + String(p) + " [" + typeof(obj[p]) + "]: " + obj[p]; - } // if - } catch (e) { s+= e;} - } // for - alert(s); -} // inspectObj - -// ----- End ----- diff --git a/src/Umbraco.Web.UI/Umbraco/webservices/codeEditorSave.asmx b/src/Umbraco.Web.UI/Umbraco/webservices/codeEditorSave.asmx deleted file mode 100644 index 947a6fb889..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/webservices/codeEditorSave.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService Language="C#" CodeBehind="codeEditorSave.asmx.cs" Class="umbraco.presentation.webservices.codeEditorSave" %> diff --git a/src/Umbraco.Web.UI/Umbraco/webservices/legacyAjaxCalls.asmx b/src/Umbraco.Web.UI/Umbraco/webservices/legacyAjaxCalls.asmx deleted file mode 100644 index 5acc6e6022..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/webservices/legacyAjaxCalls.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService Language="C#" CodeBehind="legacyAjaxCalls.asmx.cs" Class="umbraco.presentation.webservices.legacyAjaxCalls" %> diff --git a/src/Umbraco.Web.UI/Umbraco/webservices/nodeSorter.asmx b/src/Umbraco.Web.UI/Umbraco/webservices/nodeSorter.asmx deleted file mode 100644 index ae09ffa463..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/webservices/nodeSorter.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService Language="C#" CodeBehind="nodeSorter.asmx.cs" Class="umbraco.presentation.webservices.nodeSorter" %> diff --git a/src/Umbraco.Web.UI/Umbraco/webservices/templates.asmx b/src/Umbraco.Web.UI/Umbraco/webservices/templates.asmx deleted file mode 100644 index 4a6e4ee189..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/webservices/templates.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService Language="c#" Codebehind="templates.asmx.cs" Class="umbraco.webservices.templates" %> diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 656bbd29c5..8986f7eff6 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1314,19 +1314,6 @@ - - - CheckForUpgrade.asmx - Component - - - legacyAjaxCalls.asmx - Component - - - nodeSorter.asmx - Component - @@ -1338,9 +1325,6 @@ - - Component - Component @@ -1367,10 +1351,7 @@ - - - ASPXCodeBehind diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CheckForUpgrade.asmx b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CheckForUpgrade.asmx deleted file mode 100644 index 226022e0fd..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CheckForUpgrade.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService Language="C#" CodeBehind="CheckForUpgrade.asmx.cs" Class="umbraco.presentation.webservices.CheckForUpgrade" %> diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CheckForUpgrade.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CheckForUpgrade.asmx.cs deleted file mode 100644 index 9007f9c41e..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CheckForUpgrade.asmx.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Web; -using System.Web.Services; -using System.Web.Script.Services; -using Umbraco.Core; -using Umbraco.Web.WebServices; -using Umbraco.Core.Configuration; -using Umbraco.Web; -using Umbraco.Web.Composing; -using Umbraco.Web.Install; - - -namespace umbraco.presentation.webservices -{ - /// - /// Summary description for CheckForUpgrade - /// - [WebService(Namespace = "http://umbraco.org/")] - [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] - [System.ComponentModel.ToolboxItem(false)] - [ScriptService] - public class CheckForUpgrade : UmbracoAuthorizedWebService - { - - [WebMethod] - [ScriptMethod] - public UpgradeResult CallUpgradeService() - { - if (!AuthorizeRequest()) return null; - - var check = new global::Umbraco.Web.org.umbraco.update.CheckForUpgrade(); - var result = check.CheckUpgrade(UmbracoVersion.Current.Major, - UmbracoVersion.Current.Minor, - UmbracoVersion.Current.Build, - UmbracoVersion.CurrentComment); - return new UpgradeResult(result.UpgradeType.ToString(), result.Comment, result.UpgradeUrl); - } - - [WebMethod] - [ScriptMethod] - public void InstallStatus(bool isCompleted, string userAgent, string errorMsg) - { - bool isUpgrade = false; - // if it's an upgrade, you'll need to be logged in before we allow this call - if (string.IsNullOrEmpty(GlobalSettings.ConfigurationStatus) == false) - { - isUpgrade = true; - try - { - AuthorizeRequest(true); - } - catch (Exception) - { - //we don't want to throw the exception back to JS - return; - } - } - - // Check for current install Id - Guid installId = Guid.NewGuid(); - var installCookie = Context.Request.GetCookieValue(Constants.Web.InstallerCookieName); - if (string.IsNullOrEmpty(installCookie) == false) - { - if (Guid.TryParse(installCookie, out installId)) - { - // check that it's a valid Guid - if (installId == Guid.Empty) - installId = Guid.NewGuid(); - - } - } - Context.Response.Cookies.Set(new HttpCookie(Constants.Web.InstallerCookieName, installId.ToString())); - - string dbProvider = string.Empty; - if (string.IsNullOrEmpty(GlobalSettings.ConfigurationStatus) == false) - dbProvider = InstallHelper.GetDbProviderString(Current.SqlContext); - - var check = new global::Umbraco.Web.org.umbraco.update.CheckForUpgrade(); - check.Install(installId, - isUpgrade, - isCompleted, - DateTime.Now, - UmbracoVersion.Current.Major, - UmbracoVersion.Current.Minor, - UmbracoVersion.Current.Build, - UmbracoVersion.CurrentComment, - errorMsg, - userAgent, - dbProvider); - } - } - - - public class UpgradeResult - { - public string UpgradeType { get; set; } - public string UpgradeComment { get; set; } - public string UpgradeUrl { get; set; } - - public UpgradeResult() { } - public UpgradeResult(string upgradeType, string upgradeComment, string upgradeUrl) - { - UpgradeType = upgradeType; - UpgradeComment = upgradeComment; - UpgradeUrl = upgradeUrl + "?version=" + HttpContext.Current.Server.UrlEncode(UmbracoVersion.Current.ToString(3)); - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/UmbracoAuthorizedWebService.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/UmbracoAuthorizedWebService.cs deleted file mode 100644 index 33c4831c40..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/UmbracoAuthorizedWebService.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System; -using System.Security; -using Umbraco.Web.Security; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Services; -using Umbraco.Web; -using Umbraco.Web.WebServices; - -namespace umbraco.presentation.webservices -{ - /// - /// An abstract web service class that has the methods and properties to correct validate an Umbraco user - /// - public abstract class UmbracoAuthorizedWebService : UmbracoWebService - { - private bool _hasValidated = false; - - /// - /// Checks if the umbraco context id is valid - /// - /// - /// - protected bool ValidateUserContextId(string currentUmbracoUserContextId) - { - return UmbracoContext.Security.ValidateCurrentUser(); - } - - /// - /// Checks if the username/password credentials are valid - /// - /// - /// - /// - protected bool ValidateCredentials(string username, string password) - { - return UmbracoContext.Security.ValidateBackOfficeCredentials(username, password); - } - - /// - /// Validates the user for access to a certain application - /// - /// The application alias. - /// true if an exception should be thrown if authorization fails - /// - protected bool AuthorizeRequest(string app, bool throwExceptions = false) - { - //ensure we have a valid user first! - if (!AuthorizeRequest(throwExceptions)) return false; - - //if it is empty, don't validate - if (app.IsNullOrWhiteSpace()) - { - return true; - } - var hasAccess = UserHasAppAccess(app, Security.CurrentUser); - if (!hasAccess && throwExceptions) - throw new SecurityException("The user does not have access to the required application"); - return hasAccess; - } - - /// - /// Checks if the specified user as access to the app - /// - /// - /// - /// - protected bool UserHasAppAccess(string app, IUser user) - { - return Security.UserHasSectionAccess(app, user); - } - - /// - /// Checks if the specified user by username as access to the app - /// - /// - /// - /// - protected bool UserHasAppAccess(string app, string username) - { - return Security.UserHasSectionAccess(app, username); - } - - /// - /// Returns true if there is a valid logged in user and that ssl is enabled if required - /// - /// true if an exception should be thrown if authorization fails - /// - protected bool AuthorizeRequest(bool throwExceptions = false) - { - var result = Security.AuthorizeRequest(throwExceptions); - return result == ValidateRequestAttempt.Success; - } - - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/ajaxHelpers.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/ajaxHelpers.cs deleted file mode 100644 index 3e6ad7d221..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/ajaxHelpers.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Data; -using System.Configuration; -using System.Web; -using System.Web.Security; -using System.Web.UI; -using System.Web.UI.WebControls; -using System.Web.UI.WebControls.WebParts; -using System.Web.UI.HtmlControls; -using Umbraco.Core.IO; - -namespace umbraco.presentation.webservices -{ - public class ajaxHelpers - { - public static void EnsureLegacyCalls(Page page) - { - var sm = ScriptManager.GetCurrent(page); - var legacyPath = new ServiceReference(SystemDirectories.WebServices + "/legacyAjaxCalls.asmx"); - - if (!sm.Services.Contains(legacyPath)) - sm.Services.Add(legacyPath); - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/legacyAjaxCalls.asmx b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/legacyAjaxCalls.asmx deleted file mode 100644 index 5acc6e6022..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/legacyAjaxCalls.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService Language="C#" CodeBehind="legacyAjaxCalls.asmx.cs" Class="umbraco.presentation.webservices.legacyAjaxCalls" %> diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/legacyAjaxCalls.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/legacyAjaxCalls.asmx.cs deleted file mode 100644 index 28ba66709a..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/legacyAjaxCalls.asmx.cs +++ /dev/null @@ -1,141 +0,0 @@ -using System; -using System.Web; -using System.Web.Services; -using System.ComponentModel; -using System.Web.Script.Services; -using Umbraco.Core; -using Umbraco.Web; -using Umbraco.Web.WebServices; -using Umbraco.Core.Models.Membership; -using Umbraco.Web.Composing; -using Umbraco.Web._Legacy.UI; -using Umbraco.Core.Services; - -namespace umbraco.presentation.webservices -{ - /// - /// Summary description for legacyAjaxCalls - /// - [WebService(Namespace = "http://umbraco.org/webservices")] - [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] - [ToolboxItem(false)] - [ScriptService] - public class legacyAjaxCalls : UmbracoAuthorizedWebService - { - /// - /// method to accept a string value for the node id. Used for tree's such as python - /// and xslt since the file names are the node IDs - /// - /// - /// - /// - [WebMethod] - [ScriptMethod] - public void Delete(string nodeId, string alias, string nodeType) - { - if (!AuthorizeRequest()) - return; - - //U4-2686 - alias is html encoded, make sure to decode - alias = HttpUtility.HtmlDecode(alias); - - //check which parameters to pass depending on the types passed in - int intNodeId; - if (nodeType == "memberGroups") - { - LegacyDialogHandler.Delete( - new HttpContextWrapper(HttpContext.Current), - Security.CurrentUser, - nodeType, 0, nodeId); - } - else if (int.TryParse(nodeId, out intNodeId) && nodeType != "member") // Fix for #26965 - numeric member login gets parsed as nodeId - { - LegacyDialogHandler.Delete( - new HttpContextWrapper(HttpContext.Current), - Security.CurrentUser, - nodeType, intNodeId, alias); - } - else - { - LegacyDialogHandler.Delete( - new HttpContextWrapper(HttpContext.Current), - Security.CurrentUser, - nodeType, 0, nodeId); - } - } - - /// - /// Permanently deletes a document/media object. - /// Used to remove an item from the recycle bin. - /// - /// - /// - [WebMethod] - [ScriptMethod] - public void DeleteContentPermanently(string nodeId, string nodeType) - { - int intNodeId; - if (int.TryParse(nodeId, out intNodeId)) - { - switch (nodeType) - { - case "media": - case "mediaRecycleBin": - //ensure user has access to media - AuthorizeRequest(Constants.Applications.Media.ToString(), true); - var media = Current.Services.MediaService.GetById(intNodeId); - if (media != null) - Current.Services.MediaService.Delete(media); - break; - case "content": - case "contentRecycleBin": - default: - //ensure user has access to content - AuthorizeRequest(Constants.Applications.Content.ToString(), true); - var content = Current.Services.ContentService.GetById(intNodeId); - if (content != null) - Current.Services.ContentService.Delete(content); - break; - } - } - else - { - throw new ArgumentException("The nodeId argument could not be parsed to an integer"); - } - } - - [WebMethod] - [ScriptMethod] - public void DisableUser(int userId) - { - AuthorizeRequest(Constants.Applications.Users.ToString(), true); - - var user = Services.UserService.GetUserById(userId); - if (user == null) return; - - user.IsApproved = false; - Services.UserService.Save(user); - } - - [WebMethod] - [ScriptMethod] - public string NiceUrl(int nodeId) - { - - AuthorizeRequest(true); - - var umbHelper = new UmbracoHelper(Current.UmbracoContext, Current.Services, Current.ApplicationCache); - - return umbHelper.Url(nodeId); - } - - [WebMethod] - [ScriptMethod] - public string ProgressStatus(string Key) - { - AuthorizeRequest(true); - - return Application[Context.Request.GetItemAsString("key")].ToString(); - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx deleted file mode 100644 index ae09ffa463..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService Language="C#" CodeBehind="nodeSorter.asmx.cs" Class="umbraco.presentation.webservices.nodeSorter" %> diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx.cs deleted file mode 100644 index f82587a413..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx.cs +++ /dev/null @@ -1,262 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Web.Script.Services; -using System.Web.Services; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Web; -using Umbraco.Web.Composing; -using Umbraco.Web._Legacy.Actions; - -namespace umbraco.presentation.webservices -{ - /// - /// Summary description for nodeSorter - /// - [WebService(Namespace = "http://umbraco.org/")] - [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] - [ToolboxItem(false)] - [ScriptService] - public class nodeSorter : UmbracoAuthorizedWebService - { - [WebMethod] - public SortNode GetNodes(string ParentId, string App) - { - if (AuthorizeRequest()) - { - var nodes = new List(); - - // "hack for stylesheet" - if (App == "settings") - { - var stylesheet = Services.FileService.GetStylesheetByName(ParentId.EnsureEndsWith(".css")); - if (stylesheet == null) throw new InvalidOperationException("No stylesheet found by name " + ParentId); - - var sort = 0; - foreach (var child in stylesheet.Properties) - { - nodes.Add(new SortNode(child.Name.GetHashCode(), sort, child.Name, DateTime.Now)); - sort++; - } - - return new SortNode() - { - SortNodes = nodes.ToArray() - }; - } - else - { - var asInt = int.Parse(ParentId); - - var parent = new SortNode { Id = asInt }; - - var entityService = Services.EntityService; - - // Root nodes? - if (asInt == -1) - { - if (App == "media") - { - var rootMedia = entityService.GetRootEntities(UmbracoObjectTypes.Media); - nodes.AddRange(rootMedia.Select(media => new SortNode(media.Id, media.SortOrder, media.Name, media.CreateDate))); - } - else - { - var rootContent = entityService.GetRootEntities(UmbracoObjectTypes.Document); - nodes.AddRange(rootContent.Select(content => new SortNode(content.Id, content.SortOrder, content.Name, content.CreateDate))); - } - } - else - { - var children = entityService.GetChildren(asInt); - nodes.AddRange(children.Select(child => new SortNode(child.Id, child.SortOrder, child.Name, child.CreateDate))); - } - - - parent.SortNodes = nodes.ToArray(); - - return parent; - } - } - - throw new ArgumentException("User not logged in"); - } - - public void UpdateSortOrder(int ParentId, string SortOrder) - { - UpdateSortOrder(ParentId.ToString(), SortOrder); - } - - [WebMethod] - public void UpdateSortOrder(string ParentId, string SortOrder) - { - if (AuthorizeRequest() == false) return; - if (SortOrder.Trim().Length <= 0) return; - - var isContent = Context.Request.GetItemAsString("app") == "content" | Context.Request.GetItemAsString("app") == ""; - var isMedia = Context.Request.GetItemAsString("app") == "media"; - - //ensure user is authorized for the app requested - if (isContent && AuthorizeRequest(Constants.Applications.Content.ToString()) == false) return; - if (isMedia && AuthorizeRequest(Constants.Applications.Media.ToString()) == false) return; - - var ids = SortOrder.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); - if (isContent) - { - SortContent(ids, int.Parse(ParentId)); - } - else if (isMedia) - { - SortMedia(ids); - } - else - { - SortStylesheetProperties(ParentId, ids); - } - } - - private void SortMedia(string[] ids) - { - var mediaService = Services.MediaService; - var sortedMedia = new List(); - try - { - for (var i = 0; i < ids.Length; i++) - { - var id = int.Parse(ids[i]); - var m = mediaService.GetById(id); - sortedMedia.Add(m); - } - - // Save Media with new sort order and update content xml in db accordingly - var sorted = mediaService.Sort(sortedMedia); - } - catch (Exception ex) - { - Current.Logger.Error(ex, "Could not update media sort order"); - } - } - - - private void SortStylesheetProperties(string stylesheetName, string[] names) - { - var stylesheet = Services.FileService.GetStylesheetByName(stylesheetName.EnsureEndsWith(".css")); - if (stylesheet == null) throw new InvalidOperationException("No stylesheet found by name " + stylesheetName); - - var currProps = stylesheet.Properties.ToArray(); - //remove them all first - foreach (var prop in currProps) - { - stylesheet.RemoveProperty(prop.Name); - } - - //re-add them in the right order - for (var i = 0; i < names.Length; i++) - { - var found = currProps.Single(x => x.Name == names[i]); - stylesheet.AddProperty(found); - } - - Services.FileService.SaveStylesheet(stylesheet); - } - - private void SortContent(string[] ids, int parentId) - { - var contentService = Services.ContentService; - try - { - // Save content with new sort order and update db+cache accordingly - var intIds = new List(); - foreach (var stringId in ids) - { - int intId; - if (int.TryParse(stringId, out intId)) - intIds.Add(intId); - } - var sorted = contentService.Sort(intIds.ToArray()); - - // refresh sort order on cached xml - // but no... this is not distributed - solely relying on content service & events should be enough - //content.Instance.SortNodes(parentId); - - //send notifications! TODO: This should be put somewhere centralized instead of hard coded directly here - if (parentId > 0) - { - Services.NotificationService.SendNotification(contentService.GetById(parentId), ActionSort.Instance, UmbracoContext, Services.TextService, GlobalSettings); - } - - } - catch (Exception ex) - { - Current.Logger.Error(ex, "Could not update content sort order"); - } - } - - } - - [Serializable] - public class SortNode - { - public SortNode() - { - } - - private SortNode[] _sortNodes; - - public SortNode[] SortNodes - { - get { return _sortNodes; } - set { _sortNodes = value; } - } - - public int TotalNodes - { - get { return _sortNodes != null ? _sortNodes.Length : 0; } - set { int test = value; } - } - - public SortNode(int Id, int SortOrder, string Name, DateTime CreateDate) - { - _id = Id; - _sortOrder = SortOrder; - _name = Name; - _createDate = CreateDate; - } - - private DateTime _createDate; - - public DateTime CreateDate - { - get { return _createDate; } - set { _createDate = value; } - } - - private string _name; - - public string Name - { - get { return _name; } - set { _name = value; } - } - - private int _sortOrder; - - public int SortOrder - { - get { return _sortOrder; } - set { _sortOrder = value; } - } - - private int _id; - - public int Id - { - get { return _id; } - set { _id = value; } - } - } -} From 81c7a1de78d278cf2bad707ad0a214565acd8185 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 24 Oct 2018 23:55:55 +1100 Subject: [PATCH 02/10] Cleans up the notification system, removes coupling on System.Web, simplifies the whole thing, removes old legacy files, adds sort event, moves sort notification to be event based and not part of the controller --- .../Models/NotificationEmailBodyParams.cs | 28 +++ .../Models/NotificationEmailSubjectParams.cs | 23 +++ src/Umbraco.Core/Services/IContentService.cs | 4 +- .../Services/INotificationService.cs | 21 +- .../Services/Implement/ContentService.cs | 49 +++-- .../Services/Implement/NotificationService.cs | 165 +++++---------- src/Umbraco.Core/Umbraco.Core.csproj | 2 + src/Umbraco.Tests/TestHelpers/TestObjects.cs | 4 +- .../views/content/content.sort.controller.js | 6 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 3 - .../Umbraco/dialogs/SendPublish.aspx | 13 -- .../Components/NotificationsComponent.cs | 190 ++++++++++++++---- src/Umbraco.Web/Editors/ContentController.cs | 9 +- .../NotificationServiceExtensions.cs | 145 ------------- .../Trees/LegacyTreeDataConverter.cs | 27 +-- src/Umbraco.Web/Umbraco.Web.csproj | 9 - .../umbraco/dialogs/SendPublish.aspx | 13 -- .../umbraco/dialogs/SendPublish.aspx.cs | 37 ---- .../dialogs/SendPublish.aspx.designer.cs | 16 -- 19 files changed, 302 insertions(+), 462 deletions(-) create mode 100644 src/Umbraco.Core/Models/NotificationEmailBodyParams.cs create mode 100644 src/Umbraco.Core/Models/NotificationEmailSubjectParams.cs delete mode 100644 src/Umbraco.Web.UI/Umbraco/dialogs/SendPublish.aspx delete mode 100644 src/Umbraco.Web/NotificationServiceExtensions.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/SendPublish.aspx delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/SendPublish.aspx.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/SendPublish.aspx.designer.cs diff --git a/src/Umbraco.Core/Models/NotificationEmailBodyParams.cs b/src/Umbraco.Core/Models/NotificationEmailBodyParams.cs new file mode 100644 index 0000000000..ff316b1cf7 --- /dev/null +++ b/src/Umbraco.Core/Models/NotificationEmailBodyParams.cs @@ -0,0 +1,28 @@ +using System; + +namespace Umbraco.Core.Models +{ + public class NotificationEmailBodyParams + { + public NotificationEmailBodyParams(string recipientName, string action, string itemName, string itemId, string itemUrl, string editedUser, string siteUrl, string summary) + { + RecipientName = recipientName ?? throw new ArgumentNullException(nameof(recipientName)); + Action = action ?? throw new ArgumentNullException(nameof(action)); + ItemName = itemName ?? throw new ArgumentNullException(nameof(itemName)); + ItemId = itemId ?? throw new ArgumentNullException(nameof(itemId)); + ItemUrl = itemUrl ?? throw new ArgumentNullException(nameof(itemUrl)); + Summary = summary ?? throw new ArgumentNullException(nameof(summary)); + EditedUser = editedUser ?? throw new ArgumentNullException(nameof(editedUser)); + SiteUrl = siteUrl ?? throw new ArgumentNullException(nameof(siteUrl)); + } + + public string RecipientName { get; } + public string Action { get; } + public string ItemName { get; } + public string ItemId { get; } + public string ItemUrl { get; } + public string Summary { get; } + public string EditedUser { get; } + public string SiteUrl { get; } + } +} diff --git a/src/Umbraco.Core/Models/NotificationEmailSubjectParams.cs b/src/Umbraco.Core/Models/NotificationEmailSubjectParams.cs new file mode 100644 index 0000000000..07b26dbcc1 --- /dev/null +++ b/src/Umbraco.Core/Models/NotificationEmailSubjectParams.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.Core.Models +{ + + public class NotificationEmailSubjectParams + { + public NotificationEmailSubjectParams(string siteUrl, string action, string itemName) + { + SiteUrl = siteUrl ?? throw new ArgumentNullException(nameof(siteUrl)); + Action = action ?? throw new ArgumentNullException(nameof(action)); + ItemName = itemName ?? throw new ArgumentNullException(nameof(itemName)); + } + + public string SiteUrl { get; } + public string Action { get; } + public string ItemName { get; } + } +} diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 64877e393e..ed682096c0 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -337,12 +337,12 @@ namespace Umbraco.Core.Services /// /// Sorts documents. /// - bool Sort(IEnumerable items, int userId = 0, bool raiseEvents = true); + OperationResult Sort(IEnumerable items, int userId = 0, bool raiseEvents = true); /// /// Sorts documents. /// - bool Sort(IEnumerable ids, int userId = 0, bool raiseEvents = true); + OperationResult Sort(IEnumerable ids, int userId = 0, bool raiseEvents = true); #endregion diff --git a/src/Umbraco.Core/Services/INotificationService.cs b/src/Umbraco.Core/Services/INotificationService.cs index 3af603a31c..92a1e93e2f 100644 --- a/src/Umbraco.Core/Services/INotificationService.cs +++ b/src/Umbraco.Core/Services/INotificationService.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -using System.Web; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; @@ -13,20 +12,6 @@ namespace Umbraco.Core.Services { public interface INotificationService : IService { - /// - /// Sends the notifications for the specified user regarding the specified node and action. - /// - /// - /// - /// - /// - /// - /// - /// - void SendNotifications(IUser operatingUser, IUmbracoEntity entity, string action, string actionName, HttpContextBase http, - Func createSubject, - Func createBody); - /// /// Sends the notifications for the specified user regarding the specified nodes and action. /// @@ -37,9 +22,9 @@ namespace Umbraco.Core.Services /// /// /// - void SendNotifications(IUser operatingUser, IEnumerable entities, string action, string actionName, HttpContextBase http, - Func createSubject, - Func createBody); + void SendNotifications(IUser operatingUser, IEnumerable entities, string action, string actionName, Uri siteUri, + Func<(IUser user, NotificationEmailSubjectParams subject), string> createSubject, + Func<(IUser user, NotificationEmailBodyParams body, bool isHtml), string> createBody); /// /// Gets the notifications for the user diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 2cd9051e72..ac1171cfdd 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -1906,16 +1906,18 @@ namespace Umbraco.Core.Services.Implement /// /// /// True if sorting succeeded, otherwise False - public bool Sort(IEnumerable items, int userId = 0, bool raiseEvents = true) + public OperationResult Sort(IEnumerable items, int userId = 0, bool raiseEvents = true) { + var evtMsgs = EventMessagesFactory.Get(); + var itemsA = items.ToArray(); - if (itemsA.Length == 0) return true; + if (itemsA.Length == 0) return new OperationResult(OperationResultType.NoOperation, evtMsgs); using (var scope = ScopeProvider.CreateScope()) { scope.WriteLock(Constants.Locks.ContentTree); - var ret = Sort(scope, itemsA, userId, raiseEvents); + var ret = Sort(scope, itemsA, userId, evtMsgs, raiseEvents); scope.Complete(); return ret; } @@ -1933,27 +1935,37 @@ namespace Umbraco.Core.Services.Implement /// /// /// True if sorting succeeded, otherwise False - public bool Sort(IEnumerable ids, int userId = 0, bool raiseEvents = true) + public OperationResult Sort(IEnumerable ids, int userId = 0, bool raiseEvents = true) { + var evtMsgs = EventMessagesFactory.Get(); + var idsA = ids.ToArray(); - if (idsA.Length == 0) return true; + if (idsA.Length == 0) return new OperationResult(OperationResultType.NoOperation, evtMsgs); using (var scope = ScopeProvider.CreateScope()) { scope.WriteLock(Constants.Locks.ContentTree); var itemsA = GetByIds(idsA).ToArray(); - var ret = Sort(scope, itemsA, userId, raiseEvents); + var ret = Sort(scope, itemsA, userId, evtMsgs, raiseEvents); scope.Complete(); return ret; } } - private bool Sort(IScope scope, IContent[] itemsA, int userId, bool raiseEvents) + private OperationResult Sort(IScope scope, IContent[] itemsA, int userId, EventMessages evtMsgs, bool raiseEvents) { var saveEventArgs = new SaveEventArgs(itemsA); - if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) - return false; + if (raiseEvents) + { + //raise cancelable sorting event + if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Sorting))) + return OperationResult.Cancel(evtMsgs); + + //raise saving event (this one cannot be canceled) + saveEventArgs.CanCancel = false; + scope.Events.Dispatch(Saving, this, saveEventArgs, nameof(Saving)); + } var published = new List(); var saved = new List(); @@ -1985,8 +1997,9 @@ namespace Umbraco.Core.Services.Implement if (raiseEvents) { - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); + //first saved, then sorted + scope.Events.Dispatch(Saved, this, saveEventArgs, nameof(Saved)); + scope.Events.Dispatch(Sorted, this, saveEventArgs, nameof(Sorted)); } scope.Events.Dispatch(TreeChanged, this, saved.Select(x => new TreeChange(x, TreeChangeTypes.RefreshNode)).ToEventArgs()); @@ -1994,8 +2007,8 @@ namespace Umbraco.Core.Services.Implement if (raiseEvents && published.Any()) scope.Events.Dispatch(Published, this, new PublishEventArgs(published, false, false), "Published"); - Audit(AuditType.Sort, "Sorting content performed by user", userId, 0); - return true; + Audit(AuditType.Sort, userId, 0, "Sorting content performed by user"); + return OperationResult.Succeed(evtMsgs); } #endregion @@ -2070,6 +2083,16 @@ namespace Umbraco.Core.Services.Implement /// public static event TypedEventHandler DeletedVersions; + /// + /// Occurs before Sorting + /// + public static event TypedEventHandler> Sorting; + + /// + /// Occurs after Sorting + /// + public static event TypedEventHandler> Sorted; + /// /// Occurs before Save /// diff --git a/src/Umbraco.Core/Services/Implement/NotificationService.cs b/src/Umbraco.Core/Services/Implement/NotificationService.cs index cc76374715..12e575d903 100644 --- a/src/Umbraco.Core/Services/Implement/NotificationService.cs +++ b/src/Umbraco.Core/Services/Implement/NotificationService.cs @@ -6,8 +6,8 @@ using System.Linq; using System.Net.Mail; using System.Text; using System.Threading; -using System.Web; using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -26,89 +26,21 @@ namespace Umbraco.Core.Services.Implement private readonly IContentService _contentService; private readonly INotificationsRepository _notificationsRepository; private readonly IGlobalSettings _globalSettings; + private readonly IContentSection _contentSection; private readonly ILogger _logger; - public NotificationService(IScopeProvider provider, IUserService userService, IContentService contentService, ILogger logger, - INotificationsRepository notificationsRepository, IGlobalSettings globalSettings) + public NotificationService(IScopeProvider provider, IUserService userService, IContentService contentService, + ILogger logger, INotificationsRepository notificationsRepository, IGlobalSettings globalSettings, IContentSection contentSection) { _notificationsRepository = notificationsRepository; _globalSettings = globalSettings; + _contentSection = contentSection; _uowProvider = provider ?? throw new ArgumentNullException(nameof(provider)); _userService = userService ?? throw new ArgumentNullException(nameof(userService)); _contentService = contentService ?? throw new ArgumentNullException(nameof(contentService)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } - /// - /// Sends the notifications for the specified user regarding the specified node and action. - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// Currently this will only work for Content entities! - /// - public void SendNotifications(IUser operatingUser, IUmbracoEntity entity, string action, string actionName, HttpContextBase http, - Func createSubject, - Func createBody) - { - if (entity is IContent == false) - throw new NotSupportedException(); - - var content = (IContent) entity; - - // lazily get previous version - IContentBase prevVersion = null; - - // do not load *all* users in memory at once - // do not load notifications *per user* (N+1 select) - // cannot load users & notifications in 1 query (combination btw User2AppDto and User2NodeNotifyDto) - // => get batches of users, get all their notifications in 1 query - // re. users: - // users being (dis)approved = not an issue, filtered in memory not in SQL - // users being modified or created = not an issue, ordering by ID, as long as we don't *insert* low IDs - // users being deleted = not an issue for GetNextUsers - var id = Constants.Security.SuperUserId; - var nodeIds = content.Path.Split(',').Select(int.Parse).ToArray(); - const int pagesz = 400; // load batches of 400 users - do - { - // users are returned ordered by id, notifications are returned ordered by user id - var users = ((UserService) _userService).GetNextUsers(id, pagesz).Where(x => x.IsApproved).ToList(); - var notifications = GetUsersNotifications(users.Select(x => x.Id), action, nodeIds, Constants.ObjectTypes.Document).ToList(); - if (notifications.Count == 0) break; - - var i = 0; - foreach (var user in users) - { - // continue if there's no notification for this user - if (notifications[i].UserId != user.Id) continue; // next user - - // lazy load prev version - if (prevVersion == null) - { - prevVersion = GetPreviousVersion(entity.Id); - } - - // queue notification - var req = CreateNotificationRequest(operatingUser, user, content, prevVersion, actionName, http, createSubject, createBody); - Enqueue(req); - - // skip other notifications for this user - while (i < notifications.Count && notifications[i++].UserId == user.Id) ; - if (i >= notifications.Count) break; // break if no more notifications - } - - // load more users if any - id = users.Count == pagesz ? users.Last().Id + 1 : -1; - - } while (id > 0); - } - /// /// Gets the previous version to the latest version of the content item if there is one /// @@ -131,15 +63,15 @@ namespace Umbraco.Core.Services.Implement /// /// /// - /// + /// /// /// /// /// Currently this will only work for Content entities! /// - public void SendNotifications(IUser operatingUser, IEnumerable entities, string action, string actionName, HttpContextBase http, - Func createSubject, - Func createBody) + public void SendNotifications(IUser operatingUser, IEnumerable entities, string action, string actionName, Uri siteUri, + Func<(IUser user, NotificationEmailSubjectParams subject), string> createSubject, + Func<(IUser user, NotificationEmailBodyParams body, bool isHtml), string> createBody) { if (entities is IEnumerable == false) throw new NotSupportedException(); @@ -156,7 +88,7 @@ namespace Umbraco.Core.Services.Implement var prevVersionDictionary = new Dictionary(); // see notes above - var id = 0; + var id = Constants.Security.SuperUserId; const int pagesz = 400; // load batches of 400 users do { @@ -185,7 +117,7 @@ namespace Umbraco.Core.Services.Implement } // queue notification - var req = CreateNotificationRequest(operatingUser, user, content, prevVersionDictionary[content.Id], actionName, http, createSubject, createBody); + var req = CreateNotificationRequest(operatingUser, user, content, prevVersionDictionary[content.Id], actionName, siteUri, createSubject, createBody); Enqueue(req); } @@ -350,18 +282,19 @@ namespace Umbraco.Core.Services.Implement /// /// /// The action readable name - currently an action is just a single letter, this is the name associated with the letter - /// + /// /// Callback to create the mail subject /// Callback to create the mail body private NotificationRequest CreateNotificationRequest(IUser performingUser, IUser mailingUser, IContentBase content, IContentBase oldDoc, - string actionName, HttpContextBase http, - Func createSubject, - Func createBody) + string actionName, + Uri siteUri, + Func<(IUser user, NotificationEmailSubjectParams subject), string> createSubject, + Func<(IUser user, NotificationEmailBodyParams body, bool isHtml), string> createBody) { if (performingUser == null) throw new ArgumentNullException("performingUser"); if (mailingUser == null) throw new ArgumentNullException("mailingUser"); if (content == null) throw new ArgumentNullException("content"); - if (http == null) throw new ArgumentNullException("http"); + if (siteUri == null) throw new ArgumentNullException("siteUri"); if (createSubject == null) throw new ArgumentNullException("createSubject"); if (createBody == null) throw new ArgumentNullException("createBody"); @@ -402,7 +335,7 @@ namespace Umbraco.Core.Services.Implement summary.Append(p.PropertyType.Name); summary.Append(""); summary.Append(""); - summary.Append(ReplaceLinks(CompareText(oldText, newText, true, false, "", string.Empty), http.Request)); + summary.Append(ReplaceLinks(CompareText(oldText, newText, true, false, "", string.Empty), siteUri)); summary.Append(""); summary.Append(""); summary.Append(""); @@ -410,7 +343,7 @@ namespace Umbraco.Core.Services.Implement summary.Append(p.PropertyType.Name); summary.Append(""); summary.Append(""); - summary.Append(ReplaceLinks(CompareText(newText, oldText, true, false, "", string.Empty), http.Request)); + summary.Append(ReplaceLinks(CompareText(newText, oldText, true, false, "", string.Empty), siteUri)); summary.Append(""); summary.Append(""); } @@ -429,39 +362,39 @@ namespace Umbraco.Core.Services.Implement " "); } - string protocol = _globalSettings.UseHttps ? "https" : "http"; + var protocol = _globalSettings.UseHttps ? "https" : "http"; + var subjectVars = new NotificationEmailSubjectParams( + string.Concat(siteUri.Authority, IOHelper.ResolveUrl(SystemDirectories.Umbraco)), + actionName, + content.Name); - string[] subjectVars = { - string.Concat(http.Request.ServerVariables["SERVER_NAME"], ":", http.Request.Url.Port, IOHelper.ResolveUrl(SystemDirectories.Umbraco)), - actionName, - content.Name - }; - string[] bodyVars = { - mailingUser.Name, - actionName, - content.Name, - performingUser.Name, - string.Concat(http.Request.ServerVariables["SERVER_NAME"], ":", http.Request.Url.Port, IOHelper.ResolveUrl(SystemDirectories.Umbraco)), - content.Id.ToString(CultureInfo.InvariantCulture), summary.ToString(), - string.Format("{2}://{0}/{1}", - string.Concat(http.Request.ServerVariables["SERVER_NAME"], ":", http.Request.Url.Port), - //TODO: RE-enable this so we can have a nice url - /*umbraco.library.NiceUrl(documentObject.Id))*/ - string.Concat(content.Id, ".aspx"), - protocol) - - }; + var bodyVars = new NotificationEmailBodyParams( + mailingUser.Name, + actionName, + content.Name, + content.Id.ToString(CultureInfo.InvariantCulture), + string.Format("{2}://{0}/{1}", + string.Concat(siteUri.Authority), + //TODO: RE-enable this so we can have a nice url + /*umbraco.library.NiceUrl(documentObject.Id))*/ + string.Concat(content.Id, ".aspx"), + protocol), + performingUser.Name, + string.Concat(siteUri.Authority, IOHelper.ResolveUrl(SystemDirectories.Umbraco)), + summary.ToString()); // create the mail message - var mail = new MailMessage(UmbracoConfig.For.UmbracoSettings().Content.NotificationEmailAddress, mailingUser.Email); + var mail = new MailMessage(_contentSection.NotificationEmailAddress, mailingUser.Email); // populate the message - mail.Subject = createSubject(mailingUser, subjectVars); - if (UmbracoConfig.For.UmbracoSettings().Content.DisableHtmlEmail) + + + mail.Subject = createSubject((mailingUser, subjectVars)); + if (_contentSection.DisableHtmlEmail) { mail.IsBodyHtml = false; - mail.Body = createBody(mailingUser, bodyVars); + mail.Body = createBody((user: mailingUser, body: bodyVars, false)); } else { @@ -470,14 +403,14 @@ namespace Umbraco.Core.Services.Implement string.Concat(@" -", createBody(mailingUser, bodyVars)); +", createBody((user: mailingUser, body: bodyVars, true))); } // nh, issue 30724. Due to hardcoded http strings in resource files, we need to check for https replacements here // adding the server name to make sure we don't replace external links if (_globalSettings.UseHttps && string.IsNullOrEmpty(mail.Body) == false) { - string serverName = http.Request.ServerVariables["SERVER_NAME"]; + string serverName = siteUri.Host; mail.Body = mail.Body.Replace( string.Format("http://{0}", serverName), string.Format("https://{0}", serverName)); @@ -486,12 +419,10 @@ namespace Umbraco.Core.Services.Implement return new NotificationRequest(mail, actionName, mailingUser.Name, mailingUser.Email); } - private string ReplaceLinks(string text, HttpRequestBase request) + private string ReplaceLinks(string text, Uri siteUri) { var sb = new StringBuilder(_globalSettings.UseHttps ? "https://" : "http://"); - sb.Append(request.ServerVariables["SERVER_NAME"]); - sb.Append(":"); - sb.Append(request.Url.Port); + sb.Append(siteUri.Authority); sb.Append("/"); var domain = sb.ToString(); text = text.Replace("href=\"/", "href=\"" + domain); diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index e0aa93efec..ad6e6f9310 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -394,6 +394,8 @@ + + diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index 2707c73607..6d0d14ce11 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -10,6 +10,7 @@ using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -106,6 +107,7 @@ namespace Umbraco.Tests.TestHelpers CacheHelper cache, ILogger logger, IGlobalSettings globalSettings, + IUmbracoSettingsSection umbracoSettings, IEventMessagesFactory eventMessagesFactory, IEnumerable urlSegmentProviders, IServiceFactory container = null) @@ -161,7 +163,7 @@ namespace Umbraco.Tests.TestHelpers var userService = GetLazyService(container, c => new UserService(scopeProvider, logger, eventMessagesFactory, runtimeState, GetRepo(c), GetRepo(c),globalSettings)); var dataTypeService = GetLazyService(container, c => new DataTypeService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); var contentService = GetLazyService(container, c => new ContentService(scopeProvider, logger, eventMessagesFactory, mediaFileSystem, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); - var notificationService = GetLazyService(container, c => new NotificationService(scopeProvider, userService.Value, contentService.Value, logger, GetRepo(c),globalSettings)); + var notificationService = GetLazyService(container, c => new NotificationService(scopeProvider, userService.Value, contentService.Value, logger, GetRepo(c), globalSettings, umbracoSettings.Content)); var serverRegistrationService = GetLazyService(container, c => new ServerRegistrationService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); var memberGroupService = GetLazyService(container, c => new MemberGroupService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); var memberService = GetLazyService(container, c => new MemberService(scopeProvider, logger, eventMessagesFactory, memberGroupService.Value, mediaFileSystem, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.sort.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.sort.controller.js index 4581b78c8c..53759d7d97 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.sort.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.sort.controller.js @@ -1,7 +1,7 @@ (function () { "use strict"; - function ContentSortController($scope, $filter, contentResource, navigationService) { + function ContentSortController($scope, $filter, $routeParams, contentResource, navigationService) { var vm = this; var parentId = $scope.currentNode.parentId ? $scope.currentNode.parentId : "-1"; @@ -30,7 +30,7 @@ function onInit() { vm.loading = true; - contentResource.getChildren(id) + contentResource.getChildren(id, { cultureName: $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture }) .then(function(data){ vm.children = data.items; vm.loading = false; @@ -79,4 +79,4 @@ } angular.module("umbraco").controller("Umbraco.Editors.Content.SortController", ContentSortController); -})(); \ No newline at end of file +})(); diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index d95d8ca664..55ecd9834c 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -344,12 +344,9 @@ - - - Designer diff --git a/src/Umbraco.Web.UI/Umbraco/dialogs/SendPublish.aspx b/src/Umbraco.Web.UI/Umbraco/dialogs/SendPublish.aspx deleted file mode 100644 index 8b190d575c..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/dialogs/SendPublish.aspx +++ /dev/null @@ -1,13 +0,0 @@ -<%@ Page language="c#" Codebehind="SendPublish.aspx.cs" AutoEventWireup="True" Inherits="umbraco.dialogs.SendPublish" %> - - - - umbraco - <%=Services.TextService.Localize("editContentSendToPublish")%> - - - -

Republish <%=Services.TextService.Localize("editContentSendToPublishText")%>

-
- <%=Services.TextService.Localize("closewindow")%> - - diff --git a/src/Umbraco.Web/Components/NotificationsComponent.cs b/src/Umbraco.Web/Components/NotificationsComponent.cs index 27fc604d29..f0605a7673 100644 --- a/src/Umbraco.Web/Components/NotificationsComponent.cs +++ b/src/Umbraco.Web/Components/NotificationsComponent.cs @@ -1,67 +1,175 @@ using System.Collections.Generic; using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.Components; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; using Umbraco.Web._Legacy.Actions; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; +using System.Linq; +using Umbraco.Core.Models.Membership; +using System; +using System.Globalization; namespace Umbraco.Web.Components { [RuntimeLevel(MinLevel = RuntimeLevel.Run)] public sealed class NotificationsComponent : UmbracoComponentBase, IUmbracoCoreComponent { - public void Initialize(INotificationService notificationService) + public override void Compose(Composition composition) { - ContentService.SentToPublish += (sender, args) => - notificationService.SendNotification(args.Entity, ActionToPublish.Instance); + base.Compose(composition); + composition.Container.RegisterSingleton(); + } + + public void Initialize(INotificationService notificationService, Notifier notifier) + { + //Send notifications for the send to publish action + ContentService.SentToPublish += (sender, args) => notifier.Notify(ActionToPublish.Instance, args.Entity); //Send notifications for the published action - ContentService.Published += (sender, args) => - { - foreach (var content in args.PublishedEntities) - notificationService.SendNotification(content, ActionPublish.Instance); - }; + ContentService.Published += (sender, args) => notifier.Notify(ActionPublish.Instance, args.PublishedEntities.ToArray()); + + //Send notifications for the saved action + ContentService.Sorted += (sender, args) => ContentServiceSorted(notifier, sender, args); //Send notifications for the update and created actions - ContentService.Saved += (sender, args) => - { - var newEntities = new List(); - var updatedEntities = new List(); - - //need to determine if this is updating or if it is new - foreach (var entity in args.SavedEntities) - { - var dirty = (IRememberBeingDirty) entity; - if (dirty.WasPropertyDirty("Id")) - { - //it's new - newEntities.Add(entity); - } - else - { - //it's updating - updatedEntities.Add(entity); - } - } - notificationService.SendNotification(newEntities, ActionNew.Instance); - notificationService.SendNotification(updatedEntities, ActionUpdate.Instance); - }; + ContentService.Saved += (sender, args) => ContentServiceSaved(notifier, sender, args); //Send notifications for the delete action - ContentService.Deleted += (sender, args) => - { - foreach (var content in args.DeletedEntities) - notificationService.SendNotification(content, ActionDelete.Instance); - }; - + ContentService.Deleted += (sender, args) => notifier.Notify(ActionDelete.Instance, args.DeletedEntities.ToArray()); + //Send notifications for the unpublish action - ContentService.Unpublished += (sender, args) => + ContentService.Unpublished += (sender, args) => notifier.Notify(ActionUnpublish.Instance, args.PublishedEntities.ToArray()); + } + + private void ContentServiceSorted(Notifier notifier, IContentService sender, Core.Events.SaveEventArgs args) + { + var parentId = args.SavedEntities.Select(x => x.ParentId).Distinct().ToList(); + if (parentId.Count != 1) return; // this shouldn't happen, for sorting all entities will have the same parent id + + // in this case there's nothing to report since if the root is sorted we can't report on a fake entity. + // this is how it was in v7, we can't report on root changes because you can't subscribe to root changes. + if (parentId[0] <= 0) return; + + var parent = sender.GetById(parentId[0]); + if (parent == null) return; // this shouldn't happen + + notifier.Notify(ActionSort.Instance, new[] { parent }); + } + + private void ContentServiceSaved(Notifier notifier, IContentService sender, Core.Events.SaveEventArgs args) + { + var newEntities = new List(); + var updatedEntities = new List(); + + //need to determine if this is updating or if it is new + foreach (var entity in args.SavedEntities) { - foreach (var content in args.PublishedEntities) - notificationService.SendNotification(content, ActionUnpublish.Instance); - }; + var dirty = (IRememberBeingDirty)entity; + if (dirty.WasPropertyDirty("Id")) + { + //it's new + newEntities.Add(entity); + } + else + { + //it's updating + updatedEntities.Add(entity); + } + } + notifier.Notify(ActionNew.Instance, newEntities.ToArray()); + notifier.Notify(ActionUpdate.Instance, updatedEntities.ToArray()); + } + + /// + /// This class is used to send the notifications + /// + public sealed class Notifier + { + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly IRuntimeState _runtimeState; + private readonly INotificationService _notificationService; + private readonly IUserService _userService; + private readonly ILocalizedTextService _textService; + private readonly IGlobalSettings _globalSettings; + private readonly IContentSection _contentConfig; + private readonly ILogger _logger; + + /// + /// Constructor + /// + /// + /// + /// + /// + /// + /// + /// + public Notifier(IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtimeState, INotificationService notificationService, IUserService userService, ILocalizedTextService textService, IGlobalSettings globalSettings, IContentSection contentConfig, ILogger logger) + { + _umbracoContextAccessor = umbracoContextAccessor; + _runtimeState = runtimeState; + _notificationService = notificationService; + _userService = userService; + _textService = textService; + _globalSettings = globalSettings; + _contentConfig = contentConfig; + _logger = logger; + } + + public void Notify(IAction action, params IUmbracoEntity[] entities) + { + IUser user = null; + if (_umbracoContextAccessor.UmbracoContext != null) + { + user = _umbracoContextAccessor.UmbracoContext.Security.CurrentUser; + } + + //if there is no current user, then use the admin + if (user == null) + { + _logger.Debug(typeof(Notifier), "There is no current Umbraco user logged in, the notifications will be sent from the administrator"); + user = _userService.GetUserById(Constants.Security.SuperUserId); + if (user == null) + { + _logger.Warn(typeof(Notifier), "Noticiations can not be sent, no admin user with id {SuperUserId} could be resolved", Constants.Security.SuperUserId); + return; + } + } + + SendNotification(user, entities, action, _runtimeState.ApplicationUrl); + } + + private void SendNotification(IUser sender, IEnumerable entities, IAction action, Uri siteUri) + { + if (sender == null) throw new ArgumentNullException(nameof(sender)); + if (siteUri == null) throw new ArgumentNullException(nameof(siteUri)); + + _notificationService.SendNotifications( + sender, + entities, + action.Letter.ToString(CultureInfo.InvariantCulture), + _textService.Localize("actions", action.Alias), + siteUri, + ((IUser user, NotificationEmailSubjectParams subject) x) + => _textService.Localize( + "notifications/mailSubject", + x.user.GetUserCulture(_textService, _globalSettings), + new[] { x.subject.SiteUrl, x.subject.Action, x.subject.ItemName }), + ((IUser user, NotificationEmailBodyParams body, bool isHtml) x) + => _textService.Localize( + x.isHtml ? "notifications/mailBodyHtml" : "notifications/mailBody", + x.user.GetUserCulture(_textService, _globalSettings), + new[] { x.body.RecipientName, x.body.Action, x.body.ItemName, x.body.EditedUser, x.body.SiteUrl, x.body.ItemId, x.body.Summary, x.body.ItemUrl })); + } + } } + + } diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 6e095e8393..ea5546b6ea 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1068,17 +1068,14 @@ namespace Umbraco.Web.Editors var contentService = Services.ContentService; // Save content with new sort order and update content xml in db accordingly - if (contentService.Sort(sorted.IdSortOrder, Security.CurrentUser.Id) == false) + var sortResult = contentService.Sort(sorted.IdSortOrder, Security.CurrentUser.Id); + if (!sortResult.Success) { Logger.Warn("Content sorting failed, this was probably caused by an event being cancelled"); + //TODO: Now you can cancel sorting, does the event messages bubble up automatically? return Request.CreateValidationErrorResponse("Content sorting failed, this was probably caused by an event being cancelled"); } - if (sorted.ParentId > 0) - { - Services.NotificationService.SendNotification(contentService.GetById(sorted.ParentId), ActionSort.Instance, UmbracoContext, Services.TextService, GlobalSettings); - } - return Request.CreateResponse(HttpStatusCode.OK); } catch (Exception ex) diff --git a/src/Umbraco.Web/NotificationServiceExtensions.cs b/src/Umbraco.Web/NotificationServiceExtensions.cs deleted file mode 100644 index 1fb229a66e..0000000000 --- a/src/Umbraco.Web/NotificationServiceExtensions.cs +++ /dev/null @@ -1,145 +0,0 @@ -using System; -using System.Globalization; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Services; -using Umbraco.Core.Models; -using Umbraco.Web._Legacy.Actions; -using System.Collections.Generic; -using Umbraco.Core.Models.Entities; -using Umbraco.Web.Composing; - -namespace Umbraco.Web -{ - // TODO: all of these require an UmbracoContext because currently to send the notifications we need an HttpContext, this is based on legacy code - // for which probably requires updating so that these can be sent outside of the http context. - - internal static class NotificationServiceExtensions - { - internal static void SendNotification(this INotificationService service, IUmbracoEntity entity, IAction action) - { - if (Current.UmbracoContext == null) - { - Current.Logger.Warn(typeof(NotificationServiceExtensions), "Cannot send notifications, there is no current UmbracoContext"); - return; - } - service.SendNotification(entity, action, Current.UmbracoContext, Current.Services.TextService, UmbracoConfig.For.GlobalSettings()); - } - - internal static void SendNotification(this INotificationService service, IEnumerable entities, IAction action) - { - if (Current.UmbracoContext == null) - { - Current.Logger.Warn(typeof(NotificationServiceExtensions), "Cannot send notifications, there is no current UmbracoContext"); - return; - } - service.SendNotification(entities, action, Current.UmbracoContext, Current.Services.TextService, UmbracoConfig.For.GlobalSettings()); - } - - //internal static void SendNotification(this INotificationService service, IUmbracoEntity entity, IAction action, UmbracoContext umbracoContext) - //{ - // if (umbracoContext == null) - // { - // Current.Logger.Warn(typeof(NotificationServiceExtensions), "Cannot send notifications, there is no current UmbracoContext"); - // return; - // } - // service.SendNotification(entity, action, umbracoContext); - //} - - //internal static void SendNotification(this INotificationService service, IEnumerable entities, IAction action, UmbracoContext umbracoContext) - //{ - // if (umbracoContext == null) - // { - // Current.Logger.Warn(typeof(NotificationServiceExtensions), "Cannot send notifications, there is no current UmbracoContext"); - // return; - // } - // service.SendNotification(entities, action, umbracoContext); - //} - - internal static void SendNotification(this INotificationService service, IUmbracoEntity entity, IAction action, UmbracoContext umbracoContext, ILocalizedTextService textService, IGlobalSettings globalSettings) - { - if (umbracoContext == null) - { - Current.Logger.Warn(typeof(NotificationServiceExtensions), "Cannot send notifications, there is no current UmbracoContext"); - return; - } - - var user = umbracoContext.Security.CurrentUser; - var userService = Current.Services.UserService; // fixme inject - - //if there is no current user, then use the admin - if (user == null) - { - Current.Logger.Debug(typeof(NotificationServiceExtensions), "There is no current Umbraco user logged in, the notifications will be sent from the administrator"); - user = userService.GetUserById(Constants.Security.SuperUserId); - if (user == null) - { - Current.Logger.Warn(typeof(NotificationServiceExtensions), "Noticiations can not be sent, no admin user with id {SuperUserId} could be resolved", Constants.Security.SuperUserId); - return; - } - } - service.SendNotification(user, entity, action, umbracoContext, textService, globalSettings); - } - - internal static void SendNotification(this INotificationService service, IEnumerable entities, IAction action, UmbracoContext umbracoContext, ILocalizedTextService textService, IGlobalSettings globalSettings) - { - if (umbracoContext == null) - { - Current.Logger.Warn(typeof(NotificationServiceExtensions), "Cannot send notifications, there is no current UmbracoContext"); - return; - } - - var user = umbracoContext.Security.CurrentUser; - var userService = Current.Services.UserService; // fixme inject - - //if there is no current user, then use the admin - if (user == null) - { - Current.Logger.Debug(typeof(NotificationServiceExtensions), "There is no current Umbraco user logged in, the notifications will be sent from the administrator"); - user = userService.GetUserById(Constants.Security.SuperUserId); - if (user == null) - { - Current.Logger.Warn(typeof(NotificationServiceExtensions), "Noticiations can not be sent, no admin user with id {SuperUserId} could be resolved", Constants.Security.SuperUserId); - return; - } - } - service.SendNotification(user, entities, action, umbracoContext, textService, globalSettings); - } - - internal static void SendNotification(this INotificationService service, IUser sender, IUmbracoEntity entity, IAction action, UmbracoContext umbracoContext, ILocalizedTextService textService, IGlobalSettings globalSettings) - { - if (sender == null) throw new ArgumentNullException(nameof(sender)); - if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext)); - - service.SendNotifications( - sender, - entity, - action.Letter.ToString(CultureInfo.InvariantCulture), - textService.Localize("actions", action.Alias), - umbracoContext.HttpContext, - (mailingUser, strings) => textService.Localize("notifications/mailSubject", mailingUser.GetUserCulture(textService, globalSettings), strings), - (mailingUser, strings) => UmbracoConfig.For.UmbracoSettings().Content.DisableHtmlEmail - ? textService.Localize("notifications/mailBody", mailingUser.GetUserCulture(textService, globalSettings), strings) - : textService.Localize("notifications/mailBodyHtml", mailingUser.GetUserCulture(textService, globalSettings), strings)); - } - - internal static void SendNotification(this INotificationService service, IUser sender, IEnumerable entities, IAction action, UmbracoContext umbracoContext, ILocalizedTextService textService, IGlobalSettings globalSettings) - { - if (sender == null) throw new ArgumentNullException(nameof(sender)); - if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext)); - - service.SendNotifications( - sender, - entities, - action.Letter.ToString(CultureInfo.InvariantCulture), - textService.Localize("actions", action.Alias), - umbracoContext.HttpContext, - (mailingUser, strings) => textService.Localize("notifications/mailSubject", mailingUser.GetUserCulture(textService, globalSettings), strings), - (mailingUser, strings) => UmbracoConfig.For.UmbracoSettings().Content.DisableHtmlEmail - ? textService.Localize("notifications/mailBody", mailingUser.GetUserCulture(textService, globalSettings), strings) - : textService.Localize("notifications/mailBodyHtml", mailingUser.GetUserCulture(textService, globalSettings), strings)); - } - } -} diff --git a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs index 131c7954c8..663a6904e9 100644 --- a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs +++ b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs @@ -49,6 +49,8 @@ namespace Umbraco.Web.Trees /// internal static Attempt GetUrlAndTitleFromLegacyAction(IAction action, string nodeId, string nodeType, string nodeName, string currentSection) { + //TODO: DO we need any of this anymore? + if (action.JsFunctionName.IsNullOrWhiteSpace()) { return Attempt.Fail(); @@ -71,41 +73,16 @@ namespace Umbraco.Web.Trees new LegacyUrlAction( "dialogs/protectPage.aspx?mode=cut&nodeId=" + nodeId + "&rnd=" + DateTime.UtcNow.Ticks, Current.Services.TextService.Localize("actions/protect"))); - case "UmbClientMgr.appActions().actionRollback()": - return Attempt.Succeed( - new LegacyUrlAction( - "dialogs/rollback.aspx?nodeId=" + nodeId + "&rnd=" + DateTime.UtcNow.Ticks, - Current.Services.TextService.Localize("actions/rollback"))); case "UmbClientMgr.appActions().actionNotify()": return Attempt.Succeed( new LegacyUrlAction( "dialogs/notifications.aspx?id=" + nodeId + "&rnd=" + DateTime.UtcNow.Ticks, Current.Services.TextService.Localize("actions/notify"))); - case "UmbClientMgr.appActions().actionPublish()": - return Attempt.Succeed( - new LegacyUrlAction( - "dialogs/publish.aspx?id=" + nodeId + "&rnd=" + DateTime.UtcNow.Ticks, - Current.Services.TextService.Localize("actions/publish"))); case "UmbClientMgr.appActions().actionChangeDocType()": return Attempt.Succeed( new LegacyUrlAction( "dialogs/ChangeDocType.aspx?id=" + nodeId + "&rnd=" + DateTime.UtcNow.Ticks, Current.Services.TextService.Localize("actions/changeDocType"))); - case "UmbClientMgr.appActions().actionToPublish()": - return Attempt.Succeed( - new LegacyUrlAction( - "dialogs/SendPublish.aspx?id=" + nodeId + "&rnd=" + DateTime.UtcNow.Ticks, - Current.Services.TextService.Localize("actions/sendtopublish"))); - case "UmbClientMgr.appActions().actionRePublish()": - return Attempt.Succeed( - new LegacyUrlAction( - "dialogs/republish.aspx?rnd=" + DateTime.UtcNow.Ticks, - Current.Services.TextService.Localize("actions/republish"))); - case "UmbClientMgr.appActions().actionSendToTranslate()": - return Attempt.Succeed( - new LegacyUrlAction( - "dialogs/sendToTranslation.aspx?id=" + nodeId + "&rnd=" + DateTime.UtcNow.Ticks, - Current.Services.TextService.Localize("actions/sendToTranslate"))); } return Attempt.Fail(); diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 8986f7eff6..0d40846fae 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -777,7 +777,6 @@ - @@ -1295,13 +1294,6 @@ republish.aspx - - SendPublish.aspx - ASPXCodeBehind - - - SendPublish.aspx - editPackage.aspx ASPXCodeBehind @@ -1370,7 +1362,6 @@ ASPXCodeBehind - ASPXCodeBehind diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/SendPublish.aspx b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/SendPublish.aspx deleted file mode 100644 index 8b190d575c..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/SendPublish.aspx +++ /dev/null @@ -1,13 +0,0 @@ -<%@ Page language="c#" Codebehind="SendPublish.aspx.cs" AutoEventWireup="True" Inherits="umbraco.dialogs.SendPublish" %> - - - - umbraco - <%=Services.TextService.Localize("editContentSendToPublish")%> - - - -

Republish <%=Services.TextService.Localize("editContentSendToPublishText")%>

-
- <%=Services.TextService.Localize("closewindow")%> - - diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/SendPublish.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/SendPublish.aspx.cs deleted file mode 100644 index 727af897ca..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/SendPublish.aspx.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using Umbraco.Web; -using Umbraco.Core; -using Umbraco.Web._Legacy.Actions; - -namespace umbraco.dialogs -{ - /// - /// Runs all action handlers for the ActionToPublish action for the document with - /// the corresponding document id passed in by query string - /// - public partial class SendPublish : Umbraco.Web.UI.Pages.UmbracoEnsuredPage - { - public SendPublish() - { - CurrentApp = Constants.Applications.Content.ToString(); - } - - protected void Page_Load(object sender, EventArgs e) - { - if (!string.IsNullOrEmpty(Request.QueryString["id"])) - { - int docId; - if (int.TryParse(Request.QueryString["id"], out docId)) - { - //send notifications! TODO: This should be put somewhere centralized instead of hard coded directly here - Services.NotificationService.SendNotification( - Services.ContentService.GetById(docId), ActionToPublish.Instance); - } - - } - - } - - - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/SendPublish.aspx.designer.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/SendPublish.aspx.designer.cs deleted file mode 100644 index 36e42291f7..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/SendPublish.aspx.designer.cs +++ /dev/null @@ -1,16 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:2.0.50727.4200 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace umbraco.dialogs { - - - public partial class SendPublish { - } -} From b716ed196467708c1af2e164e58cccfea9efa436 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 25 Oct 2018 00:28:37 +1100 Subject: [PATCH 03/10] Fixes UI error when there is an unathorized request, fixes NotificationService to accept IContent since that's all it supports --- src/Umbraco.Core/Services/INotificationService.cs | 2 +- .../Services/Implement/NotificationService.cs | 10 ++-------- .../src/common/interceptors/security.interceptor.js | 7 ++----- src/Umbraco.Web/Components/NotificationsComponent.cs | 4 ++-- 4 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Core/Services/INotificationService.cs b/src/Umbraco.Core/Services/INotificationService.cs index 92a1e93e2f..a990b1e0ff 100644 --- a/src/Umbraco.Core/Services/INotificationService.cs +++ b/src/Umbraco.Core/Services/INotificationService.cs @@ -22,7 +22,7 @@ namespace Umbraco.Core.Services /// /// /// - void SendNotifications(IUser operatingUser, IEnumerable entities, string action, string actionName, Uri siteUri, + void SendNotifications(IUser operatingUser, IEnumerable entities, string action, string actionName, Uri siteUri, Func<(IUser user, NotificationEmailSubjectParams subject), string> createSubject, Func<(IUser user, NotificationEmailBodyParams body, bool isHtml), string> createBody); diff --git a/src/Umbraco.Core/Services/Implement/NotificationService.cs b/src/Umbraco.Core/Services/Implement/NotificationService.cs index 12e575d903..0bc2f993d7 100644 --- a/src/Umbraco.Core/Services/Implement/NotificationService.cs +++ b/src/Umbraco.Core/Services/Implement/NotificationService.cs @@ -66,17 +66,11 @@ namespace Umbraco.Core.Services.Implement /// /// /// - /// - /// Currently this will only work for Content entities! - /// - public void SendNotifications(IUser operatingUser, IEnumerable entities, string action, string actionName, Uri siteUri, + public void SendNotifications(IUser operatingUser, IEnumerable entities, string action, string actionName, Uri siteUri, Func<(IUser user, NotificationEmailSubjectParams subject), string> createSubject, Func<(IUser user, NotificationEmailBodyParams body, bool isHtml), string> createBody) { - if (entities is IEnumerable == false) - throw new NotSupportedException(); - - var entitiesL = entities as List ?? entities.Cast().ToList(); + var entitiesL = entities.ToList(); //exit if there are no entities if (entitiesL.Count == 0) return; diff --git a/src/Umbraco.Web.UI.Client/src/common/interceptors/security.interceptor.js b/src/Umbraco.Web.UI.Client/src/common/interceptors/security.interceptor.js index b636a0a51c..13d5fbc057 100644 --- a/src/Umbraco.Web.UI.Client/src/common/interceptors/security.interceptor.js +++ b/src/Umbraco.Web.UI.Client/src/common/interceptors/security.interceptor.js @@ -102,11 +102,8 @@ //It was decided to just put these messages into the normal status messages. - var msg = "Unauthorized access to URL:
" + rejection.config.url.split('?')[0] + ""; - if (rejection.config.data) { - msg += "
with data:
" + angular.toJson(rejection.config.data) + "
Contact your administrator for information."; - } - + var msg = "Unauthorized access to URL:
" + rejection.config.url.split('?')[0] + "
Contact your administrator for information."; + notificationsService.error("Authorization error", msg); } diff --git a/src/Umbraco.Web/Components/NotificationsComponent.cs b/src/Umbraco.Web/Components/NotificationsComponent.cs index f0605a7673..7eaaf0e18c 100644 --- a/src/Umbraco.Web/Components/NotificationsComponent.cs +++ b/src/Umbraco.Web/Components/NotificationsComponent.cs @@ -122,7 +122,7 @@ namespace Umbraco.Web.Components _logger = logger; } - public void Notify(IAction action, params IUmbracoEntity[] entities) + public void Notify(IAction action, params IContent[] entities) { IUser user = null; if (_umbracoContextAccessor.UmbracoContext != null) @@ -145,7 +145,7 @@ namespace Umbraco.Web.Components SendNotification(user, entities, action, _runtimeState.ApplicationUrl); } - private void SendNotification(IUser sender, IEnumerable entities, IAction action, Uri siteUri) + private void SendNotification(IUser sender, IEnumerable entities, IAction action, Uri siteUri) { if (sender == null) throw new ArgumentNullException(nameof(sender)); if (siteUri == null) throw new ArgumentNullException(nameof(siteUri)); From c3e50ecc48359da9e8cd73dd339efd7f0b258ef4 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 25 Oct 2018 00:45:45 +1100 Subject: [PATCH 04/10] Removes the broken text comparison for RTE --- .../Services/Implement/NotificationService.cs | 115 ++---------------- 1 file changed, 11 insertions(+), 104 deletions(-) diff --git a/src/Umbraco.Core/Services/Implement/NotificationService.cs b/src/Umbraco.Core/Services/Implement/NotificationService.cs index 0bc2f993d7..5d7860026b 100644 --- a/src/Umbraco.Core/Services/Implement/NotificationService.cs +++ b/src/Umbraco.Core/Services/Implement/NotificationService.cs @@ -313,47 +313,15 @@ namespace Umbraco.Core.Services.Implement ReplaceHtmlSymbols(ref newText); } - - // make sure to only highlight changes done using TinyMCE editor... other changes will be displayed using default summary - // TODO: We should probably allow more than just tinymce?? - if ((p.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.TinyMce) - && string.CompareOrdinal(oldText, newText) != 0) - { - summary.Append(""); - summary.Append(" Note: "); - summary.Append( - " Red for deleted characters Yellow for inserted characters"); - summary.Append(""); - summary.Append(""); - summary.Append(" New "); - summary.Append(p.PropertyType.Name); - summary.Append(""); - summary.Append(""); - summary.Append(ReplaceLinks(CompareText(oldText, newText, true, false, "", string.Empty), siteUri)); - summary.Append(""); - summary.Append(""); - summary.Append(""); - summary.Append(" Old "); - summary.Append(p.PropertyType.Name); - summary.Append(""); - summary.Append(""); - summary.Append(ReplaceLinks(CompareText(newText, oldText, true, false, "", string.Empty), siteUri)); - summary.Append(""); - summary.Append(""); - } - else - { - summary.Append(""); - summary.Append(""); - summary.Append(p.PropertyType.Name); - summary.Append(""); - summary.Append(""); - summary.Append(newText); - summary.Append(""); - summary.Append(""); - } - summary.Append( - " "); + //show the values + summary.Append(""); + summary.Append(""); + summary.Append(p.PropertyType.Name); + summary.Append(""); + summary.Append(""); + summary.Append(newText); + summary.Append(""); + summary.Append(""); } var protocol = _globalSettings.UseHttps ? "https" : "http"; @@ -430,6 +398,7 @@ namespace Umbraco.Core.Services.Implement /// The old string. private static void ReplaceHtmlSymbols(ref string oldString) { + if (oldString.IsNullOrWhiteSpace()) return; oldString = oldString.Replace(" ", " "); oldString = oldString.Replace("’", "'"); oldString = oldString.Replace("&", "&"); @@ -437,69 +406,7 @@ namespace Umbraco.Core.Services.Implement oldString = oldString.Replace("”", "”"); oldString = oldString.Replace(""", "\""); } - - /// - /// Compares the text. - /// - /// The old text. - /// The new text. - /// if set to true [display inserted text]. - /// if set to true [display deleted text]. - /// The inserted style. - /// The deleted style. - /// - private static string CompareText(string oldText, string newText, bool displayInsertedText, - bool displayDeletedText, string insertedStyle, string deletedStyle) - { - var sb = new StringBuilder(); - var diffs = Diff.DiffText1(oldText, newText); - - int pos = 0; - for (var n = 0; n < diffs.Length; n++) - { - var it = diffs[n]; - - // write unchanged chars - while ((pos < it.StartB) && (pos < newText.Length)) - { - sb.Append(newText[pos]); - pos++; - } // while - - // write deleted chars - if (displayDeletedText && it.DeletedA > 0) - { - sb.Append(deletedStyle); - for (var m = 0; m < it.DeletedA; m++) - { - sb.Append(oldText[it.StartA + m]); - } // for - sb.Append(""); - } - - // write inserted chars - if (displayInsertedText && pos < it.StartB + it.InsertedB) - { - sb.Append(insertedStyle); - while (pos < it.StartB + it.InsertedB) - { - sb.Append(newText[pos]); - pos++; - } // while - sb.Append(""); - } // if - } // while - - // write rest of unchanged chars - while (pos < newText.Length) - { - sb.Append(newText[pos]); - pos++; - } // while - - return sb.ToString(); - } - + // manage notifications // ideally, would need to use IBackgroundTasks - but they are not part of Core! From 63fbe6365609960c75da7f8c52868a2baf1e1209 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 25 Oct 2018 01:01:42 +1100 Subject: [PATCH 05/10] Fixes js error and removes Current usage --- .../validation/valsubview.directive.js | 128 +++++++++--------- .../Editors/BackOfficeController.cs | 6 +- 2 files changed, 70 insertions(+), 64 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valsubview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valsubview.directive.js index 097602fe20..d5a21e0ba6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valsubview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valsubview.directive.js @@ -5,74 +5,78 @@ * @description Used to show validation warnings for a editor sub view to indicate that the section content has validation errors in its data. * In order for this directive to work, the valFormManager directive must be placed on the containing form. **/ -(function() { - 'use strict'; +(function () { + 'use strict'; - function valSubViewDirective() { + function valSubViewDirective() { - function controller($scope, $element) { - //expose api - return { - valStatusChanged: function(args) { - if (!args.form.$valid) { - var subViewContent = $element.find(".ng-invalid"); + function controller($scope, $element) { + //expose api + return { + valStatusChanged: function (args) { - if (subViewContent.length > 0) { - $scope.model.hasError = true; - $scope.model.errorClass = args.showValidation ? 'show-validation' : null; - } else { - $scope.model.hasError = false; - $scope.model.errorClass = null; + //TODO: Verify this is correct, does $scope.model ever exist? + if ($scope.model) { + if (!args.form.$valid) { + var subViewContent = $element.find(".ng-invalid"); + + if (subViewContent.length > 0) { + $scope.model.hasError = true; + $scope.model.errorClass = args.showValidation ? 'show-validation' : null; + } else { + $scope.model.hasError = false; + $scope.model.errorClass = null; + } + } + else { + $scope.model.hasError = false; + $scope.model.errorClass = null; + } + } + } } - } - else { - $scope.model.hasError = false; - $scope.model.errorClass = null; - } } - } + + function link(scope, el, attr, ctrl) { + + //if there are no containing form or valFormManager controllers, then we do nothing + if (!ctrl || !angular.isArray(ctrl) || ctrl.length !== 2 || !ctrl[0] || !ctrl[1]) { + return; + } + + var valFormManager = ctrl[1]; + scope.model.hasError = false; + + //listen for form validation changes + valFormManager.onValidationStatusChanged(function (evt, args) { + if (!args.form.$valid) { + + var subViewContent = el.find(".ng-invalid"); + + if (subViewContent.length > 0) { + scope.model.hasError = true; + } else { + scope.model.hasError = false; + } + + } + else { + scope.model.hasError = false; + } + }); + + } + + var directive = { + require: ['?^^form', '?^^valFormManager'], + restrict: "A", + link: link, + controller: controller + }; + + return directive; } - function link(scope, el, attr, ctrl) { - - //if there are no containing form or valFormManager controllers, then we do nothing - if (!ctrl || !angular.isArray(ctrl) || ctrl.length !== 2 || !ctrl[0] || !ctrl[1]) { - return; - } - - var valFormManager = ctrl[1]; - scope.model.hasError = false; - - //listen for form validation changes - valFormManager.onValidationStatusChanged(function (evt, args) { - if (!args.form.$valid) { - - var subViewContent = el.find(".ng-invalid"); - - if (subViewContent.length > 0) { - scope.model.hasError = true; - } else { - scope.model.hasError = false; - } - - } - else { - scope.model.hasError = false; - } - }); - - } - - var directive = { - require: ['?^^form', '?^^valFormManager'], - restrict: "A", - link: link, - controller: controller - }; - - return directive; - } - - angular.module('umbraco.directives').directive('valSubView', valSubViewDirective); + angular.module('umbraco.directives').directive('valSubView', valSubViewDirective); })(); diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 15554b7f50..8a81cedc2d 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -49,6 +49,7 @@ namespace Umbraco.Web.Editors { private readonly ManifestParser _manifestParser; private readonly UmbracoFeatures _features; + private readonly IRuntimeState _runtimeState; private BackOfficeUserManager _userManager; private BackOfficeSignInManager _signInManager; @@ -56,10 +57,11 @@ namespace Umbraco.Web.Editors private const string TokenPasswordResetCode = "PasswordResetCode"; private static readonly string[] TempDataTokenNames = { TokenExternalSignInError, TokenPasswordResetCode }; - public BackOfficeController(ManifestParser manifestParser, UmbracoFeatures features) + public BackOfficeController(ManifestParser manifestParser, UmbracoFeatures features, IRuntimeState runtimeState) { _manifestParser = manifestParser; _features = features; + _runtimeState = runtimeState; } protected BackOfficeSignInManager SignInManager => _signInManager ?? (_signInManager = OwinContext.GetBackOfficeSignInManager()); @@ -261,7 +263,7 @@ namespace Umbraco.Web.Editors [MinifyJavaScriptResult(Order = 1)] public JavaScriptResult ServerVariables() { - var serverVars = new BackOfficeServerVariables(Url, Current.RuntimeState, _features, GlobalSettings); + var serverVars = new BackOfficeServerVariables(Url, _runtimeState, _features, GlobalSettings); //cache the result if debugging is disabled var result = HttpContext.IsDebuggingEnabled From 42f0d2b356f02de5e6d156324fa2776100254f6f Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 25 Oct 2018 15:47:25 +1100 Subject: [PATCH 06/10] Fixes dirty tracking with resetting properties --- src/Umbraco.Core/Models/Content.cs | 23 ++++++--------- src/Umbraco.Core/Models/ContentBase.cs | 29 ++++++++----------- src/Umbraco.Core/Models/ContentTypeBase.cs | 28 +++++++----------- .../Models/ContentTypeCompositionBase.cs | 19 +++++------- .../Models/DictionaryTranslation.cs | 19 ++++-------- .../Models/Entities/EntityBase.cs | 21 ++++++++++---- src/Umbraco.Core/Models/File.cs | 21 ++++---------- src/Umbraco.Core/Models/Macro.cs | 24 +++++++-------- src/Umbraco.Core/Models/Member.cs | 19 +++++------- src/Umbraco.Core/Models/Membership/User.cs | 29 ++++++++----------- src/Umbraco.Core/Models/Property.cs | 15 +++------- src/Umbraco.Core/Models/PropertyGroup.cs | 20 +++++-------- src/Umbraco.Core/Models/PropertyType.cs | 17 ++++------- src/Umbraco.Core/Models/PublicAccessEntry.cs | 18 ++++-------- 14 files changed, 117 insertions(+), 185 deletions(-) diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index 3e5becf021..d46e318aea 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -499,28 +499,23 @@ namespace Umbraco.Core.Models return clone; } - public override object DeepClone() + protected override void PerformDeepClone(object clone) { - var clone = (Content) base.DeepClone(); + base.PerformDeepClone(clone); - //turn off change tracking - clone.DisableChangeTracking(); + var clonedContent = (Content)clone; //need to manually clone this since it's not settable - clone._contentType = (IContentType) ContentType.DeepClone(); + clonedContent._contentType = (IContentType) ContentType.DeepClone(); //if culture infos exist then deal with event bindings - if (clone._publishInfos != null) + if (clonedContent._publishInfos != null) { - clone._publishInfos.CollectionChanged -= PublishNamesCollectionChanged; //clear this event handler if any - clone._publishInfos = (ContentCultureInfosCollection) _publishInfos.DeepClone(); //manually deep clone - clone._publishInfos.CollectionChanged += clone.PublishNamesCollectionChanged; //re-assign correct event handler + clonedContent._publishInfos.CollectionChanged -= PublishNamesCollectionChanged; //clear this event handler if any + clonedContent._publishInfos = (ContentCultureInfosCollection) _publishInfos.DeepClone(); //manually deep clone + clonedContent._publishInfos.CollectionChanged += clonedContent.PublishNamesCollectionChanged; //re-assign correct event handler } - - //re-enable tracking - clone.EnableChangeTracking(); - - return clone; + } } } diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index 863374726d..7e70238d2f 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -475,33 +475,28 @@ namespace Umbraco.Core.Models /// /// Overriden to deal with specific object instances /// - public override object DeepClone() + protected override void PerformDeepClone(object clone) { - var clone = (ContentBase) base.DeepClone(); + base.PerformDeepClone(clone); - //turn off change tracking - clone.DisableChangeTracking(); + var clonedContent = (ContentBase)clone; //if culture infos exist then deal with event bindings - if (clone._cultureInfos != null) + if (clonedContent._cultureInfos != null) { - clone._cultureInfos.CollectionChanged -= CultureInfosCollectionChanged; //clear this event handler if any - clone._cultureInfos = (ContentCultureInfosCollection) _cultureInfos.DeepClone(); //manually deep clone - clone._cultureInfos.CollectionChanged += clone.CultureInfosCollectionChanged; //re-assign correct event handler + clonedContent._cultureInfos.CollectionChanged -= CultureInfosCollectionChanged; //clear this event handler if any + clonedContent._cultureInfos = (ContentCultureInfosCollection) _cultureInfos.DeepClone(); //manually deep clone + clonedContent._cultureInfos.CollectionChanged += clonedContent.CultureInfosCollectionChanged; //re-assign correct event handler } //if properties exist then deal with event bindings - if (clone._properties != null) + if (clonedContent._properties != null) { - clone._properties.CollectionChanged -= PropertiesChanged; //clear this event handler if any - clone._properties = (PropertyCollection) _properties.DeepClone(); //manually deep clone - clone._properties.CollectionChanged += clone.PropertiesChanged; //re-assign correct event handler + clonedContent._properties.CollectionChanged -= PropertiesChanged; //clear this event handler if any + clonedContent._properties = (PropertyCollection) _properties.DeepClone(); //manually deep clone + clonedContent._properties.CollectionChanged += clonedContent.PropertiesChanged; //re-assign correct event handler } - - //re-enable tracking - clone.EnableChangeTracking(); - - return clone; + } } } diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index 9f848c6d14..caa63d7526 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -467,35 +467,29 @@ namespace Umbraco.Core.Models } } - public override object DeepClone() + protected override void PerformDeepClone(object clone) { - var clone = (ContentTypeBase) base.DeepClone(); + base.PerformDeepClone(clone); - //turn off change tracking - clone.DisableChangeTracking(); + var clonedEntity = (ContentTypeBase) clone; - if (clone._noGroupPropertyTypes != null) + if (clonedEntity._noGroupPropertyTypes != null) { //need to manually wire up the event handlers for the property type collections - we've ensured // its ignored from the auto-clone process because its return values are unions, not raw and // we end up with duplicates, see: http://issues.umbraco.org/issue/U4-4842 - clone._noGroupPropertyTypes.CollectionChanged -= PropertyTypesChanged; //clear this event handler if any - clone._noGroupPropertyTypes = (PropertyTypeCollection) _noGroupPropertyTypes.DeepClone(); //manually deep clone - clone._noGroupPropertyTypes.CollectionChanged += clone.PropertyTypesChanged; //re-assign correct event handler + clonedEntity._noGroupPropertyTypes.CollectionChanged -= PropertyTypesChanged; //clear this event handler if any + clonedEntity._noGroupPropertyTypes = (PropertyTypeCollection) _noGroupPropertyTypes.DeepClone(); //manually deep clone + clonedEntity._noGroupPropertyTypes.CollectionChanged += clonedEntity.PropertyTypesChanged; //re-assign correct event handler } - if (clone._propertyGroups != null) + if (clonedEntity._propertyGroups != null) { - clone._propertyGroups.CollectionChanged -= PropertyGroupsChanged; //clear this event handler if any - clone._propertyGroups = (PropertyGroupCollection) _propertyGroups.DeepClone(); //manually deep clone - clone._propertyGroups.CollectionChanged += clone.PropertyGroupsChanged; //re-assign correct event handler + clonedEntity._propertyGroups.CollectionChanged -= PropertyGroupsChanged; //clear this event handler if any + clonedEntity._propertyGroups = (PropertyGroupCollection) _propertyGroups.DeepClone(); //manually deep clone + clonedEntity._propertyGroups.CollectionChanged += clonedEntity.PropertyGroupsChanged; //re-assign correct event handler } - - //re-enable tracking - clone.EnableChangeTracking(); - - return clone; } public IContentType DeepCloneWithResetIdentities(string alias) diff --git a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs index 838a75b98b..cf43b661c7 100644 --- a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs @@ -290,20 +290,15 @@ namespace Umbraco.Core.Models .Union(ContentTypeComposition.SelectMany(x => x.CompositionIds())); } - public override object DeepClone() + protected override void PerformDeepClone(object clone) { - var clone = (ContentTypeCompositionBase)base.DeepClone(); - //turn off change tracking - clone.DisableChangeTracking(); - //need to manually assign since this is an internal field and will not be automatically mapped - clone.RemovedContentTypeKeyTracker = new List(); - clone._contentTypeComposition = ContentTypeComposition.Select(x => (IContentTypeComposition)x.DeepClone()).ToList(); - //this shouldn't really be needed since we're not tracking - clone.ResetDirtyProperties(false); - //re-enable tracking - clone.EnableChangeTracking(); + base.PerformDeepClone(clone); - return clone; + var clonedEntity = (ContentTypeCompositionBase)clone; + + //need to manually assign since this is an internal field and will not be automatically mapped + clonedEntity.RemovedContentTypeKeyTracker = new List(); + clonedEntity._contentTypeComposition = ContentTypeComposition.Select(x => (IContentTypeComposition)x.DeepClone()).ToList(); } } } diff --git a/src/Umbraco.Core/Models/DictionaryTranslation.cs b/src/Umbraco.Core/Models/DictionaryTranslation.cs index 2105e8057c..c3b5a8a3b2 100644 --- a/src/Umbraco.Core/Models/DictionaryTranslation.cs +++ b/src/Umbraco.Core/Models/DictionaryTranslation.cs @@ -104,23 +104,14 @@ namespace Umbraco.Core.Models set { SetPropertyValueAndDetectChanges(value, ref _value, Ps.Value.ValueSelector); } } - public override object DeepClone() + protected override void PerformDeepClone(object clone) { - var clone = (DictionaryTranslation)base.DeepClone(); + base.PerformDeepClone(clone); + + var clonedEntity = (DictionaryTranslation)clone; // clear fields that were memberwise-cloned and that we don't want to clone - clone._language = null; - - // turn off change tracking - clone.DisableChangeTracking(); - - // this shouldn't really be needed since we're not tracking - clone.ResetDirtyProperties(false); - - // re-enable tracking - clone.EnableChangeTracking(); - - return clone; + clonedEntity._language = null; } } } diff --git a/src/Umbraco.Core/Models/Entities/EntityBase.cs b/src/Umbraco.Core/Models/Entities/EntityBase.cs index 0b69586abf..5c6f943c60 100644 --- a/src/Umbraco.Core/Models/Entities/EntityBase.cs +++ b/src/Umbraco.Core/Models/Entities/EntityBase.cs @@ -159,7 +159,7 @@ namespace Umbraco.Core.Models.Entities } } - public virtual object DeepClone() + public object DeepClone() { // memberwise-clone (ie shallow clone) the entity var unused = Key; // ensure that 'this' has a key, before cloning @@ -169,20 +169,29 @@ namespace Umbraco.Core.Models.Entities clone.InstanceId = Guid.NewGuid(); #endif - // clear changes (ensures the clone has its own dictionaries) - // then disable change tracking - clone.ResetDirtyProperties(false); + //disable change tracking while we deep clone IDeepCloneable properties clone.DisableChangeTracking(); // deep clone ref properties that are IDeepCloneable DeepCloneHelper.DeepCloneRefProperties(this, clone); - // clear changes again (just to be sure, because we were not tracking) - // then enable change tracking + PerformDeepClone(clone); + + // clear changes (ensures the clone has its own dictionaries) clone.ResetDirtyProperties(false); + + //re-enable change tracking clone.EnableChangeTracking(); return clone; } + + /// + /// Used by inheritors to modify the DeepCloning logic + /// + /// + protected virtual void PerformDeepClone(object clone) + { + } } } diff --git a/src/Umbraco.Core/Models/File.cs b/src/Umbraco.Core/Models/File.cs index 2e85b13261..2f8e021f4c 100644 --- a/src/Umbraco.Core/Models/File.cs +++ b/src/Umbraco.Core/Models/File.cs @@ -156,26 +156,17 @@ namespace Umbraco.Core.Models clone._alias = Alias; } - public override object DeepClone() + protected override void PerformDeepClone(object clone) { - var clone = (File) base.DeepClone(); + base.PerformDeepClone(clone); + + var clonedFile = (File)clone; // clear fields that were memberwise-cloned and that we don't want to clone - clone._content = null; - - // turn off change tracking - clone.DisableChangeTracking(); + clonedFile._content = null; // ... - DeepCloneNameAndAlias(clone); - - // this shouldn't really be needed since we're not tracking - clone.ResetDirtyProperties(false); - - // re-enable tracking - clone.EnableChangeTracking(); - - return clone; + DeepCloneNameAndAlias(clonedFile); } } } diff --git a/src/Umbraco.Core/Models/Macro.cs b/src/Umbraco.Core/Models/Macro.cs index 6e68bda439..5ef49305ac 100644 --- a/src/Umbraco.Core/Models/Macro.cs +++ b/src/Umbraco.Core/Models/Macro.cs @@ -284,22 +284,18 @@ namespace Umbraco.Core.Models get { return _properties; } } - public override object DeepClone() + protected override void PerformDeepClone(object clone) { - var clone = (Macro)base.DeepClone(); - //turn off change tracking - clone.DisableChangeTracking(); - clone._addedProperties = new List(); - clone._removedProperties = new List(); - clone._properties = (MacroPropertyCollection)Properties.DeepClone(); - //re-assign the event handler - clone._properties.CollectionChanged += clone.PropertiesChanged; - //this shouldn't really be needed since we're not tracking - clone.ResetDirtyProperties(false); - //re-enable tracking - clone.EnableChangeTracking(); + base.PerformDeepClone(clone); - return clone; + var clonedEntity = (Macro)clone; + + clonedEntity._addedProperties = new List(); + clonedEntity._removedProperties = new List(); + clonedEntity._properties = (MacroPropertyCollection)Properties.DeepClone(); + //re-assign the event handler + clonedEntity._properties.CollectionChanged += clonedEntity.PropertiesChanged; + } } } diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index 7576f01ce0..38927898cf 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -598,20 +598,15 @@ namespace Umbraco.Core.Models return true; } - public override object DeepClone() + protected override void PerformDeepClone(object clone) { - var clone = (Member)base.DeepClone(); - //turn off change tracking - clone.DisableChangeTracking(); + base.PerformDeepClone(clone); + + var clonedEntity = (Member)clone; + //need to manually clone this since it's not settable - clone._contentType = (IMemberType)ContentType.DeepClone(); - //this shouldn't really be needed since we're not tracking - clone.ResetDirtyProperties(false); - //re-enable tracking - clone.EnableChangeTracking(); - - return clone; - + clonedEntity._contentType = (IMemberType)ContentType.DeepClone(); + } /// diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index 9066674193..0694194996 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -448,18 +448,19 @@ namespace Umbraco.Core.Models.Membership [DoNotClone] internal object AdditionalDataLock { get { return _additionalDataLock; } } - public override object DeepClone() + protected override void PerformDeepClone(object clone) { - var clone = (User)base.DeepClone(); - //turn off change tracking - clone.DisableChangeTracking(); + base.PerformDeepClone(clone); + + var clonedEntity = (User)clone; + //manually clone the start node props - clone._startContentIds = _startContentIds.ToArray(); - clone._startMediaIds = _startMediaIds.ToArray(); + clonedEntity._startContentIds = _startContentIds.ToArray(); + clonedEntity._startMediaIds = _startMediaIds.ToArray(); // this value has been cloned and points to the same object // which obviously is bad - needs to point to a new object - clone._additionalDataLock = new object(); + clonedEntity._additionalDataLock = new object(); if (_additionalData != null) { @@ -467,7 +468,7 @@ namespace Umbraco.Core.Models.Membership // changing one clone impacts all of them - so we need to reset it with a fresh // dictionary that will contain the same values - and, if some values are deep // cloneable, they should be deep-cloned too - var cloneAdditionalData = clone._additionalData = new Dictionary(); + var cloneAdditionalData = clonedEntity._additionalData = new Dictionary(); lock (_additionalDataLock) { @@ -480,15 +481,9 @@ namespace Umbraco.Core.Models.Membership } //need to create new collections otherwise they'll get copied by ref - clone._userGroups = new HashSet(_userGroups); - clone._allowedSections = _allowedSections != null ? new List(_allowedSections) : null; - //re-create the event handler - //this shouldn't really be needed since we're not tracking - clone.ResetDirtyProperties(false); - //re-enable tracking - clone.EnableChangeTracking(); - - return clone; + clonedEntity._userGroups = new HashSet(_userGroups); + clonedEntity._allowedSections = _allowedSections != null ? new List(_allowedSections) : null; + } /// diff --git a/src/Umbraco.Core/Models/Property.cs b/src/Umbraco.Core/Models/Property.cs index 8a97dc2cfc..0c71544111 100644 --- a/src/Umbraco.Core/Models/Property.cs +++ b/src/Umbraco.Core/Models/Property.cs @@ -392,21 +392,14 @@ namespace Umbraco.Core.Models return PropertyType.IsPropertyValueValid(value); } - public override object DeepClone() + protected override void PerformDeepClone(object clone) { - var clone = (Property) base.DeepClone(); + base.PerformDeepClone(clone); - //turn off change tracking - clone.DisableChangeTracking(); + var clonedEntity = (Property)clone; //need to manually assign since this is a readonly property - clone.PropertyType = (PropertyType) PropertyType.DeepClone(); - - //re-enable tracking - clone.ResetDirtyProperties(false); // not needed really, since we're not tracking - clone.EnableChangeTracking(); - - return clone; + clonedEntity.PropertyType = (PropertyType) PropertyType.DeepClone(); } } } diff --git a/src/Umbraco.Core/Models/PropertyGroup.cs b/src/Umbraco.Core/Models/PropertyGroup.cs index 6c1f2e5c61..1d0b949932 100644 --- a/src/Umbraco.Core/Models/PropertyGroup.cs +++ b/src/Umbraco.Core/Models/PropertyGroup.cs @@ -100,24 +100,18 @@ namespace Umbraco.Core.Models return baseHash ^ nameHash; } - public override object DeepClone() + protected override void PerformDeepClone(object clone) { - var clone = (PropertyGroup)base.DeepClone(); + base.PerformDeepClone(clone); - //turn off change tracking - clone.DisableChangeTracking(); + var clonedEntity = (PropertyGroup)clone; - if (clone._propertyTypes != null) + if (clonedEntity._propertyTypes != null) { - clone._propertyTypes.CollectionChanged -= PropertyTypesChanged; //clear this event handler if any - clone._propertyTypes = (PropertyTypeCollection) _propertyTypes.DeepClone(); //manually deep clone - clone._propertyTypes.CollectionChanged += clone.PropertyTypesChanged; //re-assign correct event handler + clonedEntity._propertyTypes.CollectionChanged -= PropertyTypesChanged; //clear this event handler if any + clonedEntity._propertyTypes = (PropertyTypeCollection) _propertyTypes.DeepClone(); //manually deep clone + clonedEntity._propertyTypes.CollectionChanged += clonedEntity.PropertyTypesChanged; //re-assign correct event handler } - - //re-enable tracking - clone.EnableChangeTracking(); - - return clone; } } } diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index a34fdb04ed..d44e7d464f 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -424,22 +424,17 @@ namespace Umbraco.Core.Models } /// - public override object DeepClone() + protected override void PerformDeepClone(object clone) { - var clone = (PropertyType)base.DeepClone(); - //turn off change tracking - clone.DisableChangeTracking(); + base.PerformDeepClone(clone); + + var clonedEntity = (PropertyType)clone; + //need to manually assign the Lazy value as it will not be automatically mapped if (PropertyGroupId != null) { - clone._propertyGroupId = new Lazy(() => PropertyGroupId.Value); + clonedEntity._propertyGroupId = new Lazy(() => PropertyGroupId.Value); } - //this shouldn't really be needed since we're not tracking - clone.ResetDirtyProperties(false); - //re-enable tracking - clone.EnableChangeTracking(); - - return clone; } } } diff --git a/src/Umbraco.Core/Models/PublicAccessEntry.cs b/src/Umbraco.Core/Models/PublicAccessEntry.cs index 7fd0849e27..e93dc56e35 100644 --- a/src/Umbraco.Core/Models/PublicAccessEntry.cs +++ b/src/Umbraco.Core/Models/PublicAccessEntry.cs @@ -153,23 +153,17 @@ namespace Umbraco.Core.Models } } - public override object DeepClone() + protected override void PerformDeepClone(object clone) { - var clone = (PublicAccessEntry) base.DeepClone(); + base.PerformDeepClone(clone); - //turn off change tracking - clone.DisableChangeTracking(); + var cloneEntity = (PublicAccessEntry)clone; - if (clone._ruleCollection != null) + if (cloneEntity._ruleCollection != null) { - clone._ruleCollection.CollectionChanged -= _ruleCollection_CollectionChanged; //clear this event handler if any - clone._ruleCollection.CollectionChanged += clone._ruleCollection_CollectionChanged; //re-assign correct event handler + cloneEntity._ruleCollection.CollectionChanged -= _ruleCollection_CollectionChanged; //clear this event handler if any + cloneEntity._ruleCollection.CollectionChanged += cloneEntity._ruleCollection_CollectionChanged; //re-assign correct event handler } - - //re-enable tracking - clone.EnableChangeTracking(); - - return clone; } } } From de313a5d195596b6b5214f075462bca33a99a7a6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 25 Oct 2018 16:09:58 +1100 Subject: [PATCH 07/10] Adds tests for verifying remember dirty properties on content --- src/Umbraco.Core/Models/Content.cs | 3 + src/Umbraco.Tests/Models/ContentTests.cs | 104 +++++++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index d46e318aea..dfd0313078 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -467,6 +467,9 @@ namespace Umbraco.Core.Models { base.ResetDirtyProperties(rememberDirty); + Template.ResetDirtyProperties(rememberDirty); + ContentType.ResetDirtyProperties(rememberDirty); + // take care of the published state _publishedState = _published ? PublishedState.Published : PublishedState.Unpublished; diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs index 807231730b..31110d7196 100644 --- a/src/Umbraco.Tests/Models/ContentTests.cs +++ b/src/Umbraco.Tests/Models/ContentTests.cs @@ -271,8 +271,13 @@ namespace Umbraco.Tests.Models // Arrange var contentType = MockedContentTypes.CreateTextpageContentType(); contentType.Id = 99; + contentType.Variations = ContentVariation.Culture; var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); + content.SetCultureName("Hello", "en-US"); + content.SetCultureName("World", "es-ES"); + content.PublishCulture("en-US"); + // should not try to clone something that's not Published or Unpublished // (and in fact it will not work) // but we cannot directly set the state to Published - hence this trick @@ -301,6 +306,8 @@ namespace Umbraco.Tests.Models content.UpdateDate = DateTime.Now; content.WriterId = 23; + + // Act var clone = (Content)content.DeepClone(); @@ -349,6 +356,22 @@ namespace Umbraco.Tests.Models Assert.AreEqual(clone.Properties[index], content.Properties[index]); } + Assert.AreNotSame(clone.PublishCultureInfos, content.PublishCultureInfos); + Assert.AreEqual(clone.PublishCultureInfos.Count, content.PublishCultureInfos.Count); + foreach (var key in content.PublishCultureInfos.Keys) + { + Assert.AreNotSame(clone.PublishCultureInfos[key], content.PublishCultureInfos[key]); + Assert.AreEqual(clone.PublishCultureInfos[key], content.PublishCultureInfos[key]); + } + + Assert.AreNotSame(clone.CultureInfos, content.CultureInfos); + Assert.AreEqual(clone.CultureInfos.Count, content.CultureInfos.Count); + foreach (var key in content.CultureInfos.Keys) + { + Assert.AreNotSame(clone.CultureInfos[key], content.CultureInfos[key]); + Assert.AreEqual(clone.CultureInfos[key], content.CultureInfos[key]); + } + //This double verifies by reflection var allProps = clone.GetType().GetProperties(); foreach (var propertyInfo in allProps) @@ -369,6 +392,87 @@ namespace Umbraco.Tests.Models Assert.IsTrue(asDirty.IsPropertyDirty("Properties")); } + [Test] + public void Remember_Dirty_Properties() + { + // Arrange + var contentType = MockedContentTypes.CreateTextpageContentType(); + contentType.Id = 99; + contentType.Variations = ContentVariation.Culture; + var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); + + content.SetCultureName("Hello", "en-US"); + content.SetCultureName("World", "es-ES"); + content.PublishCulture("en-US"); + + var i = 200; + foreach (var property in content.Properties) + { + property.Id = ++i; + } + content.Id = 10; + content.CreateDate = DateTime.Now; + content.CreatorId = 22; + content.ExpireDate = DateTime.Now; + content.Key = Guid.NewGuid(); + content.Level = 3; + content.Path = "-1,4,10"; + content.ReleaseDate = DateTime.Now; + content.SortOrder = 5; + content.Template = new Template((string)"Test Template", (string)"testTemplate") + { + Id = 88 + }; + + content.Trashed = true; + content.UpdateDate = DateTime.Now; + content.WriterId = 23; + + content.Template.UpdateDate = DateTime.Now; //update a child object + content.ContentType.UpdateDate = DateTime.Now; //update a child object + + // Act + content.ResetDirtyProperties(); + + // Assert + Assert.IsTrue(content.WasDirty()); + Assert.IsTrue(content.WasPropertyDirty("Id")); + Assert.IsTrue(content.WasPropertyDirty("CreateDate")); + Assert.IsTrue(content.WasPropertyDirty("CreatorId")); + Assert.IsTrue(content.WasPropertyDirty("ExpireDate")); + Assert.IsTrue(content.WasPropertyDirty("Key")); + Assert.IsTrue(content.WasPropertyDirty("Level")); + Assert.IsTrue(content.WasPropertyDirty("Path")); + Assert.IsTrue(content.WasPropertyDirty("ReleaseDate")); + Assert.IsTrue(content.WasPropertyDirty("SortOrder")); + Assert.IsTrue(content.WasPropertyDirty("Template")); + Assert.IsTrue(content.WasPropertyDirty("Trashed")); + Assert.IsTrue(content.WasPropertyDirty("UpdateDate")); + Assert.IsTrue(content.WasPropertyDirty("WriterId")); + foreach (var prop in content.Properties) + { + Assert.IsTrue(prop.WasDirty()); + Assert.IsTrue(prop.WasPropertyDirty("Id")); + } + Assert.IsTrue(content.WasPropertyDirty("CultureInfos")); + foreach(var culture in content.CultureInfos) + { + Assert.IsTrue(culture.Value.WasDirty()); + Assert.IsTrue(culture.Value.WasPropertyDirty("Name")); + Assert.IsTrue(culture.Value.WasPropertyDirty("Date")); + } + Assert.IsTrue(content.WasPropertyDirty("PublishCultureInfos")); + foreach (var culture in content.PublishCultureInfos) + { + Assert.IsTrue(culture.Value.WasDirty()); + Assert.IsTrue(culture.Value.WasPropertyDirty("Name")); + Assert.IsTrue(culture.Value.WasPropertyDirty("Date")); + } + //verify child objects were reset too + Assert.IsTrue(content.Template.WasPropertyDirty("UpdateDate")); + Assert.IsTrue(content.ContentType.WasPropertyDirty("UpdateDate")); + } + [Test] public void Can_Serialize_Without_Error() { From ca6fd56f09d6cbe27dd44cf1379d89cb6d23b02c Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 25 Oct 2018 17:21:10 +1100 Subject: [PATCH 08/10] Gets send to publish emails working for variants --- src/Umbraco.Core/Models/Content.cs | 6 +- .../Models/NotificationEmailBodyParams.cs | 4 + .../Services/Implement/NotificationService.cs | 106 +++++++++++++----- src/Umbraco.Tests/TestHelpers/TestObjects.cs | 4 +- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 10 +- .../Umbraco/config/lang/en_us.xml | 10 +- .../Components/NotificationsComponent.cs | 52 ++++++--- 7 files changed, 141 insertions(+), 51 deletions(-) diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index dfd0313078..b048998691 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -467,8 +467,10 @@ namespace Umbraco.Core.Models { base.ResetDirtyProperties(rememberDirty); - Template.ResetDirtyProperties(rememberDirty); - ContentType.ResetDirtyProperties(rememberDirty); + if (Template != null) + Template.ResetDirtyProperties(rememberDirty); + if (ContentType != null) + ContentType.ResetDirtyProperties(rememberDirty); // take care of the published state _publishedState = _published ? PublishedState.Published : PublishedState.Unpublished; diff --git a/src/Umbraco.Core/Models/NotificationEmailBodyParams.cs b/src/Umbraco.Core/Models/NotificationEmailBodyParams.cs index ff316b1cf7..e85284fe5a 100644 --- a/src/Umbraco.Core/Models/NotificationEmailBodyParams.cs +++ b/src/Umbraco.Core/Models/NotificationEmailBodyParams.cs @@ -21,6 +21,10 @@ namespace Umbraco.Core.Models public string ItemName { get; } public string ItemId { get; } public string ItemUrl { get; } + + /// + /// This will either be an HTML or text based summary depending on the email type being sent + /// public string Summary { get; } public string EditedUser { get; } public string SiteUrl { get; } diff --git a/src/Umbraco.Core/Services/Implement/NotificationService.cs b/src/Umbraco.Core/Services/Implement/NotificationService.cs index 5d7860026b..ef2bfafcf6 100644 --- a/src/Umbraco.Core/Services/Implement/NotificationService.cs +++ b/src/Umbraco.Core/Services/Implement/NotificationService.cs @@ -24,12 +24,13 @@ namespace Umbraco.Core.Services.Implement private readonly IScopeProvider _uowProvider; private readonly IUserService _userService; private readonly IContentService _contentService; + private readonly ILocalizationService _localizationService; private readonly INotificationsRepository _notificationsRepository; private readonly IGlobalSettings _globalSettings; private readonly IContentSection _contentSection; private readonly ILogger _logger; - public NotificationService(IScopeProvider provider, IUserService userService, IContentService contentService, + public NotificationService(IScopeProvider provider, IUserService userService, IContentService contentService, ILocalizationService localizationService, ILogger logger, INotificationsRepository notificationsRepository, IGlobalSettings globalSettings, IContentSection contentSection) { _notificationsRepository = notificationsRepository; @@ -38,6 +39,7 @@ namespace Umbraco.Core.Services.Implement _uowProvider = provider ?? throw new ArgumentNullException(nameof(provider)); _userService = userService ?? throw new ArgumentNullException(nameof(userService)); _contentService = contentService ?? throw new ArgumentNullException(nameof(contentService)); + _localizationService = localizationService; _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } @@ -279,7 +281,7 @@ namespace Umbraco.Core.Services.Implement /// /// Callback to create the mail subject /// Callback to create the mail body - private NotificationRequest CreateNotificationRequest(IUser performingUser, IUser mailingUser, IContentBase content, IContentBase oldDoc, + private NotificationRequest CreateNotificationRequest(IUser performingUser, IUser mailingUser, IContent content, IContentBase oldDoc, string actionName, Uri siteUri, Func<(IUser user, NotificationEmailSubjectParams subject), string> createSubject, @@ -294,34 +296,88 @@ namespace Umbraco.Core.Services.Implement // build summary var summary = new StringBuilder(); - var props = content.Properties.ToArray(); - foreach (var p in props) + + if (content.ContentType.VariesByNothing()) { - //fixme doesn't take into account variants - - var newText = p.GetValue() != null ? p.GetValue().ToString() : ""; - var oldText = newText; - - // check if something was changed and display the changes otherwise display the fields - if (oldDoc.Properties.Contains(p.PropertyType.Alias)) + if (!_contentSection.DisableHtmlEmail) { - var oldProperty = oldDoc.Properties[p.PropertyType.Alias]; - oldText = oldProperty.GetValue() != null ? oldProperty.GetValue().ToString() : ""; + //create the html summary for invariant content - // replace html with char equivalent - ReplaceHtmlSymbols(ref oldText); - ReplaceHtmlSymbols(ref newText); + //list all of the property values like we used to + summary.Append(""); + foreach (var p in content.Properties) + { + //fixme doesn't take into account variants + + var newText = p.GetValue() != null ? p.GetValue().ToString() : ""; + var oldText = newText; + + // check if something was changed and display the changes otherwise display the fields + if (oldDoc.Properties.Contains(p.PropertyType.Alias)) + { + var oldProperty = oldDoc.Properties[p.PropertyType.Alias]; + oldText = oldProperty.GetValue() != null ? oldProperty.GetValue().ToString() : ""; + + // replace html with char equivalent + ReplaceHtmlSymbols(ref oldText); + ReplaceHtmlSymbols(ref newText); + } + + //show the values + summary.Append(""); + summary.Append(""); + summary.Append(""); + summary.Append(""); + } + summary.Append("
"); + summary.Append(p.PropertyType.Name); + summary.Append(""); + summary.Append(newText); + summary.Append("
"); } + + } + else if (content.ContentType.VariesByCulture()) + { + //it's variant, so detect what cultures have changed - //show the values - summary.Append(""); - summary.Append(""); - summary.Append(p.PropertyType.Name); - summary.Append(""); - summary.Append(""); - summary.Append(newText); - summary.Append(""); - summary.Append(""); + if (!_contentSection.DisableHtmlEmail) + { + //Create the html based summary (ul of culture names) + + var culturesChanged = content.CultureInfos.Where(x => x.Value.WasDirty()) + .Select(x => x.Key) + .Select(_localizationService.GetLanguageByIsoCode) + .WhereNotNull() + .Select(x => x.CultureName); + summary.Append("
    "); + foreach (var culture in culturesChanged) + { + summary.Append("
  • "); + summary.Append(culture); + summary.Append("
  • "); + } + summary.Append("
"); + } + else + { + //Create the text based summary (csv of culture names) + + var culturesChanged = string.Join(", ", content.CultureInfos.Where(x => x.Value.WasDirty()) + .Select(x => x.Key) + .Select(_localizationService.GetLanguageByIsoCode) + .WhereNotNull() + .Select(x => x.CultureName)); + + summary.Append("'"); + summary.Append(culturesChanged); + summary.Append("'"); + } + } + else + { + //not supported yet... + throw new NotSupportedException(); } var protocol = _globalSettings.UseHttps ? "https" : "http"; diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index 6d0d14ce11..088a696986 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -160,10 +160,11 @@ namespace Umbraco.Tests.TestHelpers var runtimeState = Mock.Of(); var idkMap = new IdkMap(scopeProvider); + var localizationService = GetLazyService(container, c => new LocalizationService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c))); var userService = GetLazyService(container, c => new UserService(scopeProvider, logger, eventMessagesFactory, runtimeState, GetRepo(c), GetRepo(c),globalSettings)); var dataTypeService = GetLazyService(container, c => new DataTypeService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); var contentService = GetLazyService(container, c => new ContentService(scopeProvider, logger, eventMessagesFactory, mediaFileSystem, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); - var notificationService = GetLazyService(container, c => new NotificationService(scopeProvider, userService.Value, contentService.Value, logger, GetRepo(c), globalSettings, umbracoSettings.Content)); + var notificationService = GetLazyService(container, c => new NotificationService(scopeProvider, userService.Value, contentService.Value, localizationService.Value, logger, GetRepo(c), globalSettings, umbracoSettings.Content)); var serverRegistrationService = GetLazyService(container, c => new ServerRegistrationService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); var memberGroupService = GetLazyService(container, c => new MemberGroupService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); var memberService = GetLazyService(container, c => new MemberService(scopeProvider, logger, eventMessagesFactory, memberGroupService.Value, mediaFileSystem, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); @@ -171,7 +172,6 @@ namespace Umbraco.Tests.TestHelpers var contentTypeService = GetLazyService(container, c => new ContentTypeService(scopeProvider, logger, eventMessagesFactory, contentService.Value, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); var mediaTypeService = GetLazyService(container, c => new MediaTypeService(scopeProvider, logger, eventMessagesFactory, mediaService.Value, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); var fileService = GetLazyService(container, c => new FileService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); - var localizationService = GetLazyService(container, c => new LocalizationService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c))); var memberTypeService = GetLazyService(container, c => new MemberTypeService(scopeProvider, logger, eventMessagesFactory, memberService.Value, GetRepo(c), GetRepo(c), GetRepo(c))); var entityService = GetLazyService(container, c => new EntityService( diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index e852be1a4e..cd1591df8d 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -943,10 +943,13 @@ To manage your website, simply open the Umbraco back office and start adding con Go to http://%4%/#/content/content/edit/%5% to edit. + %6% + Have a nice day! Cheers from the Umbraco robot ]]> + The following languages have been modified %0% @@ -1002,9 +1005,7 @@ To manage your website, simply open the Umbraco back office and start adding con

Update summary:

- - %6% -
+ %6%

Have a nice day!

@@ -1025,6 +1026,9 @@ To manage your website, simply open the Umbraco back office and start adding con ]]> + The following languages have been modified:

+ %0% + ]]>
[%0%] Notification about %1% performed on %2% Notifications diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index 54d86eee5c..c6574198e8 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -963,10 +963,13 @@ To manage your website, simply open the Umbraco back office and start adding con Go to http://%4%/#/content/content/edit/%5% to edit. + %6% + Have a nice day! Cheers from the Umbraco robot ]]> + The following languages have been modified %0% @@ -1022,9 +1025,7 @@ To manage your website, simply open the Umbraco back office and start adding con

Update summary:

- - %6% -
+ %6%

Have a nice day!

@@ -1045,6 +1046,9 @@ To manage your website, simply open the Umbraco back office and start adding con ]]> + The following languages have been modified:

+ %0% + ]]>
[%0%] Notification about %1% performed on %2% Notifications diff --git a/src/Umbraco.Web/Components/NotificationsComponent.cs b/src/Umbraco.Web/Components/NotificationsComponent.cs index 7eaaf0e18c..40c19d0df4 100644 --- a/src/Umbraco.Web/Components/NotificationsComponent.cs +++ b/src/Umbraco.Web/Components/NotificationsComponent.cs @@ -150,22 +150,42 @@ namespace Umbraco.Web.Components if (sender == null) throw new ArgumentNullException(nameof(sender)); if (siteUri == null) throw new ArgumentNullException(nameof(siteUri)); - _notificationService.SendNotifications( - sender, - entities, - action.Letter.ToString(CultureInfo.InvariantCulture), - _textService.Localize("actions", action.Alias), - siteUri, - ((IUser user, NotificationEmailSubjectParams subject) x) - => _textService.Localize( - "notifications/mailSubject", - x.user.GetUserCulture(_textService, _globalSettings), - new[] { x.subject.SiteUrl, x.subject.Action, x.subject.ItemName }), - ((IUser user, NotificationEmailBodyParams body, bool isHtml) x) - => _textService.Localize( - x.isHtml ? "notifications/mailBodyHtml" : "notifications/mailBody", - x.user.GetUserCulture(_textService, _globalSettings), - new[] { x.body.RecipientName, x.body.Action, x.body.ItemName, x.body.EditedUser, x.body.SiteUrl, x.body.ItemId, x.body.Summary, x.body.ItemUrl })); + //group by the content type variation since the emails will be different + foreach(var contentVariantGroup in entities.GroupBy(x => x.ContentType.Variations)) + { + if (contentVariantGroup.Key == ContentVariation.CultureAndSegment || contentVariantGroup.Key == ContentVariation.Segment) + throw new NotSupportedException("Segments are not yet supported in Umbraco"); + + _notificationService.SendNotifications( + sender, + contentVariantGroup, + action.Letter.ToString(CultureInfo.InvariantCulture), + _textService.Localize("actions", action.Alias), + siteUri, + ((IUser user, NotificationEmailSubjectParams subject) x) + => _textService.Localize( + "notifications/mailSubject", + x.user.GetUserCulture(_textService, _globalSettings), + new[] { x.subject.SiteUrl, x.subject.Action, x.subject.ItemName }), + ((IUser user, NotificationEmailBodyParams body, bool isHtml) x) + => _textService.Localize( + x.isHtml ? "notifications/mailBodyHtml" : "notifications/mailBody", + x.user.GetUserCulture(_textService, _globalSettings), + new[] + { + x.body.RecipientName, + x.body.Action, + x.body.ItemName, + x.body.EditedUser, + x.body.SiteUrl, + x.body.ItemId, + //format the summary depending on if it's variant or not + contentVariantGroup.Key == ContentVariation.Culture + ? (x.isHtml ? _textService.Localize("notifications/mailBodyVariantHtmlSummary", new[]{ x.body.Summary }) : _textService.Localize("notifications/mailBodyVariantSummary", new []{ x.body.Summary })) + : x.body.Summary, + x.body.ItemUrl + })); + } } } From 853fbdaa614a285fbc8eace1f997ae7942bb1938 Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 2 Nov 2018 10:27:12 +0100 Subject: [PATCH 09/10] Typo --- src/Umbraco.Web/Components/NotificationsComponent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Components/NotificationsComponent.cs b/src/Umbraco.Web/Components/NotificationsComponent.cs index 5623a09581..38ec283cfe 100644 --- a/src/Umbraco.Web/Components/NotificationsComponent.cs +++ b/src/Umbraco.Web/Components/NotificationsComponent.cs @@ -137,7 +137,7 @@ namespace Umbraco.Web.Components user = _userService.GetUserById(Constants.Security.SuperUserId); if (user == null) { - _logger.Warn(typeof(Notifier), "Noticiations can not be sent, no admin user with id {SuperUserId} could be resolved", Constants.Security.SuperUserId); + _logger.Warn(typeof(Notifier), "Notifications can not be sent, no admin user with id {SuperUserId} could be resolved", Constants.Security.SuperUserId); return; } } From 61b0c4916028cb04df9531e1189af7199f90e150 Mon Sep 17 00:00:00 2001 From: Claus Date: Tue, 6 Nov 2018 14:19:10 +0100 Subject: [PATCH 10/10] updating comment. --- src/Umbraco.Core/Services/Implement/ContentService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 87730d165e..e48ba98dfc 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -1952,7 +1952,7 @@ namespace Umbraco.Core.Services.Implement /// /// /// - /// True if sorting succeeded, otherwise False + /// Result indicating what action was taken when handling the command. public OperationResult Sort(IEnumerable items, int userId = 0, bool raiseEvents = true) { var evtMsgs = EventMessagesFactory.Get(); @@ -1981,7 +1981,7 @@ namespace Umbraco.Core.Services.Implement /// /// /// - /// True if sorting succeeded, otherwise False + /// Result indicating what action was taken when handling the command. public OperationResult Sort(IEnumerable ids, int userId = 0, bool raiseEvents = true) { var evtMsgs = EventMessagesFactory.Get();