diff --git a/src/Umbraco.Core/EventArgs.cs b/src/Umbraco.Core/EventArgs.cs index c6582a712d..af2ebd208d 100644 --- a/src/Umbraco.Core/EventArgs.cs +++ b/src/Umbraco.Core/EventArgs.cs @@ -1,12 +1,10 @@ -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Services; - -namespace Umbraco.Core +namespace Umbraco.Core { //Publishing Events - public class PublishingEventArgs : System.ComponentModel.CancelEventArgs { } + public class PublishingEventArgs : System.ComponentModel.CancelEventArgs { + public bool IsAllRepublished { get; set; } + } public class SendToPublishEventArgs : System.ComponentModel.CancelEventArgs { } //Moving object Events diff --git a/src/Umbraco.Core/Models/ContentExtensions.cs b/src/Umbraco.Core/Models/ContentExtensions.cs index 71e5df37ea..9f83eaca5c 100644 --- a/src/Umbraco.Core/Models/ContentExtensions.cs +++ b/src/Umbraco.Core/Models/ContentExtensions.cs @@ -1,9 +1,8 @@ using System; using System.Linq; -using Umbraco.Core.Configuration; +using System.Xml.Linq; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Models @@ -69,5 +68,63 @@ namespace Umbraco.Core.Models return repository.GetProfileById(content.WriterId); } } + + + /// + /// Creates the xml representation for the object + /// + /// to generate xml for + /// Xml representation of the passed in + public static XElement ToXml(this IContent content) + { + //nodeName should match Casing.SafeAliasWithForcingCheck(content.ContentType.Alias); + //var nodeName = content.ContentType.Alias.ToUmbracoAlias(StringAliasCaseType.CamelCase, true); + var nodeName = content.ContentType.Alias; + var niceUrl = content.Name.Replace(" ", "-").ToLower(); + + var xml = new XElement(nodeName, + new XAttribute("id", content.Id), + new XAttribute("parentID", content.Level > 1 ? content.ParentId : -1), + new XAttribute("level", content.Level), + new XAttribute("writerID", content.WriterId), + new XAttribute("creatorID", content.CreatorId), + new XAttribute("nodeType", content.ContentType.Id), + new XAttribute("template", content.Template == null ? "0" : content.Template.Id.ToString()), + new XAttribute("sortOrder", content.SortOrder), + new XAttribute("createDate", content.CreateDate.ToString("s")), + new XAttribute("updateDate", content.UpdateDate.ToString("s")), + new XAttribute("nodeName", content.Name), + new XAttribute("urlName", niceUrl),//Format Url ? + new XAttribute("writerName", content.GetWriterProfile().Name), + new XAttribute("creatorName", content.GetCreatorProfile().Name), + new XAttribute("path", content.Path), + new XAttribute("isDoc", "")); + + foreach (var property in content.Properties) + { + if (property == null) continue; + + xml.Add(property.ToXml()); + + //Check for umbracoUrlName convention + if (property.Alias == "umbracoUrlName" && property.Value.ToString().Trim() != string.Empty) + xml.SetAttributeValue("urlName", property.Value); + } + + return xml; + } + + /// + /// Creates the xml representation for the object + /// + /// to generate xml for + /// Boolean indicating whether the xml should be generated for preview + /// Xml representation of the passed in + public static XElement ToXml(this IContent content, bool isPreview) + { + //TODO Do a proper implementation of this + //If current IContent is published we should get latest unpublished version + return content.ToXml(); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PropertyExtensions.cs b/src/Umbraco.Core/Models/PropertyExtensions.cs index 460174e8c0..0f3a40fe07 100644 --- a/src/Umbraco.Core/Models/PropertyExtensions.cs +++ b/src/Umbraco.Core/Models/PropertyExtensions.cs @@ -18,14 +18,8 @@ namespace Umbraco.Core.Models var xd = new XmlDocument(); XmlNode xmlNode = xd.CreateNode(XmlNodeType.Element, nodeName, ""); - XmlNode child = property.PropertyType.DataTypeDatabaseType == DataTypeDatabaseType.Ntext - ? xd.CreateCDataSection(property.Value.ToString()) as XmlNode - : xd.CreateTextNode(property.Value.ToString()); - - xmlNode.AppendChild(child); - //This seems to fail during testing - //xmlNode.AppendChild(property.PropertyType.DataType(property.Id).Data.ToXMl(xd)); + xmlNode.AppendChild(property.PropertyType.DataType(property.Id).Data.ToXMl(xd)); var element = xmlNode.GetXElement(); return element; diff --git a/src/Umbraco.Core/Models/PropertyTypeExtensions.cs b/src/Umbraco.Core/Models/PropertyTypeExtensions.cs index 97c803deb7..9347760b41 100644 --- a/src/Umbraco.Core/Models/PropertyTypeExtensions.cs +++ b/src/Umbraco.Core/Models/PropertyTypeExtensions.cs @@ -19,7 +19,7 @@ namespace Umbraco.Core.Models internal static IDataType DataType(this PropertyType propertyType, int propertyId) { Mandate.ParameterNotNull(propertyType, "propertyType"); - var dataType = DataTypesResolver.Current.GetById(propertyType.DataTypeControlId); + var dataType = ApplicationContext.Current.Services.DataTypeService.GetDataTypeById(propertyType.DataTypeControlId); dataType.DataTypeDefinitionId = propertyType.DataTypeId; dataType.Data.PropertyId = propertyId; return dataType; diff --git a/src/Umbraco.Core/Models/Relation.cs b/src/Umbraco.Core/Models/Relation.cs index a501a2e341..8193d529c9 100644 --- a/src/Umbraco.Core/Models/Relation.cs +++ b/src/Umbraco.Core/Models/Relation.cs @@ -18,10 +18,11 @@ namespace Umbraco.Core.Models private RelationType _relationType; private string _comment; - public Relation(int parentId, int childId) + public Relation(int parentId, int childId, RelationType relationType) { _parentId = parentId; _childId = childId; + _relationType = relationType; } private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); diff --git a/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs index 83ba2cf3e3..fe4574b4b0 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs @@ -30,47 +30,54 @@ namespace Umbraco.Core.Persistence.Factories public IContent BuildEntity(DocumentDto dto) { return new Content(dto.ContentVersionDto.ContentDto.NodeDto.ParentId, _contentType) - { - Id = _id, - Key = - dto.ContentVersionDto.ContentDto.NodeDto.UniqueId.HasValue - ? dto.ContentVersionDto.ContentDto.NodeDto.UniqueId.Value - : _id.ToGuid(), - Name = dto.Text, - NodeName = dto.ContentVersionDto.ContentDto.NodeDto.Text, - Language = dto.ContentVersionDto.Language, - Path = dto.ContentVersionDto.ContentDto.NodeDto.Path, - CreatorId = dto.ContentVersionDto.ContentDto.NodeDto.UserId.Value, - WriterId = dto.WriterUserId, - Level = dto.ContentVersionDto.ContentDto.NodeDto.Level, - ParentId = dto.ContentVersionDto.ContentDto.NodeDto.ParentId, - SortOrder = dto.ContentVersionDto.ContentDto.NodeDto.SortOrder, - Trashed = dto.ContentVersionDto.ContentDto.NodeDto.Trashed, - Published = dto.Published, - CreateDate = dto.ContentVersionDto.ContentDto.NodeDto.CreateDate, - UpdateDate = dto.ContentVersionDto.VersionDate, - ExpireDate = dto.ExpiresDate, - ReleaseDate = dto.ReleaseDate, - Version = dto.ContentVersionDto.VersionId - }; + { + Id = _id, + Key = + dto.ContentVersionDto.ContentDto.NodeDto.UniqueId.HasValue + ? dto.ContentVersionDto.ContentDto.NodeDto.UniqueId.Value + : _id.ToGuid(), + Name = dto.Text, + NodeName = dto.ContentVersionDto.ContentDto.NodeDto.Text, + Language = dto.ContentVersionDto.Language, + Path = dto.ContentVersionDto.ContentDto.NodeDto.Path, + CreatorId = dto.ContentVersionDto.ContentDto.NodeDto.UserId.Value, + WriterId = dto.WriterUserId, + Level = dto.ContentVersionDto.ContentDto.NodeDto.Level, + ParentId = dto.ContentVersionDto.ContentDto.NodeDto.ParentId, + SortOrder = dto.ContentVersionDto.ContentDto.NodeDto.SortOrder, + Trashed = dto.ContentVersionDto.ContentDto.NodeDto.Trashed, + Published = dto.Published, + CreateDate = dto.ContentVersionDto.ContentDto.NodeDto.CreateDate, + UpdateDate = dto.ContentVersionDto.VersionDate, + ExpireDate = dto.ExpiresDate.HasValue ? dto.ExpiresDate.Value : (DateTime?)null, + ReleaseDate = dto.ReleaseDate.HasValue ? dto.ReleaseDate.Value : (DateTime?)null, + Version = dto.ContentVersionDto.VersionId + }; } public DocumentDto BuildDto(IContent entity) { //NOTE Currently doesn't add Alias and templateId (legacy stuff that eventually will go away) var documentDto = new DocumentDto - { - ExpiresDate = entity.ExpireDate, - Newest = true, - NodeId = entity.Id, - Published = entity.Published, - ReleaseDate = entity.ReleaseDate, - Text = entity.Name, - UpdateDate = entity.UpdateDate, - WriterUserId = entity.WriterId, - VersionId = entity.Version, - ContentVersionDto = BuildContentVersionDto(entity) - }; + { + Newest = true, + NodeId = entity.Id, + Published = entity.Published, + Text = entity.Name, + UpdateDate = entity.UpdateDate, + WriterUserId = entity.WriterId, + VersionId = entity.Version, + ExpiresDate = null, + ReleaseDate = null, + ContentVersionDto = BuildContentVersionDto(entity) + }; + + if (entity.ExpireDate.HasValue) + documentDto.ExpiresDate = entity.ExpireDate.Value; + + if (entity.ReleaseDate.HasValue) + documentDto.ReleaseDate = entity.ReleaseDate.Value; + return documentDto; } @@ -88,24 +95,24 @@ namespace Umbraco.Core.Persistence.Factories var lang = content == null ? string.Empty : content.Language; var contentVersionDto = new ContentVersionDto - { - NodeId = entity.Id, - VersionDate = entity.UpdateDate, - VersionId = entity.Version, - Language = lang, - ContentDto = BuildContentDto(entity) - }; + { + NodeId = entity.Id, + VersionDate = entity.UpdateDate, + VersionId = entity.Version, + Language = lang, + ContentDto = BuildContentDto(entity) + }; return contentVersionDto; } private ContentDto BuildContentDto(IContent entity) { var contentDto = new ContentDto - { - NodeId = entity.Id, - ContentTypeId = entity.ContentTypeId, - NodeDto = BuildNodeDto(entity) - }; + { + NodeId = entity.Id, + ContentTypeId = entity.ContentTypeId, + NodeDto = BuildNodeDto(entity) + }; if (_primaryKey > 0) { @@ -119,22 +126,22 @@ namespace Umbraco.Core.Persistence.Factories { //TODO: Change this once the Language property is public on IContent var content = entity as Content; - var nodeName = content == null ? entity.Name : content.NodeName; + var nodeName = content != null && string.IsNullOrEmpty(content.NodeName) == false ? content.NodeName : entity.Name; var nodeDto = new NodeDto - { - CreateDate = entity.CreateDate, - NodeId = entity.Id, - Level = short.Parse(entity.Level.ToString(CultureInfo.InvariantCulture)), - NodeObjectType = _nodeObjectTypeId, - ParentId = entity.ParentId, - Path = entity.Path, - SortOrder = entity.SortOrder, - Text = nodeName, - Trashed = entity.Trashed, - UniqueId = entity.Key, - UserId = entity.CreatorId - }; + { + CreateDate = entity.CreateDate, + NodeId = entity.Id, + Level = short.Parse(entity.Level.ToString(CultureInfo.InvariantCulture)), + NodeObjectType = _nodeObjectTypeId, + ParentId = entity.ParentId, + Path = entity.Path, + SortOrder = entity.SortOrder, + Text = nodeName, + Trashed = entity.Trashed, + UniqueId = entity.Key, + UserId = entity.CreatorId + }; return nodeDto; } diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs index ccd2071b76..93d137d1b1 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs @@ -45,18 +45,15 @@ namespace Umbraco.Core.Persistence.Factories public IEnumerable BuildDto(IEnumerable properties) { var propertyDataDtos = new List(); - /*var serviceStackSerializer = new ServiceStackXmlSerializer(); - var service = new SerializationService(serviceStackSerializer);*/ foreach (var property in properties) { var dto = new PropertyDataDto { NodeId = _id, PropertyTypeId = property.PropertyTypeId, VersionId = _version }; - //TODO Add complex (PropertyEditor) ValueModels to the Ntext/Nvarchar column as a serialized 'Object' (DataTypeDatabaseType.Object) - /*if (property.Value is IEditorModel) - { - var result = service.ToStream(property.Value); - dto.Text = result.ResultStream.ToJsonString(); - }*/ + + //Check if property has an Id and set it, so that it can be updated if it already exists + if (property.HasIdentity) + dto.Id = property.Id; + if (property.DataTypeDatabaseType == DataTypeDatabaseType.Integer && property.Value != null) { dto.Integer = int.Parse(property.Value.ToString()); diff --git a/src/Umbraco.Core/Persistence/Factories/RelationFactory.cs b/src/Umbraco.Core/Persistence/Factories/RelationFactory.cs index 9c5d23682d..030cad9254 100644 --- a/src/Umbraco.Core/Persistence/Factories/RelationFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/RelationFactory.cs @@ -16,13 +16,12 @@ namespace Umbraco.Core.Persistence.Factories public Relation BuildEntity(RelationDto dto) { - var entity = new Relation(dto.ParentId, dto.ChildId) + var entity = new Relation(dto.ParentId, dto.ChildId, _relationType) { Comment = dto.Comment, CreateDate = dto.Datetime, Id = dto.Id, - UpdateDate = dto.Datetime, - RelationType = _relationType + UpdateDate = dto.Datetime }; return entity; } diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index d0c087d9a2..9b0ebd8c62 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Rdbms; @@ -101,7 +103,7 @@ namespace Umbraco.Core.Persistence.Repositories yield return Get(dto.NodeId); } } - + #endregion #region Overrides of PetaPocoRepositoryBase @@ -127,18 +129,19 @@ namespace Umbraco.Core.Persistence.Repositories { var list = new List { - string.Format("DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id"), - string.Format("DELETE FROM umbracoUser2NodePermission WHERE nodeId = @Id"), - string.Format("DELETE FROM umbracoRelation WHERE parentId = @Id"), - string.Format("DELETE FROM umbracoRelation WHERE childId = @Id"), - string.Format("DELETE FROM cmsTagRelationship WHERE nodeId = @Id"), - string.Format("DELETE FROM cmsDocument WHERE NodeId = @Id"), - string.Format("DELETE FROM cmsPropertyData WHERE contentNodeId = @Id"), - string.Format("DELETE FROM cmsPreviewXml WHERE nodeId = @Id"), - string.Format("DELETE FROM cmsContentVersion WHERE ContentId = @Id"), - string.Format("DELETE FROM cmsContentXml WHERE nodeID = @Id"), - string.Format("DELETE FROM cmsContent WHERE NodeId = @Id"), - string.Format("DELETE FROM umbracoNode WHERE id = @Id") + "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", + "DELETE FROM umbracoUser2NodePermission WHERE nodeId = @Id", + "DELETE FROM umbracoRelation WHERE parentId = @Id", + "DELETE FROM umbracoRelation WHERE childId = @Id", + "DELETE FROM cmsTagRelationship WHERE nodeId = @Id", + "DELETE FROM umbracoDomains WHERE domainRootStructureID = @Id", + "DELETE FROM cmsDocument WHERE NodeId = @Id", + "DELETE FROM cmsPropertyData WHERE contentNodeId = @Id", + "DELETE FROM cmsPreviewXml WHERE nodeId = @Id", + "DELETE FROM cmsContentVersion WHERE ContentId = @Id", + "DELETE FROM cmsContentXml WHERE nodeID = @Id", + "DELETE FROM cmsContent WHERE NodeId = @Id", + "DELETE FROM umbracoNode WHERE id = @Id" }; return list; } @@ -223,8 +226,24 @@ namespace Umbraco.Core.Persistence.Repositories protected override void PersistUpdatedItem(IContent entity) { - //Updates Modified date and Version Guid - ((Content)entity).UpdatingEntity(); + //A new version should only be created if published state has changed + bool shouldCreateNewVersion = ((ICanBeDirty)entity).IsPropertyDirty("Published") || ((ICanBeDirty)entity).IsPropertyDirty("Language"); + if (shouldCreateNewVersion) + { + //Updates Modified date and Version Guid + ((Content)entity).UpdatingEntity(); + } + else + { + entity.UpdateDate = DateTime.UtcNow; + } + + //Look up parent to get and set the correct Path if ParentId has changed + if (((ICanBeDirty)entity).IsPropertyDirty("ParentId")) + { + var parent = Database.First("WHERE id = @ParentId", new { ParentId = entity.ParentId }); + entity.Path = string.Concat(parent.Path, ",", entity.Id); + } var factory = new ContentFactory(NodeObjectTypeId, entity.Id); //Look up Content entry to get Primary for updating the DTO @@ -235,7 +254,7 @@ namespace Umbraco.Core.Persistence.Repositories //Updates the (base) node data - umbracoNode var nodeDto = dto.ContentVersionDto.ContentDto.NodeDto; var o = Database.Update(nodeDto); - + //Only update this DTO if the contentType has actually changed if (contentDto.ContentTypeId != entity.ContentTypeId) { @@ -244,36 +263,97 @@ namespace Umbraco.Core.Persistence.Repositories Database.Update(newContentDto); } - //Look up (newest) entries by id in cmsDocument table to set newest = false - var documentDtos = Database.Fetch("WHERE nodeId = @Id AND newest = @IsNewest", new { Id = entity.Id, IsNewest = true }); - foreach (var documentDto in documentDtos) + //If Published state has changed then previous versions should have their publish state reset + if (shouldCreateNewVersion && entity.Published) { - var docDto = documentDto; - docDto.Newest = false; - Database.Update(docDto); + var publishedDocs = Database.Fetch("WHERE nodeId = @Id AND published = @IsPublished", new { Id = entity.Id, IsPublished = true }); + foreach (var doc in publishedDocs) + { + var docDto = doc; + docDto.Published = false; + Database.Update(docDto); + } } - //Create a new version - cmsContentVersion - //Assumes a new Version guid and Version date (modified date) has been set var contentVersionDto = dto.ContentVersionDto; - Database.Insert(contentVersionDto); + if (shouldCreateNewVersion) + { + //Look up (newest) entries by id in cmsDocument table to set newest = false + //NOTE: This is only relevant when a new version is created, which is why its done inside this if-statement. + var documentDtos = Database.Fetch("WHERE nodeId = @Id AND newest = @IsNewest", new { Id = entity.Id, IsNewest = true }); + foreach (var documentDto in documentDtos) + { + var docDto = documentDto; + docDto.Newest = false; + Database.Update(docDto); + } - //Create the Document specific data for this version - cmsDocument - //Assumes a new Version guid has been generated - Database.Insert(dto); + //Create a new version - cmsContentVersion + //Assumes a new Version guid and Version date (modified date) has been set + Database.Insert(contentVersionDto); + //Create the Document specific data for this version - cmsDocument + //Assumes a new Version guid has been generated + Database.Insert(dto); + } + else + { + //In order to update the ContentVersion we need to retreive its primary key id + var contentVerDto = Database.SingleOrDefault("WHERE VersionId = @Version", new { Version = entity.Version }); + contentVersionDto.Id = contentVerDto.Id; + + Database.Update(contentVersionDto); + Database.Update(dto); + } //Create the PropertyData for this version - cmsPropertyData - var propertyFactory = new PropertyFactory(entity.ContentType, entity.Version, entity.Id); + var propertyFactory = new PropertyFactory(((Content)entity).ContentType, entity.Version, entity.Id); var propertyDataDtos = propertyFactory.BuildDto(entity.Properties); //Add Properties foreach (var propertyDataDto in propertyDataDtos) { - Database.Insert(propertyDataDto); + if (shouldCreateNewVersion == false && propertyDataDto.Id > 0) + { + Database.Update(propertyDataDto); + } + else + { + Database.Insert(propertyDataDto); + } } ((ICanBeDirty)entity).ResetDirtyProperties(); } - + + protected override void PersistDeletedItem(IContent entity) + { + var fs = FileSystemProviderManager.Current.GetFileSystemProvider(); + var uploadFieldId = new Guid("5032a6e6-69e3-491d-bb28-cd31cd11086c"); + //Loop through properties to check if the content contains media that should be deleted + foreach (var property in entity.Properties) + { + if (property.PropertyType.DataTypeControlId == uploadFieldId && + string.IsNullOrEmpty(property.Value.ToString()) == false + && fs.FileExists(IOHelper.MapPath(property.Value.ToString()))) + { + var relativeFilePath = fs.GetRelativePath(property.Value.ToString()); + var parentDirectory = System.IO.Path.GetDirectoryName(relativeFilePath); + + // don't want to delete the media folder if not using directories. + if (UmbracoSettings.UploadAllowDirectories && parentDirectory != fs.GetRelativePath("/")) + { + //issue U4-771: if there is a parent directory the recursive parameter should be true + fs.DeleteDirectory(parentDirectory, String.IsNullOrEmpty(parentDirectory) == false); + } + else + { + fs.DeleteFile(relativeFilePath, true); + } + } + } + + base.PersistDeletedItem(entity); + } + #endregion #region Implementation of IContentRepository @@ -334,7 +414,7 @@ namespace Umbraco.Core.Persistence.Repositories var documentDto = Database.FirstOrDefault("WHERE nodeId = @Id AND versionId = @VersionId AND newest = @Newest", new { Id = id, VersionId = versionId, Newest = false }); Mandate.That(documentDto != null); - using(var transaction = Database.GetTransaction()) + using (var transaction = Database.GetTransaction()) { DeleteVersion(id, versionId); @@ -344,7 +424,7 @@ namespace Umbraco.Core.Persistence.Repositories public void Delete(int id, DateTime versionDate) { - var list = Database.Fetch("WHERE nodeId = @Id AND VersionDate < @VersionDate", new {Id = id, VersionDate = versionDate}); + var list = Database.Fetch("WHERE nodeId = @Id AND VersionDate < @VersionDate", new { Id = id, VersionDate = versionDate }); Mandate.That(list.Any()); using (var transaction = Database.GetTransaction()) @@ -357,7 +437,7 @@ namespace Umbraco.Core.Persistence.Repositories transaction.Complete(); } } - + /// /// Private method to execute the delete statements for removing a single version for a Content item. /// diff --git a/src/Umbraco.Core/Publishing/BasePublishingStrategy.cs b/src/Umbraco.Core/Publishing/BasePublishingStrategy.cs index 2673426ade..36656e5e27 100644 --- a/src/Umbraco.Core/Publishing/BasePublishingStrategy.cs +++ b/src/Umbraco.Core/Publishing/BasePublishingStrategy.cs @@ -99,5 +99,34 @@ namespace Umbraco.Core.Publishing if (UnPublished != null) UnPublished(content, e); } + + /// + /// Call to fire event that updating the published content has finalized. + /// + /// + /// This seperation of the OnPublished event is done to ensure that the Content + /// has been properly updated (committed unit of work) and xml saved in the db. + /// + /// thats being published + public abstract void PublishingFinalized(IContent content); + + /// + /// Call to fire event that updating the published content has finalized. + /// + /// An enumerable list of thats being published + /// Boolean indicating whether its all content that is republished + public abstract void PublishingFinalized(IEnumerable content, bool isAllRepublished); + + /// + /// Call to fire event that updating the unpublished content has finalized. + /// + /// thats being unpublished + public abstract void UnPublishingFinalized(IContent content); + + /// + /// Call to fire event that updating the unpublished content has finalized. + /// + /// An enumerable list of thats being unpublished + public abstract void UnPublishingFinalized(IEnumerable content); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Publishing/IPublishingStrategy.cs b/src/Umbraco.Core/Publishing/IPublishingStrategy.cs index c2861eac8b..0408409488 100644 --- a/src/Umbraco.Core/Publishing/IPublishingStrategy.cs +++ b/src/Umbraco.Core/Publishing/IPublishingStrategy.cs @@ -39,5 +39,34 @@ namespace Umbraco.Core.Publishing /// Id of the User issueing the unpublish operation /// True if the unpublish operation was successfull and not cancelled, otherwise false bool UnPublish(IEnumerable content, int userId); + + /// + /// Call to fire event that updating the published content has finalized. + /// + /// + /// This seperation of the OnPublished event is done to ensure that the Content + /// has been properly updated (committed unit of work) and xml saved in the db. + /// + /// thats being published + void PublishingFinalized(IContent content); + + /// + /// Call to fire event that updating the published content has finalized. + /// + /// An enumerable list of thats being published + /// Boolean indicating whether its all content that is republished + void PublishingFinalized(IEnumerable content, bool isAllRepublished); + + /// + /// Call to fire event that updating the unpublished content has finalized. + /// + /// thats being unpublished + void UnPublishingFinalized(IContent content); + + /// + /// Call to fire event that updating the unpublished content has finalized. + /// + /// An enumerable list of thats being unpublished + void UnPublishingFinalized(IEnumerable content); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Publishing/PublishingStrategy.cs b/src/Umbraco.Core/Publishing/PublishingStrategy.cs index ffd5077af4..618c731302 100644 --- a/src/Umbraco.Core/Publishing/PublishingStrategy.cs +++ b/src/Umbraco.Core/Publishing/PublishingStrategy.cs @@ -30,7 +30,7 @@ namespace Umbraco.Core.Publishing if (!e.Cancel) { //Check if the Content is Expired to verify that it can in fact be published - if(content.Status == ContentStatus.Expired) + if (content.Status == ContentStatus.Expired) { LogHelper.Info( string.Format("Content '{0}' with Id '{1}' has expired and could not be published.", @@ -62,11 +62,6 @@ namespace Umbraco.Core.Publishing string.Format("Content '{0}' with Id '{1}' has been published.", content.Name, content.Id)); - //Fire Published event - OnPublished(content, e); - - //NOTE: Ideally the xml cache should be refreshed here - as part of the publishing - return true; } @@ -121,20 +116,12 @@ namespace Umbraco.Core.Publishing } item.ChangePublishedState(true); - + LogHelper.Info( string.Format("Content '{0}' with Id '{1}' has been published.", item.Name, item.Id)); - - //Fire Published event - OnPublished(item, e); } - OnPublished(content, e); - - //NOTE: Ideally the xml cache should be refreshed here - as part of the publishing - //OnCacheContentAfterPublish(content, e) - return true; } @@ -169,13 +156,6 @@ namespace Umbraco.Core.Publishing LogHelper.Info( string.Format("Content '{0}' with Id '{1}' has been unpublished.", content.Name, content.Id)); - - //Fire UnPublishing event - OnUnPublished(content, e); - - //NOTE: Ideally the xml cache should be refreshed here - as part of the unpublishing - //OnRemoveCacheContentAfterPublish(content, e) - return true; } @@ -216,16 +196,50 @@ namespace Umbraco.Core.Publishing LogHelper.Info( string.Format("Content '{0}' with Id '{1}' has been unpublished.", item.Name, item.Id)); - - //Fire AfterUnPublish event - OnUnPublished(item, e); } - OnUnPublished(content, e); - - //NOTE: Ideally the xml cache should be refreshed here - as part of the publishing - return true; } + + /// + /// Call to fire event that updating the published content has finalized. + /// + /// + /// This seperation of the OnPublished event is done to ensure that the Content + /// has been properly updated (committed unit of work) and xml saved in the db. + /// + /// thats being published + public override void PublishingFinalized(IContent content) + { + OnPublished(content, new PublishingEventArgs()); + } + + /// + /// Call to fire event that updating the published content has finalized. + /// + /// An enumerable list of thats being published + /// Boolean indicating whether its all content that is republished + public override void PublishingFinalized(IEnumerable content, bool isAllRepublished) + { + OnPublished(content, new PublishingEventArgs { IsAllRepublished = isAllRepublished }); + } + + /// + /// Call to fire event that updating the unpublished content has finalized. + /// + /// thats being unpublished + public override void UnPublishingFinalized(IContent content) + { + OnUnPublished(content, new PublishingEventArgs()); + } + + /// + /// Call to fire event that updating the unpublished content has finalized. + /// + /// An enumerable list of thats being unpublished + public override void UnPublishingFinalized(IEnumerable content) + { + OnUnPublished(content, new PublishingEventArgs()); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 13adfb2df7..d8f0f0e746 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -2,10 +2,13 @@ using System; using System.Collections.Generic; using System.Linq; using System.Web; +using System.Xml.Linq; using Umbraco.Core.Auditing; +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.UnitOfWork; @@ -178,6 +181,65 @@ namespace Umbraco.Core.Services } } + /// + /// 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 GetChildrenByName(int parentId, string name) + { + using (var repository = _repositoryFactory.CreateContentRepository(_uowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.ParentId == parentId && x.Name.Contains(name)); + var contents = repository.GetByQuery(query); + + return contents; + } + } + + /// + /// 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) + { + var content = GetById(id); + return GetDescendants(content); + } + + /// + /// 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 repository = _repositoryFactory.CreateContentRepository(_uowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Path.StartsWith(content.Path)); + var contents = repository.GetByQuery(query); + + return contents; + } + } + + + /// + /// Gets a specific version of an item. + /// + /// Id of the to retrieve version from + /// Id of the version to retrieve + /// An item + public IContent GetByIdVersion(int id, Guid versionId) + { + using (var repository = _repositoryFactory.CreateContentRepository(_uowProvider.GetUnitOfWork())) + { + return repository.GetByVersion(id, versionId); + } + } + /// /// Gets a collection of an objects versions by Id /// @@ -190,9 +252,22 @@ namespace Umbraco.Core.Services var versions = repository.GetAllVersions(id); return versions; } - } + /// + /// Gets the published version of an item + /// + /// Id of the to retrieve version from + /// An item + public IContent GetPublishedVersion(int id) + { + using (var repository = _repositoryFactory.CreateContentRepository(_uowProvider.GetUnitOfWork())) + { + var version = GetVersions(id); + return version.FirstOrDefault(x => x.Published == true); + } + } + /// /// Gets a collection of objects, which reside at the first level / root /// @@ -253,17 +328,49 @@ namespace Umbraco.Core.Services } } - /// - /// Re-Publishes all Content - /// - /// Optional Id of the User issueing the publishing - /// True if publishing succeeded, otherwise False - public bool RePublishAll(int userId = -1) + /// + /// Checks whether an item has any children + /// + /// Id of the + /// True if the content has any children otherwise False + public bool HasChildren(int id) + { + using (var repository = _repositoryFactory.CreateContentRepository(_uowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.ParentId == id); + int count = repository.Count(query); + return count > 0; + } + } + + /// + /// Checks whether an item has any published versions + /// + /// Id of the + /// True if the content has any published version otherwise False + public bool HasPublishedVersion(int id) + { + using (var repository = _repositoryFactory.CreateContentRepository(_uowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Published == true && x.Id == id); + int count = repository.Count(query); + return count > 0; + } + } + + /// + /// Re-Publishes all Content + /// + /// Optional Id of the User issueing the publishing + /// Optional boolean to avoid having the cache refreshed when calling this RePublish method. By default this method will update the cache. + /// True if publishing succeeded, otherwise False + public bool RePublishAll(int userId = -1, bool omitCacheRefresh = false) { var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateContentRepository(uow)) { var list = new List(); + var updated = new List(); //Consider creating a Path query instead of recursive method: //var query = Query.Builder.Where(x => x.Path.StartsWith("-1")); @@ -287,12 +394,24 @@ namespace Umbraco.Core.Services { SetWriter(item, userId); repository.AddOrUpdate(item); + updated.Add(item); } uow.Commit(); - //TODO Change this so we can avoid a depencency to the horrible library method / umbraco.content (singleton) class. - //global::umbraco.library.RefreshContent(); + foreach (var c in updated) + { + var xml = c.ToXml(); + var poco = new ContentXmlDto { NodeId = c.Id, Xml = xml.ToString(SaveOptions.None) }; + var exists = uow.Database.FirstOrDefault("WHERE nodeId = @Id", new { Id = c.Id }) != null; + int result = exists + ? uow.Database.Update(poco) + : Convert.ToInt32(uow.Database.Insert(poco)); + } + + //Updating content to published state is finished, so we fire event through PublishingStrategy to have cache updated + if (omitCacheRefresh == false) + _publishingStrategy.PublishingFinalized(updated, true); Audit.Add(AuditTypes.Publish, "RePublish All performed by user", userId == -1 ? 0 : userId, -1); } @@ -301,30 +420,32 @@ namespace Umbraco.Core.Services } } - /// - /// Publishes a single object - /// - /// The to publish - /// Optional Id of the User issueing the publishing - /// True if publishing succeeded, otherwise False - public bool Publish(IContent content, int userId = -1) - { - return SaveAndPublish(content, userId); - } + /// + /// Publishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// Optional boolean to avoid having the cache refreshed when calling this Publish method. By default this method will update the cache. + /// True if publishing succeeded, otherwise False + public bool Publish(IContent content, int userId = -1, bool omitCacheRefresh = false) + { + return SaveAndPublish(content, userId, omitCacheRefresh); + } - /// - /// Publishes a object and all its children - /// - /// The to publish along with its children - /// Optional Id of the User issueing the publishing - /// True if publishing succeeded, otherwise False - public bool PublishWithChildren(IContent content, int userId = -1) + /// + /// Publishes a object and all its children + /// + /// The to publish along with its children + /// Optional Id of the User issueing the publishing + /// Optional boolean to avoid having the cache refreshed when calling this Publish method. By default this method will update the cache. + /// True if publishing succeeded, otherwise False + public bool PublishWithChildren(IContent content, int userId = -1, bool omitCacheRefresh = false) { var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateContentRepository(uow)) { //Check if parent is published (although not if its a root node) - if parent isn't published this Content cannot be published - if (content.ParentId != -1 && content.ParentId != -20 && !GetById(content.ParentId).Published) + if (content.ParentId != -1 && content.ParentId != -20 && HasPublishedVersion(content.ParentId) == false) { LogHelper.Info( string.Format("Content '{0}' with Id '{1}' could not be published because its parent is not published.", @@ -344,6 +465,7 @@ namespace Umbraco.Core.Services //Consider creating a Path query instead of recursive method: //var query = Query.Builder.Where(x => x.Path.StartsWith(content.Path)); + var updated = new List(); var list = new List(); list.Add(content); list.AddRange(GetChildrenDeep(content.Id)); @@ -357,13 +479,24 @@ namespace Umbraco.Core.Services { SetWriter(item, userId); repository.AddOrUpdate(item); + updated.Add(item); } uow.Commit(); - //TODO Change this so we can avoid a depencency to the horrible library method / umbraco.content (singleton) class. - //TODO Need to investigate if it will also update the cache for children of the Content object - //global::umbraco.library.UpdateDocumentCache(content.Id); + foreach (var c in updated) + { + var xml = c.ToXml(); + var poco = new ContentXmlDto { NodeId = c.Id, Xml = xml.ToString(SaveOptions.None) }; + var exists = uow.Database.FirstOrDefault("WHERE nodeId = @Id", new { Id = c.Id }) != null; + int result = exists + ? uow.Database.Update(poco) + : Convert.ToInt32(uow.Database.Insert(poco)); + } + + //Save xml to db and call following method to fire event: + if (omitCacheRefresh == false) + _publishingStrategy.PublishingFinalized(updated, false); Audit.Add(AuditTypes.Publish, "Publish with Children performed by user", userId == -1 ? 0 : userId, content.Id); } @@ -372,13 +505,14 @@ namespace Umbraco.Core.Services } } - /// - /// UnPublishes a single object - /// - /// The to publish - /// Optional Id of the User issueing the publishing - /// True if unpublishing succeeded, otherwise False - public bool UnPublish(IContent content, int userId = -1) + /// + /// UnPublishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// Optional boolean to avoid having the cache refreshed when calling this Unpublish method. By default this method will update the cache. + /// True if unpublishing succeeded, otherwise False + public bool UnPublish(IContent content, int userId = -1, bool omitCacheRefresh = false) { var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateContentRepository(uow)) @@ -409,10 +543,21 @@ namespace Umbraco.Core.Services uow.Commit(); - //TODO Change this so we can avoid a depencency to the horrible library method / umbraco.content class. - //global::umbraco.library.UnPublishSingleNode(content.Id); + //Remove 'published' xml from the cmsContentXml table for the unpublished content and its (possible) children + uow.Database.Delete("WHERE nodeId = @Id", new { Id = content.Id }); + if (hasChildren) + { + foreach (var child in children) + { + uow.Database.Delete("WHERE nodeId = @Id", new { Id = child.Id }); + } + } - Audit.Add(AuditTypes.Publish, "UnPublish performed by user", userId == -1 ? 0 : userId, content.Id); + //Delete xml from db? and call following method to fire event through PublishingStrategy to update cache + if (omitCacheRefresh == false) + _publishingStrategy.UnPublishingFinalized(content); + + Audit.Add(AuditTypes.UnPublish, "UnPublish performed by user", userId == -1 ? 0 : userId, content.Id); } return unpublished; @@ -445,13 +590,14 @@ namespace Umbraco.Core.Services return list; } - /// - /// Saves and Publishes a single object - /// - /// The to save and publish - /// Optional Id of the User issueing the publishing - /// True if publishing succeeded, otherwise False - public bool SaveAndPublish(IContent content, int userId = -1) + /// + /// Saves and Publishes a single object + /// + /// The to save and publish + /// Optional Id of the User issueing the publishing + /// Optional boolean to avoid having the cache refreshed when calling this Publish method. By default this method will update the cache. + /// True if publishing succeeded, otherwise False + public bool SaveAndPublish(IContent content, int userId = -1, bool omitCacheRefresh = false) { var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateContentRepository(uow)) @@ -462,9 +608,8 @@ namespace Umbraco.Core.Services if (!e.Cancel) { - //Check if parent is published (although not if its a root node) - if parent isn't published this Content cannot be published - if (content.ParentId != -1 && content.ParentId != -20 && GetById(content.ParentId).Published == false) + if (content.ParentId != -1 && content.ParentId != -20 && HasPublishedVersion(content.ParentId) == false) { LogHelper.Info( string.Format( @@ -485,15 +630,25 @@ namespace Umbraco.Core.Services //Publish and then update the database with new status bool published = _publishingStrategy.Publish(content, userId); - if (published) - { - SetWriter(content, userId); - repository.AddOrUpdate(content); - uow.Commit(); - //TODO Change this so we can avoid a depencency to the horrible library method / umbraco.content (singleton) class. - //global::umbraco.library.UpdateDocumentCache(content.Id); - } + //Since this is the Save and Publish method, the content should be saved even though the publish fails or isn't allowed + SetWriter(content, userId); + repository.AddOrUpdate(content); + uow.Commit(); + + if (published) + { + var xml = content.ToXml(); + var poco = new ContentXmlDto { NodeId = content.Id, Xml = xml.ToString(SaveOptions.None) }; + var exists = uow.Database.FirstOrDefault("WHERE nodeId = @Id", new { Id = content.Id }) != null; + int result = exists + ? uow.Database.Update(poco) + : Convert.ToInt32(uow.Database.Insert(poco)); + + //Save xml to db and call following method to fire event through PublishingStrategy to update cache + if (omitCacheRefresh == false) + _publishingStrategy.PublishingFinalized(content); + } if (Saved != null) Saved(content, e); @@ -524,16 +679,19 @@ namespace Umbraco.Core.Services if (!e.Cancel) { - SetWriter(content, userId); - content.ChangePublishedState(false); + + //Only change the publish state if the "previous" version was actually published + if (content.Published) + content.ChangePublishedState(false); + repository.AddOrUpdate(content); uow.Commit(); if (Saved != null) Saved(content, e); - //Audit.Add(AuditTypes.Save, "Save Content performed by user", userId == -1 ? 0 : userId, content.Id); + Audit.Add(AuditTypes.Save, "Save Content performed by user", userId == -1 ? 0 : userId, content.Id); } } } @@ -565,7 +723,11 @@ namespace Umbraco.Core.Services foreach (var content in contents) { SetWriter(content, userId); - content.ChangePublishedState(false); + + //Only change the publish state if the "previous" version was actually published + if (content.Published) + content.ChangePublishedState(false); + repository.AddOrUpdate(content); uow.Commit(); } @@ -614,7 +776,11 @@ namespace Umbraco.Core.Services foreach (var content in contents) { SetWriter(content.Value, userId); - content.Value.ChangePublishedState(false); + + //Only change the publish state if the "previous" version was actually published + if (content.Value.Published) + content.Value.ChangePublishedState(false); + repository.AddOrUpdate(content.Value); uow.Commit(); } @@ -627,55 +793,64 @@ namespace Umbraco.Core.Services } } - /// - /// Deletes all content of specified type. All children of deleted content is moved to Recycle Bin. - /// - /// This needs extra care and attention as its potentially a dangerous and extensive operation - /// Id of the - /// Optional Id of the user issueing the delete operation - public void DeleteContentOfType(int contentTypeId, int userId = -1) - { - var uow = _uowProvider.GetUnitOfWork(); - using (var repository = _repositoryFactory.CreateContentRepository(uow)) - { - //NOTE What about content that has the contenttype as part of its composition? - var query = Query.Builder.Where(x => x.ContentTypeId == contentTypeId); - var contents = repository.GetByQuery(query); + /// + /// Deletes all content of specified type. All children of deleted content is moved to Recycle Bin. + /// + /// This needs extra care and attention as its potentially a dangerous and extensive operation + /// Id of the + /// Optional Id of the user issueing the delete operation + public void DeleteContentOfType(int contentTypeId, int userId = -1) + { + using (var repository = _repositoryFactory.CreateContentRepository(_uowProvider.GetUnitOfWork())) + { + //NOTE What about content that has the contenttype as part of its composition? + var query = Query.Builder.Where(x => x.ContentTypeId == contentTypeId); + var contents = repository.GetByQuery(query); - var e = new DeleteEventArgs { Id = contentTypeId }; - if (Deleting != null) - Deleting(contents, e); + var e = new DeleteEventArgs {Id = contentTypeId}; + if (Deleting != null) + Deleting(contents, e); - if (!e.Cancel) - { - foreach (var content in contents) - { - ((Content)content).ChangeTrashedState(true); - repository.AddOrUpdate(content); - } + if (!e.Cancel) + { + foreach (var content in contents.OrderByDescending(x => x.ParentId)) + { + //Look for children of current content and move that to trash before the current content is deleted + var c = content; + var childQuery = Query.Builder.Where(x => x.Path.StartsWith(c.Path)); + var children = repository.GetByQuery(childQuery); - uow.Commit(); + foreach (var child in children) + { + if (child.ContentType.Id != contentTypeId) + MoveToRecycleBin(child, userId); + } - if (Deleted != null) - Deleted(contents, e); + //Permantly delete the content + Delete(content, userId); + } - Audit.Add(AuditTypes.Delete, string.Format("Delete Content of Type {0} performed by user", contentTypeId), userId == -1 ? 0 : userId, -1); - } - } - } + if (Deleted != null) + Deleted(contents, e); - /// - /// Permanently deletes an object - /// - /// Please note that this method will completely remove the Content from the database - /// The to delete - /// Optional Id of the User deleting the Content + Audit.Add(AuditTypes.Delete, + string.Format("Delete Content of Type {0} performed by user", contentTypeId), + userId == -1 ? 0 : userId, -1); + } + } + } + + /// + /// Permanently deletes an object. + /// + /// + /// This method will also delete associated media files, child content and possibly associated domains. + /// + /// Please note that this method will completely remove the Content from the database + /// The to delete + /// Optional Id of the User deleting the Content public void Delete(IContent content, int userId = -1) { - //TODO Ensure that content is unpublished when deleted - //TODO This method should handle/react to errors when there is a constraint issue with the content being deleted - //TODO Children should either be deleted or moved to the recycle bin - var e = new DeleteEventArgs { Id = content.Id }; if (Deleting != null) Deleting(content, e); @@ -685,8 +860,21 @@ namespace Umbraco.Core.Services var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateContentRepository(uow)) { - SetWriter(content, userId); - repository.Delete(content); + //Make sure that published content is unpublished before being deleted + if (HasPublishedVersion(content.Id)) + { + UnPublish(content, userId); + } + + //Delete children before deleting the 'possible parent' + var children = GetChildren(content.Id); + foreach (var child in children) + { + Delete(child, userId); + } + + SetWriter(content, userId); + repository.Delete(content); uow.Commit(); if (Deleted != null) @@ -789,22 +977,32 @@ namespace Umbraco.Core.Services /// Optional Id of the User deleting the Content public void MoveToRecycleBin(IContent content, int userId = -1) { - //TODO If content item has children those should also be moved to the recycle bin - //TODO Unpublish deleted content + children var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateContentRepository(uow)) { - var e = new MoveEventArgs { ParentId = -20 }; if (Trashing != null) Trashing(content, e); if (!e.Cancel) { - SetWriter(content, userId); - content.ChangeTrashedState(true); - repository.AddOrUpdate(content); - uow.Commit(); + //Make sure that published content is unpublished before being moved to the Recycle Bin + if (HasPublishedVersion(content.Id)) + { + UnPublish(content, userId); + } + + //Move children to Recycle Bin before the 'possible parent' is moved there + var children = GetChildren(content.Id); + foreach (var child in children) + { + MoveToRecycleBin(child, userId); + } + + SetWriter(content, userId); + content.ChangeTrashedState(true); + repository.AddOrUpdate(content); + uow.Commit(); if (Trashed != null) Trashed(content, e); @@ -827,6 +1025,8 @@ namespace Umbraco.Core.Services /// Optional Id of the User moving the Content public void Move(IContent content, int parentId, int userId = -1) { + //TODO Verify that SortOrder + Path is updated correctly + //TODO Add a check to see if parentId = -20 because then we should change the TrashState var e = new MoveEventArgs { ParentId = parentId }; if (Moving != null) Moving(content, e); @@ -882,15 +1082,16 @@ namespace Umbraco.Core.Services } } - /// - /// Copies an object by creating a new Content object of the same type and copies all data from the current - /// to the new copy which is returned. - /// - /// The to copy - /// Id of the Content's new Parent - /// Optional Id of the User copying the Content - /// The newly created object - public IContent Copy(IContent content, int parentId, int userId = -1) + /// + /// Copies an object by creating a new Content object of the same type and copies all data from the current + /// to the new copy which is returned. + /// + /// The to copy + /// Id of the Content's new Parent + /// Boolean indicating whether the copy should be related to the original + /// Optional Id of the User copying the Content + /// The newly created object + public IContent Copy(IContent content, int parentId, bool relateToOriginal, int userId = -1) { var e = new CopyEventArgs { ParentId = parentId }; if (Copying != null) @@ -911,13 +1112,77 @@ namespace Umbraco.Core.Services repository.AddOrUpdate(copy); uow.Commit(); + + var uploadFieldId = new Guid("5032a6e6-69e3-491d-bb28-cd31cd11086c"); + if (content.Properties.Any(x => x.PropertyType.DataTypeControlId == uploadFieldId)) + { + bool isUpdated = false; + var fs = FileSystemProviderManager.Current.GetFileSystemProvider(); + + //Loop through properties to check if the content contains media that should be deleted + foreach (var property in content.Properties.Where(x => x.PropertyType.DataTypeControlId == uploadFieldId + && string.IsNullOrEmpty(x.Value.ToString()) == false)) + { + if (fs.FileExists(IOHelper.MapPath(property.Value.ToString()))) + { + var currentPath = fs.GetRelativePath(property.Value.ToString()); + var propertyId = copy.Properties.First(x => x.Alias == property.Alias).Id; + var newPath = fs.GetRelativePath(propertyId, System.IO.Path.GetFileName(currentPath)); + + fs.CopyFile(currentPath, newPath); + copy.SetValue(property.Alias, fs.GetUrl(newPath)); + + //Copy thumbnails + foreach (var thumbPath in fs.GetThumbnails(currentPath)) + { + var newThumbPath = fs.GetRelativePath(propertyId, System.IO.Path.GetFileName(thumbPath)); + fs.CopyFile(thumbPath, newThumbPath); + } + isUpdated = true; + } + } + + if (isUpdated) + { + repository.AddOrUpdate(copy); + uow.Commit(); + } + } } + + //NOTE This 'Relation' part should eventually be delegated to a RelationService + if (relateToOriginal) + { + RelationType relationType = null; + using (var relationTypeRepository = _repositoryFactory.CreateRelationTypeRepository(uow)) + { + relationType = relationTypeRepository.Get(1); + } + + using (var relationRepository = _repositoryFactory.CreateRelationRepository(uow)) + { + var relation = new Relation(content.Id, copy.Id, relationType); + relationRepository.AddOrUpdate(relation); + uow.Commit(); + } + + Audit.Add(AuditTypes.Copy, + string.Format("Copied content with Id: '{0}' related to original content with Id: '{1}'", + copy.Id, content.Id), copy.WriterId, copy.Id); + } + + //Look for children and copy those as well + var children = GetChildren(content.Id); + foreach (var child in children) + { + Copy(child, copy.Id, relateToOriginal, userId); + } } if (Copied != null) Copied(copy, e); - Audit.Add(AuditTypes.Delete, "Copy Content performed by user", content.WriterId, content.Id); + Audit.Add(AuditTypes.Copy, "Copy Content performed by user", content.WriterId, content.Id); return copy; } @@ -928,7 +1193,7 @@ namespace Umbraco.Core.Services /// The to send to publication /// Optional Id of the User issueing the send to publication /// True if sending publication was succesfull otherwise false - public bool SendToPublication(IContent content, int userId = -1) + internal bool SendToPublication(IContent content, int userId = -1) { //TODO Implement something similar to this var e = new SendToPublishEventArgs(); diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index d27e4a467c..11a7336337 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -19,11 +19,6 @@ namespace Umbraco.Core.Services /// IContent CreateContent(int parentId, string contentTypeAlias, int userId = -1); - //TODO Add GetLatestUnpublishedVersions(int id){} - //TODO Add CreateNewVersion method? Its currently used in the Document API when Publishing - latest version is published, - //but then a new version is created so latest version is not published. - //IContent CreateNewVersion(int id); -> should not be necessary as Version number is changed when updating - /// /// Gets an object by Id /// @@ -90,45 +85,6 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects IEnumerable GetContentInRecycleBin(); - /// - /// Re-Publishes all Content - /// - /// Optional Id of the User issueing the publishing - /// True if publishing succeeded, otherwise False - bool RePublishAll(int userId = -1); - - /// - /// Publishes a single object - /// - /// The to publish - /// Optional Id of the User issueing the publishing - /// True if publishing succeeded, otherwise False - bool Publish(IContent content, int userId = -1); - - /// - /// Publishes a object and all its children - /// - /// The to publish along with its children - /// Optional Id of the User issueing the publishing - /// True if publishing succeeded, otherwise False - bool PublishWithChildren(IContent content, int userId = -1); - - /// - /// UnPublishes a single object - /// - /// The to publish - /// Optional Id of the User issueing the publishing - /// True if unpublishing succeeded, otherwise False - bool UnPublish(IContent content, int userId = -1); - - /// - /// Saves and Publishes a single object - /// - /// The to save and publish - /// Optional Id of the User issueing the publishing - /// True if publishing succeeded, otherwise False - bool SaveAndPublish(IContent content, int userId = -1); - /// /// Saves a single object /// @@ -166,14 +122,6 @@ namespace Umbraco.Core.Services /// Optional Id of the user issueing the delete operation void DeleteContentOfType(int contentTypeId, int userId = -1); - /// - /// Permanently deletes an object - /// - /// Please note that this method will completely remove the Content from the database - /// The to delete - /// Optional Id of the User deleting the Content - void Delete(IContent content, int userId = -1); - /// /// Permanently deletes versions from an object prior to a specific date. /// @@ -229,24 +177,6 @@ namespace Umbraco.Core.Services /// void EmptyRecycleBin(); - /// - /// Copies an object by creating a new Content object of the same type and copies all data from the current - /// to the new copy which is returned. - /// - /// The to copy - /// Id of the Content's new Parent - /// Optional Id of the User copying the Content - /// The newly created object - IContent Copy(IContent content, int parentId, int userId = -1); - - /// - /// Sends an to Publication, which executes handlers and events for the 'Send to Publication' action. - /// - /// The to send to publication - /// Optional Id of the User issueing the send to publication - /// True if sending publication was succesfull otherwise false - bool SendToPublication(IContent content, int userId = -1); - /// /// Rollback an object to a previous version. /// This will create a new version, which is a copy of all the old data. @@ -256,5 +186,122 @@ namespace Umbraco.Core.Services /// Optional Id of the User issueing the rollback of the Content /// The newly created object IContent Rollback(int id, Guid versionId, int userId = -1); + + /// + /// 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 + IEnumerable GetChildrenByName(int parentId, string name); + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Descendants from + /// An Enumerable list of objects + IEnumerable GetDescendants(int id); + + /// + /// Gets a collection of objects by Parent Id + /// + /// item to retrieve Descendants from + /// An Enumerable list of objects + IEnumerable GetDescendants(IContent content); + + /// + /// Gets a specific version of an item. + /// + /// Id of the to retrieve version from + /// Id of the version to retrieve + /// An item + IContent GetByIdVersion(int id, Guid versionId); + + /// + /// Gets the published version of an item + /// + /// Id of the to retrieve version from + /// An item + IContent GetPublishedVersion(int id); + + /// + /// Checks whether an item has any children + /// + /// Id of the + /// True if the content has any children otherwise False + bool HasChildren(int id); + + /// + /// Checks whether an item has any published versions + /// + /// Id of the + /// True if the content has any published version otherwise False + bool HasPublishedVersion(int id); + + /// + /// Re-Publishes all Content + /// + /// Optional Id of the User issueing the publishing + /// Optional boolean to avoid having the cache refreshed when calling this RePublish method. By default this method will update the cache. + /// True if publishing succeeded, otherwise False + bool RePublishAll(int userId = -1, bool omitCacheRefresh = false); + + /// + /// Publishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// Optional boolean to avoid having the cache refreshed when calling this Publish method. By default this method will update the cache. + /// True if publishing succeeded, otherwise False + bool Publish(IContent content, int userId = -1, bool omitCacheRefresh = false); + + /// + /// Publishes a object and all its children + /// + /// The to publish along with its children + /// Optional Id of the User issueing the publishing + /// Optional boolean to avoid having the cache refreshed when calling this Publish method. By default this method will update the cache. + /// True if publishing succeeded, otherwise False + bool PublishWithChildren(IContent content, int userId = -1, bool omitCacheRefresh = false); + + /// + /// UnPublishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// Optional boolean to avoid having the cache refreshed when calling this Unpublish method. By default this method will update the cache. + /// True if unpublishing succeeded, otherwise False + bool UnPublish(IContent content, int userId = -1, bool omitCacheRefresh = false); + + /// + /// Saves and Publishes a single object + /// + /// The to save and publish + /// Optional Id of the User issueing the publishing + /// Optional boolean to avoid having the cache refreshed when calling this Publish method. By default this method will update the cache. + /// True if publishing succeeded, otherwise False + bool SaveAndPublish(IContent content, int userId = -1, bool omitCacheRefresh = false); + + /// + /// Permanently deletes an object. + /// + /// + /// This method will also delete associated media files, child content and possibly associated domains. + /// + /// Please note that this method will completely remove the Content from the database + /// The to delete + /// Optional Id of the User deleting the Content + void Delete(IContent content, int userId = -1); + + /// + /// Copies an object by creating a new Content object of the same type and copies all data from the current + /// to the new copy which is returned. + /// + /// The to copy + /// Id of the Content's new Parent + /// Boolean indicating whether the copy should be related to the original + /// Optional Id of the User copying the Content + /// The newly created object + IContent Copy(IContent content, int parentId, bool relateToOriginal, int userId = -1); } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs index 9fba87f316..8730dc3aad 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs @@ -48,7 +48,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var relationType = repositoryType.Get(1); - var relation = new Relation(1047, 1048) { RelationType = relationType }; + var relation = new Relation(1047, 1048, relationType); repository.AddOrUpdate(relation); unitOfWork.Commit(); @@ -270,8 +270,8 @@ namespace Umbraco.Tests.Persistence.Repositories Content subpage2 = MockedContent.CreateSimpleContent(contentType, "Text Page 2", textpage.Id); ServiceContext.ContentService.Save(subpage2, 0); - var relation = new Relation(textpage.Id, subpage.Id) {RelationType = relateContent, Comment = string.Empty}; - var relation2 = new Relation(textpage.Id, subpage2.Id) { RelationType = relateContent, Comment = string.Empty}; + var relation = new Relation(textpage.Id, subpage.Id, relateContent) { Comment = string.Empty }; + var relation2 = new Relation(textpage.Id, subpage2.Id, relateContent) { Comment = string.Empty }; relationRepository.AddOrUpdate(relation); relationRepository.AddOrUpdate(relation2); unitOfWork.Commit(); diff --git a/src/Umbraco.Tests/Publishing/PublishingStrategyTests.cs b/src/Umbraco.Tests/Publishing/PublishingStrategyTests.cs index 17b9ad9130..822799e8d6 100644 --- a/src/Umbraco.Tests/Publishing/PublishingStrategyTests.cs +++ b/src/Umbraco.Tests/Publishing/PublishingStrategyTests.cs @@ -1,7 +1,5 @@ using System; using System.IO; -using System.Web; -using System.Xml; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Configuration; @@ -11,7 +9,6 @@ using Umbraco.Core.ObjectResolution; using Umbraco.Core.Persistence; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; -using Umbraco.Web.Strategies; using umbraco.editorControls.tinyMCE3; using umbraco.interfaces; @@ -66,25 +63,7 @@ namespace Umbraco.Tests.Publishing [Test] public void Can_Publish_And_Update_Xml_Cache() { - // Arrange - var httpContext = base.GetUmbracoContext("/test", 1234).HttpContext; - var updateContentCache = new UpdateContentCache(httpContext); - var contentService = ServiceContext.ContentService; - var content = contentService.GetById(1046); - - // Act - bool published = contentService.Publish(content, 0); - - // Assert - Assert.That(published, Is.True); - Assert.That(content.Published, Is.True); - Assert.IsTrue(httpContext.Items.Contains("UmbracoXmlContextContent")); - - var document = httpContext.Items["UmbracoXmlContextContent"] as XmlDocument; - Console.Write(document.OuterXml); - document.Save("umbraco.config"); - - updateContentCache.Unsubscribe(); + //TODO Create new test } public void CreateTestData() diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index b1269c0596..d04cd84b26 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -25,6 +25,19 @@ namespace Umbraco.Tests.Services [SetUp] public override void Initialize() { + //this ensures its reset + //PluginManager.Current = new PluginManager(); + + //for testing, we'll specify which assemblies are scanned for the PluginTypeResolver + /*PluginManager.Current.AssembliesToScan = new[] + { + typeof(IDataType).Assembly, + typeof(tinyMCE3dataType).Assembly + }; + + DataTypesResolver.Current = new DataTypesResolver( + PluginManager.Current.ResolveDataTypes());*/ + base.Initialize(); CreateTestData(); @@ -33,6 +46,10 @@ namespace Umbraco.Tests.Services [TearDown] public override void TearDown() { + //reset the app context + //DataTypesResolver.Reset(); + //PluginManager.Current = null; + base.TearDown(); } @@ -207,10 +224,12 @@ namespace Umbraco.Tests.Services { // Arrange var contentService = ServiceContext.ContentService; + var parent = ServiceContext.ContentService.GetById(1046); + ServiceContext.ContentService.Publish(parent);//Publishing root, so Text Page 2 can be updated. var subpage2 = contentService.GetById(1048); subpage2.Name = "Text Page 2 Updated"; subpage2.SetValue("author", "Jane Doe"); - contentService.Save(subpage2, 0); + contentService.SaveAndPublish(subpage2, 0);//NOTE New versions are only added between publish-state-changed, so publishing to ensure addition version. // Act var versions = contentService.GetVersions(1048); @@ -636,7 +655,7 @@ namespace Umbraco.Tests.Services var content = contentService.GetById(1048); // Act - var copy = contentService.Copy(content, content.ParentId, 0); + var copy = contentService.Copy(content, content.ParentId, false, 0); // Assert Assert.That(copy, Is.Not.Null); @@ -654,19 +673,21 @@ namespace Umbraco.Tests.Services { // Arrange var contentService = ServiceContext.ContentService; + var parent = ServiceContext.ContentService.GetById(1046); + ServiceContext.ContentService.Publish(parent);//Publishing root, so Text Page 2 can be updated. var subpage2 = contentService.GetById(1048); var version = subpage2.Version; var nameBeforeRollback = subpage2.Name; subpage2.Name = "Text Page 2 Updated"; subpage2.SetValue("author", "Jane Doe"); - contentService.Save(subpage2, 0); + contentService.SaveAndPublish(subpage2, 0);//Saving and publishing, so a new version is created // Act var rollback = contentService.Rollback(1048, version, 0); // Assert Assert.That(rollback, Is.Not.Null); - Assert.AreNotEqual(rollback.Version, version); + Assert.AreNotEqual(rollback.Version, subpage2.Version); Assert.That(rollback.GetValue("author"), Is.Not.EqualTo("Jane Doe")); Assert.AreEqual(nameBeforeRollback, rollback.Name); } diff --git a/src/Umbraco.Web/ContentExtensions.cs b/src/Umbraco.Web/ContentExtensions.cs deleted file mode 100644 index e135dcf24e..0000000000 --- a/src/Umbraco.Web/ContentExtensions.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System.Xml.Linq; -using Umbraco.Core; -using Umbraco.Core.Models; - -namespace Umbraco.Web -{ - public static class ContentExtensions - { - /// - /// Creates the xml representation for the object - /// - /// to generate xml for - /// Xml representation of the passed in - public static XElement ToXml(this IContent content) - { - //nodeName should match Casing.SafeAliasWithForcingCheck(content.ContentType.Alias); - var nodeName = content.ContentType.Alias.ToUmbracoAlias(StringAliasCaseType.CamelCase, true); - var niceUrl = content.Name.Replace(" ", "-").ToLower(); - - if (UmbracoContext.Current != null) - { - var niceUrlsProvider = UmbracoContext.Current.NiceUrlProvider; - niceUrl = niceUrlsProvider.GetNiceUrl(content.Id); - } - - var xml = new XElement(nodeName, - new XAttribute("id", content.Id), - new XAttribute("parentID", content.Level > 1 ? content.ParentId : -1), - new XAttribute("level", content.Level), - new XAttribute("writerID", content.WriterId), - new XAttribute("creatorID", content.CreatorId), - new XAttribute("nodeType", content.ContentType.Id), - new XAttribute("template", content.Template == null ? string.Empty : content.Template.Id.ToString()), - new XAttribute("sortOrder", content.SortOrder), - new XAttribute("createDate", content.CreateDate), - new XAttribute("updateDate", content.UpdateDate), - new XAttribute("nodeName", content.Name), - new XAttribute("urlName", niceUrl),//Format Url ? - new XAttribute("writerName", content.GetWriterProfile().Name), - new XAttribute("creatorName", content.GetCreatorProfile().Name), - new XAttribute("path", content.Path)); - - foreach (var property in content.Properties) - { - if (property == null) continue; - - xml.Add(property.ToXml()); - - //Check for umbracoUrlName convention - if (property.Alias == "umbracoUrlName" && property.Value.ToString().Trim() != string.Empty) - xml.SetAttributeValue("urlName", property.Value); - } - - return xml; - } - - /// - /// Creates the xml representation for the object - /// - /// to generate xml for - /// Boolean indicating whether the xml should be generated for preview - /// Xml representation of the passed in - public static XElement ToXml(this IContent content, bool isPreview) - { - //TODO Do a proper implementation of this - //If current IContent is published we should get latest unpublished version - return content.ToXml(); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Strategies/DeletedTemplate.cs b/src/Umbraco.Web/Strategies/DeletedTemplate.cs deleted file mode 100644 index 2e2bcb27c1..0000000000 --- a/src/Umbraco.Web/Strategies/DeletedTemplate.cs +++ /dev/null @@ -1,14 +0,0 @@ -using umbraco.interfaces; - -namespace Umbraco.Web.Strategies -{ - /// - /// Subscribes to Template Deleted event in order to remove Foreign key reference - /// from cmsDocument table (Template Id set explicitly on content), and from cmsDocumentType - /// table where the ContentType references the Template Allowed/Default. - /// - internal class DeletedTemplate : IApplicationStartupHandler - { - - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Strategies/TrashedContent.cs b/src/Umbraco.Web/Strategies/TrashedContent.cs deleted file mode 100644 index 731b995f6b..0000000000 --- a/src/Umbraco.Web/Strategies/TrashedContent.cs +++ /dev/null @@ -1,9 +0,0 @@ -using umbraco.interfaces; - -namespace Umbraco.Web.Strategies -{ - internal class TrashedContent : IApplicationStartupHandler - { - - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Strategies/UpdateContentCache.cs b/src/Umbraco.Web/Strategies/UpdateContentCache.cs deleted file mode 100644 index c9054391d0..0000000000 --- a/src/Umbraco.Web/Strategies/UpdateContentCache.cs +++ /dev/null @@ -1,238 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Web; -using System.Web.Caching; -using System.Xml; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Models; -using Umbraco.Core.Publishing; -using Umbraco.Core.Services; -using umbraco.interfaces; -using umbraco.presentation.nodeFactory; -using Node = umbraco.NodeFactory.Node; - -namespace Umbraco.Web.Strategies -{ - internal class UpdateContentCache : IApplicationStartupHandler - { - // Sync access to internal cache - private static readonly object XmlContentInternalSyncLock = new object(); - private const string XmlContextContentItemKey = "UmbracoXmlContextContent"; - private readonly HttpContextBase _httpContext; - private readonly ServiceContext _serviceContext; - - public UpdateContentCache() - { - _httpContext = new HttpContextWrapper(HttpContext.Current); - _serviceContext = ApplicationContext.Current.Services; - - BasePublishingStrategy.Published += PublishingStrategy_Published; - } - - public UpdateContentCache(HttpContextBase httpContext) - { - _httpContext = httpContext; - _serviceContext = ApplicationContext.Current.Services; - - BasePublishingStrategy.Published += PublishingStrategy_Published; - } - - void PublishingStrategy_Published(object sender, PublishingEventArgs e) - { - if((sender is IContent) == false) return; - - //var e = new DocumentCacheEventArgs(); - //FireBeforeUpdateDocumentCache(d, e); - - var content = sender as IContent; - - // lock the xml cache so no other thread can write to it at the same time - // note that some threads could read from it while we hold the lock, though - lock (XmlContentInternalSyncLock) - { - XmlDocument wip = XmlContent; - - ClearContextCache(); - - XmlContent = UpdateXmlAndSitemap(content, wip, false);//Update sitemap is usually set to true - } - - // clear cached field values - if (_httpContext != null) - { - Cache httpCache = _httpContext.Cache; - string cachedFieldKeyStart = String.Format("contentItem{0}_", content.Id); - var foundKeys = new List(); - foreach (DictionaryEntry cacheItem in httpCache) - { - string key = cacheItem.Key.ToString(); - if (key.StartsWith(cachedFieldKeyStart)) - foundKeys.Add(key); - } - foreach (string foundKey in foundKeys) - { - httpCache.Remove(foundKey); - } - } - - //Action.RunActionHandlers(d, ActionPublish.Instance); - //FireAfterUpdateDocumentCache(d, e); - } - - private XmlDocument UpdateXmlAndSitemap(IContent content, XmlDocument xmlContentCopy, bool updateSitemapProvider) - { - // check if document *is* published, it could be unpublished by an event - if (content.Published) - { - int parentId = content.Level == 1 ? -1 : content.ParentId; - var contentXmlNode = content.ToXml(false).GetXmlNode(); - var xmlNode = xmlContentCopy.ImportNode(contentXmlNode, true); - xmlContentCopy = AppendContentXml(content.Id, content.Level, parentId, xmlNode, xmlContentCopy); - - // update sitemapprovider - if (updateSitemapProvider && SiteMap.Provider is UmbracoSiteMapProvider) - { - try - { - var prov = (UmbracoSiteMapProvider)SiteMap.Provider; - var n = new Node(content.Id, true); - if (!String.IsNullOrEmpty(n.Url) && n.Url != "/#") - { - prov.UpdateNode(n); - } - else - { - //Log.Add(LogTypes.Error, content.Id, "Can't update Sitemap Provider due to empty Url in node"); - } - } - catch (Exception ee) - { - //Log.Add(LogTypes.Error, content.Id, string.Format("Error adding node to Sitemap Provider in PublishNodeDo(): {0}", ee)); - } - } - } - - return xmlContentCopy; - } - - private XmlDocument AppendContentXml(int id, int level, int parentId, XmlNode docNode, XmlDocument xmlContentCopy) - { - // Find the document in the xml cache - XmlNode x = xmlContentCopy.GetElementById(id.ToString()); - - // if the document is not there already then it's a new document - // we must make sure that its document type exists in the schema - var xmlContentCopy2 = xmlContentCopy; - if (x == null && UmbracoSettings.UseLegacyXmlSchema == false) - { - //TODO Look into the validate schema method - seems a bit odd - //Move to Contract ? - xmlContentCopy = ValidateSchema(docNode.Name, xmlContentCopy); - if (xmlContentCopy != xmlContentCopy2) - docNode = xmlContentCopy.ImportNode(docNode, true); - } - - // Find the parent (used for sortering and maybe creation of new node) - XmlNode parentNode; - if (level == 1) - parentNode = xmlContentCopy.DocumentElement; - else - parentNode = xmlContentCopy.GetElementById(parentId.ToString()); - - if (parentNode != null) - { - if (x == null) - { - x = docNode; - parentNode.AppendChild(x); - } - else - { - //TODO - //TransferValuesFromDocumentXmlToPublishedXml(docNode, x); - } - - // TODO: Update with new schema! - string xpath = UmbracoSettings.UseLegacyXmlSchema ? "./node" : "./* [@id]"; - XmlNodeList childNodes = parentNode.SelectNodes(xpath); - - // Maybe sort the nodes if the added node has a lower sortorder than the last - if (childNodes.Count > 0) - { - int siblingSortOrder = - int.Parse(childNodes[childNodes.Count - 1].Attributes.GetNamedItem("sortOrder").Value); - int currentSortOrder = int.Parse(x.Attributes.GetNamedItem("sortOrder").Value); - if (childNodes.Count > 1 && siblingSortOrder > currentSortOrder) - { - //SortNodes(ref parentNode); - } - } - } - - return xmlContentCopy; - } - - /// - /// Clear HTTPContext cache if any - /// - private void ClearContextCache() - { - // If running in a context very important to reset context cache or else new nodes are missing - if (_httpContext != null && _httpContext.Items.Contains(XmlContextContentItemKey)) - _httpContext.Items.Remove(XmlContextContentItemKey); - } - - private XmlDocument ValidateSchema(string docTypeAlias, XmlDocument xmlDoc) - { - // check if doctype is defined in schema else add it - // can't edit the doctype of an xml document, must create a new document - - var doctype = xmlDoc.DocumentType; - var subset = doctype.InternalSubset; - if (!subset.Contains(string.Format("", docTypeAlias))) - { - subset = string.Format("\r\n\r\n{1}", docTypeAlias, subset); - var xmlDoc2 = new XmlDocument(); - doctype = xmlDoc2.CreateDocumentType("root", null, null, subset); - xmlDoc2.AppendChild(doctype); - var root = xmlDoc2.ImportNode(xmlDoc.DocumentElement, true); - xmlDoc2.AppendChild(root); - - // apply - xmlDoc = xmlDoc2; - } - - return xmlDoc; - } - - private XmlDocument XmlContent - { - get - { - var content = _httpContext.Items[XmlContextContentItemKey] as XmlDocument; - if (content == null) - { - //content = global::umbraco.content.Instance.XmlContent; - var dtd = _serviceContext.ContentTypeService.GetDtd(); - - content = new XmlDocument(); - content.LoadXml( - String.Format("{0}{1}{0}", - Environment.NewLine, - dtd)); - - _httpContext.Items[XmlContextContentItemKey] = content; - } - return content; - } - set { _httpContext.Items[XmlContextContentItemKey] = value; } - } - - internal void Unsubscribe() - { - BasePublishingStrategy.Published -= PublishingStrategy_Published; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Strategies/UpdateMultipleContentCache.cs b/src/Umbraco.Web/Strategies/UpdateMultipleContentCache.cs deleted file mode 100644 index fa06e82927..0000000000 --- a/src/Umbraco.Web/Strategies/UpdateMultipleContentCache.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Core.Models; -using Umbraco.Core.Publishing; - -namespace Umbraco.Web.Strategies -{ - public class UpdateMultipleContentCache - { - public UpdateMultipleContentCache() - { - PublishingStrategy.Published += PublishingStrategy_Published; - } - - void PublishingStrategy_Published(object sender, Core.PublishingEventArgs e) - { - if ((sender is IEnumerable) == false) return; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index d53a84f417..bfc30126df 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -247,7 +247,6 @@ - @@ -328,10 +327,6 @@ - - - - ASPXCodeBehind @@ -2186,7 +2181,9 @@ umbraco_org_umbraco_update_CheckForUpgrade - + + + 10.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)