From adae4325c9735c0b10c3b40f916ea2f42b7a574e Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 24 Oct 2018 19:07:35 +1100 Subject: [PATCH 01/71] 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/71] 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/71] 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/71] 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/71] 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/71] 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/71] 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/71] 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 68f05e05d1ecb2e5676439750212381040521268 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sat, 27 Oct 2018 21:18:03 +0200 Subject: [PATCH 09/71] Initial create and edit --- src/Umbraco.Core/IO/SystemDirectories.cs | 2 + src/Umbraco.Core/Models/Stylesheet.cs | 2 +- .../views/stylesheets/create.controller.js | 18 ++ .../src/views/stylesheets/create.html | 23 ++ .../src/views/stylesheets/edit.controller.js | 205 ++++++++++++++++++ .../src/views/stylesheets/edit.html | 58 +++++ .../Umbraco/config/lang/en_us.xml | 1 + src/Umbraco.Web/Editors/CodeFileController.cs | 79 ++++++- .../Models/Mapping/CodeFileMapperProfile.cs | 7 + 9 files changed, 389 insertions(+), 6 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/views/stylesheets/create.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/stylesheets/create.html create mode 100644 src/Umbraco.Web.UI.Client/src/views/stylesheets/edit.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/stylesheets/edit.html diff --git a/src/Umbraco.Core/IO/SystemDirectories.cs b/src/Umbraco.Core/IO/SystemDirectories.cs index a0fa732c8c..46c5bf851c 100644 --- a/src/Umbraco.Core/IO/SystemDirectories.cs +++ b/src/Umbraco.Core/IO/SystemDirectories.cs @@ -36,6 +36,8 @@ namespace Umbraco.Core.IO public static string Scripts => IOHelper.ReturnPath("umbracoScriptsPath", "~/scripts"); + public static string StyleSheets => IOHelper.ReturnPath("umbracoStylesheetsPath", "~/css"); + public static string Umbraco => IOHelper.ReturnPath("umbracoPath", "~/umbraco"); //TODO: Consider removing this diff --git a/src/Umbraco.Core/Models/Stylesheet.cs b/src/Umbraco.Core/Models/Stylesheet.cs index a228b70105..87632fac27 100644 --- a/src/Umbraco.Core/Models/Stylesheet.cs +++ b/src/Umbraco.Core/Models/Stylesheet.cs @@ -21,7 +21,7 @@ namespace Umbraco.Core.Models { } internal Stylesheet(string path, Func getFileContent) - : base(path.EnsureEndsWith(".css"), getFileContent) + : base(string.IsNullOrEmpty(path) ? path : path.EnsureEndsWith(".css"), getFileContent) { InitializeProperties(); } diff --git a/src/Umbraco.Web.UI.Client/src/views/stylesheets/create.controller.js b/src/Umbraco.Web.UI.Client/src/views/stylesheets/create.controller.js new file mode 100644 index 0000000000..229587cc63 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/stylesheets/create.controller.js @@ -0,0 +1,18 @@ +(function () { + "use strict"; + + function StyleSheetsCreateController($scope, $location, navigationService) { + + var vm = this; + var node = $scope.dialogOptions.currentNode; + + vm.createFile = createFile; + + function createFile() { + $location.path("/settings/stylesheets/edit/" + node.id).search("create", "true"); + navigationService.hideMenu(); + } + } + + angular.module("umbraco").controller("Umbraco.Editors.StyleSheets.CreateController", StyleSheetsCreateController); +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/stylesheets/create.html b/src/Umbraco.Web.UI.Client/src/views/stylesheets/create.html new file mode 100644 index 0000000000..82854635f9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/stylesheets/create.html @@ -0,0 +1,23 @@ + + + diff --git a/src/Umbraco.Web.UI.Client/src/views/stylesheets/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/stylesheets/edit.controller.js new file mode 100644 index 0000000000..33aa0f979b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/stylesheets/edit.controller.js @@ -0,0 +1,205 @@ +(function () { + "use strict"; + + function StyleSheetsEditController($scope, $routeParams, $timeout, appState, editorState, navigationService, assetsService, codefileResource, contentEditingHelper, notificationsService, localizationService, templateHelper, angularHelper) { + + var vm = this; + var currentPosition = null; + + vm.page = {}; + vm.page.loading = true; + vm.page.menu = {}; + vm.page.menu.currentSection = appState.getSectionState("currentSection"); + vm.page.menu.currentNode = null; + vm.page.saveButtonState = "init"; + + //Used to toggle the keyboard shortcut modal + //From a custom keybinding in ace editor - that conflicts with our own to show the dialog + vm.showKeyboardShortcut = false; + + //Keyboard shortcuts for help dialog + vm.page.keyboardShortcutsOverview = []; + + templateHelper.getGeneralShortcuts().then(function(shortcuts){ + vm.page.keyboardShortcutsOverview.push(shortcuts); + }); + + templateHelper.getEditorShortcuts().then(function(shortcuts){ + vm.page.keyboardShortcutsOverview.push(shortcuts); + }); + + vm.stylesheet = {}; + + // bind functions to view model + vm.save = save; + + /* Function bound to view model */ + + function save() { + + vm.page.saveButtonState = "busy"; + + vm.stylesheet.content = vm.editor.getValue(); + + contentEditingHelper.contentEditorPerformSave({ + saveMethod: codefileResource.save, + scope: $scope, + content: vm.stylesheet, + // We do not redirect on failure for style sheets - this is because it is not possible to actually save the style sheet + // when server side validation fails - as opposed to content where we are capable of saving the content + // item if server side validation fails + redirectOnFailure: false, + rebindCallback: function (orignal, saved) {} + }).then(function (saved) { + + localizationService.localizeMany(["speechBubbles_fileSavedHeader", "speechBubbles_fileSavedText"]).then(function(data){ + var header = data[0]; + var message = data[1]; + notificationsService.success(header, message); + }); + + //check if the name changed, if so we need to redirect + if (vm.stylesheet.id !== saved.id) { + contentEditingHelper.redirectToRenamedContent(saved.id); + } + else { + vm.page.saveButtonState = "success"; + vm.stylesheet = saved; + + //sync state + editorState.set(vm.stylesheet); + + // sync tree + navigationService.syncTree({ tree: "stylesheets", path: vm.stylesheet.path, forceReload: true }).then(function (syncArgs) { + vm.page.menu.currentNode = syncArgs.node; + }); + } + + }, function (err) { + + vm.page.saveButtonState = "error"; + + localizationService.localizeMany(["speechBubbles_validationFailedHeader", "speechBubbles_validationFailedMessage"]).then(function(data){ + var header = data[0]; + var message = data[1]; + notificationsService.error(header, message); + }); + + }); + + + } + + /* Local functions */ + + function init() { + + //we need to load this somewhere, for now its here. + assetsService.loadCss("lib/ace-razor-mode/theme/razor_chrome.css", $scope); + + if ($routeParams.create) { + codefileResource.getScaffold("stylesheets", $routeParams.id).then(function (stylesheet) { + ready(stylesheet, false); + }); + } else { + codefileResource.getByPath('stylesheets', $routeParams.id).then(function (stylesheet) { + ready(stylesheet, true); + }); + } + + } + + function ready(stylesheet, syncTree) { + + vm.page.loading = false; + + vm.stylesheet = stylesheet; + + //sync state + editorState.set(vm.stylesheet); + + if (syncTree) { + navigationService.syncTree({ tree: "stylesheets", path: vm.stylesheet.path, forceReload: true }).then(function (syncArgs) { + vm.page.menu.currentNode = syncArgs.node; + }); + } + + vm.aceOption = { + mode: "stylesheet", + theme: "chrome", + showPrintMargin: false, + advanced: { + fontSize: '14px', + enableSnippets: true, + enableBasicAutocompletion: true, + enableLiveAutocompletion: false + }, + onLoad: function(_editor) { + + vm.editor = _editor; + + //Update the auto-complete method to use ctrl+alt+space + _editor.commands.bindKey("ctrl-alt-space", "startAutocomplete"); + + //Unassigns the keybinding (That was previously auto-complete) + //As conflicts with our own tree search shortcut + _editor.commands.bindKey("ctrl-space", null); + + //TODO: Move all these keybinding config out into some helper/service + _editor.commands.addCommands([ + //Disable (alt+shift+K) + //Conflicts with our own show shortcuts dialog - this overrides it + { + name: 'unSelectOrFindPrevious', + bindKey: 'Alt-Shift-K', + exec: function() { + //Toggle the show keyboard shortcuts overlay + $scope.$apply(function(){ + vm.showKeyboardShortcut = !vm.showKeyboardShortcut; + }); + }, + readOnly: true + } + ]); + + // initial cursor placement + // Keep cursor in name field if we are create a new style sheet + // else set the cursor at the bottom of the code editor + if(!$routeParams.create) { + $timeout(function(){ + vm.editor.navigateFileEnd(); + vm.editor.focus(); + }); + } + + vm.editor.on("change", changeAceEditor); + + } + } + + function changeAceEditor() { + setFormState("dirty"); + } + + function setFormState(state) { + + // get the current form + var currentForm = angularHelper.getCurrentForm($scope); + + // set state + if(state === "dirty") { + currentForm.$setDirty(); + } else if(state === "pristine") { + currentForm.$setPristine(); + } + } + + + } + + init(); + + } + + angular.module("umbraco").controller("Umbraco.Editors.StyleSheets.EditController", StyleSheetsEditController); +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/stylesheets/edit.html b/src/Umbraco.Web.UI.Client/src/views/stylesheets/edit.html new file mode 100644 index 0000000000..61252f5a54 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/stylesheets/edit.html @@ -0,0 +1,58 @@ +
+ + + +
+ + + + + + + + + +
+
+
+
+
+ + + + + + + + + + + + + + + + + + +
+
+ +
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 f2bf1c2c60..6dbcd0d870 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -315,6 +315,7 @@ New partial view from snippet New partial view macro from snippet New partial view macro (without macro) + New style sheet file Browse your website diff --git a/src/Umbraco.Web/Editors/CodeFileController.cs b/src/Umbraco.Web/Editors/CodeFileController.cs index edcd71b2e8..6bd1030dc3 100644 --- a/src/Umbraco.Web/Editors/CodeFileController.cs +++ b/src/Umbraco.Web/Editors/CodeFileController.cs @@ -17,6 +17,7 @@ using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; using Umbraco.Web.Trees; +using Stylesheet = Umbraco.Core.Models.Stylesheet; namespace Umbraco.Web.Editors { @@ -168,6 +169,18 @@ namespace Umbraco.Web.Editors return display; } throw new HttpResponseException(HttpStatusCode.NotFound); + + case Core.Constants.Trees.Stylesheets: + var stylesheet = Services.FileService.GetStylesheetByName(virtualPath); + if (stylesheet != null) + { + var display = Mapper.Map(stylesheet); + display.FileType = Core.Constants.Trees.Stylesheets; + display.Path = Url.GetTreePathFromFilePath(stylesheet.Path); + display.Id = System.Web.HttpUtility.UrlEncode(stylesheet.Path); + return display; + } + throw new HttpResponseException(HttpStatusCode.NotFound); } throw new HttpResponseException(HttpStatusCode.NotFound); @@ -235,6 +248,10 @@ namespace Umbraco.Web.Editors codeFileDisplay = Mapper.Map(new Script(string.Empty)); codeFileDisplay.VirtualPath = SystemDirectories.Scripts; break; + case Core.Constants.Trees.Stylesheets: + codeFileDisplay = Mapper.Map(new Stylesheet(string.Empty)); + codeFileDisplay.VirtualPath = SystemDirectories.StyleSheets; + break; default: throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Unsupported editortype")); } @@ -369,13 +386,17 @@ namespace Umbraco.Web.Editors display.Id = System.Web.HttpUtility.UrlEncode(scriptResult.Path); return display; - //display.AddErrorNotification( - // Services.TextService.Localize("speechBubbles/partialViewErrorHeader"), - // Services.TextService.Localize("speechBubbles/partialViewErrorText")); - - + //display.AddErrorNotification( + // Services.TextService.Localize("speechBubbles/partialViewErrorHeader"), + // Services.TextService.Localize("speechBubbles/partialViewErrorText")); + case Core.Constants.Trees.Stylesheets: + var stylesheetResult = CreateOrUpdateStylesheet(display); + display = Mapper.Map(stylesheetResult, display); + display.Path = Url.GetTreePathFromFilePath(stylesheetResult.Path); + display.Id = System.Web.HttpUtility.UrlEncode(stylesheetResult.Path); + return display; default: throw new HttpResponseException(HttpStatusCode.NotFound); @@ -431,6 +452,54 @@ namespace Umbraco.Web.Editors return script; } + private Stylesheet CreateOrUpdateStylesheet(CodeFileDisplay display) + { + return CreateOrUpdateFile(display, ".css", Current.FileSystems.StylesheetsFileSystem, + name => Services.FileService.GetStylesheetByName(name), + (stylesheet, userId) => Services.FileService.SaveStylesheet(stylesheet, userId), + name => new Stylesheet(name) + ); + } + + private T CreateOrUpdateFile(CodeFileDisplay display, string extension, IFileSystem fileSystem, + Func getFileByName, Action saveFile, Func createFile) where T : Core.Models.File + { + //must always end with the correct extension + display.Name = EnsureCorrectFileExtension(display.Name, extension); + + var virtualPath = display.VirtualPath ?? string.Empty; + // this is all weird, should be using relative paths everywhere! + var relPath = fileSystem.GetRelativePath(virtualPath); + + if (relPath.EndsWith(extension) == false) + { + //this would typically mean it's new + relPath = relPath.IsNullOrWhiteSpace() + ? relPath + display.Name + : relPath.EnsureEndsWith('/') + display.Name; + } + + var file = getFileByName(relPath); + if (file != null) + { + // might need to find the path + var orgPath = file.OriginalPath.Substring(0, file.OriginalPath.IndexOf(file.Name)); + file.Path = orgPath + display.Name; + + file.Content = display.Content; + //try/catch? since this doesn't return an Attempt? + saveFile(file, Security.CurrentUser.Id); + } + else + { + file = createFile(relPath); + file.Content = display.Content; + saveFile(file, Security.CurrentUser.Id); + } + + return file; + } + private Attempt CreateOrUpdatePartialView(CodeFileDisplay display) { return CreateOrUpdatePartialView(display, SystemDirectories.PartialViews, diff --git a/src/Umbraco.Web/Models/Mapping/CodeFileMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/CodeFileMapperProfile.cs index 082abfdace..94c43f8f11 100644 --- a/src/Umbraco.Web/Models/Mapping/CodeFileMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/CodeFileMapperProfile.cs @@ -1,6 +1,7 @@ using AutoMapper; using Umbraco.Core.Models; using Umbraco.Web.Models.ContentEditing; +using Stylesheet = Umbraco.Core.Models.Stylesheet; namespace Umbraco.Web.Models.Mapping { @@ -20,6 +21,12 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dest => dest.Path, opt => opt.Ignore()) .ForMember(dest => dest.Snippet, opt => opt.Ignore()); + CreateMap() + .ForMember(dest => dest.FileType, opt => opt.Ignore()) + .ForMember(dest => dest.Notifications, opt => opt.Ignore()) + .ForMember(dest => dest.Path, opt => opt.Ignore()) + .ForMember(dest => dest.Snippet, opt => opt.Ignore()); + CreateMap() .IgnoreEntityCommonProperties() .ForMember(dest => dest.Id, opt => opt.Ignore()) From a383309e46912e3f434c3852acf4c2b2d576d1b7 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 30 Oct 2018 17:32:27 +1100 Subject: [PATCH 10/71] Gets indexes updating based on content type changes --- .../Implement/DocumentRepository.cs | 2 +- src/Umbraco.Core/Services/IContentService.cs | 10 +- .../Services/Implement/ContentService.cs | 26 ++---- .../Services/ContentServiceTests.cs | 2 +- .../Services/PerformanceTests.cs | 34 ------- src/Umbraco.Web/Search/ExamineComponent.cs | 91 ++++++++++++++++++- 6 files changed, 105 insertions(+), 60 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index f3afe99b28..b3a7c31e54 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -99,7 +99,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private string VariantNameSqlExpression => SqlContext.VisitDto((ccv, node) => ccv.Name ?? node.Text, "ccv").Sql; - protected virtual Sql GetBaseQuery(QueryType queryType, bool current) + protected Sql GetBaseQuery(QueryType queryType, bool current) { var sql = SqlContext.Sql(); diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 7371686c7c..0cfa0bb601 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -79,9 +79,15 @@ namespace Umbraco.Core.Services IEnumerable GetByIds(IEnumerable ids); /// - /// Gets documents of a given document type. + /// Gets paged documents of a content content /// - IEnumerable GetByType(int documentTypeId); + /// The page number. + /// The page number. + /// The page size. + /// Total number of documents. + /// Search text filter. + /// Ordering infos. + IEnumerable GetPagedOfType(int contentType, long pageIndex, int pageSize, out long totalRecords, IQuery filter = null, Ordering ordering = null); /// /// Gets documents at a given level. diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 3953e7a640..64c6a23aff 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -405,28 +405,20 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Gets a collection of objects by the Id of the - /// - /// Id of the - /// An Enumerable list of objects - public IEnumerable GetByType(int id) + public IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int pageSize, out long totalRecords, IQuery filter, Ordering ordering = null) { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) - { - scope.ReadLock(Constants.Locks.ContentTree); - var query = Query().Where(x => x.ContentTypeId == id); - return _documentRepository.Get(query); - } - } + if(pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); + if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); + + if (ordering == null) + ordering = Ordering.By("sortOrder"); - internal IEnumerable GetPublishedContentOfContentType(int id) - { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { scope.ReadLock(Constants.Locks.ContentTree); - var query = Query().Where(x => x.ContentTypeId == id); - return _documentRepository.Get(query); + return _documentRepository.GetPage( + Query().Where(x => x.ContentTypeId == contentTypeId), + pageIndex, pageSize, out totalRecords, filter, ordering); } } diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 5aad7c4d90..5b06037f90 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -1738,7 +1738,7 @@ namespace Umbraco.Tests.Services // Act contentService.DeleteOfType(contentType.Id); var rootContent = contentService.GetRootContent(); - var contents = contentService.GetByType(contentType.Id); + var contents = contentService.GetPagedOfType(contentType.Id, 0, int.MaxValue, out var _); // Assert Assert.That(rootContent.Any(), Is.False); diff --git a/src/Umbraco.Tests/Services/PerformanceTests.cs b/src/Umbraco.Tests/Services/PerformanceTests.cs index 9b0117c266..900a466a1d 100644 --- a/src/Umbraco.Tests/Services/PerformanceTests.cs +++ b/src/Umbraco.Tests/Services/PerformanceTests.cs @@ -108,40 +108,6 @@ namespace Umbraco.Tests.Services } } - [Test] - public void Get_All_Published_Content_Of_Type() - { - var result = PrimeDbWithLotsOfContent(); - var contentSvc = (ContentService)ServiceContext.ContentService; - - var countOfPublished = result.Count(x => x.Published); - var contentTypeId = result.First().ContentTypeId; - - var proflog = GetTestProfilingLogger(); - using (proflog.DebugDuration("Getting published content of type normally")) - { - //do this 10x! - for (var i = 0; i < 10; i++) - { - - //get all content items that are published of this type - var published = contentSvc.GetByType(contentTypeId).Where(content => content.Published); - Assert.AreEqual(countOfPublished, published.Count(x => x.ContentTypeId == contentTypeId)); - } - } - - using (proflog.DebugDuration("Getting published content of type optimized")) - { - - //do this 10x! - for (var i = 0; i < 10; i++) - { - //get all content items that are published of this type - var published = contentSvc.GetPublishedContentOfContentType(contentTypeId); - Assert.AreEqual(countOfPublished, published.Count(x => x.ContentTypeId == contentTypeId)); - } - } - } [Test] public void Truncate_Insert_Vs_Update_Insert() diff --git a/src/Umbraco.Web/Search/ExamineComponent.cs b/src/Umbraco.Web/Search/ExamineComponent.cs index ff7d0c8dc4..530e8ba449 100644 --- a/src/Umbraco.Web/Search/ExamineComponent.cs +++ b/src/Umbraco.Web/Search/ExamineComponent.cs @@ -102,13 +102,10 @@ namespace Umbraco.Web.Search // bind to distributed cache events - this ensures that this logic occurs on ALL servers // that are taking part in a load balanced environment. ContentCacheRefresher.CacheUpdated += ContentCacheRefresherUpdated; + ContentTypeCacheRefresher.CacheUpdated += ContentTypeCacheRefresherUpdated; ; MediaCacheRefresher.CacheUpdated += MediaCacheRefresherUpdated; MemberCacheRefresher.CacheUpdated += MemberCacheRefresherUpdated; - // fixme - content type? - // events handling removed in ef013f9d3b945d0a48a306ff1afbd49c10c3fff8 - // because, could not make sense of it? - EnsureUnlocked(profilingLogger.Logger, examineManager); RebuildIndexesOnStartup(profilingLogger.Logger); @@ -317,6 +314,88 @@ namespace Umbraco.Web.Search } } + private void ContentTypeCacheRefresherUpdated(ContentTypeCacheRefresher sender, CacheRefresherEventArgs args) + { + + //before content type changes just caused full blown re-indexing: + // https://github.com/umbraco/Umbraco-CMS/commit/ef013f9d3b945d0a48a306ff1afbd49c10c3fff8 + + + if (Suspendable.ExamineEvents.CanIndex == false) + return; + + if (args.MessageType != MessageType.RefreshByPayload) + throw new NotSupportedException(); + + var contentService = _services.ContentService; + + var removedIds = new List(); + var refreshedIds = new List(); + + //TODO: What do we do about these? + var otherIds = new List(); + + foreach (var payload in (ContentTypeCacheRefresher.JsonPayload[])args.MessageObject) + { + if (payload.ChangeTypes.HasType(ContentTypeChangeTypes.Remove)) + removedIds.Add(payload.Id); + else if (payload.ChangeTypes.HasType(ContentTypeChangeTypes.RefreshMain)) + refreshedIds.Add(payload.Id); + else if (payload.ChangeTypes.HasType(ContentTypeChangeTypes.RefreshOther)) + otherIds.Add(payload.Id); + } + + const int pageSize = 500; + + //Re-index all content of these types + foreach(var id in refreshedIds) + { + var page = 0; + var total = long.MaxValue; + while (page * pageSize < total) + { + var contentToRefresh = _services.ContentService.GetPagedOfType(id, page++, pageSize, out total); + foreach(var c in contentToRefresh) + { + //TODO: We might have to order by Path ascending or something since we're going to need to check + // contentService.IsPathPublished(content) but we don't want to make that check for every content item + IContent published = null; + if (c.Published && contentService.IsPathPublished(c)) + published = c; + + ReIndexForContent(c, published); + } + } + } + + //Delete all content of this content type that is in any content indexer by looking up matched examine docs + foreach(var id in removedIds) + { + foreach(var index in _examineManager.IndexProviders.Values.OfType()) + { + var searcher = index.GetSearcher(); + + var page = 0; + var total = long.MaxValue; + while (page * pageSize < total) + { + //paging with examine, see https://shazwazza.com/post/paging-with-examine/ + var results = searcher.Search( + searcher.CreateCriteria().Field("nodeType", id).Compile(), + maxResults: pageSize * (page + 1)); + total = results.TotalItemCount; + var paged = results.Skip(page * pageSize); + + foreach(var item in paged) + if (int.TryParse(item.Id, out var contentId)) + DeleteIndexForEntity(contentId, false); + } + } + + + } + } + private void ContentCacheRefresherUpdated(ContentCacheRefresher sender, CacheRefresherEventArgs args) { if (Suspendable.ExamineEvents.CanIndex == false) @@ -340,6 +419,8 @@ namespace Umbraco.Web.Search // ExamineEvents does not support RefreshAll // just ignore that payload // so what?! + + //fixme: Rebuild the index at this point? } else // RefreshNode or RefreshBranch (maybe trashed) { @@ -355,7 +436,7 @@ namespace Umbraco.Web.Search } IContent published = null; - if (content.Published && ((ContentService)contentService).IsPathPublished(content)) + if (content.Published && contentService.IsPathPublished(content)) published = content; // just that content From 23860e58b93bfc9ecb1ce41a0b5ec3fddcd360e4 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 31 Oct 2018 16:33:10 +1100 Subject: [PATCH 11/71] Update re-indexing logic for content types including updating content types affected by composition changes --- src/Umbraco.Examine/UmbracoExamineIndexer.cs | 2 +- src/Umbraco.Web/Search/ExamineComponent.cs | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Examine/UmbracoExamineIndexer.cs b/src/Umbraco.Examine/UmbracoExamineIndexer.cs index 64fd7b5c71..a4c1fb4336 100644 --- a/src/Umbraco.Examine/UmbracoExamineIndexer.cs +++ b/src/Umbraco.Examine/UmbracoExamineIndexer.cs @@ -418,7 +418,7 @@ namespace Umbraco.Examine //icon if (e.IndexItem.ValueSet.Values.TryGetValue("icon", out var icon) && e.IndexItem.ValueSet.Values.ContainsKey(IconFieldName) == false) { - e.IndexItem.ValueSet.Values[IconFieldName] = new List { icon }; + e.IndexItem.ValueSet.Values[IconFieldName] = icon; } } diff --git a/src/Umbraco.Web/Search/ExamineComponent.cs b/src/Umbraco.Web/Search/ExamineComponent.cs index 530e8ba449..a1625206d1 100644 --- a/src/Umbraco.Web/Search/ExamineComponent.cs +++ b/src/Umbraco.Web/Search/ExamineComponent.cs @@ -317,7 +317,8 @@ namespace Umbraco.Web.Search private void ContentTypeCacheRefresherUpdated(ContentTypeCacheRefresher sender, CacheRefresherEventArgs args) { - //before content type changes just caused full blown re-indexing: + //before content type changes just didn't do anything for indexing, simply just updated the + //definitions to index: // https://github.com/umbraco/Umbraco-CMS/commit/ef013f9d3b945d0a48a306ff1afbd49c10c3fff8 @@ -331,8 +332,6 @@ namespace Umbraco.Web.Search var removedIds = new List(); var refreshedIds = new List(); - - //TODO: What do we do about these? var otherIds = new List(); foreach (var payload in (ContentTypeCacheRefresher.JsonPayload[])args.MessageObject) @@ -348,7 +347,7 @@ namespace Umbraco.Web.Search const int pageSize = 500; //Re-index all content of these types - foreach(var id in refreshedIds) + foreach(var id in refreshedIds.Concat(otherIds).Distinct()) { var page = 0; var total = long.MaxValue; From 18f6e7ba789702fcf2d9434fa0c4bbe1f3d73e21 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 31 Oct 2018 17:57:35 +1100 Subject: [PATCH 12/71] Updates ExamineComponent to page over descendants when caches are updated --- src/Umbraco.Web/Search/ExamineComponent.cs | 42 +++++++++++++--------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Web/Search/ExamineComponent.cs b/src/Umbraco.Web/Search/ExamineComponent.cs index a1625206d1..b1a302785d 100644 --- a/src/Umbraco.Web/Search/ExamineComponent.cs +++ b/src/Umbraco.Web/Search/ExamineComponent.cs @@ -314,14 +314,13 @@ namespace Umbraco.Web.Search } } + /// + /// Updates indexes based on content type changes + /// + /// + /// private void ContentTypeCacheRefresherUpdated(ContentTypeCacheRefresher sender, CacheRefresherEventArgs args) { - - //before content type changes just didn't do anything for indexing, simply just updated the - //definitions to index: - // https://github.com/umbraco/Umbraco-CMS/commit/ef013f9d3b945d0a48a306ff1afbd49c10c3fff8 - - if (Suspendable.ExamineEvents.CanIndex == false) return; @@ -395,6 +394,11 @@ namespace Umbraco.Web.Search } } + /// + /// Updates indexes based on content changes + /// + /// + /// private void ContentCacheRefresherUpdated(ContentCacheRefresher sender, CacheRefresherEventArgs args) { if (Suspendable.ExamineEvents.CanIndex == false) @@ -445,19 +449,25 @@ namespace Umbraco.Web.Search if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshBranch)) { var masked = published == null ? null : new List(); - var descendants = contentService.GetDescendants(content); - foreach (var descendant in descendants) + const int pageSize = 500; + var page = 0; + var total = long.MaxValue; + while(page * pageSize < total) { - published = null; - if (masked != null) // else everything is masked + var descendants = contentService.GetPagedDescendants(content.Id, page++, pageSize, out total); + foreach (var descendant in descendants) { - if (masked.Contains(descendant.ParentId) || !descendant.Published) - masked.Add(descendant.Id); - else - published = descendant; - } + published = null; + if (masked != null) // else everything is masked + { + if (masked.Contains(descendant.ParentId) || !descendant.Published) + masked.Add(descendant.Id); + else + published = descendant; + } - ReIndexForContent(descendant, published); + ReIndexForContent(descendant, published); + } } } } From 437bf978a1e67c95d09de629c527564157f0c643 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 31 Oct 2018 18:01:39 +1100 Subject: [PATCH 13/71] Removes non paged GetChildren and GetDescendants from content service --- src/Umbraco.Core/ContentExtensions.cs | 46 +--- .../Services/EntityXmlSerializer.cs | 31 ++- src/Umbraco.Core/Services/IContentService.cs | 21 +- .../Services/Implement/ContentService.cs | 214 ++++++++---------- .../Integration/ContentEventsTests.cs | 2 +- .../NPocoTests/PetaPocoCachesTest.cs | 4 - .../Services/ContentServicePerformanceTest.cs | 2 +- .../Services/ContentServiceTests.cs | 85 ++++--- .../Umbraco/dialogs/ChangeDocType.aspx.cs | 3 +- src/Umbraco.Web/Editors/ContentController.cs | 5 +- 10 files changed, 164 insertions(+), 249 deletions(-) diff --git a/src/Umbraco.Core/ContentExtensions.cs b/src/Umbraco.Core/ContentExtensions.cs index 4f88c2b803..f0fa6cdf17 100644 --- a/src/Umbraco.Core/ContentExtensions.cs +++ b/src/Umbraco.Core/ContentExtensions.cs @@ -48,28 +48,6 @@ namespace Umbraco.Core return contentService.GetAncestors(content); } - /// - /// Returns a list of the current contents children. - /// - /// Current content - /// - /// An enumerable list of objects - public static IEnumerable Children(this IContent content, IContentService contentService) - { - return contentService.GetChildren(content.Id); - } - - /// - /// Returns a list of the current contents descendants, not including the content itself. - /// - /// Current content - /// - /// An enumerable list of objects - public static IEnumerable Descendants(this IContent content, IContentService contentService) - { - return contentService.GetDescendants(content); - } - /// /// Returns the parent of the current content. /// @@ -179,29 +157,7 @@ namespace Umbraco.Core } return false; } - - /// - /// Returns the children for the content base item - /// - /// - /// - /// - /// - /// This is a bit of a hack because we need to type check! - /// - internal static IEnumerable Children(IContentBase content, ServiceContext services) - { - if (content is IContent) - { - return services.ContentService.GetChildren(content.Id); - } - if (content is IMedia) - { - return services.MediaService.GetChildren(content.Id); - } - return null; - } - + /// /// Returns properties that do not belong to a group /// diff --git a/src/Umbraco.Core/Services/EntityXmlSerializer.cs b/src/Umbraco.Core/Services/EntityXmlSerializer.cs index e418c8d3e6..ebb35a43ed 100644 --- a/src/Umbraco.Core/Services/EntityXmlSerializer.cs +++ b/src/Umbraco.Core/Services/EntityXmlSerializer.cs @@ -30,7 +30,7 @@ namespace Umbraco.Core.Services IEnumerable urlSegmentProviders, IContent content, bool published, - bool withDescendants = false) // fixme take care of usage! + bool withDescendants = false) //fixme take care of usage! only used for the packager { if (contentService == null) throw new ArgumentNullException(nameof(contentService)); if (dataTypeService == null) throw new ArgumentNullException(nameof(dataTypeService)); @@ -58,9 +58,15 @@ namespace Umbraco.Core.Services if (withDescendants) { - var descendants = contentService.GetDescendants(content).ToArray(); - var currentChildren = descendants.Where(x => x.ParentId == content.Id); - SerializeDescendants(contentService, dataTypeService, userService, localizationService, urlSegmentProviders, descendants, currentChildren, xml, published); + const int pageSize = 500; + var page = 0; + var total = long.MaxValue; + while(page * pageSize < total) + { + var children = contentService.GetPagedChildren(content.Id, page++, pageSize, out total); + SerializeChildren(contentService, dataTypeService, userService, localizationService, urlSegmentProviders, children, xml, published); + } + } return xml; @@ -451,7 +457,7 @@ namespace Umbraco.Core.Services } // exports an IContent item descendants. - private static void SerializeDescendants(IContentService contentService, IDataTypeService dataTypeService, IUserService userService, ILocalizationService localizationService, IEnumerable urlSegmentProviders, IContent[] originalDescendants, IEnumerable children, XElement xml, bool published) + private static void SerializeChildren(IContentService contentService, IDataTypeService dataTypeService, IUserService userService, ILocalizationService localizationService, IEnumerable urlSegmentProviders, IEnumerable children, XElement xml, bool published) { foreach (var child in children) { @@ -459,12 +465,15 @@ namespace Umbraco.Core.Services var childXml = Serialize(contentService, dataTypeService, userService, localizationService, urlSegmentProviders, child, published); xml.Add(childXml); - // capture id (out of closure) and get the grandChildren (children of the child) - var parentId = child.Id; - var grandChildren = originalDescendants.Where(x => x.ParentId == parentId); - - // recurse - SerializeDescendants(contentService, dataTypeService, userService, localizationService, urlSegmentProviders, originalDescendants, grandChildren, childXml, published); + const int pageSize = 500; + var page = 0; + var total = long.MaxValue; + while(page * pageSize < total) + { + var grandChildren = contentService.GetPagedChildren(child.Id, page++, pageSize, out total); + // recurse + SerializeChildren(contentService, dataTypeService, userService, localizationService, urlSegmentProviders, grandChildren, childXml, published); + } } } diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 0cfa0bb601..b821ae1eb0 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -94,16 +94,6 @@ namespace Umbraco.Core.Services /// IEnumerable GetByLevel(int level); - /// - /// Gets child documents of a given parent. - /// - IEnumerable GetChildren(int parentId); - - /// - /// Gets child documents of a document, (partially) matching a name. - /// - IEnumerable GetChildren(int parentId, string name); - /// /// Gets the parent of a document. /// @@ -124,16 +114,6 @@ namespace Umbraco.Core.Services /// IEnumerable GetAncestors(IContent content); - /// - /// Gets descendant documents of a document. - /// - IEnumerable GetDescendants(int id); - - /// - /// Gets descendant documents of a document. - /// - IEnumerable GetDescendants(IContent content); - /// /// Gets all versions of a document. /// @@ -172,6 +152,7 @@ namespace Umbraco.Core.Services /// IEnumerable GetContentForRelease(); + //fixme: should be paged /// /// Gets documents in the recycle bin. /// diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 64c6a23aff..1f28988fa6 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -528,21 +528,6 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// An Enumerable list of objects - public IEnumerable GetChildren(int id) - { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) - { - scope.ReadLock(Constants.Locks.ContentTree); - var query = Query().Where(x => x.ParentId == id); - return _documentRepository.Get(query).OrderBy(x => x.SortOrder); - } - } - /// /// Gets a collection of published objects by Parent Id /// @@ -626,60 +611,6 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Gets a collection of objects by its name or partial name - /// - /// Id of the Parent to retrieve Children from - /// Full or partial name of the children - /// An Enumerable list of objects - public IEnumerable GetChildren(int parentId, string name) - { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) - { - scope.ReadLock(Constants.Locks.ContentTree); - var query = Query().Where(x => x.ParentId == parentId && x.Name.Contains(name)); - return _documentRepository.Get(query); - } - } - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Descendants from - /// An Enumerable list of objects - public IEnumerable GetDescendants(int id) - { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) - { - scope.ReadLock(Constants.Locks.ContentTree); - var content = GetById(id); - if (content == null) - { - scope.Complete(); // else causes rollback - return Enumerable.Empty(); - } - var pathMatch = content.Path + ","; - var query = Query().Where(x => x.Id != content.Id && x.Path.StartsWith(pathMatch)); - return _documentRepository.Get(query); - } - } - - /// - /// Gets a collection of objects by Parent Id - /// - /// item to retrieve Descendants from - /// An Enumerable list of objects - public IEnumerable GetDescendants(IContent content) - { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) - { - scope.ReadLock(Constants.Locks.ContentTree); - var pathMatch = content.Path + ","; - var query = Query().Where(x => x.Id != content.Id && x.Path.StartsWith(pathMatch)); - return _documentRepository.Get(query); - } - } - /// /// Gets the parent of the current content as an item. /// @@ -1347,27 +1278,35 @@ namespace Umbraco.Core.Services.Implement // if one fails, abort its branch var exclude = new HashSet(); - //fixme: should be paged to not overwhelm the database (timeouts) - foreach (var d in GetDescendants(document)) + const int pageSize = 500; + var page = 0; + var total = long.MaxValue; + while (page * pageSize < total) { - // if parent is excluded, exclude document and ignore - // if not forcing, and not publishing, exclude document and ignore - if (exclude.Contains(d.ParentId) || !force && !d.Published) + var descendants = GetPagedDescendants(document.Id, page++, pageSize, out total); + + foreach (var d in descendants) { + // if parent is excluded, exclude document and ignore + // if not forcing, and not publishing, exclude document and ignore + if (exclude.Contains(d.ParentId) || !force && !d.Published) + { + exclude.Add(d.Id); + continue; + } + + // no need to check path here, + // 1. because we know the parent is path-published (we just published it) + // 2. because it would not work as nothing's been written out to the db until the uow completes + result = SaveAndPublishBranchOne(scope, d, editing, publishCultures, false, publishedDocuments, evtMsgs, userId); + results.Add(result); + if (result.Success) continue; + + // abort branch exclude.Add(d.Id); - continue; } - - // no need to check path here, - // 1. because we know the parent is path-published (we just published it) - // 2. because it would not work as nothing's been written out to the db until the uow completes - result = SaveAndPublishBranchOne(scope, d, editing, publishCultures, false, publishedDocuments, evtMsgs, userId); - results.Add(result); - if (result.Success) continue; - - // abort branch - exclude.Add(d.Id); } + scope.Events.Dispatch(TreeChanged, this, new TreeChange(document, TreeChangeTypes.RefreshBranch).ToEventArgs()); scope.Events.Dispatch(Published, this, new PublishEventArgs(publishedDocuments, false, false), "Published"); @@ -1450,6 +1389,8 @@ namespace Umbraco.Core.Services.Implement private void DeleteLocked(IScope scope, IContent content) { + //TODO: Test this + // then recursively delete descendants, bottom-up // just repository.Delete + an event var stack = new Stack(); @@ -1458,14 +1399,37 @@ namespace Umbraco.Core.Services.Implement while (stack.Count > 0) { var c = stack.Peek(); - IContent[] cc; if (c.Level == level) - while ((cc = c.Children(this).ToArray()).Length > 0) + { + var hasChildren = true; + while (hasChildren) { - foreach (var ci in cc) - stack.Push(ci); - c = cc[cc.Length - 1]; + IContent last = null; + const int pageSize = 500; + var page = 0; + var total = long.MaxValue; + var countChildren = 0; + + //get all children of c in pages + while (page * pageSize < total) + { + var children = GetPagedChildren(c.Id, page++, pageSize, out total); + + foreach (var ci in children) + { + countChildren++; + stack.Push(ci); + last = ci; + } + + if (countChildren == 0) + hasChildren = false; //exit, there are no children left to load + else + c = last; //recurse with the last child + } } + } + c = stack.Pop(); level = c.Level; @@ -1686,9 +1650,6 @@ namespace Umbraco.Core.Services.Implement moves.Add(Tuple.Create(content, content.Path)); // capture original path - // get before moving, in case uow is immediate - var descendants = GetDescendants(content); - // these will be updated by the repo because we changed parentId //content.Path = (parent == null ? "-1" : parent.Path) + "," + content.Id; //content.SortOrder = ((ContentRepository) repository).NextChildSortOrder(parentId); @@ -1700,18 +1661,26 @@ namespace Umbraco.Core.Services.Implement //paths[content.Id] = content.Path; paths[content.Id] = (parent == null ? (parentId == Constants.System.RecycleBinContent ? "-1,-20" : "-1") : parent.Path) + "," + content.Id; - foreach (var descendant in descendants) + const int pageSize = 500; + var page = 0; + var total = long.MaxValue; + while(page * pageSize < total) { - moves.Add(Tuple.Create(descendant, descendant.Path)); // capture original path + var descendants = GetPagedDescendants(content.Id, page++, pageSize, out total); + foreach (var descendant in descendants) + { + moves.Add(Tuple.Create(descendant, descendant.Path)); // capture original path - // update path and level since we do not update parentId - if (paths.ContainsKey(descendant.ParentId) == false) - Console.WriteLine("oops on " + descendant.ParentId + " for " + content.Path + " " + parent?.Path); - descendant.Path = paths[descendant.Id] = paths[descendant.ParentId] + "," + descendant.Id; - Console.WriteLine("path " + descendant.Id + " = " + paths[descendant.Id]); - descendant.Level += levelDelta; - PerformMoveContentLocked(descendant, userId, trash); + // update path and level since we do not update parentId + if (paths.ContainsKey(descendant.ParentId) == false) + Console.WriteLine("oops on " + descendant.ParentId + " for " + content.Path + " " + parent?.Path); + descendant.Path = paths[descendant.Id] = paths[descendant.ParentId] + "," + descendant.Id; + Console.WriteLine("path " + descendant.Id + " = " + paths[descendant.Id]); + descendant.Level += levelDelta; + PerformMoveContentLocked(descendant, userId, trash); + } } + } private void PerformMoveContentLocked(IContent content, int userId, bool? trash) @@ -1844,29 +1813,36 @@ namespace Umbraco.Core.Services.Implement if (recursive) // process descendants { - foreach (var descendant in GetDescendants(content)) + const int pageSize = 500; + var page = 0; + var total = long.MaxValue; + while(page * pageSize < total) { - // if parent has not been copied, skip, else gets its copy id - if (idmap.TryGetValue(descendant.ParentId, out parentId) == false) continue; + var descendants = GetPagedDescendants(content.Id, page++, pageSize, out total); + foreach (var descendant in descendants) + { + // if parent has not been copied, skip, else gets its copy id + if (idmap.TryGetValue(descendant.ParentId, out parentId) == false) continue; - var descendantCopy = descendant.DeepCloneWithResetIdentities(); - descendantCopy.ParentId = parentId; + var descendantCopy = descendant.DeepCloneWithResetIdentities(); + descendantCopy.ParentId = parentId; - if (scope.Events.DispatchCancelable(Copying, this, new CopyEventArgs(descendant, descendantCopy, parentId))) - continue; + if (scope.Events.DispatchCancelable(Copying, this, new CopyEventArgs(descendant, descendantCopy, parentId))) + continue; - // a copy is not published (but not really unpublishing either) - // update the create author and last edit author - if (descendantCopy.Published) - ((Content) descendantCopy).Published = false; - descendantCopy.CreatorId = userId; - descendantCopy.WriterId = userId; + // a copy is not published (but not really unpublishing either) + // update the create author and last edit author + if (descendantCopy.Published) + ((Content)descendantCopy).Published = false; + descendantCopy.CreatorId = userId; + descendantCopy.WriterId = userId; - // save and flush (see above) - _documentRepository.Save(descendantCopy); + // save and flush (see above) + _documentRepository.Save(descendantCopy); - copies.Add(Tuple.Create(descendant, descendantCopy)); - idmap[descendant.Id] = descendantCopy.Id; + copies.Add(Tuple.Create(descendant, descendantCopy)); + idmap[descendant.Id] = descendantCopy.Id; + } } } diff --git a/src/Umbraco.Tests/Integration/ContentEventsTests.cs b/src/Umbraco.Tests/Integration/ContentEventsTests.cs index 4ca63e9e96..655d44e86c 100644 --- a/src/Umbraco.Tests/Integration/ContentEventsTests.cs +++ b/src/Umbraco.Tests/Integration/ContentEventsTests.cs @@ -459,7 +459,7 @@ namespace Umbraco.Tests.Integration #region Utils private IEnumerable Children(IContent content) - => ServiceContext.ContentService.GetChildren(content.Id); + => ServiceContext.ContentService.GetPagedChildren(content.Id, 0, int.MaxValue, out var total); #endregion diff --git a/src/Umbraco.Tests/Persistence/NPocoTests/PetaPocoCachesTest.cs b/src/Umbraco.Tests/Persistence/NPocoTests/PetaPocoCachesTest.cs index 21a75b2e24..b1ebee108a 100644 --- a/src/Umbraco.Tests/Persistence/NPocoTests/PetaPocoCachesTest.cs +++ b/src/Umbraco.Tests/Persistence/NPocoTests/PetaPocoCachesTest.cs @@ -125,10 +125,6 @@ namespace Umbraco.Tests.Persistence.NPocoTests contentService.GetByLevel(2); - contentService.GetChildren(id1); - - contentService.GetDescendants(id2); - contentService.GetVersions(id3); contentService.GetRootContent(); diff --git a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs index 28021f1e22..4326eee273 100644 --- a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs +++ b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs @@ -105,7 +105,7 @@ namespace Umbraco.Tests.Services total.AddRange(ServiceContext.ContentService.GetRootContent()); foreach (var content in total.ToArray()) { - total.AddRange(ServiceContext.ContentService.GetDescendants(content)); + total.AddRange(ServiceContext.ContentService.GetPagedDescendants(content.Id, 0, int.MaxValue, out var _)); } TestProfiler.Disable(); Current.Logger.Info("Returned " + total.Count + " items"); diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 5b06037f90..952c8f9dc1 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -1038,37 +1038,6 @@ namespace Umbraco.Tests.Services Assert.That(contents.Count(), Is.GreaterThanOrEqualTo(2)); } - [Test] - public void Can_Get_Children_Of_Content_Id() - { - // Arrange - var contentService = ServiceContext.ContentService; - - // Act - var contents = contentService.GetChildren(NodeDto.NodeIdSeed + 2).ToList(); - - // Assert - Assert.That(contents, Is.Not.Null); - Assert.That(contents.Any(), Is.True); - Assert.That(contents.Count(), Is.GreaterThanOrEqualTo(2)); - } - - [Test] - public void Can_Get_Descendents_Of_Content() - { - // Arrange - var contentService = ServiceContext.ContentService; - var hierarchy = CreateContentHierarchy(); - contentService.Save(hierarchy, 0); - - // Act - var contents = contentService.GetDescendants(NodeDto.NodeIdSeed + 2).ToList(); - - // Assert - Assert.That(contents, Is.Not.Null); - Assert.That(contents.Any(), Is.True); - Assert.That(contents.Count(), Is.EqualTo(52)); - } [Test] public void Can_Get_All_Versions_Of_Content() @@ -1476,8 +1445,16 @@ namespace Umbraco.Tests.Services var parent = contentService.GetById(parentId); Console.WriteLine(" " + parent.Id); - foreach (var x in contentService.GetDescendants(parent)) - Console.WriteLine(" ".Substring(0, x.Level) + x.Id); + const int pageSize = 500; + var page = 0; + var total = long.MaxValue; + while(page * pageSize < total) + { + var descendants = contentService.GetPagedDescendants(parent.Id, page++, pageSize, out total); + foreach (var x in descendants) + Console.WriteLine(" ".Substring(0, x.Level) + x.Id); + } + Console.WriteLine(); // publish parent & its branch @@ -1492,7 +1469,7 @@ namespace Umbraco.Tests.Services Assert.IsTrue(parentPublished.All(x => x.Success)); Assert.IsTrue(parent.Published); - var children = contentService.GetChildren(parentId); + var children = contentService.GetPagedChildren(parentId, 0, 500, out var totalChildren); //we only want the first so page size, etc.. is abitrary // children are published including ... that was released 5 mins ago Assert.IsTrue(children.First(x => x.Id == NodeDto.NodeIdSeed + 4).Published); @@ -1785,7 +1762,13 @@ namespace Umbraco.Tests.Services contentService.Save(subsubpage, 0); var content = contentService.GetById(NodeDto.NodeIdSeed + 2); - var descendants = contentService.GetDescendants(content).ToList(); + const int pageSize = 500; + var page = 0; + var total = long.MaxValue; + var descendants = new List(); + while(page * pageSize < total) + descendants.AddRange(contentService.GetPagedDescendants(content.Id, page++, pageSize, out total)); + Assert.AreNotEqual(-20, content.ParentId); Assert.IsFalse(content.Trashed); Assert.AreEqual(3, descendants.Count); @@ -1793,7 +1776,11 @@ namespace Umbraco.Tests.Services Assert.IsFalse(descendants.Any(x => x.Trashed)); contentService.MoveToRecycleBin(content, 0); - descendants = contentService.GetDescendants(content).ToList(); + + descendants.Clear(); + page = 0; + while (page * pageSize < total) + descendants.AddRange(contentService.GetPagedDescendants(content.Id, page++, pageSize, out total)); Assert.AreEqual(-20, content.ParentId); Assert.IsTrue(content.Trashed); @@ -1883,8 +1870,13 @@ namespace Umbraco.Tests.Services ServiceContext.ContentService.Save(childPage3); //Verify that the children have the inherited permissions - var descendants = ServiceContext.ContentService.GetDescendants(parentPage).ToArray(); - Assert.AreEqual(3, descendants.Length); + var descendants = new List(); + const int pageSize = 500; + var page = 0; + var total = long.MaxValue; + while(page * pageSize < total) + descendants.AddRange(ServiceContext.ContentService.GetPagedDescendants(parentPage.Id, page++, pageSize, out total)); + Assert.AreEqual(3, descendants.Count); foreach (var descendant in descendants) { @@ -1902,8 +1894,11 @@ namespace Umbraco.Tests.Services //Now copy, what should happen is the child pages will now have permissions inherited from the new parent var copy = ServiceContext.ContentService.Copy(childPage1, parentPage2.Id, false, true); - descendants = ServiceContext.ContentService.GetDescendants(parentPage2).ToArray(); - Assert.AreEqual(3, descendants.Length); + descendants.Clear(); + page = 0; + while (page * pageSize < total) + descendants.AddRange(ServiceContext.ContentService.GetPagedDescendants(parentPage2.Id, page++, pageSize, out total)); + Assert.AreEqual(3, descendants.Count); foreach (var descendant in descendants) { @@ -2035,7 +2030,7 @@ namespace Umbraco.Tests.Services var contentService = ServiceContext.ContentService; var temp = contentService.GetById(NodeDto.NodeIdSeed + 2); Assert.AreEqual("Home", temp.Name); - Assert.AreEqual(2, temp.Children(contentService).Count()); + Assert.AreEqual(2, contentService.CountChildren(temp.Id)); // Act var copy = contentService.Copy(temp, temp.ParentId, false, true, 0); @@ -2045,10 +2040,10 @@ namespace Umbraco.Tests.Services Assert.That(copy, Is.Not.Null); Assert.That(copy.Id, Is.Not.EqualTo(content.Id)); Assert.AreNotSame(content, copy); - Assert.AreEqual(2, copy.Children(contentService).Count()); + Assert.AreEqual(2, contentService.CountChildren(copy.Id)); var child = contentService.GetById(NodeDto.NodeIdSeed + 3); - var childCopy = copy.Children(contentService).First(); + var childCopy = contentService.GetPagedChildren(copy.Id, 0, 500, out var total).First(); Assert.AreEqual(childCopy.Name, child.Name); Assert.AreNotEqual(childCopy.Id, child.Id); Assert.AreNotEqual(childCopy.Key, child.Key); @@ -2061,7 +2056,7 @@ namespace Umbraco.Tests.Services var contentService = ServiceContext.ContentService; var temp = contentService.GetById(NodeDto.NodeIdSeed + 2); Assert.AreEqual("Home", temp.Name); - Assert.AreEqual(2, temp.Children(contentService).Count()); + Assert.AreEqual(2, contentService.CountChildren(temp.Id)); // Act var copy = contentService.Copy(temp, temp.ParentId, false, false, 0); @@ -2071,7 +2066,7 @@ namespace Umbraco.Tests.Services Assert.That(copy, Is.Not.Null); Assert.That(copy.Id, Is.Not.EqualTo(content.Id)); Assert.AreNotSame(content, copy); - Assert.AreEqual(0, copy.Children(contentService).Count()); + Assert.AreEqual(0, contentService.CountChildren(copy.Id)); } [Test] diff --git a/src/Umbraco.Web.UI/Umbraco/dialogs/ChangeDocType.aspx.cs b/src/Umbraco.Web.UI/Umbraco/dialogs/ChangeDocType.aspx.cs index 2297ea1aa7..8111410edd 100644 --- a/src/Umbraco.Web.UI/Umbraco/dialogs/ChangeDocType.aspx.cs +++ b/src/Umbraco.Web.UI/Umbraco/dialogs/ChangeDocType.aspx.cs @@ -113,7 +113,8 @@ namespace Umbraco.Web.UI.Umbraco.Dialogs private IEnumerable RemoveInvalidByChildrenDocumentTypesFromAlternatives(IEnumerable documentTypes) { - var docTypeIdsOfChildren = _content.Children(Services.ContentService) + //fixme Should do proper paging here ... when this is refactored we will + var docTypeIdsOfChildren = Services.ContentService.GetPagedChildren(_content.Id, 0, int.MaxValue, out var total) .Select(x => x.ContentType.Id) .Distinct() .ToList(); diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 9681a79ed1..e5d9e8fc3c 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -456,7 +456,7 @@ namespace Umbraco.Web.Editors public PagedResult> GetChildren( int id, string includeProperties, - int pageNumber = 0, //TODO: This should be '1' as it's not the index + int pageNumber = 0, int pageSize = 0, string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, @@ -483,7 +483,8 @@ namespace Umbraco.Web.Editors } else { - children = Services.ContentService.GetChildren(id).ToList(); + //better to not use this without paging where possible, currently only the sort dialog does + children = Services.ContentService.GetPagedChildren(id, 0, int.MaxValue, out var total).ToList(); totalChildren = children.Count; } From 40eafe57b1f489266e4f570aea0887b067f12b54 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 31 Oct 2018 22:38:58 +1100 Subject: [PATCH 14/71] Fixes tests --- src/Umbraco.Core/Services/IContentService.cs | 2 +- .../Services/Implement/ContentService.cs | 30 ++++- .../Integration/ContentEventsTests.cs | 113 +++++++++--------- .../Services/ContentServiceTests.cs | 23 ++-- 4 files changed, 93 insertions(+), 75 deletions(-) diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index b821ae1eb0..c6efe926c2 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -193,7 +193,7 @@ namespace Umbraco.Core.Services /// The ordering direction. /// Search text filter. IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = ""); + string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = ""); /// /// Gets descendant documents of a given parent. diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 1f28988fa6..d2a74ba37e 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -593,8 +593,6 @@ namespace Umbraco.Core.Services.Implement { scope.ReadLock(Constants.Locks.ContentTree); - var query = Query(); - //if the id is System Root, then just get all if (id != Constants.System.Root) { @@ -604,8 +602,24 @@ namespace Umbraco.Core.Services.Implement totalChildren = 0; return Enumerable.Empty(); } - query.Where(x => x.Path.SqlStartsWith($"{contentPath[0].Path},", TextColumnType.NVarchar)); + return GetPagedDescendants(contentPath[0].Path, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); } + return GetPagedDescendants(null, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); + } + } + + private IEnumerable GetPagedDescendants(string contentPath, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter) + { + if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); + if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); + + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + { + scope.ReadLock(Constants.Locks.ContentTree); + + var query = Query(); + if (!contentPath.IsNullOrWhiteSpace()) + query.Where(x => x.Path.SqlStartsWith($"{contentPath},", TextColumnType.NVarchar)); return _documentRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)); } @@ -794,7 +808,7 @@ namespace Umbraco.Core.Services.Implement var langs = string.Join(", ", _languageRepository.GetMany() .Where(x => culturesChanging.InvariantContains(x.IsoCode)) .Select(x => x.CultureName)); - Audit(AuditType.SaveVariant, userId, content.Id, $"Saved languagues: {langs}", langs); + Audit(AuditType.SaveVariant, userId, content.Id, $"Saved languages: {langs}", langs); } else Audit(AuditType.Save, userId, content.Id); @@ -1125,7 +1139,7 @@ namespace Umbraco.Core.Services.Implement var langs = string.Join(", ", _languageRepository.GetMany() .Where(x => culturesChanging.InvariantContains(x.IsoCode)) .Select(x => x.CultureName)); - Audit(AuditType.PublishVariant, userId, content.Id, $"Published languagues: {langs}", langs); + Audit(AuditType.PublishVariant, userId, content.Id, $"Published languages: {langs}", langs); } else Audit(AuditType.Publish, userId, content.Id); @@ -1650,6 +1664,9 @@ namespace Umbraco.Core.Services.Implement moves.Add(Tuple.Create(content, content.Path)); // capture original path + //need to store the original path to lookup descendants based on it below + var originalPath = content.Path; + // these will be updated by the repo because we changed parentId //content.Path = (parent == null ? "-1" : parent.Path) + "," + content.Id; //content.SortOrder = ((ContentRepository) repository).NextChildSortOrder(parentId); @@ -1666,7 +1683,7 @@ namespace Umbraco.Core.Services.Implement var total = long.MaxValue; while(page * pageSize < total) { - var descendants = GetPagedDescendants(content.Id, page++, pageSize, out total); + var descendants = GetPagedDescendants(originalPath, page++, pageSize, out total, "Path", Direction.Ascending, true, null); foreach (var descendant in descendants) { moves.Add(Tuple.Create(descendant, descendant.Path)); // capture original path @@ -1685,6 +1702,7 @@ namespace Umbraco.Core.Services.Implement private void PerformMoveContentLocked(IContent content, int userId, bool? trash) { + //fixme no casting if (trash.HasValue) ((ContentBase) content).Trashed = trash.Value; content.WriterId = userId; _documentRepository.Save(content); diff --git a/src/Umbraco.Tests/Integration/ContentEventsTests.cs b/src/Umbraco.Tests/Integration/ContentEventsTests.cs index 655d44e86c..af188c6a09 100644 --- a/src/Umbraco.Tests/Integration/ContentEventsTests.cs +++ b/src/Umbraco.Tests/Integration/ContentEventsTests.cs @@ -846,22 +846,18 @@ namespace Umbraco.Tests.Integration // force:true => all nodes are republished, refreshing all nodes - but only with changes - published w/out changes are not repub Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1.Id}.u+p", _events[i++].ToString()); - //Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[0].Id}.p+p", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[1].Id}.u+p", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[2].Id}.p+p", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[3].Id}.u+p", _events[i++].ToString()); - - // remember: ordered by level, sortOrder //Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content2C[0].Id}.p+p", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content3C[0].Id}.u+p", _events[i++].ToString()); - //Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content4C[0].Id}.p+p", _events[i++].ToString()); - //Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content5C[0].Id}.p+p", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content2C[1].Id}.u+p", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[1].Id}.u+p", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content3C[0].Id}.u+p", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content3C[1].Id}.u+p", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[2].Id}.p+p", _events[i++].ToString()); + //Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content4C[0].Id}.p+p", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content4C[1].Id}.u+p", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[3].Id}.u+p", _events[i++].ToString()); + //Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content5C[0].Id}.p+p", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content5C[1].Id}.u+p", _events[i++].ToString()); - Assert.AreEqual($"{m:000}: ContentCacheRefresher/RefreshBranch/{content1.Id}", _events[i++].ToString()); // repub content1 } @@ -1073,17 +1069,18 @@ namespace Umbraco.Tests.Integration Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1.Id}.p=m", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[0].Id}.p=m", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[1].Id}.u=u", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[2].Id}.p=m", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[3].Id}.u=u", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content2C[0].Id}.p=m", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content3C[0].Id}.u=u", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content4C[0].Id}.p=m", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content5C[0].Id}.p=m", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content2C[1].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[1].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content3C[0].Id}.u=u", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content3C[1].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[2].Id}.p=m", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content4C[0].Id}.p=m", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content4C[1].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[3].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content5C[0].Id}.p=m", _events[i++].ToString()); Assert.AreEqual($"{m:000}: ContentRepository/Refresh/{content5C[1].Id}.u=u", _events[i++].ToString()); + m++; Assert.AreEqual($"{m:000}: ContentCacheRefresher/RefreshBranch/{content1.Id}", _events[i].ToString()); } @@ -1706,16 +1703,16 @@ namespace Umbraco.Tests.Integration var content5C = Children(content1C[3]).ToArray(); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1.Id}.p=m", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[0].Id}.p=m", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[1].Id}.u=u", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[2].Id}.p=m", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[3].Id}.u=u", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content2C[0].Id}.p=m", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content3C[0].Id}.u=u", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content4C[0].Id}.p=m", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content5C[0].Id}.p=m", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content2C[1].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[1].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content3C[0].Id}.u=u", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content3C[1].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[2].Id}.p=m", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content4C[0].Id}.p=m", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content4C[1].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[3].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content5C[0].Id}.p=m", _events[i++].ToString()); Assert.AreEqual($"{m:000}: ContentRepository/Refresh/{content5C[1].Id}.u=u", _events[i++].ToString()); m++; Assert.AreEqual($"{m:000}: ContentCacheRefresher/RefreshBranch/{content1.Id}", _events[i++].ToString()); @@ -1759,16 +1756,16 @@ namespace Umbraco.Tests.Integration var content5C = Children(content1C[3]).ToArray(); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1.Id}.p=p", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[0].Id}.p=p", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[1].Id}.u=u", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[2].Id}.p=p", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[3].Id}.u=u", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content2C[0].Id}.p=p", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content3C[0].Id}.u=u", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content4C[0].Id}.p=p", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content5C[0].Id}.p=m", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content2C[1].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[1].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content3C[0].Id}.u=u", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content3C[1].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[2].Id}.p=p", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content4C[0].Id}.p=p", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content4C[1].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[3].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content5C[0].Id}.p=m", _events[i++].ToString()); Assert.AreEqual($"{m:000}: ContentRepository/Refresh/{content5C[1].Id}.u=u", _events[i++].ToString()); m++; Assert.AreEqual($"{m:000}: ContentCacheRefresher/RefreshBranch/{content1.Id}", _events[i++].ToString()); @@ -1816,16 +1813,16 @@ namespace Umbraco.Tests.Integration var content5C = Children(content1C[3]).ToArray(); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1.Id}.p=m", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[0].Id}.p=m", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[1].Id}.u=u", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[2].Id}.p=m", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[3].Id}.u=u", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content2C[0].Id}.p=m", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content3C[0].Id}.u=u", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content4C[0].Id}.p=m", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content5C[0].Id}.p=m", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content2C[1].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[1].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content3C[0].Id}.u=u", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content3C[1].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[2].Id}.p=m", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content4C[0].Id}.p=m", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content4C[1].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[3].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content5C[0].Id}.p=m", _events[i++].ToString()); Assert.AreEqual($"{m:000}: ContentRepository/Refresh/{content5C[1].Id}.u=u", _events[i++].ToString()); m++; Assert.AreEqual($"{m:000}: ContentCacheRefresher/RefreshBranch/{content1.Id}", _events[i++].ToString()); @@ -1871,16 +1868,16 @@ namespace Umbraco.Tests.Integration var content5C = Children(content1C[3]).ToArray(); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1.Id}.p=p", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[0].Id}.p=p", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[1].Id}.u=u", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[2].Id}.p=p", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[3].Id}.u=u", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content2C[0].Id}.p=p", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content3C[0].Id}.u=u", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content4C[0].Id}.p=p", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content5C[0].Id}.p=m", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content2C[1].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[1].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content3C[0].Id}.u=u", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content3C[1].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[2].Id}.p=p", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content4C[0].Id}.p=p", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content4C[1].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[3].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content5C[0].Id}.p=m", _events[i++].ToString()); Assert.AreEqual($"{m:000}: ContentRepository/Refresh/{content5C[1].Id}.u=u", _events[i++].ToString()); m++; Assert.AreEqual($"{m:000}: ContentCacheRefresher/RefreshBranch/{content1.Id}", _events[i++].ToString()); @@ -1925,16 +1922,16 @@ namespace Umbraco.Tests.Integration var content5C = Children(content1C[3]).ToArray(); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1.Id}.p=p", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[0].Id}.p=p", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[1].Id}.u=u", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[2].Id}.p=p", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[3].Id}.u=u", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content2C[0].Id}.p=p", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content3C[0].Id}.u=u", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content4C[0].Id}.p=p", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content5C[0].Id}.p=m", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content2C[1].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[1].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content3C[0].Id}.u=u", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content3C[1].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[2].Id}.p=p", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content4C[0].Id}.p=p", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content4C[1].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[3].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content5C[0].Id}.p=m", _events[i++].ToString()); Assert.AreEqual($"{m:000}: ContentRepository/Refresh/{content5C[1].Id}.u=u", _events[i++].ToString()); m++; Assert.AreEqual($"{m:000}: ContentCacheRefresher/RefreshBranch/{content1.Id}", _events[i++].ToString()); @@ -1984,16 +1981,16 @@ namespace Umbraco.Tests.Integration var content5C = Children(content1C[3]).ToArray(); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1.Id}.p=p", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[0].Id}.p=p", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[1].Id}.u=u", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[2].Id}.p=p", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[3].Id}.u=u", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content2C[0].Id}.p=p", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content3C[0].Id}.u=u", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content4C[0].Id}.p=p", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content5C[0].Id}.p=m", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content2C[1].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[1].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content3C[0].Id}.u=u", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content3C[1].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[2].Id}.p=p", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content4C[0].Id}.p=p", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content4C[1].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[3].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content5C[0].Id}.p=m", _events[i++].ToString()); Assert.AreEqual($"{m:000}: ContentRepository/Refresh/{content5C[1].Id}.u=u", _events[i++].ToString()); m++; Assert.AreEqual($"{m:000}: ContentCacheRefresher/RefreshBranch/{content1.Id}", _events[i++].ToString()); @@ -2098,16 +2095,16 @@ namespace Umbraco.Tests.Integration var m = 0; Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{copy.Id}.u=u", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{copyC[0].Id}.u=u", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{copyC[1].Id}.u=u", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{copyC[2].Id}.u=u", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{copyC[3].Id}.u=u", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{copy2C[0].Id}.u=u", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{copy3C[0].Id}.u=u", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{copy4C[0].Id}.u=u", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{copy5C[0].Id}.u=u", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{copy2C[1].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{copyC[1].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{copy3C[0].Id}.u=u", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{copy3C[1].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{copyC[2].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{copy4C[0].Id}.u=u", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{copy4C[1].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{copyC[3].Id}.u=u", _events[i++].ToString()); + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{copy5C[0].Id}.u=u", _events[i++].ToString()); Assert.AreEqual($"{m:000}: ContentRepository/Refresh/{copy5C[1].Id}.u=u", _events[i++].ToString()); m++; Assert.AreEqual($"{m:000}: ContentCacheRefresher/RefreshBranch/{copy.Id}", _events[i].ToString()); diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 952c8f9dc1..1f99bd1127 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -35,7 +35,10 @@ namespace Umbraco.Tests.Services /// as well as configuration. /// [TestFixture] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, PublishedRepositoryEvents = true, WithApplication = true)] + [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, + PublishedRepositoryEvents = true, + WithApplication = true, + Logger = UmbracoTestOptions.Logger.Console)] public class ContentServiceTests : TestWithSomeContentBase { //TODO Add test to verify there is only ONE newest document/content in {Constants.DatabaseSchema.Tables.Document} table after updating. @@ -1209,7 +1212,7 @@ namespace Umbraco.Tests.Services { // Arrange - var langUk = new Language("en-UK") { IsDefault = true }; + var langUk = new Language("en-GB") { IsDefault = true }; var langFr = new Language("fr-FR"); ServiceContext.LocalizationService.Save(langFr); @@ -1262,7 +1265,7 @@ namespace Umbraco.Tests.Services { // Arrange - var langUk = new Language("en-UK") { IsDefault = true }; + var langUk = new Language("en-GB") { IsDefault = true }; var langFr = new Language("fr-FR"); ServiceContext.LocalizationService.Save(langFr); @@ -1278,7 +1281,7 @@ namespace Umbraco.Tests.Services var published = ServiceContext.ContentService.SavePublishing(content); //audit log will only show that french was published var lastLog = ServiceContext.AuditService.GetLogs(content.Id).Last(); - Assert.AreEqual($"Published cultures: fr-fr", lastLog.Comment); + Assert.AreEqual($"Published languages: French (France)", lastLog.Comment); //re-get content = ServiceContext.ContentService.GetById(content.Id); @@ -1287,7 +1290,7 @@ namespace Umbraco.Tests.Services published = ServiceContext.ContentService.SavePublishing(content); //audit log will only show that english was published lastLog = ServiceContext.AuditService.GetLogs(content.Id).Last(); - Assert.AreEqual($"Published cultures: en-uk", lastLog.Comment); + Assert.AreEqual($"Published languages: English (United Kingdom)", lastLog.Comment); } [Test] @@ -2775,7 +2778,7 @@ namespace Umbraco.Tests.Services { var languageService = ServiceContext.LocalizationService; - var langUk = new Language("en-UK") { IsDefault = true }; + var langUk = new Language("en-GB") { IsDefault = true }; var langFr = new Language("fr-FR"); languageService.Save(langFr); @@ -2810,7 +2813,7 @@ namespace Umbraco.Tests.Services { var languageService = ServiceContext.LocalizationService; - var langUk = new Language("en-UK") { IsDefault = true }; + var langUk = new Language("en-GB") { IsDefault = true }; var langFr = new Language("fr-FR"); languageService.Save(langFr); @@ -2847,7 +2850,7 @@ namespace Umbraco.Tests.Services { var languageService = ServiceContext.LocalizationService; - var langUk = new Language("en-UK") { IsDefault = true }; + var langUk = new Language("en-GB") { IsDefault = true }; var langFr = new Language("fr-FR"); var langDa = new Language("da-DK"); @@ -2939,7 +2942,7 @@ namespace Umbraco.Tests.Services private void WriteList(List list) { foreach (var content in list) - Console.WriteLine("[{0}] {1} {2} {3} {4}", content.Id, content.Name, content.GetCultureName("en-UK"), content.GetCultureName("fr-FR"), content.GetCultureName("da-DK")); + Console.WriteLine("[{0}] {1} {2} {3} {4}", content.Id, content.Name, content.GetCultureName("en-GB"), content.GetCultureName("fr-FR"), content.GetCultureName("da-DK")); Console.WriteLine("-"); } @@ -2951,7 +2954,7 @@ namespace Umbraco.Tests.Services //var langFr = new Language("fr-FR") { IsDefaultVariantLanguage = true }; var langXx = new Language("pt-PT") { IsDefault = true }; var langFr = new Language("fr-FR"); - var langUk = new Language("en-UK"); + var langUk = new Language("en-GB"); var langDe = new Language("de-DE"); languageService.Save(langFr); From 25dcc3f2c185d84fe9b08f0e6c887f0a43ad3079 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 31 Oct 2018 23:11:37 +1100 Subject: [PATCH 15/71] Removes GetChildren and GetDescendants from IMediaService, paging is now required --- src/Umbraco.Core/ContentExtensions.cs | 73 ----------- .../Services/EntityXmlSerializer.cs | 28 +++-- src/Umbraco.Core/Services/IMediaService.cs | 23 +--- .../Services/Implement/ContentService.cs | 83 ++++--------- .../Services/Implement/MediaService.cs | 117 ++++++------------ src/Umbraco.Web/Editors/ContentController.cs | 2 +- src/Umbraco.Web/Editors/MediaController.cs | 29 ++++- .../Routing/UrlProviderExtensions.cs | 2 +- src/Umbraco.Web/Search/ExamineComponent.cs | 12 +- 9 files changed, 112 insertions(+), 257 deletions(-) diff --git a/src/Umbraco.Core/ContentExtensions.cs b/src/Umbraco.Core/ContentExtensions.cs index f0fa6cdf17..910717304c 100644 --- a/src/Umbraco.Core/ContentExtensions.cs +++ b/src/Umbraco.Core/ContentExtensions.cs @@ -37,79 +37,6 @@ namespace Umbraco.Core return dirty.WasPropertyDirty("Published") && entity.Published; } - /// - /// Returns a list of the current contents ancestors, not including the content itself. - /// - /// Current content - /// - /// An enumerable list of objects - public static IEnumerable Ancestors(this IContent content, IContentService contentService) - { - return contentService.GetAncestors(content); - } - - /// - /// Returns the parent of the current content. - /// - /// Current content - /// - /// An object - public static IContent Parent(this IContent content, IContentService contentService) - { - return contentService.GetById(content.ParentId); - } - - #endregion - - #region IMedia - - /// - /// Returns a list of the current medias ancestors, not including the media itself. - /// - /// Current media - /// - /// An enumerable list of objects - public static IEnumerable Ancestors(this IMedia media, IMediaService mediaService) - { - return mediaService.GetAncestors(media); - } - - - /// - /// Returns a list of the current medias children. - /// - /// Current media - /// - /// An enumerable list of objects - public static IEnumerable Children(this IMedia media, IMediaService mediaService) - { - return mediaService.GetChildren(media.Id); - } - - - /// - /// Returns a list of the current medias descendants, not including the media itself. - /// - /// Current media - /// - /// An enumerable list of objects - public static IEnumerable Descendants(this IMedia media, IMediaService mediaService) - { - return mediaService.GetDescendants(media); - } - - - /// - /// Returns the parent of the current media. - /// - /// Current media - /// - /// An object - public static IMedia Parent(this IMedia media, IMediaService mediaService) - { - return mediaService.GetById(media.ParentId); - } - #endregion /// diff --git a/src/Umbraco.Core/Services/EntityXmlSerializer.cs b/src/Umbraco.Core/Services/EntityXmlSerializer.cs index ebb35a43ed..5b64584dc6 100644 --- a/src/Umbraco.Core/Services/EntityXmlSerializer.cs +++ b/src/Umbraco.Core/Services/EntityXmlSerializer.cs @@ -109,9 +109,14 @@ namespace Umbraco.Core.Services if (withDescendants) { - var descendants = mediaService.GetDescendants(media).ToArray(); - var currentChildren = descendants.Where(x => x.ParentId == media.Id); - SerializeDescendants(mediaService, dataTypeService, userService, localizationService, urlSegmentProviders, descendants, currentChildren, xml); + const int pageSize = 500; + var page = 0; + var total = long.MaxValue; + while (page * pageSize < total) + { + var children = mediaService.GetPagedChildren(media.Id, page++, pageSize, out total); + SerializeChildren(mediaService, dataTypeService, userService, localizationService, urlSegmentProviders, children, xml); + } } return xml; @@ -478,7 +483,7 @@ namespace Umbraco.Core.Services } // exports an IMedia item descendants. - private static void SerializeDescendants(IMediaService mediaService, IDataTypeService dataTypeService, IUserService userService, ILocalizationService localizationService, IEnumerable urlSegmentProviders, IMedia[] originalDescendants, IEnumerable children, XElement xml) + private static void SerializeChildren(IMediaService mediaService, IDataTypeService dataTypeService, IUserService userService, ILocalizationService localizationService, IEnumerable urlSegmentProviders, IEnumerable children, XElement xml) { foreach (var child in children) { @@ -486,12 +491,15 @@ namespace Umbraco.Core.Services var childXml = Serialize(mediaService, dataTypeService, userService, localizationService, urlSegmentProviders, child); xml.Add(childXml); - // capture id (out of closure) and get the grandChildren (children of the child) - var parentId = child.Id; - var grandChildren = originalDescendants.Where(x => x.ParentId == parentId); - - // recurse - SerializeDescendants(mediaService, dataTypeService, userService, localizationService, urlSegmentProviders, originalDescendants, grandChildren, childXml); + const int pageSize = 500; + var page = 0; + var total = long.MaxValue; + while (page * pageSize < total) + { + var grandChildren = mediaService.GetPagedChildren(child.Id, page++, pageSize, out total); + // recurse + SerializeChildren(mediaService, dataTypeService, userService, localizationService, urlSegmentProviders, grandChildren, childXml); + } } } } diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs index 31c2e74fd4..6976a09b76 100644 --- a/src/Umbraco.Core/Services/IMediaService.cs +++ b/src/Umbraco.Core/Services/IMediaService.cs @@ -78,13 +78,6 @@ namespace Umbraco.Core.Services /// IMedia GetById(int id); - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// An Enumerable list of objects - IEnumerable GetChildren(int id); - /// /// Gets a collection of objects by Parent Id /// @@ -158,14 +151,7 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter); - - /// - /// Gets descendants of a object by its Id - /// - /// Id of the Parent to retrieve descendants from - /// An Enumerable flat list of objects - IEnumerable GetDescendants(int id); - + /// /// Gets a collection of objects by the Id of the /// @@ -321,13 +307,6 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects IEnumerable GetAncestors(IMedia media); - /// - /// Gets descendants of a object by its Id - /// - /// The Parent object to retrieve descendants from - /// An Enumerable flat list of objects - IEnumerable GetDescendants(IMedia media); - /// /// Gets the parent of the current media as an item. /// diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index d2a74ba37e..2c8e38777b 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -586,9 +586,6 @@ namespace Umbraco.Core.Services.Implement /// public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter) { - if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); - if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { scope.ReadLock(Constants.Locks.ContentTree); @@ -602,27 +599,22 @@ namespace Umbraco.Core.Services.Implement totalChildren = 0; return Enumerable.Empty(); } - return GetPagedDescendants(contentPath[0].Path, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); + return GetPagedDescendantsLocked(contentPath[0].Path, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); } - return GetPagedDescendants(null, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); + return GetPagedDescendantsLocked(null, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); } } - private IEnumerable GetPagedDescendants(string contentPath, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter) + private IEnumerable GetPagedDescendantsLocked(string contentPath, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter) { if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) - { - scope.ReadLock(Constants.Locks.ContentTree); + var query = Query(); + if (!contentPath.IsNullOrWhiteSpace()) + query.Where(x => x.Path.SqlStartsWith($"{contentPath},", TextColumnType.NVarchar)); - var query = Query(); - if (!contentPath.IsNullOrWhiteSpace()) - query.Where(x => x.Path.SqlStartsWith($"{contentPath},", TextColumnType.NVarchar)); - - return _documentRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)); - } + return _documentRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)); } /// @@ -1403,50 +1395,8 @@ namespace Umbraco.Core.Services.Implement private void DeleteLocked(IScope scope, IContent content) { - //TODO: Test this - - // then recursively delete descendants, bottom-up - // just repository.Delete + an event - var stack = new Stack(); - stack.Push(content); - var level = 1; - while (stack.Count > 0) + void DoDelete(IContent c) { - var c = stack.Peek(); - if (c.Level == level) - { - var hasChildren = true; - while (hasChildren) - { - IContent last = null; - const int pageSize = 500; - var page = 0; - var total = long.MaxValue; - var countChildren = 0; - - //get all children of c in pages - while (page * pageSize < total) - { - var children = GetPagedChildren(c.Id, page++, pageSize, out total); - - foreach (var ci in children) - { - countChildren++; - stack.Push(ci); - last = ci; - } - - if (countChildren == 0) - hasChildren = false; //exit, there are no children left to load - else - c = last; //recurse with the last child - } - } - } - - c = stack.Pop(); - level = c.Level; - _documentRepository.Delete(c); var args = new DeleteEventArgs(c, false); // raise event & get flagged files scope.Events.Dispatch(Deleted, this, args, nameof(Deleted)); @@ -1455,6 +1405,18 @@ namespace Umbraco.Core.Services.Implement _mediaFileSystem.DeleteFiles(args.MediaFilesToDelete, // remove flagged files (file, e) => Logger.Error(e, "An error occurred while deleting file attached to nodes: {File}", file)); } + + const int pageSize = 500; + var page = 0; + var total = long.MaxValue; + while (page * pageSize < total) + { + //get descendants - ordered from deepest to shallowest + var descendants = GetPagedDescendants(content.Id, page, pageSize, out total, "Path", Direction.Descending); + foreach (var c in descendants) + DoDelete(c); + } + DoDelete(content); } //TODO: @@ -1683,16 +1645,13 @@ namespace Umbraco.Core.Services.Implement var total = long.MaxValue; while(page * pageSize < total) { - var descendants = GetPagedDescendants(originalPath, page++, pageSize, out total, "Path", Direction.Ascending, true, null); + var descendants = GetPagedDescendantsLocked(originalPath, page++, pageSize, out total, "Path", Direction.Ascending, true, null); foreach (var descendant in descendants) { moves.Add(Tuple.Create(descendant, descendant.Path)); // capture original path // update path and level since we do not update parentId - if (paths.ContainsKey(descendant.ParentId) == false) - Console.WriteLine("oops on " + descendant.ParentId + " for " + content.Path + " " + parent?.Path); descendant.Path = paths[descendant.Id] = paths[descendant.ParentId] + "," + descendant.Id; - Console.WriteLine("path " + descendant.Id + " = " + paths[descendant.Id]); descendant.Level += levelDelta; PerformMoveContentLocked(descendant, userId, trash); } diff --git a/src/Umbraco.Core/Services/Implement/MediaService.cs b/src/Umbraco.Core/Services/Implement/MediaService.cs index da04f41e18..25aa02befa 100644 --- a/src/Umbraco.Core/Services/Implement/MediaService.cs +++ b/src/Umbraco.Core/Services/Implement/MediaService.cs @@ -460,21 +460,6 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// An Enumerable list of objects - public IEnumerable GetChildren(int id) - { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) - { - scope.ReadLock(Constants.Locks.MediaTree); - var query = Query().Where(x => x.ParentId == id); - return _mediaRepository.Get(query).OrderBy(x => x.SortOrder); - } - } - /// /// Gets a collection of objects by Parent Id /// @@ -594,15 +579,10 @@ namespace Umbraco.Core.Services.Implement /// An Enumerable list of objects public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter) { - if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); - if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { scope.ReadLock(Constants.Locks.MediaTree); - var query = Query(); - //if the id is System Root, then just get all if (id != Constants.System.Root) { @@ -612,47 +592,22 @@ namespace Umbraco.Core.Services.Implement totalChildren = 0; return Enumerable.Empty(); } - query.Where(x => x.Path.SqlStartsWith(mediaPath[0].Path + ",", TextColumnType.NVarchar)); + return GetPagedDescendantsLocked(mediaPath[0].Path, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); } - - return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)); + return GetPagedDescendantsLocked(null, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); } } - /// - /// Gets descendants of a object by its Id - /// - /// Id of the Parent to retrieve descendants from - /// An Enumerable flat list of objects - public IEnumerable GetDescendants(int id) + private IEnumerable GetPagedDescendantsLocked(string mediaPath, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter) { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) - { - scope.ReadLock(Constants.Locks.MediaTree); - var media = GetById(id); - if (media == null) - return Enumerable.Empty(); + if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); + if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); - var pathMatch = media.Path + ","; - var query = Query().Where(x => x.Id != media.Id && x.Path.StartsWith(pathMatch)); - return _mediaRepository.Get(query); - } - } + var query = Query(); + if (!mediaPath.IsNullOrWhiteSpace()) + query.Where(x => x.Path.SqlStartsWith(mediaPath + ",", TextColumnType.NVarchar)); - /// - /// Gets descendants of a object by its Id - /// - /// The Parent object to retrieve descendants from - /// An Enumerable flat list of objects - public IEnumerable GetDescendants(IMedia media) - { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) - { - scope.ReadLock(Constants.Locks.MediaTree); - var pathMatch = media.Path + ","; - var query = Query().Where(x => x.Id != media.Id && x.Path.StartsWith(pathMatch)); - return _mediaRepository.Get(query); - } + return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)); } /// @@ -865,25 +820,8 @@ namespace Umbraco.Core.Services.Implement private void DeleteLocked(IScope scope, IMedia media) { - // then recursively delete descendants, bottom-up - // just repository.Delete + an event - var stack = new Stack(); - stack.Push(media); - var level = 1; - while (stack.Count > 0) + void DoDelete(IMedia c) { - var c = stack.Peek(); - IMedia[] cc; - if (c.Level == level) - while ((cc = c.Children(this).ToArray()).Length > 0) - { - foreach (var ci in cc) - stack.Push(ci); - c = cc[cc.Length - 1]; - } - c = stack.Pop(); - level = c.Level; - _mediaRepository.Delete(c); var args = new DeleteEventArgs(c, false); // raise event & get flagged files scope.Events.Dispatch(Deleted, this, args); @@ -891,6 +829,18 @@ namespace Umbraco.Core.Services.Implement _mediaFileSystem.DeleteFiles(args.MediaFilesToDelete, // remove flagged files (file, e) => Logger.Error(e, "An error occurred while deleting file attached to nodes: {File}", file)); } + + const int pageSize = 500; + var page = 0; + var total = long.MaxValue; + while(page * pageSize < total) + { + //get descendants - ordered from deepest to shallowest + var descendants = GetPagedDescendants(media.Id, page, pageSize, out total, "Path", Direction.Descending); + foreach (var c in descendants) + DoDelete(c); + } + DoDelete(media); } //TODO: @@ -1100,8 +1050,8 @@ namespace Umbraco.Core.Services.Implement moves.Add(Tuple.Create(media, media.Path)); // capture original path - // get before moving, in case uow is immediate - var descendants = GetDescendants(media); + //need to store the original path to lookup descendants based on it below + var originalPath = media.Path; // these will be updated by the repo because we changed parentId //media.Path = (parent == null ? "-1" : parent.Path) + "," + media.Id; @@ -1114,14 +1064,21 @@ namespace Umbraco.Core.Services.Implement //paths[media.Id] = media.Path; paths[media.Id] = (parent == null ? (parentId == Constants.System.RecycleBinMedia ? "-1,-21" : "-1") : parent.Path) + "," + media.Id; - foreach (var descendant in descendants) + const int pageSize = 500; + var page = 0; + var total = long.MaxValue; + while (page * pageSize < total) { - moves.Add(Tuple.Create(descendant, descendant.Path)); // capture original path + var descendants = GetPagedDescendantsLocked(originalPath, page++, pageSize, out total, "Path", Direction.Ascending, true, null); + foreach (var descendant in descendants) + { + moves.Add(Tuple.Create(descendant, descendant.Path)); // capture original path - // update path and level since we do not update parentId - descendant.Path = paths[descendant.Id] = paths[descendant.ParentId] + "," + descendant.Id; - descendant.Level += levelDelta; - PerformMoveMediaLocked(descendant, userId, trash); + // update path and level since we do not update parentId + descendant.Path = paths[descendant.Id] = paths[descendant.ParentId] + "," + descendant.Id; + descendant.Level += levelDelta; + PerformMoveMediaLocked(descendant, userId, trash); + } } } diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index e5d9e8fc3c..ed87f770ca 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1318,7 +1318,7 @@ namespace Umbraco.Web.Editors xnames.Add(xcontent.Name); if (xcontent.ParentId < -1) xnames.Add("Recycle Bin"); - xcontent = xcontent.Parent(Services.ContentService); + xcontent = Services.ContentService.GetParent(xcontent); } xnames.Reverse(); domainModel.Other = "/" + string.Join("/", xnames); diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index dc744ea361..3dfd435d8f 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -271,7 +271,7 @@ namespace Umbraco.Web.Editors // else proceed as usual long totalChildren; - IMedia[] children; + List children; if (pageNumber > 0 && pageSize > 0) { IQuery queryFilter = null; @@ -287,12 +287,13 @@ namespace Umbraco.Web.Editors id, (pageNumber - 1), pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, - queryFilter).ToArray(); + queryFilter).ToList(); } else { - children = Services.MediaService.GetChildren(id).ToArray(); - totalChildren = children.Length; + //better to not use this without paging where possible, currently only the sort dialog does + children = Services.MediaService.GetPagedChildren(id, 0, int.MaxValue, out var total).ToList(); + totalChildren = children.Count; } if (totalChildren == 0) @@ -663,7 +664,8 @@ namespace Umbraco.Web.Editors " returned null"); //look for matching folder - folderMediaItem = mediaRoot.Children(Services.MediaService).FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); + folderMediaItem = FindInChildren(mediaRoot.Id, folderName, Constants.Conventions.MediaTypes.Folder); + if (folderMediaItem == null) { //if null, create a folder @@ -753,6 +755,23 @@ namespace Umbraco.Web.Editors return Request.CreateResponse(HttpStatusCode.OK, tempFiles); } + private IMedia FindInChildren(int mediaId, string nameToFind, string contentTypeAlias) + { + const int pageSize = 500; + var page = 0; + var total = long.MaxValue; + while (page * pageSize < total) + { + var children = Services.MediaService.GetPagedChildren(mediaId, page, pageSize, out total); + foreach (var c in children) + { + if (c.Name == nameToFind && c.ContentType.Alias == contentTypeAlias) + return c; + } + } + return null; + } + /// /// Given a parent id which could be a GUID, UDI or an INT, this will resolve the INT /// diff --git a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs index 2be0dce096..14b9c0a465 100644 --- a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs +++ b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs @@ -129,7 +129,7 @@ namespace Umbraco.Web.Routing var parent = content; do { - parent = parent.ParentId > 0 ? parent.Parent(contentService) : null; + parent = parent.ParentId > 0 ? contentService.GetParent(parent) : null; } while (parent != null && parent.Published && (!parent.ContentType.VariesByCulture() || parent.IsCulturePublished(culture))); diff --git a/src/Umbraco.Web/Search/ExamineComponent.cs b/src/Umbraco.Web/Search/ExamineComponent.cs index b1a302785d..05be969f92 100644 --- a/src/Umbraco.Web/Search/ExamineComponent.cs +++ b/src/Umbraco.Web/Search/ExamineComponent.cs @@ -304,10 +304,16 @@ namespace Umbraco.Web.Search // branch if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshBranch)) { - var descendants = mediaService.GetDescendants(media); - foreach (var descendant in descendants) + const int pageSize = 500; + var page = 0; + var total = long.MaxValue; + while (page * pageSize < total) { - ReIndexForMedia(descendant, descendant.Trashed == false); + var descendants = mediaService.GetPagedDescendants(media.Id, page++, pageSize, out total); + foreach (var descendant in descendants) + { + ReIndexForMedia(descendant, descendant.Trashed == false); + } } } } From 4d594e17aafa01a07a105a038f6431b50195f15e Mon Sep 17 00:00:00 2001 From: elitsa Date: Wed, 31 Oct 2018 13:40:23 +0100 Subject: [PATCH 16/71] Adding a check to verify if there are any children which will resolve the problem with showing a right-pointing arrow in front of empty trees in the Settings section. --- src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs | 3 +++ src/Umbraco.Web/Trees/FileSystemTreeController.cs | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs b/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs index 5b45888dca..f3be39b6f8 100644 --- a/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs @@ -32,6 +32,9 @@ namespace Umbraco.Web.Trees //this will load in a custom UI instead of the dashboard for the root node root.RoutePath = $"{Constants.Applications.Settings}/{Constants.Trees.ContentBlueprints}/intro"; + //check if there are any content blueprints + root.HasChildren = Services.ContentService.GetBlueprintsForContentTypes().Any(); + return root; } protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) diff --git a/src/Umbraco.Web/Trees/FileSystemTreeController.cs b/src/Umbraco.Web/Trees/FileSystemTreeController.cs index 21e81a58af..33c5c360b2 100644 --- a/src/Umbraco.Web/Trees/FileSystemTreeController.cs +++ b/src/Umbraco.Web/Trees/FileSystemTreeController.cs @@ -74,6 +74,14 @@ namespace Umbraco.Web.Trees return nodes; } + protected override TreeNode CreateRootNode(FormDataCollection queryStrings) + { + var root = base.CreateRootNode(queryStrings); + //check if there are any children + root.HasChildren = GetTreeNodes(Constants.System.Root.ToInvariantString(), queryStrings).Any(); + return root; + } + protected virtual MenuItemCollection GetMenuForRootNode(FormDataCollection queryStrings) { var menu = new MenuItemCollection(); From b8d1dd7684a5dabb3359ba8fbfee926e2ae7126d Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 1 Nov 2018 00:05:17 +1100 Subject: [PATCH 17/71] Cleans up paged methods of IContentService to use the Ordering parameter for consistency --- src/Umbraco.Core/Services/IContentService.cs | 39 ++++++++++----- .../Services/Implement/ContentService.cs | 47 +++++++++++++++---- src/Umbraco.Examine/UmbracoContentIndexer.cs | 3 +- .../Services/ContentServiceTests.cs | 2 +- .../UmbracoExamine/IndexInitializer.cs | 4 +- .../UmbracoExamine/SearchTests.cs | 2 +- src/Umbraco.Web/Search/ExamineComponent.cs | 45 ++++++++++++++---- 7 files changed, 105 insertions(+), 37 deletions(-) diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index c6efe926c2..c609a7493d 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -78,17 +78,6 @@ namespace Umbraco.Core.Services /// IEnumerable GetByIds(IEnumerable ids); - /// - /// Gets paged documents of a content content - /// - /// The page number. - /// The page number. - /// The page size. - /// Total number of documents. - /// Search text filter. - /// Ordering infos. - IEnumerable GetPagedOfType(int contentType, long pageIndex, int pageSize, out long totalRecords, IQuery filter = null, Ordering ordering = null); - /// /// Gets documents at a given level. /// @@ -193,7 +182,7 @@ namespace Umbraco.Core.Services /// The ordering direction. /// Search text filter. IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = ""); + string filter = null, Ordering ordering = null); /// /// Gets descendant documents of a given parent. @@ -207,7 +196,31 @@ namespace Umbraco.Core.Services /// A flag indicating whether the ordering field is a system field. /// Query filter. IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter); + IQuery filter, Ordering ordering = null); + + /// + /// Gets paged documents of a content content + /// + /// The page number. + /// The page number. + /// The page size. + /// Total number of documents. + /// Search text filter. + /// Ordering infos. + IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int pageSize, out long totalRecords, + IQuery filter, Ordering ordering = null); + + /// + /// Gets paged documents for specified content types + /// + /// The page number. + /// The page number. + /// The page size. + /// Total number of documents. + /// Search text filter. + /// Ordering infos. + IEnumerable GetPagedOfTypes(int[] contentTypeIds, long pageIndex, int pageSize, out long totalRecords, + IQuery filter, Ordering ordering = null); /// /// Counts documents of a given document type. diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 2c8e38777b..1c3f5203f9 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -405,6 +405,7 @@ namespace Umbraco.Core.Services.Implement } } + /// public IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int pageSize, out long totalRecords, IQuery filter, Ordering ordering = null) { if(pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); @@ -422,6 +423,24 @@ namespace Umbraco.Core.Services.Implement } } + /// + public IEnumerable GetPagedOfTypes(int[] contentTypeIds, long pageIndex, int pageSize, out long totalRecords, IQuery filter, Ordering ordering = null) + { + if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); + if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); + + if (ordering == null) + ordering = Ordering.By("sortOrder"); + + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + { + scope.ReadLock(Constants.Locks.ContentTree); + return _documentRepository.GetPage( + Query().Where(x => contentTypeIds.Contains(x.ContentTypeId)), + pageIndex, pageSize, out totalRecords, filter, ordering); + } + } + /// /// Gets a collection of objects by Level /// @@ -574,18 +593,24 @@ namespace Umbraco.Core.Services.Implement } /// - public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") + public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, + string filter = null, Ordering ordering = null) + //string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") { var filterQuery = filter.IsNullOrWhiteSpace() ? null : Query().Where(x => x.Name.Contains(filter)); - return GetPagedDescendants(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filterQuery); + return GetPagedDescendants(id, pageIndex, pageSize, out totalChildren, filterQuery, ordering); } /// - public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter) + public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, + IQuery filter, Ordering ordering = null) { + if (ordering == null) + ordering = Ordering.By("Path"); + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { scope.ReadLock(Constants.Locks.ContentTree); @@ -599,22 +624,26 @@ namespace Umbraco.Core.Services.Implement totalChildren = 0; return Enumerable.Empty(); } - return GetPagedDescendantsLocked(contentPath[0].Path, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); + return GetPagedDescendantsLocked(contentPath[0].Path, pageIndex, pageSize, out totalChildren, filter, ordering); } - return GetPagedDescendantsLocked(null, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); + return GetPagedDescendantsLocked(null, pageIndex, pageSize, out totalChildren, filter, ordering); } } - private IEnumerable GetPagedDescendantsLocked(string contentPath, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter) + private IEnumerable GetPagedDescendantsLocked(string contentPath, long pageIndex, int pageSize, out long totalChildren, + IQuery filter, Ordering ordering) { if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); + if (filter == null) throw new ArgumentNullException(nameof(filter)); + + if (ordering == null) throw new ArgumentNullException(nameof(ordering)); var query = Query(); if (!contentPath.IsNullOrWhiteSpace()) query.Where(x => x.Path.SqlStartsWith($"{contentPath},", TextColumnType.NVarchar)); - return _documentRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)); + return _documentRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, ordering); } /// @@ -1412,7 +1441,7 @@ namespace Umbraco.Core.Services.Implement while (page * pageSize < total) { //get descendants - ordered from deepest to shallowest - var descendants = GetPagedDescendants(content.Id, page, pageSize, out total, "Path", Direction.Descending); + var descendants = GetPagedDescendants(content.Id, page, pageSize, out total, ordering: Ordering.By("Path", Direction.Descending)); foreach (var c in descendants) DoDelete(c); } @@ -1645,7 +1674,7 @@ namespace Umbraco.Core.Services.Implement var total = long.MaxValue; while(page * pageSize < total) { - var descendants = GetPagedDescendantsLocked(originalPath, page++, pageSize, out total, "Path", Direction.Ascending, true, null); + var descendants = GetPagedDescendantsLocked(originalPath, page++, pageSize, out total, null, Ordering.By("Path", Direction.Ascending)); foreach (var descendant in descendants) { moves.Add(Tuple.Create(descendant, descendant.Path)); // capture original path diff --git a/src/Umbraco.Examine/UmbracoContentIndexer.cs b/src/Umbraco.Examine/UmbracoContentIndexer.cs index 94982c8591..fab9f226a4 100644 --- a/src/Umbraco.Examine/UmbracoContentIndexer.cs +++ b/src/Umbraco.Examine/UmbracoContentIndexer.cs @@ -258,7 +258,8 @@ namespace Umbraco.Examine else { //add the published filter - descendants = ContentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out total, "Path", Direction.Ascending, true, _publishedQuery); + descendants = ContentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out total, + _publishedQuery, Ordering.By("Path", Direction.Ascending)); } //if specific types are declared we need to post filter them diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 1f99bd1127..c2b1cdc52d 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -1718,7 +1718,7 @@ namespace Umbraco.Tests.Services // Act contentService.DeleteOfType(contentType.Id); var rootContent = contentService.GetRootContent(); - var contents = contentService.GetPagedOfType(contentType.Id, 0, int.MaxValue, out var _); + var contents = contentService.GetPagedOfType(contentType.Id, 0, int.MaxValue, out var _, null); // Assert Assert.That(rootContent.Any(), Is.False); diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs index dedf04488c..b197cea5bc 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs @@ -71,11 +71,11 @@ namespace Umbraco.Tests.UmbracoExamine contentService = Mock.Of( x => x.GetPagedDescendants( - It.IsAny(), It.IsAny(), It.IsAny(), out longTotalRecs, It.IsAny(), It.IsAny(), It.IsAny()) + It.IsAny(), It.IsAny(), It.IsAny(), out longTotalRecs, It.IsAny(), It.IsAny()) == allRecs && x.GetPagedDescendants( - It.IsAny(), It.IsAny(), It.IsAny(), out longTotalRecs, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()) + It.IsAny(), It.IsAny(), It.IsAny(), out longTotalRecs, It.IsAny>(), It.IsAny()) == allRecs); } diff --git a/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs b/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs index 2d440b8453..768d1c735c 100644 --- a/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs +++ b/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs @@ -49,7 +49,7 @@ namespace Umbraco.Tests.UmbracoExamine .ToArray(); var contentService = Mock.Of( x => x.GetPagedDescendants( - It.IsAny(), It.IsAny(), It.IsAny(), out totalRecs, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()) + It.IsAny(), It.IsAny(), It.IsAny(), out totalRecs, It.IsAny>(), It.IsAny()) == allRecs); diff --git a/src/Umbraco.Web/Search/ExamineComponent.cs b/src/Umbraco.Web/Search/ExamineComponent.cs index 05be969f92..b5cfac7e99 100644 --- a/src/Umbraco.Web/Search/ExamineComponent.cs +++ b/src/Umbraco.Web/Search/ExamineComponent.cs @@ -26,6 +26,7 @@ using Umbraco.Web.Cache; using Umbraco.Web.Composing; using Umbraco.Web.PropertyEditors; using Umbraco.Examine; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Web.Search { @@ -351,21 +352,42 @@ namespace Umbraco.Web.Search const int pageSize = 500; - //Re-index all content of these types - foreach(var id in refreshedIds.Concat(otherIds).Distinct()) - { + if (refreshedIds.Count > 0 || otherIds.Count > 0) + { var page = 0; var total = long.MaxValue; while (page * pageSize < total) { - var contentToRefresh = _services.ContentService.GetPagedOfType(id, page++, pageSize, out total); - foreach(var c in contentToRefresh) + var contentToRefresh = _services.ContentService.GetPagedOfTypes( + //Re-index all content of these types + refreshedIds.Concat(otherIds).Distinct().ToArray(), + page++, pageSize, out total, null, + //order by shallowest to deepest, this allows us to check it's published state without checking every item + Ordering.By("Path", Direction.Ascending)); + + //track which Ids have their paths are published + var publishChecked = new Dictionary(); + + foreach (var c in contentToRefresh) { - //TODO: We might have to order by Path ascending or something since we're going to need to check - // contentService.IsPathPublished(content) but we don't want to make that check for every content item IContent published = null; - if (c.Published && contentService.IsPathPublished(c)) - published = c; + if (c.Published) + { + if (publishChecked.TryGetValue(c.ParentId, out var isPublished)) + { + //if the parent's published path has already been verified then this is published + if (isPublished) + published = c; + } + else + { + //nothing by parent id, so query the service and cache the result for the next child to check against + isPublished = contentService.IsPathPublished(c); + publishChecked[c.Id] = isPublished; + if (isPublished) + published = c; + } + } ReIndexForContent(c, published); } @@ -460,7 +482,10 @@ namespace Umbraco.Web.Search var total = long.MaxValue; while(page * pageSize < total) { - var descendants = contentService.GetPagedDescendants(content.Id, page++, pageSize, out total); + var descendants = contentService.GetPagedDescendants(content.Id, page++, pageSize, out total, + //order by shallowest to deepest, this allows us to check it's published state without checking every item + ordering: Ordering.By("Path", Direction.Ascending)); + foreach (var descendant in descendants) { published = null; From ae5bda0910e98144cfde1624ca8cbd3ad1889a5d Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 1 Nov 2018 00:21:52 +1100 Subject: [PATCH 18/71] Streamlines IMediaService to be consistent with IContentService for paging and using the Ordering class --- src/Umbraco.Core/Services/IMediaService.cs | 26 ++----- .../Services/Implement/ContentService.cs | 2 - .../Services/Implement/MediaService.cs | 78 +++++++------------ .../Services/MediaServiceTests.cs | 8 +- .../UmbracoExamine/IndexInitializer.cs | 4 +- src/Umbraco.Web/Editors/MediaController.cs | 17 ++-- 6 files changed, 48 insertions(+), 87 deletions(-) diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs index 6976a09b76..6fa211f4f3 100644 --- a/src/Umbraco.Core/Services/IMediaService.cs +++ b/src/Umbraco.Core/Services/IMediaService.cs @@ -90,7 +90,7 @@ namespace Umbraco.Core.Services /// Search text filter /// An Enumerable list of objects IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); + string filter = null, Ordering ordering = null); /// /// Gets a collection of objects by Parent Id @@ -105,23 +105,7 @@ namespace Umbraco.Core.Services /// /// An Enumerable list of objects IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter); - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Flag to indicate when ordering by system field - /// Search text filter - /// A list of content type Ids to filter the list by - /// An Enumerable list of objects - IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, string filter, int[] contentTypeFilter); + IQuery filter, Ordering ordering = null); /// /// Gets a collection of objects by Parent Id @@ -135,7 +119,7 @@ namespace Umbraco.Core.Services /// Search text filter /// An Enumerable list of objects IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = ""); + string filter = null, Ordering ordering = null); /// /// Gets a collection of objects by Parent Id @@ -150,8 +134,8 @@ namespace Umbraco.Core.Services /// /// An Enumerable list of objects IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter); - + IQuery filter, Ordering ordering = null); + /// /// Gets a collection of objects by the Id of the /// diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 1c3f5203f9..debf7dcb34 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -595,7 +595,6 @@ namespace Umbraco.Core.Services.Implement /// public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string filter = null, Ordering ordering = null) - //string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") { var filterQuery = filter.IsNullOrWhiteSpace() ? null @@ -636,7 +635,6 @@ namespace Umbraco.Core.Services.Implement if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); if (filter == null) throw new ArgumentNullException(nameof(filter)); - if (ordering == null) throw new ArgumentNullException(nameof(ordering)); var query = Query(); diff --git a/src/Umbraco.Core/Services/Implement/MediaService.cs b/src/Umbraco.Core/Services/Implement/MediaService.cs index 25aa02befa..a584521c33 100644 --- a/src/Umbraco.Core/Services/Implement/MediaService.cs +++ b/src/Umbraco.Core/Services/Implement/MediaService.cs @@ -471,13 +471,14 @@ namespace Umbraco.Core.Services.Implement /// Direction to order by /// Search text filter /// An Enumerable list of objects - public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, string filter = "") + public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, + string filter = null, Ordering ordering = null) { var filterQuery = filter.IsNullOrWhiteSpace() ? null : Query().Where(x => x.Name.Contains(filter)); - return GetPagedChildren(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filterQuery); + return GetPagedChildren(id, pageIndex, pageSize, out totalChildren, filterQuery, ordering); } /// @@ -492,56 +493,21 @@ namespace Umbraco.Core.Services.Implement /// Flag to indicate when ordering by system field /// /// An Enumerable list of objects - public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter) + public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, + IQuery filter, Ordering ordering = null) { if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); + if (ordering == null) + ordering = Ordering.By("sortOrder"); + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { scope.ReadLock(Constants.Locks.MediaTree); var query = Query().Where(x => x.ParentId == id); - return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)); - } - } - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Flag to indicate when ordering by system field - /// Search text filter - /// A list of content type Ids to filter the list by - /// An Enumerable list of objects - public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, string filter, int[] contentTypeFilter) - { - if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); - if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); - - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) - { - scope.ReadLock(Constants.Locks.MediaTree); - - var query = Query(); - // always check for a parent - else it will also get decendants (and then you should use the GetPagedDescendants method) - - query.Where(x => x.ParentId == id); - - if (contentTypeFilter != null && contentTypeFilter.Length > 0) - { - query.Where(x => contentTypeFilter.Contains(x.ContentTypeId)); - } - - var filterQuery = filter.IsNullOrWhiteSpace() - ? null - : Query().Where(x => x.Name.Contains(filter)); - return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filterQuery, Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)); + return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, ordering); } } @@ -556,13 +522,14 @@ namespace Umbraco.Core.Services.Implement /// Direction to order by /// Search text filter /// An Enumerable list of objects - public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") + public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, + string filter = null, Ordering ordering = null) { var filterQuery = filter.IsNullOrWhiteSpace() ? null : Query().Where(x => x.Name.Contains(filter)); - return GetPagedDescendants(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filterQuery); + return GetPagedDescendants(id, pageIndex, pageSize, out totalChildren, filterQuery, ordering); } /// @@ -577,8 +544,12 @@ namespace Umbraco.Core.Services.Implement /// Flag to indicate when ordering by system field /// /// An Enumerable list of objects - public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter) + public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, + IQuery filter, Ordering ordering = null) { + if (ordering == null) + ordering = Ordering.By("Path"); + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { scope.ReadLock(Constants.Locks.MediaTree); @@ -592,22 +563,25 @@ namespace Umbraco.Core.Services.Implement totalChildren = 0; return Enumerable.Empty(); } - return GetPagedDescendantsLocked(mediaPath[0].Path, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); + return GetPagedDescendantsLocked(mediaPath[0].Path, pageIndex, pageSize, out totalChildren, filter, ordering); } - return GetPagedDescendantsLocked(null, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); + return GetPagedDescendantsLocked(null, pageIndex, pageSize, out totalChildren, filter, ordering); } } - private IEnumerable GetPagedDescendantsLocked(string mediaPath, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter) + private IEnumerable GetPagedDescendantsLocked(string mediaPath, long pageIndex, int pageSize, out long totalChildren, + IQuery filter, Ordering ordering) { if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); + if (filter == null) throw new ArgumentNullException(nameof(filter)); + if (ordering == null) throw new ArgumentNullException(nameof(ordering)); var query = Query(); if (!mediaPath.IsNullOrWhiteSpace()) query.Where(x => x.Path.SqlStartsWith(mediaPath + ",", TextColumnType.NVarchar)); - return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)); + return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, ordering); } /// @@ -836,7 +810,7 @@ namespace Umbraco.Core.Services.Implement while(page * pageSize < total) { //get descendants - ordered from deepest to shallowest - var descendants = GetPagedDescendants(media.Id, page, pageSize, out total, "Path", Direction.Descending); + var descendants = GetPagedDescendants(media.Id, page, pageSize, out total, ordering: Ordering.By("Path", Direction.Descending)); foreach (var c in descendants) DoDelete(c); } @@ -1069,7 +1043,7 @@ namespace Umbraco.Core.Services.Implement var total = long.MaxValue; while (page * pageSize < total) { - var descendants = GetPagedDescendantsLocked(originalPath, page++, pageSize, out total, "Path", Direction.Ascending, true, null); + var descendants = GetPagedDescendantsLocked(originalPath, page++, pageSize, out total, null, Ordering.By("Path", Direction.Ascending)); foreach (var descendant in descendants) { moves.Add(Tuple.Create(descendant, descendant.Path)); // capture original path diff --git a/src/Umbraco.Tests/Services/MediaServiceTests.cs b/src/Umbraco.Tests/Services/MediaServiceTests.cs index 68fd2c3e11..b9e1fee0db 100644 --- a/src/Umbraco.Tests/Services/MediaServiceTests.cs +++ b/src/Umbraco.Tests/Services/MediaServiceTests.cs @@ -69,11 +69,15 @@ namespace Umbraco.Tests.Services } long total; - var result = ServiceContext.MediaService.GetPagedChildren(-1, 0, 11, out total, "SortOrder", Direction.Ascending, true, null, new[] { mediaType1.Id, mediaType2.Id }); + var result = ServiceContext.MediaService.GetPagedChildren(-1, 0, 11, out total, + SqlContext.Query().Where(x => new[] { mediaType1.Id, mediaType2.Id }.Contains(x.ContentTypeId)), + Ordering.By("SortOrder", Direction.Ascending)); Assert.AreEqual(11, result.Count()); Assert.AreEqual(20, total); - result = ServiceContext.MediaService.GetPagedChildren(-1, 1, 11, out total, "SortOrder", Direction.Ascending, true, null, new[] { mediaType1.Id, mediaType2.Id }); + result = ServiceContext.MediaService.GetPagedChildren(-1, 1, 11, out total, + SqlContext.Query().Where(x => new[] { mediaType1.Id, mediaType2.Id }.Contains(x.ContentTypeId)), + Ordering.By("SortOrder", Direction.Ascending)); Assert.AreEqual(9, result.Count()); Assert.AreEqual(20, total); } diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs index b197cea5bc..8aec854441 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs @@ -116,12 +116,12 @@ namespace Umbraco.Tests.UmbracoExamine mediaServiceMock .Setup(x => x.GetPagedDescendants( - It.IsAny(), It.IsAny(), It.IsAny(), out totalRecs, It.IsAny(), It.IsAny(), It.IsAny()) + It.IsAny(), It.IsAny(), It.IsAny(), out totalRecs, It.IsAny(), It.IsAny()) ).Returns(() => allRecs); mediaServiceMock .Setup(x => x.GetPagedDescendants( - It.IsAny(), It.IsAny(), It.IsAny(), out totalRecs, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()) + It.IsAny(), It.IsAny(), It.IsAny(), out totalRecs, It.IsAny>(), It.IsAny()) ).Returns(() => allRecs); //mediaServiceMock.Setup(service => service.GetPagedXmlEntries(It.IsAny(), It.IsAny(), It.IsAny(), out longTotalRecs)) diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 3dfd435d8f..f61cbc5952 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -209,7 +209,10 @@ namespace Umbraco.Web.Editors } long total; - var children = Services.MediaService.GetPagedChildren(id, pageNumber - 1, pageSize, out total, "Name", Direction.Ascending, true, null, folderTypes.ToArray()); + var children = Services.MediaService.GetPagedChildren(id, pageNumber - 1, pageSize, out total, + //lookup these content types + SqlContext.Query().Where(x => folderTypes.Contains(x.ContentTypeId)), + Ordering.By("Name", Direction.Ascending)); return new PagedResult>(total, pageNumber, pageSize) { @@ -286,8 +289,8 @@ namespace Umbraco.Web.Editors .GetPagedChildren( id, (pageNumber - 1), pageSize, out totalChildren, - orderBy, orderDirection, orderBySystemField, - queryFilter).ToList(); + queryFilter, + Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)).ToList(); } else { @@ -762,12 +765,10 @@ namespace Umbraco.Web.Editors var total = long.MaxValue; while (page * pageSize < total) { - var children = Services.MediaService.GetPagedChildren(mediaId, page, pageSize, out total); + var children = Services.MediaService.GetPagedChildren(mediaId, page, pageSize, out total, + SqlContext.Query().Where(x => x.Name == nameToFind)); foreach (var c in children) - { - if (c.Name == nameToFind && c.ContentType.Alias == contentTypeAlias) - return c; - } + return c; //return first one if any are found } return null; } From 6a1d6668c4e42651dc834483c6d980e8dc5c9a91 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 1 Nov 2018 01:01:50 +1100 Subject: [PATCH 19/71] Gets both media and member indexing working for when media/member types are changed --- src/Umbraco.Core/Services/IMediaService.cs | 25 ++- .../Services/Implement/MediaService.cs | 39 +++- .../Services/Implement/MemberService.cs | 6 +- src/Umbraco.Web/Search/ExamineComponent.cs | 191 ++++++++++++------ 4 files changed, 182 insertions(+), 79 deletions(-) diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs index 6fa211f4f3..0c6e528dee 100644 --- a/src/Umbraco.Core/Services/IMediaService.cs +++ b/src/Umbraco.Core/Services/IMediaService.cs @@ -137,11 +137,28 @@ namespace Umbraco.Core.Services IQuery filter, Ordering ordering = null); /// - /// Gets a collection of objects by the Id of the + /// Gets paged documents of a content content /// - /// Id of the - /// An Enumerable list of objects - IEnumerable GetMediaOfMediaType(int id); + /// The page number. + /// The page number. + /// The page size. + /// Total number of documents. + /// Search text filter. + /// Ordering infos. + IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int pageSize, out long totalRecords, + IQuery filter, Ordering ordering = null); + + /// + /// Gets paged documents for specified content types + /// + /// The page number. + /// The page number. + /// The page size. + /// Total number of documents. + /// Search text filter. + /// Ordering infos. + IEnumerable GetPagedOfTypes(int[] contentTypeIds, long pageIndex, int pageSize, out long totalRecords, + IQuery filter, Ordering ordering = null); /// /// Gets a collection of objects, which reside at the first level / root diff --git a/src/Umbraco.Core/Services/Implement/MediaService.cs b/src/Umbraco.Core/Services/Implement/MediaService.cs index a584521c33..a69b39943b 100644 --- a/src/Umbraco.Core/Services/Implement/MediaService.cs +++ b/src/Umbraco.Core/Services/Implement/MediaService.cs @@ -364,18 +364,39 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Gets a collection of objects by the Id of the - /// - /// Id of the - /// An Enumerable list of objects - public IEnumerable GetMediaOfMediaType(int id) + /// + public IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int pageSize, out long totalRecords, IQuery filter, Ordering ordering = null) { + if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); + if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); + + if (ordering == null) + ordering = Ordering.By("sortOrder"); + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { - scope.ReadLock(Constants.Locks.MediaTree); - var query = Query().Where(x => x.ContentTypeId == id); - return _mediaRepository.Get(query); + scope.ReadLock(Constants.Locks.ContentTree); + return _mediaRepository.GetPage( + Query().Where(x => x.ContentTypeId == contentTypeId), + pageIndex, pageSize, out totalRecords, filter, ordering); + } + } + + /// + public IEnumerable GetPagedOfTypes(int[] contentTypeIds, long pageIndex, int pageSize, out long totalRecords, IQuery filter, Ordering ordering = null) + { + if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); + if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); + + if (ordering == null) + ordering = Ordering.By("sortOrder"); + + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + { + scope.ReadLock(Constants.Locks.ContentTree); + return _mediaRepository.GetPage( + Query().Where(x => contentTypeIds.Contains(x.ContentTypeId)), + pageIndex, pageSize, out totalRecords, filter, ordering); } } diff --git a/src/Umbraco.Core/Services/Implement/MemberService.cs b/src/Umbraco.Core/Services/Implement/MemberService.cs index 3fd714f974..5a644cfec1 100644 --- a/src/Umbraco.Core/Services/Implement/MemberService.cs +++ b/src/Umbraco.Core/Services/Implement/MemberService.cs @@ -393,12 +393,14 @@ namespace Umbraco.Core.Services.Implement // fixme get rid of string filter? - public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, string orderBy, Direction orderDirection, string memberTypeAlias = null, string filter = "") + public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, string memberTypeAlias = null, string filter = "") { return GetAll(pageIndex, pageSize, out totalRecords, orderBy, orderDirection, true, memberTypeAlias, filter); } - public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, string orderBy, Direction orderDirection, bool orderBySystemField, string memberTypeAlias, string filter) + public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField, string memberTypeAlias, string filter) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { diff --git a/src/Umbraco.Web/Search/ExamineComponent.cs b/src/Umbraco.Web/Search/ExamineComponent.cs index b5cfac7e99..7a036ef712 100644 --- a/src/Umbraco.Web/Search/ExamineComponent.cs +++ b/src/Umbraco.Web/Search/ExamineComponent.cs @@ -333,92 +333,155 @@ namespace Umbraco.Web.Search if (args.MessageType != MessageType.RefreshByPayload) throw new NotSupportedException(); - - var contentService = _services.ContentService; - - var removedIds = new List(); - var refreshedIds = new List(); - var otherIds = new List(); - + + var changedIds = new Dictionary removedIds, List refreshedIds, List otherIds)>(); + foreach (var payload in (ContentTypeCacheRefresher.JsonPayload[])args.MessageObject) { + if (!changedIds.TryGetValue(payload.ItemType, out var idLists)) + { + idLists = (removedIds: new List(), refreshedIds: new List(), otherIds: new List()); + changedIds.Add(payload.ItemType, idLists); + } + if (payload.ChangeTypes.HasType(ContentTypeChangeTypes.Remove)) - removedIds.Add(payload.Id); + idLists.removedIds.Add(payload.Id); else if (payload.ChangeTypes.HasType(ContentTypeChangeTypes.RefreshMain)) - refreshedIds.Add(payload.Id); + idLists.refreshedIds.Add(payload.Id); else if (payload.ChangeTypes.HasType(ContentTypeChangeTypes.RefreshOther)) - otherIds.Add(payload.Id); + idLists.otherIds.Add(payload.Id); } const int pageSize = 500; - if (refreshedIds.Count > 0 || otherIds.Count > 0) - { + foreach(var ci in changedIds) + { + if (ci.Value.refreshedIds.Count > 0 || ci.Value.otherIds.Count > 0) + { + switch(ci.Key) + { + case var itemType when itemType == typeof(IContentType).Name: + RefreshContentOfContentTypes(ci.Value.refreshedIds.Concat(ci.Value.otherIds).Distinct().ToArray()); + break; + case var itemType when itemType == typeof(IMediaType).Name: + RefreshMediaOfMediaTypes(ci.Value.refreshedIds.Concat(ci.Value.otherIds).Distinct().ToArray()); + break; + case var itemType when itemType == typeof(IMemberType).Name: + RefreshMemberOfMemberTypes(ci.Value.refreshedIds.Concat(ci.Value.otherIds).Distinct().ToArray()); + break; + } + } + + //Delete all content of this content/media/member type that is in any content indexer by looking up matched examine docs + foreach (var id in ci.Value.removedIds) + { + foreach (var index in _examineManager.IndexProviders.Values.OfType()) + { + var searcher = index.GetSearcher(); + + var page = 0; + var total = long.MaxValue; + while (page * pageSize < total) + { + //paging with examine, see https://shazwazza.com/post/paging-with-examine/ + var results = searcher.Search( + searcher.CreateCriteria().Field("nodeType", id).Compile(), + maxResults: pageSize * (page + 1)); + total = results.TotalItemCount; + var paged = results.Skip(page * pageSize); + + foreach (var item in paged) + if (int.TryParse(item.Id, out var contentId)) + DeleteIndexForEntity(contentId, false); + } + } + } + } + } + + private void RefreshMemberOfMemberTypes(int[] memberTypeIds) + { + const int pageSize = 500; + + var memberTypes = _services.MemberTypeService.GetAll(memberTypeIds); + foreach(var memberType in memberTypes) + { var page = 0; var total = long.MaxValue; while (page * pageSize < total) { - var contentToRefresh = _services.ContentService.GetPagedOfTypes( - //Re-index all content of these types - refreshedIds.Concat(otherIds).Distinct().ToArray(), - page++, pageSize, out total, null, - //order by shallowest to deepest, this allows us to check it's published state without checking every item - Ordering.By("Path", Direction.Ascending)); + var memberToRefresh = _services.MemberService.GetAll( + page++, pageSize, out total, "LoginName", Direction.Ascending, + memberType.Alias); - //track which Ids have their paths are published - var publishChecked = new Dictionary(); - - foreach (var c in contentToRefresh) + foreach (var c in memberToRefresh) { - IContent published = null; - if (c.Published) - { - if (publishChecked.TryGetValue(c.ParentId, out var isPublished)) - { - //if the parent's published path has already been verified then this is published - if (isPublished) - published = c; - } - else - { - //nothing by parent id, so query the service and cache the result for the next child to check against - isPublished = contentService.IsPathPublished(c); - publishChecked[c.Id] = isPublished; - if (isPublished) - published = c; - } - } - - ReIndexForContent(c, published); + ReIndexForMember(c); } } } + } - //Delete all content of this content type that is in any content indexer by looking up matched examine docs - foreach(var id in removedIds) + private void RefreshMediaOfMediaTypes(int[] mediaTypeIds) + { + const int pageSize = 500; + var page = 0; + var total = long.MaxValue; + while (page * pageSize < total) { - foreach(var index in _examineManager.IndexProviders.Values.OfType()) + var mediaToRefresh = _services.MediaService.GetPagedOfTypes( + //Re-index all content of these types + mediaTypeIds, + page++, pageSize, out total, null, + Ordering.By("Path", Direction.Ascending)); + + foreach (var c in mediaToRefresh) { - var searcher = index.GetSearcher(); - - var page = 0; - var total = long.MaxValue; - while (page * pageSize < total) - { - //paging with examine, see https://shazwazza.com/post/paging-with-examine/ - var results = searcher.Search( - searcher.CreateCriteria().Field("nodeType", id).Compile(), - maxResults: pageSize * (page + 1)); - total = results.TotalItemCount; - var paged = results.Skip(page * pageSize); - - foreach(var item in paged) - if (int.TryParse(item.Id, out var contentId)) - DeleteIndexForEntity(contentId, false); - } + ReIndexForMedia(c, c.Trashed == false); + } + } + } + + private void RefreshContentOfContentTypes(int[] contentTypeIds) + { + const int pageSize = 500; + var page = 0; + var total = long.MaxValue; + while (page * pageSize < total) + { + var contentToRefresh = _services.ContentService.GetPagedOfTypes( + //Re-index all content of these types + contentTypeIds, + page++, pageSize, out total, null, + //order by shallowest to deepest, this allows us to check it's published state without checking every item + Ordering.By("Path", Direction.Ascending)); + + //track which Ids have their paths are published + var publishChecked = new Dictionary(); + + foreach (var c in contentToRefresh) + { + IContent published = null; + if (c.Published) + { + if (publishChecked.TryGetValue(c.ParentId, out var isPublished)) + { + //if the parent's published path has already been verified then this is published + if (isPublished) + published = c; + } + else + { + //nothing by parent id, so query the service and cache the result for the next child to check against + isPublished = _services.ContentService.IsPathPublished(c); + publishChecked[c.Id] = isPublished; + if (isPublished) + published = c; + } + } + + ReIndexForContent(c, published); } - - } } From 082d860a5b2f97319d25db61afdaf0c34fca2f9b Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 1 Nov 2018 01:28:12 +1100 Subject: [PATCH 20/71] fixes tests --- src/Umbraco.Core/Services/Implement/ContentService.cs | 1 - src/Umbraco.Core/Services/Implement/MediaService.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index debf7dcb34..64e536b7e2 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -634,7 +634,6 @@ namespace Umbraco.Core.Services.Implement { if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); - if (filter == null) throw new ArgumentNullException(nameof(filter)); if (ordering == null) throw new ArgumentNullException(nameof(ordering)); var query = Query(); diff --git a/src/Umbraco.Core/Services/Implement/MediaService.cs b/src/Umbraco.Core/Services/Implement/MediaService.cs index a69b39943b..a41d12ac59 100644 --- a/src/Umbraco.Core/Services/Implement/MediaService.cs +++ b/src/Umbraco.Core/Services/Implement/MediaService.cs @@ -595,7 +595,6 @@ namespace Umbraco.Core.Services.Implement { if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); - if (filter == null) throw new ArgumentNullException(nameof(filter)); if (ordering == null) throw new ArgumentNullException(nameof(ordering)); var query = Query(); From a3232bb55c73d4193f1dc3150af01f8e3c6a3cd1 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 31 Oct 2018 21:33:32 +0100 Subject: [PATCH 21/71] WIP: split editing into tabs --- src/Umbraco.Web.UI.Client/gulpfile.js | 1 + src/Umbraco.Web.UI.Client/src/less/belle.less | 1 + .../src/less/components/umb-stylesheet.less | 38 ++++++++++++ .../src/views/stylesheets/edit.controller.js | 61 ++++++++++++++++++- .../src/views/stylesheets/edit.html | 16 +++-- .../views/stylesheets/views/code/code.html | 9 +++ .../views/rules/rules.controller.js | 26 ++++++++ .../views/stylesheets/views/rules/rules.html | 51 ++++++++++++++++ .../Editors/StylesheetController.cs | 61 ++++++++++++++++++- .../Models/ContentEditing/StylesheetRule.cs | 2 + 10 files changed, 253 insertions(+), 13 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/less/components/umb-stylesheet.less create mode 100644 src/Umbraco.Web.UI.Client/src/views/stylesheets/views/code/code.html create mode 100644 src/Umbraco.Web.UI.Client/src/views/stylesheets/views/rules/rules.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/stylesheets/views/rules/rules.html diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js index d1bbfe65db..3e37f902d9 100644 --- a/src/Umbraco.Web.UI.Client/gulpfile.js +++ b/src/Umbraco.Web.UI.Client/gulpfile.js @@ -162,6 +162,7 @@ gulp.task('dependencies', function () { "./node_modules/ace-builds/src-min-noconflict/theme-chrome.js", "./node_modules/ace-builds/src-min-noconflict/mode-razor.js", "./node_modules/ace-builds/src-min-noconflict/mode-javascript.js", + "./node_modules/ace-builds/src-min-noconflict/mode-css.js", "./node_modules/ace-builds/src-min-noconflict/worker-javascript.js" ], "base": "./node_modules/ace-builds" diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index 3100433a4a..4fb16c08ec 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -149,6 +149,7 @@ @import "components/umb-box.less"; @import "components/umb-number-badge.less"; @import "components/umb-progress-circle.less"; +@import "components/umb-stylesheet.less"; @import "components/buttons/umb-button.less"; @import "components/buttons/umb-button-group.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-stylesheet.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-stylesheet.less new file mode 100644 index 0000000000..fd976bba32 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-stylesheet.less @@ -0,0 +1,38 @@ +.umb-stylesheet-rules { + width: 600px; +} + +.umb-stylesheet-rules__listitem { + display: flex; + padding: 6px; + margin: 10px 0 !important; + background: @gray-10; + cursor: move; +} + +.umb-stylesheet-rules__listitem i { + display: flex; + align-items: center; + margin-right: 5px +} + +.umb-stylesheet-rules__listitem a { + cursor: pointer; + margin-left: auto; +} + +.umb-stylesheet-rules__listitem input { + width: 295px; +} + +.umb-styleheet-rules__left { + display: block; + flex: 1 1 auto; + overflow: hidden; +} + +.umb-styleheet-rules__right { + display: flex; + flex: 0 0 auto; + align-items: center; +} diff --git a/src/Umbraco.Web.UI.Client/src/views/stylesheets/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/stylesheets/edit.controller.js index 33aa0f979b..09278a5521 100644 --- a/src/Umbraco.Web.UI.Client/src/views/stylesheets/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/stylesheets/edit.controller.js @@ -1,7 +1,7 @@ (function () { "use strict"; - function StyleSheetsEditController($scope, $routeParams, $timeout, appState, editorState, navigationService, assetsService, codefileResource, contentEditingHelper, notificationsService, localizationService, templateHelper, angularHelper) { + function StyleSheetsEditController($scope, $routeParams, $timeout, $http, appState, editorState, navigationService, assetsService, codefileResource, contentEditingHelper, notificationsService, localizationService, templateHelper, angularHelper, umbRequestHelper) { var vm = this; var currentPosition = null; @@ -12,6 +12,22 @@ vm.page.menu.currentSection = appState.getSectionState("currentSection"); vm.page.menu.currentNode = null; vm.page.saveButtonState = "init"; + // TODO: localization + vm.page.navigation = [ + { + "name": "Code", + "alias": "code", + "icon": "icon-brackets", + "view": "views/stylesheets/views/code/code.html", + "active": true + }, + { + "name": "Styles", + "alias": "rules", + "icon": "icon-font", + "view": "views/stylesheets/views/rules/rules.html" + } + ]; //Used to toggle the keyboard shortcut modal //From a custom keybinding in ace editor - that conflicts with our own to show the dialog @@ -125,7 +141,7 @@ } vm.aceOption = { - mode: "stylesheet", + mode: "css", theme: "chrome", showPrintMargin: false, advanced: { @@ -193,8 +209,49 @@ currentForm.$setPristine(); } } + } + $scope.selectApp = function (app) { + vm.page.loading = true; + var payload = { + content: vm.stylesheet.content, + rules: vm.stylesheet.rules + }; + if (app.alias === "code") { + umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "stylesheetApiBaseUrl", + "PostInterpolateStylesheetRules"), + payload), + "Failed to interpolate sheet rules") + .then( + function(content) { + vm.page.loading = false; + vm.stylesheet.content = content; + }, + function(err) { + } + ); + } + else { + umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "stylesheetApiBaseUrl", + "PostExtractStylesheetRules"), + payload), + "Failed to extract style sheet rules") + .then( + function (rules) { + vm.page.loading = false; + vm.stylesheet.rules = rules; + }, + function(err) { + } + ); + } } init(); diff --git a/src/Umbraco.Web.UI.Client/src/views/stylesheets/edit.html b/src/Umbraco.Web.UI.Client/src/views/stylesheets/edit.html index 61252f5a54..7daac56b38 100644 --- a/src/Umbraco.Web.UI.Client/src/views/stylesheets/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/stylesheets/edit.html @@ -13,19 +13,17 @@ hide-alias="true" description="vm.stylesheet.virtualPath" description-locked="true" + navigation="vm.page.navigation" + on-select-navigation-item="selectApp(item)" hide-icon="true"> - - -
-
-
-
+ +
diff --git a/src/Umbraco.Web.UI.Client/src/views/stylesheets/views/code/code.html b/src/Umbraco.Web.UI.Client/src/views/stylesheets/views/code/code.html new file mode 100644 index 0000000000..ce8b2c8b2f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/stylesheets/views/code/code.html @@ -0,0 +1,9 @@ + + +
+
+
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/stylesheets/views/rules/rules.controller.js b/src/Umbraco.Web.UI.Client/src/views/stylesheets/views/rules/rules.controller.js new file mode 100644 index 0000000000..59293c1b9e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/stylesheets/views/rules/rules.controller.js @@ -0,0 +1,26 @@ +angular.module("umbraco").controller("Umbraco.Editors.StyleSheets.RulesController", + function ($scope) { + $scope.sortableOptions = { + axis: 'y', + containment: 'parent', + cursor: 'move', + items: '> div.control-group', + tolerance: 'pointer', + update: function (e, ui) { + // TODO + console.log("TODO: set dirty") + } + }; + + $scope.add = function (evt) { + evt.preventDefault(); + + $scope.model.stylesheet.rules.push({}); + } + + $scope.remove = function (rule, evt) { + evt.preventDefault(); + + $scope.model.stylesheet.rules = _.without($scope.model.stylesheet.rules, rule); + } + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/stylesheets/views/rules/rules.html b/src/Umbraco.Web.UI.Client/src/views/stylesheets/views/rules/rules.html new file mode 100644 index 0000000000..4e6ba8c8a1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/stylesheets/views/rules/rules.html @@ -0,0 +1,51 @@ + + + + +
+
+
Rich text editor styles
+ Some explanatory text about rich text editor styles goes here +
+
+
+
+
+ +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ Remove +
+
+
+ +
+
+
+
+
diff --git a/src/Umbraco.Web/Editors/StylesheetController.cs b/src/Umbraco.Web/Editors/StylesheetController.cs index 99ab0add34..28b1930fb4 100644 --- a/src/Umbraco.Web/Editors/StylesheetController.cs +++ b/src/Umbraco.Web/Editors/StylesheetController.cs @@ -1,8 +1,11 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Umbraco.Core; +using Umbraco.Core.Strings.Css; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; +using StylesheetRule = Umbraco.Web.Models.ContentEditing.StylesheetRule; namespace Umbraco.Web.Editors { @@ -30,6 +33,60 @@ namespace Umbraco.Web.Editors return css.Properties.Select(x => new StylesheetRule() { Name = x.Name, Selector = x.Alias }); } - } + public StylesheetRule[] PostExtractStylesheetRules(StylesheetData data) + { + if (data.Content.IsNullOrWhiteSpace()) + { + return new StylesheetRule[0]; + } + + return StylesheetHelper.ParseRules(data.Content)?.Select(rule => new StylesheetRule + { + Name = rule.Name, + Selector = rule.Selector, + Styles = rule.Styles + }).ToArray(); + } + + public string PostInterpolateStylesheetRules(StylesheetData data) + { + // first remove all existing rules + var existingRules = data.Content.IsNullOrWhiteSpace() + ? new Core.Strings.Css.StylesheetRule[0] + : StylesheetHelper.ParseRules(data.Content).ToArray(); + foreach (var rule in existingRules) + { + data.Content = StylesheetHelper.ReplaceRule(data.Content, rule.Name, null); + } + + data.Content = data.Content.TrimEnd('\n', '\r'); + + // now add all the posted rules + if (data.Rules != null && data.Rules.Any()) + { + foreach (var rule in data.Rules) + { + data.Content = StylesheetHelper.AppendRule(data.Content, new Core.Strings.Css.StylesheetRule + { + Name = rule.Name, + Selector = rule.Selector, + Styles = rule.Styles + }); + } + + data.Content += Environment.NewLine; + } + + return data.Content; + } + + // this is an internal class for passing stylesheet data from the client to the controller while editing + public class StylesheetData + { + public string Content { get; set; } + + public StylesheetRule[] Rules { get; set; } + } + } } diff --git a/src/Umbraco.Web/Models/ContentEditing/StylesheetRule.cs b/src/Umbraco.Web/Models/ContentEditing/StylesheetRule.cs index 9219cce4f5..b3212445ae 100644 --- a/src/Umbraco.Web/Models/ContentEditing/StylesheetRule.cs +++ b/src/Umbraco.Web/Models/ContentEditing/StylesheetRule.cs @@ -16,5 +16,7 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "selector")] public string Selector { get; set; } + [DataMember(Name = "styles")] + public string Styles { get; set; } } } From 76a8cd6dcf59ed61d4674d6385fb74ffa68cff82 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 1 Nov 2018 10:22:45 +1100 Subject: [PATCH 22/71] Changes recycle bin service queries to paged --- src/Umbraco.Core/Services/IContentService.cs | 4 ++-- src/Umbraco.Core/Services/IMediaService.cs | 3 ++- .../Services/Implement/ContentService.cs | 8 ++++++-- src/Umbraco.Core/Services/Implement/MediaService.cs | 13 +++++++------ .../Persistence/NPocoTests/PetaPocoCachesTest.cs | 2 -- src/Umbraco.Tests/Services/ContentServiceTests.cs | 8 ++++---- 6 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index c609a7493d..ccf88a7b1e 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -141,11 +141,11 @@ namespace Umbraco.Core.Services ///
IEnumerable GetContentForRelease(); - //fixme: should be paged /// /// Gets documents in the recycle bin. /// - IEnumerable GetContentInRecycleBin(); + IEnumerable GetPagedContentInRecycleBin(long pageIndex, int pageSize, out long totalRecords, + IQuery filter = null, Ordering ordering = null); /// /// Gets child documents of a parent. diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs index 0c6e528dee..2476af94dd 100644 --- a/src/Umbraco.Core/Services/IMediaService.cs +++ b/src/Umbraco.Core/Services/IMediaService.cs @@ -170,7 +170,8 @@ namespace Umbraco.Core.Services /// Gets a collection of an objects, which resides in the Recycle Bin /// /// An Enumerable list of objects - IEnumerable GetMediaInRecycleBin(); + IEnumerable GetPagedMediaInRecycleBin(long pageIndex, int pageSize, out long totalRecords, + IQuery filter = null, Ordering ordering = null); /// /// Moves an object to a new location diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 64e536b7e2..63d6167f39 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -727,13 +727,17 @@ namespace Umbraco.Core.Services.Implement /// Gets a collection of an objects, which resides in the Recycle Bin /// /// An Enumerable list of objects - public IEnumerable GetContentInRecycleBin() + public IEnumerable GetPagedContentInRecycleBin(long pageIndex, int pageSize, out long totalRecords, + IQuery filter = null, Ordering ordering = null) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { + if (ordering == null) + ordering = Ordering.By("Path"); + scope.ReadLock(Constants.Locks.ContentTree); var query = Query().Where(x => x.Path.StartsWith(Constants.System.RecycleBinContentPathPrefix)); - return _documentRepository.Get(query); + return _documentRepository.GetPage(query, pageIndex, pageSize, out totalRecords, filter, ordering); } } diff --git a/src/Umbraco.Core/Services/Implement/MediaService.cs b/src/Umbraco.Core/Services/Implement/MediaService.cs index a41d12ac59..4be75487b7 100644 --- a/src/Umbraco.Core/Services/Implement/MediaService.cs +++ b/src/Umbraco.Core/Services/Implement/MediaService.cs @@ -643,17 +643,18 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Gets a collection of an objects, which resides in the Recycle Bin - /// - /// An Enumerable list of objects - public IEnumerable GetMediaInRecycleBin() + /// + public IEnumerable GetPagedMediaInRecycleBin(long pageIndex, int pageSize, out long totalRecords, + IQuery filter = null, Ordering ordering = null) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { + if (ordering == null) + ordering = Ordering.By("Path"); + scope.ReadLock(Constants.Locks.MediaTree); var query = Query().Where(x => x.Path.StartsWith(Constants.System.RecycleBinMediaPathPrefix)); - return _mediaRepository.Get(query); + return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalRecords, filter, ordering); } } diff --git a/src/Umbraco.Tests/Persistence/NPocoTests/PetaPocoCachesTest.cs b/src/Umbraco.Tests/Persistence/NPocoTests/PetaPocoCachesTest.cs index b1ebee108a..570fbfb7e3 100644 --- a/src/Umbraco.Tests/Persistence/NPocoTests/PetaPocoCachesTest.cs +++ b/src/Umbraco.Tests/Persistence/NPocoTests/PetaPocoCachesTest.cs @@ -133,8 +133,6 @@ namespace Umbraco.Tests.Persistence.NPocoTests contentService.GetContentForRelease(); - contentService.GetContentInRecycleBin(); - ((ContentService)contentService).GetPublishedDescendants(new Content("Test", -1, new ContentType(-1)) { Id = id1, diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index c2b1cdc52d..b22a1dfd6a 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -1172,7 +1172,7 @@ namespace Umbraco.Tests.Services var contentService = ServiceContext.ContentService; // Act - var contents = contentService.GetContentInRecycleBin().ToList(); + var contents = contentService.GetPagedContentInRecycleBin(0, int.MaxValue, out var _).ToList(); // Assert Assert.That(contents, Is.Not.Null); @@ -1792,7 +1792,7 @@ namespace Umbraco.Tests.Services Assert.True(descendants.All(x => x.Trashed)); contentService.EmptyRecycleBin(); - var trashed = contentService.GetContentInRecycleBin(); + var trashed = contentService.GetPagedContentInRecycleBin(0, int.MaxValue, out var _).ToList(); Assert.IsEmpty(trashed); } @@ -1804,7 +1804,7 @@ namespace Umbraco.Tests.Services // Act contentService.EmptyRecycleBin(); - var contents = contentService.GetContentInRecycleBin(); + var contents = contentService.GetPagedContentInRecycleBin(0, int.MaxValue, out var _).ToList(); // Assert Assert.That(contents.Any(), Is.False); @@ -1982,7 +1982,7 @@ namespace Umbraco.Tests.Services // Act ServiceContext.ContentService.MoveToRecycleBin(content1); ServiceContext.ContentService.EmptyRecycleBin(); - var contents = ServiceContext.ContentService.GetContentInRecycleBin(); + var contents = ServiceContext.ContentService.GetPagedContentInRecycleBin(0, int.MaxValue, out var _).ToList(); // Assert Assert.That(contents.Any(), Is.False); From 047bce17bf3dc85ae9522b63ac2abe87586577b1 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 1 Nov 2018 10:28:53 +1100 Subject: [PATCH 23/71] Cleans up more IContentService and IMediaService and removes any string filtering methods --- src/Umbraco.Core/Services/IContentService.cs | 29 +------ src/Umbraco.Core/Services/IMediaService.cs | 36 +-------- .../Services/Implement/ContentService.cs | 29 +------ .../Services/Implement/MediaService.cs | 76 ++----------------- .../UmbracoExamine/IndexInitializer.cs | 9 --- 5 files changed, 16 insertions(+), 163 deletions(-) diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index ccf88a7b1e..6cfc923ebe 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -147,18 +147,6 @@ namespace Umbraco.Core.Services IEnumerable GetPagedContentInRecycleBin(long pageIndex, int pageSize, out long totalRecords, IQuery filter = null, Ordering ordering = null); - /// - /// Gets child documents of a parent. - /// - /// The parent identifier. - /// The page number. - /// The page size. - /// Total number of documents. - /// Search text filter. - /// Ordering infos. - IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, - string filter = null, Ordering ordering = null); - /// /// Gets child documents of a parent. /// @@ -169,20 +157,7 @@ namespace Umbraco.Core.Services /// Query filter. /// Ordering infos. IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, - IQuery filter, Ordering ordering = null); - - /// - /// Gets descendant documents of a given parent. - /// - /// The parent identifier. - /// The page number. - /// The page size. - /// Total number of documents. - /// A field to order by. - /// The ordering direction. - /// Search text filter. - IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, - string filter = null, Ordering ordering = null); + IQuery filter = null, Ordering ordering = null); /// /// Gets descendant documents of a given parent. @@ -196,7 +171,7 @@ namespace Umbraco.Core.Services /// A flag indicating whether the ordering field is a system field. /// Query filter. IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, - IQuery filter, Ordering ordering = null); + IQuery filter = null, Ordering ordering = null); /// /// Gets paged documents of a content content diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs index 2476af94dd..9cc559ccd5 100644 --- a/src/Umbraco.Core/Services/IMediaService.cs +++ b/src/Umbraco.Core/Services/IMediaService.cs @@ -78,20 +78,6 @@ namespace Umbraco.Core.Services /// IMedia GetById(int id); - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Search text filter - /// An Enumerable list of objects - IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, - string filter = null, Ordering ordering = null); - /// /// Gets a collection of objects by Parent Id /// @@ -105,21 +91,7 @@ namespace Umbraco.Core.Services /// /// An Enumerable list of objects IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, - IQuery filter, Ordering ordering = null); - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Descendants from - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Search text filter - /// An Enumerable list of objects - IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, - string filter = null, Ordering ordering = null); + IQuery filter = null, Ordering ordering = null); /// /// Gets a collection of objects by Parent Id @@ -134,7 +106,7 @@ namespace Umbraco.Core.Services /// /// An Enumerable list of objects IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, - IQuery filter, Ordering ordering = null); + IQuery filter = null, Ordering ordering = null); /// /// Gets paged documents of a content content @@ -146,7 +118,7 @@ namespace Umbraco.Core.Services /// Search text filter. /// Ordering infos. IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int pageSize, out long totalRecords, - IQuery filter, Ordering ordering = null); + IQuery filter = null, Ordering ordering = null); /// /// Gets paged documents for specified content types @@ -158,7 +130,7 @@ namespace Umbraco.Core.Services /// Search text filter. /// Ordering infos. IEnumerable GetPagedOfTypes(int[] contentTypeIds, long pageIndex, int pageSize, out long totalRecords, - IQuery filter, Ordering ordering = null); + IQuery filter = null, Ordering ordering = null); /// /// Gets a collection of objects, which reside at the first level / root diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 63d6167f39..386c76db88 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -406,7 +406,8 @@ namespace Umbraco.Core.Services.Implement } /// - public IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int pageSize, out long totalRecords, IQuery filter, Ordering ordering = null) + public IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int pageSize, out long totalRecords + , IQuery filter = null, Ordering ordering = null) { if(pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); @@ -564,18 +565,7 @@ namespace Umbraco.Core.Services.Implement /// public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, - string filter = null, Ordering ordering = null) - { - var filterQuery = filter.IsNullOrWhiteSpace() - ? null - : Query().Where(x => x.Name.Contains(filter)); - - return GetPagedChildren(id, pageIndex, pageSize, out totalChildren, filterQuery, ordering); - } - - /// - public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, - IQuery filter, Ordering ordering = null) + IQuery filter = null, Ordering ordering = null) { if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); @@ -594,18 +584,7 @@ namespace Umbraco.Core.Services.Implement /// public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, - string filter = null, Ordering ordering = null) - { - var filterQuery = filter.IsNullOrWhiteSpace() - ? null - : Query().Where(x => x.Name.Contains(filter)); - - return GetPagedDescendants(id, pageIndex, pageSize, out totalChildren, filterQuery, ordering); - } - - /// - public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, - IQuery filter, Ordering ordering = null) + IQuery filter = null, Ordering ordering = null) { if (ordering == null) ordering = Ordering.By("Path"); diff --git a/src/Umbraco.Core/Services/Implement/MediaService.cs b/src/Umbraco.Core/Services/Implement/MediaService.cs index 4be75487b7..5f35e35acf 100644 --- a/src/Umbraco.Core/Services/Implement/MediaService.cs +++ b/src/Umbraco.Core/Services/Implement/MediaService.cs @@ -365,7 +365,7 @@ namespace Umbraco.Core.Services.Implement } /// - public IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int pageSize, out long totalRecords, IQuery filter, Ordering ordering = null) + public IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int pageSize, out long totalRecords, IQuery filter = null, Ordering ordering = null) { if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); @@ -383,7 +383,7 @@ namespace Umbraco.Core.Services.Implement } /// - public IEnumerable GetPagedOfTypes(int[] contentTypeIds, long pageIndex, int pageSize, out long totalRecords, IQuery filter, Ordering ordering = null) + public IEnumerable GetPagedOfTypes(int[] contentTypeIds, long pageIndex, int pageSize, out long totalRecords, IQuery filter = null, Ordering ordering = null) { if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); @@ -481,41 +481,9 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// Page index (zero based) - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Search text filter - /// An Enumerable list of objects + /// public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, - string filter = null, Ordering ordering = null) - { - var filterQuery = filter.IsNullOrWhiteSpace() - ? null - : Query().Where(x => x.Name.Contains(filter)); - - return GetPagedChildren(id, pageIndex, pageSize, out totalChildren, filterQuery, ordering); - } - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// Page index (zero based) - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Flag to indicate when ordering by system field - /// - /// An Enumerable list of objects - public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, - IQuery filter, Ordering ordering = null) + IQuery filter = null, Ordering ordering = null) { if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); @@ -532,41 +500,9 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Descendants from - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Search text filter - /// An Enumerable list of objects + /// public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, - string filter = null, Ordering ordering = null) - { - var filterQuery = filter.IsNullOrWhiteSpace() - ? null - : Query().Where(x => x.Name.Contains(filter)); - - return GetPagedDescendants(id, pageIndex, pageSize, out totalChildren, filterQuery, ordering); - } - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Descendants from - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Flag to indicate when ordering by system field - /// - /// An Enumerable list of objects - public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, - IQuery filter, Ordering ordering = null) + IQuery filter = null, Ordering ordering = null) { if (ordering == null) ordering = Ordering.By("Path"); diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs index 8aec854441..8b57e10849 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs @@ -71,10 +71,6 @@ namespace Umbraco.Tests.UmbracoExamine contentService = Mock.Of( x => x.GetPagedDescendants( - It.IsAny(), It.IsAny(), It.IsAny(), out longTotalRecs, It.IsAny(), It.IsAny()) - == - allRecs - && x.GetPagedDescendants( It.IsAny(), It.IsAny(), It.IsAny(), out longTotalRecs, It.IsAny>(), It.IsAny()) == allRecs); @@ -114,11 +110,6 @@ namespace Umbraco.Tests.UmbracoExamine // MOCK! var mediaServiceMock = new Mock(); - mediaServiceMock - .Setup(x => x.GetPagedDescendants( - It.IsAny(), It.IsAny(), It.IsAny(), out totalRecs, It.IsAny(), It.IsAny()) - ).Returns(() => allRecs); - mediaServiceMock .Setup(x => x.GetPagedDescendants( It.IsAny(), It.IsAny(), It.IsAny(), out totalRecs, It.IsAny>(), It.IsAny()) From 0e8bac0b0907456da24e517fa36497450a71f237 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 1 Nov 2018 11:24:03 +1100 Subject: [PATCH 24/71] fixes member indexing with list object --- src/Umbraco.Examine/UmbracoMemberIndexer.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Examine/UmbracoMemberIndexer.cs b/src/Umbraco.Examine/UmbracoMemberIndexer.cs index b7cbcc19bc..82bf3b9cf6 100644 --- a/src/Umbraco.Examine/UmbracoMemberIndexer.cs +++ b/src/Umbraco.Examine/UmbracoMemberIndexer.cs @@ -175,13 +175,17 @@ namespace Umbraco.Examine if (e.IndexItem.ValueSet.Values.TryGetValue("key", out var key) && e.IndexItem.ValueSet.Values.ContainsKey("__key") == false) { //double __ prefix means it will be indexed as culture invariant - e.IndexItem.ValueSet.Values["__key"] = new List { key }; + e.IndexItem.ValueSet.Values["__key"] = key; } if (e.IndexItem.ValueSet.Values.TryGetValue("email", out var email) && e.IndexItem.ValueSet.Values.ContainsKey("_searchEmail") == false) { - //will be indexed as full text (the default anaylyzer) - e.IndexItem.ValueSet.Values["_searchEmail"] = new List { email?.ToString().Replace(".", " ").Replace("@", " ") }; + if (email.Count > 0) + { + //will be indexed as full text (the default anaylyzer) + e.IndexItem.ValueSet.Values["_searchEmail"] = new List { email[0]?.ToString().Replace(".", " ").Replace("@", " ") }; + } + } } From e726e6dffe6e394d0c2d278d2316bfc018ecff98 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 1 Nov 2018 11:08:41 +0100 Subject: [PATCH 25/71] Remove a bunch of inline styles --- .../src/less/components/umb-stylesheet.less | 20 ++++++++++++++-- .../views/stylesheets/views/rules/rules.html | 23 +++++++++---------- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-stylesheet.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-stylesheet.less index fd976bba32..b74e13719b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-stylesheet.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-stylesheet.less @@ -25,13 +25,29 @@ width: 295px; } -.umb-styleheet-rules__left { +.umb-stylesheet-rules__left { display: block; flex: 1 1 auto; overflow: hidden; + + .umb-el-wrap { + margin: 5px auto; + + .control-label { + text-align: left + } + + .controls { + textarea { + width: 295px; + height: 80px; + resize: none; + } + } + } } -.umb-styleheet-rules__right { +.umb-stylesheet-rules__right { display: flex; flex: 0 0 auto; align-items: center; diff --git a/src/Umbraco.Web.UI.Client/src/views/stylesheets/views/rules/rules.html b/src/Umbraco.Web.UI.Client/src/views/stylesheets/views/rules/rules.html index 4e6ba8c8a1..adb355a03c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/stylesheets/views/rules/rules.html +++ b/src/Umbraco.Web.UI.Client/src/views/stylesheets/views/rules/rules.html @@ -1,5 +1,4 @@ -
@@ -8,37 +7,37 @@ Some explanatory text about rich text editor styles goes here
-
+
-
+
-
-
-