diff --git a/umbraco.Test/DocumentTest.cs b/umbraco.Test/DocumentTest.cs
index 60ca65ac66..29a6ea7143 100644
--- a/umbraco.Test/DocumentTest.cs
+++ b/umbraco.Test/DocumentTest.cs
@@ -13,6 +13,7 @@ using umbraco.editorControls.textfield;
using umbraco.cms.businesslogic.propertytype;
using umbraco.cms.businesslogic.property;
using umbraco.cms.businesslogic.language;
+using umbraco.BusinessLogic.console;
namespace umbraco.Test
{
@@ -35,6 +36,50 @@ namespace umbraco.Test
#region Unit Tests
+ ///
+ /// Creates a bunch of nodes in a heirarchy, then deletes the top most node (moves to the recycle bin
+ /// and completely deletes from system.) This should completely delete all of these nodes from the database.
+ ///
+ [TestMethod()]
+ public void Document_DeleteHeirarchyPermanentlyTest()
+ {
+ var docList = new List();
+ var total = 20;
+ var dt = new DocumentType(GetExistingDocTypeId());
+ //allow the doc type to be created underneath itself
+ dt.AllowedChildContentTypeIDs = new int[] { dt.Id };
+ dt.Save();
+
+ //create 20 content nodes underneath each other, this will test deleting with heirarchy as well
+ var lastParentId = -1;
+ for (var i = 0; i < total; i++)
+ {
+ var newDoc = Document.MakeNew(i.ToString() + Guid.NewGuid().ToString("N"), dt, m_User, lastParentId);
+ docList.Add(newDoc);
+ Assert.IsTrue(docList[docList.Count - 1].Id > 0);
+ lastParentId = newDoc.Id;
+ }
+
+ //now delete all of them permanently, since they are nested, we only need to delete one
+ docList.First().delete(true);
+
+ //make sure they are all gone
+ foreach (var d in docList)
+ {
+ Assert.IsFalse(Document.IsNode(d.Id));
+ }
+
+ }
+
+ ///
+ ///A test for PublishWithResult
+ ///
+ [TestMethod()]
+ public void Document_PublishWithResultTest()
+ {
+ var val = m_NewRootDoc.PublishWithResult(m_User);
+ }
+
///
/// Creates a doc type, assigns a domain to it and removes it
///
@@ -83,26 +128,28 @@ namespace umbraco.Test
{
//System.Diagnostics.Debugger.Break();
Document target = new Document(GetExistingNodeId());
- int parentId = target.Level == 1 ? -1 : target.Parent.Id;
+ int parentId = target.ParentId;
bool RelateToOrignal = false;
//get children ids for the current parent
- var childrenIds = target.Level == 1 ? Document.GetRootDocuments().ToList().Select(x => x.Id) : target.Parent.Children.ToList().Select(x => x.Id);
+ var childrenIds = GetChildNodesOfParent(target).Select(x => x.Id);
//copy the node
target.Copy(parentId, m_User, RelateToOrignal);
//test that the child id count + 1 is equal to the total child count
- Assert.AreEqual(childrenIds.Count() + 1, target.Level == 1 ? Document.GetRootDocuments().Count() : target.Parent.ChildCount);
+ Assert.AreEqual(childrenIds.Count() + 1, GetChildNodesOfParent(target).Count(), "Child node counts do not match");
//get the list of new child ids from the parent
- var newChildIds = target.Level == 1 ? Document.GetRootDocuments().ToList().Select(x => x.Id) : target.Parent.Children.ToList().Select(x => x.Id);
+ var newChildIds = GetChildNodesOfParent(target).Select(x => x.Id);
//get the children difference which should be the new node
var diff = newChildIds.Except(childrenIds);
-
+
Assert.AreEqual(1, diff.Count());
+ //get the node that is the difference to compare
Document newDoc = new Document(diff.First());
+ Assert.AreEqual(parentId, newDoc.ParentId);
RecycleAndDelete(newDoc);
}
@@ -115,15 +162,15 @@ namespace umbraco.Test
{
//System.Diagnostics.Debugger.Break();
Document target = new Document(GetExistingNodeId());
- int parentId = target.Level == 1 ? -1 : target.Parent.Id;
+ int parentId = target.ParentId;
bool RelateToOrignal = true;
- //get children ids
- var childrenIds = target.Parent.Children.ToList().Select(x => x.Id);
+ //get children ids
+ var childrenIds = GetChildNodesOfParent(target).Select(x => x.Id);
target.Copy(parentId, m_User, RelateToOrignal);
- Assert.AreEqual(childrenIds.Count() + 1, target.Level == 1 ? Document.GetRootDocuments().Count() : target.Parent.ChildCount);
+ Assert.AreEqual(childrenIds.Count() + 1, GetChildNodesOfParent(target).Count());
Document parent = new Document(parentId);
//get the children difference which should be the new node
@@ -355,7 +402,7 @@ namespace umbraco.Test
var doc = new Document(GetExistingNodeId());
//create new content based on the existing content in the same heirarchy
var dt = new DocumentType(doc.ContentType.Id);
- var parentId = doc.Level == 1 ? -1 : doc.Parent.Id;
+ var parentId = doc.ParentId;
var newDoc = Document.MakeNew("NewDoc" + Guid.NewGuid().ToString("N"), dt, m_User, parentId);
Assert.IsTrue(newDoc.Id > 0);
@@ -379,34 +426,36 @@ namespace umbraco.Test
[TestMethod]
public void Document_EmptyRecycleBinTest()
{
- var totalTrashedItems = RecycleBin.Count(RecycleBin.RecycleBinType.Content);
-
var docList = new List();
var total = 20;
- var dt = new DocumentType(GetExistingDocTypeId());
- //create 20 content nodes
+ var dt = m_ExistingDocType;
+ //allow the doc type to be created underneath itself
+ dt.AllowedChildContentTypeIDs = new int[] { dt.Id };
+ dt.Save();
+
+ //create 20 content nodes underneath each other, this will test deleting with heirarchy as well
+ var lastParentId = -1;
for (var i = 0; i < total; i++)
{
- docList.Add(Document.MakeNew(i.ToString() + Guid.NewGuid().ToString("N"), dt, m_User, -1));
+ var newDoc = Document.MakeNew("R-" + i.ToString() + Guid.NewGuid().ToString("N"), dt, m_User, lastParentId);
+ docList.Add(newDoc);
Assert.IsTrue(docList[docList.Count - 1].Id > 0);
+ Assert.AreEqual(lastParentId, newDoc.ParentId);
+ lastParentId = newDoc.Id;
}
- //now delete all of them
- foreach (var d in docList)
- {
- d.delete();
- Assert.IsTrue(d.IsTrashed);
- }
+ //now delete all of them, since they are nested, we only need to delete one
+ docList.First().delete();
//a callback action for each item removed from the recycle bin
var totalDeleted = 0;
- var deleteCallback = new Action(x =>
- {
- Assert.AreEqual((total + totalTrashedItems) - (++totalDeleted), x);
- });
var bin = new RecycleBin(RecycleBin.RecycleBinType.Content);
- bin.CallTheGarbageMan(deleteCallback);
+ var totalTrashedItems = bin.GetDescendants().Cast().Count();
+ bin.CallTheGarbageMan(x =>
+ {
+ Assert.AreEqual(totalTrashedItems - (++totalDeleted), x);
+ });
Assert.AreEqual(0, RecycleBin.Count(RecycleBin.RecycleBinType.Content));
}
@@ -540,21 +589,7 @@ namespace umbraco.Test
// Assert.Inconclusive("A method that does not return a value cannot be verified.");
//}
- /////
- /////A test for PublishWithResult
- /////
- //[TestMethod()]
- //public void PublishWithResultTest()
- //{
- // Guid id = new Guid(); // TODO: Initialize to an appropriate value
- // Document target = new Document(id); // TODO: Initialize to an appropriate value
- // User u = null; // TODO: Initialize to an appropriate value
- // bool expected = false; // TODO: Initialize to an appropriate value
- // bool actual;
- // actual = target.PublishWithResult(u);
- // Assert.AreEqual(expected, actual);
- // Assert.Inconclusive("Verify the correctness of this test method.");
- //}
+
/////
/////A test for PublishWithChildrenWithResult
@@ -745,15 +780,20 @@ namespace umbraco.Test
}
var id = d.Id;
- //now recycle it
- d.delete();
- Assert.IsTrue(d.IsTrashed);
+ //check if it is already trashed
+ var alreadyTrashed = d.IsTrashed;
- Document recycled = new Document(id);
- //now delete it
- recycled.delete();
+ if (!alreadyTrashed)
+ {
+ //now recycle it
+ d.delete();
+ Assert.IsTrue(d.IsTrashed);
+ }
+
+ //now permanently delete
+ d.delete(true);
Assert.IsFalse(Document.IsNode(id));
//check with sql that it is gone
@@ -814,6 +854,32 @@ namespace umbraco.Test
return ids[index];
}
+ ///
+ /// A helper method to get the parent node.
+ /// The reason we need this is because the API currently throws an exception if we access the Parent property
+ /// of a node and the node is on level 1. This also causes issues if the node is in level 1 in the recycle bin.
+ ///
+ ///
+ ///
+ private IEnumerable GetChildNodesOfParent(Document d)
+ {
+ if (d.ParentId == (int)RecycleBin.RecycleBinType.Content)
+ {
+ return new RecycleBin(RecycleBin.RecycleBinType.Content).Children.ToList();
+ }
+ else
+ {
+ if (d.Level == 1)
+ {
+ return Document.GetRootDocuments();
+ }
+ else
+ {
+ return d.Parent.Children;
+ }
+ }
+ }
+
private DocumentType GetExistingDocType()
{
DocumentType dct = new DocumentType(GetExistingDocTypeId());
diff --git a/umbraco.Test/DocumentTypeTest.cs b/umbraco.Test/DocumentTypeTest.cs
index fe95eddd56..2ee7f4d9a4 100644
--- a/umbraco.Test/DocumentTypeTest.cs
+++ b/umbraco.Test/DocumentTypeTest.cs
@@ -22,18 +22,80 @@ namespace umbraco.Test
[TestClass()]
public class DocumentTypeTest
{
+ [TestMethod()]
+ public void DocumentType_DeleteDocTypeWithContennt()
+ {
+ var dt = CreateNewDocType();
+ var doc = Document.MakeNew("TEST" + Guid.NewGuid().ToString("N"), dt, m_User, -1);
+ Assert.IsInstanceOfType(doc, typeof(Document));
+ Assert.IsTrue(doc.Id > 0);
+
+ DeleteDocType(dt);
+
+ Assert.IsFalse(Document.IsNode(doc.Id));
+ }
+
+ ///
+ /// This will create 3 document types, and create nodes in the following structure:
+ /// - root
+ /// -- node1 (of doc type #1)
+ /// --- node 2 (of doc type #2)
+ /// ---- node 3 (of doc type #1)
+ /// ----- node 4 (of doc type #3)
+ ///
+ /// Then we'll delete doc type #1. The result should be that node1 and node3 are completely deleted from the database and node2 and node4 are
+ /// moved to the recycle bin.
+ ///
+ [TestMethod()]
+ public void DocumentType_DeleteDocTypeWithContentAndChildrenOfDifferentDocTypes()
+ {
+ //System.Diagnostics.Debugger.Break();
+
+ //create the doc types
+ var dt1 = CreateNewDocType();
+ var dt2 = CreateNewDocType();
+ var dt3 = CreateNewDocType();
+
+ //create the heirarchy
+ dt1.AllowedChildContentTypeIDs = new int[] { dt2.Id, dt3.Id };
+ dt1.Save();
+ dt2.AllowedChildContentTypeIDs = new int[] { dt1.Id };
+ dt2.Save();
+
+ //create the content tree
+ var node1 = Document.MakeNew("TEST" + Guid.NewGuid().ToString("N"), dt1, m_User, -1);
+ var node2 = Document.MakeNew("TEST" + Guid.NewGuid().ToString("N"), dt2, m_User, node1.Id);
+ var node3 = Document.MakeNew("TEST" + Guid.NewGuid().ToString("N"), dt1, m_User, node2.Id);
+ var node4 = Document.MakeNew("TEST" + Guid.NewGuid().ToString("N"), dt3, m_User, node3.Id);
+
+ //do the deletion of doc type #1
+ DeleteDocType(dt1);
+
+ //do our checks
+ Assert.IsFalse(Document.IsNode(node1.Id), "node1 is not deleted"); //this was of doc type 1, should be gone
+ Assert.IsFalse(Document.IsNode(node3.Id), "node3 is not deleted"); //this was of doc type 1, should be gone
+
+ Assert.IsTrue(Document.IsNode(node2.Id), "node2 is deleted");
+ Assert.IsTrue(Document.IsNode(node4.Id), "node4 is deleted");
+
+ node2 = new Document(node2.Id);//need to re-query the node
+ Assert.IsTrue(node2.IsTrashed, "node2 is not in the trash");
+ node4 = new Document(node4.Id); //need to re-query the node
+ Assert.IsTrue(node4.IsTrashed, "node 4 is not in the trash");
+
+ //remove the old data
+ DeleteDocType(dt2);
+ DeleteDocType(dt3);
+
+ }
///
///A test for creating a new document type
///
[TestMethod()]
public void DocumentType_MakeNewTest()
- {
- var dt = DocumentType.MakeNew(m_User, "TEST" + Guid.NewGuid().ToString("N"));
- Assert.IsTrue(dt.Id > 0);
- Assert.AreEqual(DateTime.Now.Date, dt.CreateDateTime.Date);
-
- DeleteDocType(dt);
+ {
+ Assert.IsInstanceOfType(m_NewDocType, typeof(DocumentType));
}
///
@@ -43,18 +105,14 @@ namespace umbraco.Test
public void DocumentType_AddPropertiesToTabThenDeleteItTest()
{
//System.Diagnostics.Debugger.Break();
-
- var dt = DocumentType.MakeNew(m_User, "TEST" + Guid.NewGuid().ToString("N"));
- Assert.IsTrue(dt.Id > 0);
- Assert.AreEqual(DateTime.Now.Date, dt.CreateDateTime.Date);
-
+
//allow itself to be created under itself
- dt.AllowedChildContentTypeIDs = new int[] { dt.Id };
+ m_NewDocType.AllowedChildContentTypeIDs = new int[] { m_NewDocType.Id };
//create a tab
- dt.AddVirtualTab("TEST");
+ m_NewDocType.AddVirtualTab("TEST");
//test the tab
- var tabs = dt.getVirtualTabs.ToList();
+ var tabs = m_NewDocType.getVirtualTabs.ToList();
Assert.AreEqual(1, tabs.Count);
//create a property
@@ -63,22 +121,20 @@ namespace umbraco.Test
foreach (var dataType in allDataTypes)
{
//add a property type of the first type found in the list
- dt.AddPropertyType(dataType, "testProperty" + (++i).ToString(), "Test Property" + i.ToString());
+ m_NewDocType.AddPropertyType(dataType, "testProperty" + (++i).ToString(), "Test Property" + i.ToString());
//test the prop
- var prop = dt.getPropertyType("testProperty" + i.ToString());
+ var prop = m_NewDocType.getPropertyType("testProperty" + i.ToString());
Assert.IsTrue(prop.Id > 0);
Assert.AreEqual("Test Property" + i.ToString(), prop.Name);
//put the properties to the tab
- dt.SetTabOnPropertyType(prop, tabs[0].Id);
+ m_NewDocType.SetTabOnPropertyType(prop, tabs[0].Id);
//re-get the property since data is cached in the object
- prop = dt.getPropertyType("testProperty" + i.ToString());
+ prop = m_NewDocType.getPropertyType("testProperty" + i.ToString());
Assert.AreEqual(tabs[0].Id, prop.TabId);
}
//now we need to delete the tab
- dt.DeleteVirtualTab(tabs[0].Id);
-
- dt.delete();
+ m_NewDocType.DeleteVirtualTab(tabs[0].Id);
}
///
@@ -688,6 +744,23 @@ namespace umbraco.Test
private User m_User = new User(0);
+ ///
+ /// before each test starts, this object is created so it can be used for testing.
+ ///
+ private DocumentType m_NewDocType;
+
+ ///
+ /// Create a brand new document type
+ ///
+ ///
+ private DocumentType CreateNewDocType()
+ {
+ var dt = DocumentType.MakeNew(m_User, "TEST" + Guid.NewGuid().ToString("N"));
+ Assert.IsTrue(dt.Id > 0);
+ Assert.AreEqual(DateTime.Now.Date, dt.CreateDateTime.Date);
+ return dt;
+ }
+
private void DeleteDocType(DocumentType dt)
{
var id = dt.Id;
@@ -739,18 +812,25 @@ namespace umbraco.Test
//{
//}
//
- //Use TestInitialize to run code before running each test
- //[TestInitialize()]
- //public void MyTestInitialize()
- //{
- //}
- //
- //Use TestCleanup to run code after each test has run
- //[TestCleanup()]
- //public void MyTestCleanup()
- //{
- //}
- //
+
+ ///
+ /// Create a new document type for use in tests
+ ///
+ [TestInitialize()]
+ public void MyTestInitialize()
+ {
+ m_NewDocType = CreateNewDocType();
+ }
+
+ ///
+ /// Remove the created document type
+ ///
+ [TestCleanup()]
+ public void MyTestCleanup()
+ {
+ DeleteDocType(m_NewDocType);
+ }
+
#endregion
}
diff --git a/umbraco/cms/businesslogic/CMSNode.cs b/umbraco/cms/businesslogic/CMSNode.cs
index be208fd0ad..c135609836 100644
--- a/umbraco/cms/businesslogic/CMSNode.cs
+++ b/umbraco/cms/businesslogic/CMSNode.cs
@@ -12,6 +12,7 @@ using System.Text.RegularExpressions;
using System.ComponentModel;
using umbraco.IO;
using umbraco.cms.businesslogic.media;
+using System.Collections;
namespace umbraco.cms.businesslogic
{
@@ -51,6 +52,7 @@ namespace umbraco.cms.businesslogic
#endregion
#region Private static
+
private static readonly string m_DefaultIconCssFile = IOHelper.MapPath(SystemDirectories.Umbraco_client + "/Tree/treeIcons.css");
private static List m_DefaultIconClasses = new List();
private static void initializeIconClasses()
@@ -74,6 +76,12 @@ namespace umbraco.cms.businesslogic
m_DefaultIconClasses.Add(cssClass);
}
}
+ private const string m_SQLSingle = "SELECT id, createDate, trashed, parentId, nodeObjectType, nodeUser, level, path, sortOrder, uniqueID, text FROM umbracoNode WHERE id = @id";
+ private const string m_SQLDescendants = @"
+ SELECT id, createDate, trashed, parentId, nodeObjectType, nodeUser, level, path, sortOrder, uniqueID, text
+ FROM umbracoNode
+ WHERE path LIKE '%,{0},%'";
+
#endregion
#region Public static
@@ -408,56 +416,35 @@ order by level,sortOrder";
return base.ToString();
}
- ///
- /// Moves the CMSNode from the current position in the hierarchy to the target
- ///
- /// Target CMSNode id
- public void Move(int newParentId)
+ private void Move(CMSNode parent)
{
- //first we need to establish if the node already exists under the parent node
- var isSameParent = (Path.Contains("," + newParentId + ","));
-
MoveEventArgs e = new MoveEventArgs();
FireBeforeMove(e);
if (!e.Cancel)
{
- CMSNode n = new CMSNode(newParentId);
+ //first we need to establish if the node already exists under the parent node
+ var isSameParent = (Path.Contains("," + parent.Id + ","));
//if it's the same parent, we can save some SQL calls since we know these wont change.
//level and path might change even if it's the same parent because the parent could be moving somewhere.
if (!isSameParent)
{
- int maxSortOrder = SqlHelper.ExecuteScalar(
- "select coalesce(max(sortOrder),0) from umbracoNode where parentid = @parentId",
- SqlHelper.CreateParameter("@parentId", newParentId));
+ int maxSortOrder = SqlHelper.ExecuteScalar("select coalesce(max(sortOrder),0) from umbracoNode where parentid = @parentId",
+ SqlHelper.CreateParameter("@parentId", parent.Id));
- this.Parent = n;
+ this.Parent = parent;
this.sortOrder = maxSortOrder + 1;
- }
-
- this.Level = n.Level + 1;
- this.Path = n.Path + "," + this.Id.ToString();
+ }
+
+ this.Level = parent.Level + 1;
+ this.Path = parent.Path + "," + this.Id.ToString();
//this code block should not be here but since the class structure is very poor and doesn't use
//overrides (instead using shadows/new) for the Children property, when iterating over the children
//and calling Move(), the super classes overridden OnMove or Move methods never get fired, so
//we now need to hard code this here :(
- //make sure the node type is a document/media, if it is a recycle bin then this will not be equal
- if (n.nodeObjectType == Document._objectType)
- {
- //regenerate the xml for the parent node
- var d = new Document(n.Id);
- d.XmlGenerate(new XmlDocument());
- }
- else if (n.nodeObjectType == Media._objectType)
- {
- //regenerate the xml for the parent node
- var m = new Media(n.Id);
- m.XmlGenerate(new XmlDocument());
- }
-
if (Path.Contains("," + ((int)RecycleBin.RecycleBinType.Content).ToString() + ",")
|| Path.Contains("," + ((int)RecycleBin.RecycleBinType.Media).ToString() + ","))
{
@@ -469,16 +456,40 @@ order by level,sortOrder";
if (IsTrashed) IsTrashed = false; //don't update if it's not necessary
}
+ //make sure the node type is a document/media, if it is a recycle bin then this will not be equal
+ if (!IsTrashed && parent.nodeObjectType == Document._objectType)
+ {
+ //regenerate the xml for the parent node
+ var d = new Document(parent.Id);
+ d.XmlGenerate(new XmlDocument());
+ }
+ else if (!IsTrashed && parent.nodeObjectType == Media._objectType)
+ {
+ //regenerate the xml for the parent node
+ var m = new Media(parent.Id);
+ m.XmlGenerate(new XmlDocument());
+ }
+
var children = this.Children;
foreach (CMSNode c in children)
{
- c.Move(this.Id);
+ c.Move(this);
}
FireAfterMove(e);
}
}
+ ///
+ /// Moves the CMSNode from the current position in the hierarchy to the target
+ ///
+ /// Target CMSNode id
+ public void Move(int newParentId)
+ {
+ CMSNode parent = new CMSNode(newParentId);
+ Move(parent);
+ }
+
///
/// Deletes this instance.
///
@@ -500,7 +511,6 @@ order by level,sortOrder";
}
}
-
///
/// Does the current CMSNode have any child nodes.
///
@@ -524,7 +534,31 @@ order by level,sortOrder";
_hasChildrenInitialized = true;
_hasChildren = value;
}
- }
+ }
+
+ ///
+ /// Returns all descendant nodes from this node.
+ ///
+ ///
+ ///
+ /// This doesn't return a strongly typed IEnumerable object so that we can override in in super clases
+ /// and since this class isn't a generic (thought it should be) this is not strongly typed.
+ ///
+ public virtual IEnumerable GetDescendants()
+ {
+ var descendants = new List();
+ using (IRecordsReader dr = SqlHelper.ExecuteReader(string.Format(m_SQLDescendants, Id)))
+ {
+ while (dr.Read())
+ {
+ var node = new CMSNode(dr.GetInt("id"), true);
+ node.PopulateCMSNodeFromReader(dr);
+ descendants.Add(node);
+ }
+ }
+ return descendants;
+ }
+
#endregion
#region Public properties
@@ -602,6 +636,13 @@ order by level,sortOrder";
get { return _id; }
}
+ ///
+ /// Get the parent id of the node
+ ///
+ public int ParentId
+ {
+ get { return _parentid; }
+ }
///
/// Given the hierarchical tree structure a CMSNode has only one parent but can have many children
@@ -690,7 +731,8 @@ order by level,sortOrder";
{
System.Collections.ArrayList tmp = new System.Collections.ArrayList();
using (IRecordsReader dr = SqlHelper.ExecuteReader("SELECT id, createDate, trashed, parentId, nodeObjectType, nodeUser, level, path, sortOrder, uniqueID, text FROM umbracoNode WHERE ParentID = @ParentID AND nodeObjectType = @type order by sortOrder",
- SqlHelper.CreateParameter("@type", this.nodeObjectType), SqlHelper.CreateParameter("ParentID", this.Id)))
+ SqlHelper.CreateParameter("@type", this.nodeObjectType),
+ SqlHelper.CreateParameter("ParentID", this.Id)))
{
while (dr.Read())
{
@@ -816,9 +858,8 @@ order by level,sortOrder";
///
protected virtual void setupNode()
{
- using (IRecordsReader dr = SqlHelper.ExecuteReader(
- "SELECT createDate, trashed, parentId, nodeObjectType, nodeUser, level, path, sortOrder, uniqueID, text FROM umbracoNode WHERE id = " + this.Id
- ))
+ using (IRecordsReader dr = SqlHelper.ExecuteReader(m_SQLSingle,
+ SqlHelper.CreateParameter("@id",this.Id)))
{
if (dr.Read())
{
diff --git a/umbraco/cms/businesslogic/RecycleBin.cs b/umbraco/cms/businesslogic/RecycleBin.cs
index a7aea12c35..e12f296164 100644
--- a/umbraco/cms/businesslogic/RecycleBin.cs
+++ b/umbraco/cms/businesslogic/RecycleBin.cs
@@ -5,6 +5,7 @@ using System.Linq;
using umbraco.DataLayer;
using umbraco.cms.businesslogic.web;
using umbraco.cms.businesslogic.media;
+using System.Threading;
namespace umbraco.cms.businesslogic
{
@@ -134,12 +135,18 @@ namespace umbraco.cms.businesslogic
{
lock (m_Locker)
{
+ //first, move all nodes underneath the recycle bin directly under the recycle bin node (flatten heirarchy)
+ //then delete them all.
+
+ SqlHelper.ExecuteNonQuery("UPDATE umbracoNode SET parentID=@parentID, level=1 WHERE path LIKE '%," + ((int)m_BinType).ToString() + ",%'",
+ SqlHelper.CreateParameter("@parentID", (int)m_BinType));
+
foreach (var c in Children.ToList())
{
switch (m_BinType)
{
case RecycleBinType.Content:
- new Document(c.Id).delete();
+ new Document(c.Id).delete(true);
itemDeletedCallback(RecycleBin.Count(m_BinType));
break;
case RecycleBinType.Media:
diff --git a/umbraco/cms/businesslogic/web/Document.cs b/umbraco/cms/businesslogic/web/Document.cs
index 09169bff26..b7147ef96c 100644
--- a/umbraco/cms/businesslogic/web/Document.cs
+++ b/umbraco/cms/businesslogic/web/Document.cs
@@ -146,41 +146,38 @@ namespace umbraco.cms.businesslogic.web
umbracoNode.createDate, umbracoNode.trashed, umbracoNode.parentId, umbracoNode.nodeObjectType, umbracoNode.nodeUser, umbracoNode.level, umbracoNode.path, umbracoNode.sortOrder, umbracoNode.uniqueId, umbracoNode.text
from
umbracoNode
- inner join
- cmsContentVersion on cmsContentVersion.contentID = umbracoNode.id
- inner join
- cmsDocument on cmsDocument.versionId = cmsContentVersion.versionId
- inner join
- cmsContent on cmsDocument.nodeId = cmsContent.NodeId
- inner join
- cmsContentType on cmsContentType.nodeId = cmsContent.ContentType
- inner join
- umbracoNode contentTypeNode on contentTypeNode.id = cmsContentType.nodeId
- left join cmsDocumentType on
- cmsDocumentType.contentTypeNodeId = cmsContent.contentType and cmsDocumentType.IsDefault = 1
- where
- {0}
- order by
- {1}
- ";
- private const string m_SQLOptimizedChildren = @"
- select count(children.id) as children, umbracoNode.id, umbracoNode.uniqueId, umbracoNode.level, umbracoNode.parentId, cmsDocument.documentUser,
- coalesce(cmsDocument.templateId, cmsDocumentType.templateNodeId) as templateId, umbracoNode.path, umbracoNode.sortOrder, coalesce(publishCheck.published,0) as published, umbracoNode.createDate, cmsDocument.text, cmsDocument.updateDate, cmsContentVersion.versionDate, cmsContentType.icon, cmsContentType.alias, cmsContentType.thumbnail, cmsContentType.description, cmsContentType.masterContentType, cmsContentType.nodeId as contentTypeId
- from umbracoNode
- left join umbracoNode children on children.parentId = umbracoNode.id
- inner join cmsContent on cmsContent.nodeId = umbracoNode.id
- inner join cmsContentType on cmsContentType.nodeId = cmsContent.contentType
- inner join (select contentId, max(versionDate) AS versionDate from cmsContentVersion
- inner join umbracoNode on umbracoNode.id = cmsContentVersion.contentId and umbracoNode.parentId = @parentId
- group by contentId) AS temp
- on temp.contentId = cmsContent.nodeId
- inner join cmsContentVersion on cmsContentVersion.contentId = temp.contentId and cmsContentVersion.versionDate = temp.versionDate
- inner join cmsDocument on cmsDocument.versionId = cmsContentversion.versionId
- left join cmsDocument publishCheck on publishCheck.nodeId = cmsContent.nodeID and publishCheck.published = 1
- left join cmsDocumentType on
- cmsDocumentType.contentTypeNodeId = cmsContent.contentType and cmsDocumentType.IsDefault = 1
+ inner join cmsContentVersion on cmsContentVersion.contentID = umbracoNode.id
+ inner join cmsDocument on cmsDocument.versionId = cmsContentVersion.versionId
+ inner join cmsContent on cmsDocument.nodeId = cmsContent.NodeId
+ inner join cmsContentType on cmsContentType.nodeId = cmsContent.ContentType
+ inner join umbracoNode contentTypeNode on contentTypeNode.id = cmsContentType.nodeId
+ left join cmsDocumentType on cmsDocumentType.contentTypeNodeId = cmsContent.contentType and cmsDocumentType.IsDefault = 1
where {0}
- group by umbracoNode.id, umbracoNode.uniqueId, umbracoNode.level, umbracoNode.parentId, cmsDocument.documentUser, cmsDocument.templateId, cmsDocumentType.templateNodeId, umbracoNode.path, umbracoNode.sortOrder, coalesce(publishCheck.published,0), umbracoNode.createDate, cmsDocument.text, cmsDocument.updateDate, cmsContentVersion.versionDate, cmsContentType.icon, cmsContentType.alias, cmsContentType.thumbnail, cmsContentType.description, cmsContentType.masterContentType, cmsContentType.nodeId
+ order by {1}
+ ";
+ private const string m_SQLOptimizedMany = @"
+ select count(children.id) as children, umbracoNode.id, umbracoNode.uniqueId, umbracoNode.level, umbracoNode.parentId,
+ cmsDocument.documentUser, coalesce(cmsDocument.templateId, cmsDocumentType.templateNodeId) as templateId,
+ umbracoNode.path, umbracoNode.sortOrder, coalesce(publishCheck.published,0) as published, umbracoNode.createDate,
+ cmsDocument.text, cmsDocument.updateDate, cmsContentVersion.versionDate, cmsContentType.icon, cmsContentType.alias,
+ cmsContentType.thumbnail, cmsContentType.description, cmsContentType.masterContentType, cmsContentType.nodeId as contentTypeId
+ from umbracoNode
+ left join umbracoNode children on children.parentId = umbracoNode.id
+ inner join cmsContent on cmsContent.nodeId = umbracoNode.id
+ inner join cmsContentType on cmsContentType.nodeId = cmsContent.contentType
+ inner join cmsContentVersion on cmsContentVersion.contentId = umbracoNode.id
+ inner join (select contentId, max(versionDate) as versionDate from cmsContentVersion group by contentId) temp
+ on cmsContentVersion.contentId = temp.contentId and cmsContentVersion.versionDate = temp.versionDate
+ inner join cmsDocument on cmsDocument.versionId = cmsContentversion.versionId
+ left join cmsDocument publishCheck on publishCheck.nodeId = cmsContent.nodeID and publishCheck.published = 1
+ left join cmsDocumentType on cmsDocumentType.contentTypeNodeId = cmsContent.contentType and cmsDocumentType.IsDefault = 1
+ where {0}
+ group by
+ umbracoNode.id, umbracoNode.uniqueId, umbracoNode.level, umbracoNode.parentId, cmsDocument.documentUser,
+ cmsDocument.templateId, cmsDocumentType.templateNodeId, umbracoNode.path, umbracoNode.sortOrder,
+ coalesce(publishCheck.published,0), umbracoNode.createDate, cmsDocument.text,
+ cmsContentType.icon, cmsContentType.alias, cmsContentType.thumbnail, cmsContentType.description,
+ cmsContentType.masterContentType, cmsContentType.nodeId, cmsDocument.updateDate, cmsContentVersion.versionDate
order by {1}
";
@@ -396,11 +393,13 @@ namespace umbraco.cms.businesslogic.web
//PPH make sure that there is only 1 newest node, this is important in regard to schedueled publishing...
SqlHelper.ExecuteNonQuery("update cmsDocument set newest = 0 where nodeId = " + Id);
- SqlHelper.ExecuteNonQuery("insert into cmsDocument (newest, nodeId, published, documentUser, versionId, Text, TemplateId) values (1," +
- Id + ", 0, " + u.Id + ", @versionId, @text,"
- + _template + ")",
- SqlHelper.CreateParameter("@versionId", newVersion),
- SqlHelper.CreateParameter("@text", Text));
+ SqlHelper.ExecuteNonQuery("insert into cmsDocument (newest, nodeId, published, documentUser, versionId, Text, TemplateId) values (1,@id, 0, @userId, @versionId, @text, @template)",
+ SqlHelper.CreateParameter("@id", Id),
+ SqlHelper.CreateParameter("@template", _template > 0 ? (object)_template : (object)DBNull.Value), //pass null in if the template doesn't have a valid id
+ SqlHelper.CreateParameter("@userId", u.Id),
+ SqlHelper.CreateParameter("@versionId", newVersion),
+ SqlHelper.CreateParameter("@text", Text));
+
SqlHelper.ExecuteNonQuery("update cmsDocument set published = 0 where nodeId = " + Id);
SqlHelper.ExecuteNonQuery("update cmsDocument set published = 1, newest = 0 where versionId = @versionId",
SqlHelper.CreateParameter("@versionId", tempVersion));
@@ -648,6 +647,38 @@ namespace umbraco.cms.businesslogic.web
_published = InitPublished;
}
+ protected void PopulateDocumentFromReader(IRecordsReader dr)
+ {
+ bool _hc = false;
+
+ if (dr.GetInt("children") > 0)
+ _hc = true;
+
+ int? masterContentType = null;
+
+ if (!dr.IsNull("masterContentType"))
+ masterContentType = dr.GetInt("masterContentType");
+
+ SetupDocumentForTree(dr.GetGuid("uniqueId")
+ , dr.GetShort("level")
+ , dr.GetInt("parentId")
+ , dr.GetInt("documentUser")
+ , (dr.GetInt("published") == 1)
+ , dr.GetString("path")
+ , dr.GetString("text")
+ , dr.GetDateTime("createDate")
+ , dr.GetDateTime("updateDate")
+ , dr.GetDateTime("versionDate")
+ , dr.GetString("icon")
+ , _hc
+ , dr.GetString("alias")
+ , dr.GetString("thumbnail")
+ , dr.GetString("description")
+ , masterContentType
+ , dr.GetInt("contentTypeId")
+ , dr.GetInt("templateId"));
+ }
+
public override string Text
{
get
@@ -836,8 +867,12 @@ namespace umbraco.cms.businesslogic.web
// Make the new document
Document NewDoc = MakeNew(Text, new DocumentType(ContentType.Id), u, CopyTo);
- // update template
- NewDoc.Template = Template;
+ // update template if a template is set
+ if (this.Template > 0)
+ NewDoc.Template = Template;
+
+ //update the trashed property as it could be copied inside the recycle bin
+ NewDoc.IsTrashed = this.IsTrashed;
// Copy the properties of the current document
var props = getProperties;
@@ -955,12 +990,6 @@ namespace umbraco.cms.businesslogic.web
{
get
{
- //SD: Removed old, non-optimized method!
- //IconI[] tmp = base.Children;
- //Document[] retval = new Document[tmp.Length];
- //for (int i = 0; i < tmp.Length; i++) retval[i] = new Document(tmp[i].Id);
- //return retval;
-
//cache the documents children so that this db call doesn't have to occur again
if (this._children == null)
this._children = Document.GetChildrenForTree(this.Id);
@@ -982,32 +1011,40 @@ namespace umbraco.cms.businesslogic.web
}
///
- /// Deletes the current document (and all children recursive)
+ /// Puts the current document in the trash
///
public override void delete()
{
- // Check for recyle bin
- if (!Path.Contains("," + ((int)RecycleBin.RecycleBinType.Content).ToString() + ","))
+ MoveToTrash();
+ }
+
+ ///
+ /// With either move the document to the trash or permanently remove it from the database.
+ ///
+ /// flag to set whether or not to completely remove it from the database or just send to trash
+ public void delete(bool deletePermanently)
+ {
+ if (!deletePermanently)
{
MoveToTrash();
}
else
{
- DeletePermanently(false);
+ DeletePermanently();
}
}
///
/// Used internally to permanently delete the data from the database
///
- ///
- /// if onlyCurrentDocType is set, this means that we shouldn't delete any children that are not
- /// the current document type and instead just move them to the recycle bin. This is effective if
+ ///
+ /// if onlyThisDocType is set, this means that we shouldn't delete any children that are not
+ /// the document type specified and instead just move them to the recycle bin. This is effective if
/// we're deleting an entire document type but don't want to delete other data that isn't this document type
/// but the ndoe exists as a child of the document type that is being deleted.
///
/// returns true if deletion isn't cancelled
- private bool DeletePermanently(bool onlyCurrentDocType)
+ private bool DeletePermanently()
{
DeleteEventArgs e = new DeleteEventArgs();
@@ -1015,20 +1052,9 @@ namespace umbraco.cms.businesslogic.web
if (!e.Cancel)
{
-
- var c = Children;
- foreach (Document d in c)
+ foreach (Document d in Children.ToList())
{
- if (onlyCurrentDocType && (d.ContentType.Id != this.ContentType.Id))
- {
- //if we're only supposed to be deleting an exact document type, then just move the document
- //to the trash
- d.MoveToTrash();
- }
- else
- {
- d.DeletePermanently(onlyCurrentDocType);
- }
+ d.DeletePermanently();
}
umbraco.BusinessLogic.Actions.Action.RunActionHandlers(this, ActionDelete.Instance);
@@ -1085,16 +1111,45 @@ namespace umbraco.cms.businesslogic.web
/// The type of which documents should be deleted
public static void DeleteFromType(DocumentType dt)
{
- var objs = getContentOfContentType(dt);
- foreach (Content c in objs)
+ //get all document for the document type and order by level (top level first)
+ var docs = Document.GetDocumentsOfDocumentType(dt.Id)
+ .OrderByDescending(x => x.Level);
+
+ foreach (Document doc in docs)
{
- // due to recursive structure document might already been deleted..
- if (IsNode(c.UniqueId))
+ //before we delete this document, we need to make sure we don't end up deleting other documents that
+ //are not of this document type that are children. So we'll move all of it's children to the trash first.
+ foreach (Document c in doc.GetDescendants())
{
- Document d = new Document(c.UniqueId);
- d.DeletePermanently(true);
+ if (c.ContentType.Id != dt.Id)
+ {
+ c.MoveToTrash();
+ }
+ }
+
+ doc.DeletePermanently();
+ }
+ }
+
+ ///
+ /// Returns all decendants of the current document
+ ///
+ ///
+ public override IEnumerable GetDescendants()
+ {
+ var tmp = new List();
+ using (IRecordsReader dr = SqlHelper.ExecuteReader(
+ string.Format(m_SQLOptimizedMany, "umbracoNode.path LIKE '%," + this.Id + ",%'", "umbracoNode.level")))
+ {
+ while (dr.Read())
+ {
+ Document d = new Document(dr.GetInt("id"), true);
+ d.PopulateDocumentFromReader(dr);
+ tmp.Add(d);
}
}
+
+ return tmp.ToArray();
}
///
@@ -1317,6 +1372,25 @@ namespace umbraco.cms.businesslogic.web
return temp;
}
+ public static IEnumerable GetDocumentsOfDocumentType(int docTypeId)
+ {
+ var tmp = new List();
+ using (IRecordsReader dr =
+ SqlHelper.ExecuteReader(
+ string.Format(m_SQLOptimizedMany, "cmsContent.contentType = @contentTypeId", "umbracoNode.sortOrder"),
+ SqlHelper.CreateParameter("@contentTypeId", docTypeId)))
+ {
+ while (dr.Read())
+ {
+ Document d = new Document(dr.GetInt("id"), true);
+ d.PopulateDocumentFromReader(dr);
+ tmp.Add(d);
+ }
+ }
+
+ return tmp.ToArray();
+ }
+
///
/// Performance tuned method for use in the tree
///
@@ -1324,49 +1398,21 @@ namespace umbraco.cms.businesslogic.web
///
public static Document[] GetChildrenForTree(int NodeId)
{
- ArrayList tmp = new ArrayList();
+ var tmp = new List();
using (IRecordsReader dr =
SqlHelper.ExecuteReader(
- string.Format(m_SQLOptimizedChildren, "umbracoNode.parentID = @parentId", "umbracoNode.sortOrder"),
+ string.Format(m_SQLOptimizedMany, "umbracoNode.parentID = @parentId", "umbracoNode.sortOrder"),
SqlHelper.CreateParameter("@parentId", NodeId)))
{
while (dr.Read())
{
Document d = new Document(dr.GetInt("id"), true);
- bool _hc = false;
- if (dr.GetInt("children") > 0)
- _hc = true;
- int? masterContentType = null;
- if (!dr.IsNull("masterContentType"))
- masterContentType = dr.GetInt("masterContentType");
- d.SetupDocumentForTree(dr.GetGuid("uniqueId")
- , dr.GetShort("level")
- , dr.GetInt("parentId")
- , dr.GetInt("documentUser")
- , (dr.GetInt("published") == 1)
- , dr.GetString("path")
- , dr.GetString("text")
- , dr.GetDateTime("createDate")
- , dr.GetDateTime("updateDate")
- , dr.GetDateTime("versionDate")
- , dr.GetString("icon")
- , _hc
- , dr.GetString("alias")
- , dr.GetString("thumbnail")
- , dr.GetString("description")
- , masterContentType
- , dr.GetInt("contentTypeId")
- , dr.GetInt("templateId"));
+ d.PopulateDocumentFromReader(dr);
tmp.Add(d);
}
}
- Document[] retval = new Document[tmp.Count];
-
- for (int i = 0; i < tmp.Count; i++)
- retval[i] = (Document)tmp[i];
-
- return retval;
+ return tmp.ToArray();
}
private void SetupDocumentForTree(Guid uniqueId, int level, int parentId, int user, bool publish, string path,
diff --git a/umbraco/presentation/umbraco/webservices/legacyAjaxCalls.asmx.cs b/umbraco/presentation/umbraco/webservices/legacyAjaxCalls.asmx.cs
index 785090f2e4..bce62c9926 100644
--- a/umbraco/presentation/umbraco/webservices/legacyAjaxCalls.asmx.cs
+++ b/umbraco/presentation/umbraco/webservices/legacyAjaxCalls.asmx.cs
@@ -17,6 +17,8 @@ using System.Diagnostics;
using System.Net;
using System.Web.UI;
using umbraco.IO;
+using umbraco.cms.businesslogic.web;
+using umbraco.cms.businesslogic.media;
namespace umbraco.presentation.webservices
@@ -32,7 +34,7 @@ namespace umbraco.presentation.webservices
{
///
- /// Overloaded method to accept a string value for the node id. Used for tree's such as python
+ /// 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
///
///
@@ -52,6 +54,37 @@ namespace umbraco.presentation.webservices
else
presentation.create.dialogHandler_temp.Delete(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)
+ {
+ Authorize();
+
+ int intNodeID;
+ if (int.TryParse(nodeId, out intNodeID))
+ {
+ switch (nodeType)
+ {
+ case "media":
+ new Media(intNodeID).delete();
+ break;
+ case "content":
+ new Document(intNodeID).delete(true);
+ break;
+ }
+ new Document(intNodeID).delete(true);
+ }
+ else
+ {
+ throw new ArgumentException("The nodeId argument could not be parsed to an integer");
+ }
+ }
[WebMethod]
[ScriptMethod]
diff --git a/umbraco/presentation/umbraco_client/Application/UmbracoApplicationActions.js b/umbraco/presentation/umbraco_client/Application/UmbracoApplicationActions.js
index f7d1ae9044..b8293c2c52 100644
--- a/umbraco/presentation/umbraco_client/Application/UmbracoApplicationActions.js
+++ b/umbraco/presentation/umbraco_client/Application/UmbracoApplicationActions.js
@@ -317,21 +317,41 @@ Umbraco.Application.Actions = function() {
actionDelete: function() {
///
+ var actionNode = UmbClientMgr.mainTree().getActionNode();
if (UmbClientMgr.mainTree().getActionNode().nodeType == "content" && UmbClientMgr.mainTree().getActionNode().nodeId == '-1')
return;
this._debug("actionDelete");
+
if (confirm(uiKeys['defaultdialogs_confirmdelete'] + ' "' + UmbClientMgr.mainTree().getActionNode().nodeName + '"?\n\n')) {
//raise nodeDeleting event
jQuery(window.top).trigger("nodeDeleting", []);
var _this = this;
- umbraco.presentation.webservices.legacyAjaxCalls.Delete(UmbClientMgr.mainTree().getActionNode().nodeId, "", UmbClientMgr.mainTree().getActionNode().nodeType, function() {
- _this._debug("actionDelete: Raising event");
- //raise nodeDeleted event
- jQuery(window.top).trigger("nodeDeleted", []);
- });
+
+ //check if it's in the recycle bin
+ if (actionNode.jsNode.closest("li[id='-20']").length == 1 || actionNode.jsNode.closest("li[id='-21']").length == 1) {
+ umbraco.presentation.webservices.legacyAjaxCalls.DeleteContentPermanently(
+ UmbClientMgr.mainTree().getActionNode().nodeId,
+ UmbClientMgr.mainTree().getActionNode().nodeType,
+ function() {
+ _this._debug("actionDelete: Raising event");
+ //raise nodeDeleted event
+ jQuery(window.top).trigger("nodeDeleted", []);
+ });
+ }
+ else {
+ umbraco.presentation.webservices.legacyAjaxCalls.Delete(
+ UmbClientMgr.mainTree().getActionNode().nodeId, "",
+ UmbClientMgr.mainTree().getActionNode().nodeType,
+ function() {
+ _this._debug("actionDelete: Raising event");
+ //raise nodeDeleted event
+ jQuery(window.top).trigger("nodeDeleted", []);
+ });
+ }
}
+
},
actionDisable: function() {