More unit tests.

Fixes: 16981 - no longer data loss when deleting document types with nested document type children
Fixes: 27096, 27098, 27106, 27107, 27108, 27110, 27111

[TFS Changeset #66192]
This commit is contained in:
Shandem
2010-05-19 15:55:00 +00:00
parent 267c2a1d65
commit 92ff20564b
7 changed files with 524 additions and 231 deletions

View File

@@ -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
/// <summary>
/// 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.
/// </summary>
[TestMethod()]
public void Document_DeleteHeirarchyPermanentlyTest()
{
var docList = new List<Document>();
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));
}
}
/// <summary>
///A test for PublishWithResult
///</summary>
[TestMethod()]
public void Document_PublishWithResultTest()
{
var val = m_NewRootDoc.PublishWithResult(m_User);
}
/// <summary>
/// Creates a doc type, assigns a domain to it and removes it
/// </summary>
@@ -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<int>(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<Document>();
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<int>(x =>
{
Assert.AreEqual((total + totalTrashedItems) - (++totalDeleted), x);
});
var bin = new RecycleBin(RecycleBin.RecycleBinType.Content);
bin.CallTheGarbageMan(deleteCallback);
var totalTrashedItems = bin.GetDescendants().Cast<object>().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.");
//}
///// <summary>
/////A test for PublishWithResult
/////</summary>
//[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.");
//}
///// <summary>
/////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];
}
/// <summary>
/// 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.
/// </summary>
/// <param name="d"></param>
/// <returns></returns>
private IEnumerable<IconI> 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());

View File

@@ -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));
}
/// <summary>
/// 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.
/// </summary>
[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);
}
/// <summary>
///A test for creating a new document type
///</summary>
[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));
}
/// <summary>
@@ -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<int>(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);
}
/// <summary>
@@ -688,6 +744,23 @@ namespace umbraco.Test
private User m_User = new User(0);
/// <summary>
/// before each test starts, this object is created so it can be used for testing.
/// </summary>
private DocumentType m_NewDocType;
/// <summary>
/// Create a brand new document type
/// </summary>
/// <returns></returns>
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()
//{
//}
//
/// <summary>
/// Create a new document type for use in tests
/// </summary>
[TestInitialize()]
public void MyTestInitialize()
{
m_NewDocType = CreateNewDocType();
}
/// <summary>
/// Remove the created document type
/// </summary>
[TestCleanup()]
public void MyTestCleanup()
{
DeleteDocType(m_NewDocType);
}
#endregion
}

View File

@@ -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<string> m_DefaultIconClasses = new List<string>();
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();
}
/// <summary>
/// Moves the CMSNode from the current position in the hierarchy to the target
/// </summary>
/// <param name="NewParentId">Target CMSNode id</param>
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<int>(
"select coalesce(max(sortOrder),0) from umbracoNode where parentid = @parentId",
SqlHelper.CreateParameter("@parentId", newParentId));
int maxSortOrder = SqlHelper.ExecuteScalar<int>("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);
}
}
/// <summary>
/// Moves the CMSNode from the current position in the hierarchy to the target
/// </summary>
/// <param name="NewParentId">Target CMSNode id</param>
public void Move(int newParentId)
{
CMSNode parent = new CMSNode(newParentId);
Move(parent);
}
/// <summary>
/// Deletes this instance.
/// </summary>
@@ -500,7 +511,6 @@ order by level,sortOrder";
}
}
/// <summary>
/// Does the current CMSNode have any child nodes.
/// </summary>
@@ -524,7 +534,31 @@ order by level,sortOrder";
_hasChildrenInitialized = true;
_hasChildren = value;
}
}
}
/// <summary>
/// Returns all descendant nodes from this node.
/// </summary>
/// <returns></returns>
/// <remarks>
/// 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.
/// </remarks>
public virtual IEnumerable GetDescendants()
{
var descendants = new List<CMSNode>();
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; }
}
/// <summary>
/// Get the parent id of the node
/// </summary>
public int ParentId
{
get { return _parentid; }
}
/// <summary>
/// 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";
/// </summary>
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())
{

View File

@@ -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:

View File

@@ -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
}
/// <summary>
/// Deletes the current document (and all children recursive)
/// Puts the current document in the trash
/// </summary>
public override void delete()
{
// Check for recyle bin
if (!Path.Contains("," + ((int)RecycleBin.RecycleBinType.Content).ToString() + ","))
MoveToTrash();
}
/// <summary>
/// With either move the document to the trash or permanently remove it from the database.
/// </summary>
/// <param name="deletePermanently">flag to set whether or not to completely remove it from the database or just send to trash</param>
public void delete(bool deletePermanently)
{
if (!deletePermanently)
{
MoveToTrash();
}
else
{
DeletePermanently(false);
DeletePermanently();
}
}
/// <summary>
/// Used internally to permanently delete the data from the database
/// </summary>
/// <param name="onlyCurrentDocType">
/// 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
/// <param name="onlyThisDocType">
/// 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.
/// </param>
/// <returns>returns true if deletion isn't cancelled</returns>
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
/// <param name="dt">The type of which documents should be deleted</param>
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();
}
}
/// <summary>
/// Returns all decendants of the current document
/// </summary>
/// <returns></returns>
public override IEnumerable GetDescendants()
{
var tmp = new List<Document>();
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();
}
/// <summary>
@@ -1317,6 +1372,25 @@ namespace umbraco.cms.businesslogic.web
return temp;
}
public static IEnumerable<Document> GetDocumentsOfDocumentType(int docTypeId)
{
var tmp = new List<Document>();
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();
}
/// <summary>
/// Performance tuned method for use in the tree
/// </summary>
@@ -1324,49 +1398,21 @@ namespace umbraco.cms.businesslogic.web
/// <returns></returns>
public static Document[] GetChildrenForTree(int NodeId)
{
ArrayList tmp = new ArrayList();
var tmp = new List<Document>();
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,

View File

@@ -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
{
/// <summary>
/// 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
/// </summary>
/// <param name="nodeId"></param>
@@ -52,6 +54,37 @@ namespace umbraco.presentation.webservices
else
presentation.create.dialogHandler_temp.Delete(nodeType, 0, nodeId);
}
/// <summary>
/// Permanently deletes a document/media object.
/// Used to remove an item from the recycle bin.
/// </summary>
/// <param name="nodeId"></param>
[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]

View File

@@ -317,21 +317,41 @@ Umbraco.Application.Actions = function() {
actionDelete: function() {
/// <summary></summary>
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() {