using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Xml.Linq;
using Umbraco.Core.Auditing;
using Umbraco.Core.Events;
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;
using Umbraco.Core.Publishing;
namespace Umbraco.Core.Services
{
///
/// Represents the Content Service, which is an easy access to operations involving
///
public class ContentService : IContentService
{
private readonly IDatabaseUnitOfWorkProvider _uowProvider;
private readonly IPublishingStrategy _publishingStrategy;
private readonly RepositoryFactory _repositoryFactory;
public ContentService()
: this(new RepositoryFactory())
{}
public ContentService(RepositoryFactory repositoryFactory)
: this(new PetaPocoUnitOfWorkProvider(), repositoryFactory, new PublishingStrategy())
{}
public ContentService(IDatabaseUnitOfWorkProvider provider)
: this(provider, new RepositoryFactory(), new PublishingStrategy())
{ }
public ContentService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory)
: this(provider, repositoryFactory, new PublishingStrategy())
{ }
public ContentService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, IPublishingStrategy publishingStrategy)
{
_uowProvider = provider;
_publishingStrategy = publishingStrategy;
_repositoryFactory = repositoryFactory;
}
///
/// Creates an object using the alias of the
/// that this Content is based on.
///
/// Name of the Content object
/// Id of Parent for the new Content
/// Alias of the
/// Optional id of the user creating the content
///
public IContent CreateContent(string name, int parentId, string contentTypeAlias, int userId = 0)
{
IContentType contentType = null;
IContent content = null;
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateContentTypeRepository(uow))
{
var query = Query.Builder.Where(x => x.Alias == contentTypeAlias);
var contentTypes = repository.GetByQuery(query);
if (!contentTypes.Any())
throw new Exception(string.Format("No ContentType matching the passed in Alias: '{0}' was found",
contentTypeAlias));
contentType = contentTypes.First();
if (contentType == null)
throw new Exception(string.Format("ContentType matching the passed in Alias: '{0}' was null",
contentTypeAlias));
}
content = new Content(name, parentId, contentType);
if (Creating.IsRaisedEventCancelled(new NewEventArgs(content, contentTypeAlias, parentId), this))
return content;
content.CreatorId = userId;
content.WriterId = userId;
Created.RaiseEvent(new NewEventArgs(content, false, contentTypeAlias, parentId), this);
Audit.Add(AuditTypes.New, "", content.CreatorId, content.Id);
return content;
}
///
/// Creates an object using the alias of the
/// that this Content is based on.
///
/// Name of the Content object
/// Parent object for the new Content
/// Alias of the
/// Optional id of the user creating the content
///
public IContent CreateContent(string name, IContent parent, string contentTypeAlias, int userId = 0)
{
IContentType contentType = null;
IContent content = null;
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateContentTypeRepository(uow))
{
var query = Query.Builder.Where(x => x.Alias == contentTypeAlias);
var contentTypes = repository.GetByQuery(query);
if (!contentTypes.Any())
throw new Exception(string.Format("No ContentType matching the passed in Alias: '{0}' was found",
contentTypeAlias));
contentType = contentTypes.First();
if (contentType == null)
throw new Exception(string.Format("ContentType matching the passed in Alias: '{0}' was null",
contentTypeAlias));
}
content = new Content(name, parent, contentType);
if (Creating.IsRaisedEventCancelled(new NewEventArgs(content, contentTypeAlias, parent), this))
return content;
content.CreatorId = userId;
content.WriterId = userId;
Created.RaiseEvent(new NewEventArgs(content, false, contentTypeAlias, parent), this);
Audit.Add(AuditTypes.New, "", content.CreatorId, content.Id);
return content;
}
///
/// Gets an object by Id
///
/// Id of the Content to retrieve
///
public IContent GetById(int id)
{
using (var repository = _repositoryFactory.CreateContentRepository(_uowProvider.GetUnitOfWork()))
{
return repository.Get(id);
}
}
///
/// Gets an object by its 'UniqueId'
///
/// Guid key of the Content to retrieve
///
public IContent GetById(Guid key)
{
using (var repository = _repositoryFactory.CreateContentRepository(_uowProvider.GetUnitOfWork()))
{
var query = Query.Builder.Where(x => x.Key == key);
var contents = repository.GetByQuery(query);
return contents.SingleOrDefault();
}
}
///
/// Gets a collection of objects by the Id of the
///
/// Id of the
/// An Enumerable list of objects
public IEnumerable GetContentOfContentType(int id)
{
using (var repository = _repositoryFactory.CreateContentRepository(_uowProvider.GetUnitOfWork()))
{
var query = Query.Builder.Where(x => x.ContentTypeId == id);
var contents = repository.GetByQuery(query);
return contents;
}
}
///
/// Gets a collection of objects by Level
///
/// The level to retrieve Content from
/// An Enumerable list of objects
public IEnumerable GetByLevel(int level)
{
using (var repository = _repositoryFactory.CreateContentRepository(_uowProvider.GetUnitOfWork()))
{
var query = Query.Builder.Where(x => x.Level == level);
var contents = repository.GetByQuery(query);
return contents;
}
}
///
/// Gets a specific version of an item.
///
/// Id of the version to retrieve
/// An item
public IContent GetByVersion(Guid versionId)
{
using (var repository = _repositoryFactory.CreateContentRepository(_uowProvider.GetUnitOfWork()))
{
return repository.GetByVersion(versionId);
}
}
///
/// Gets a collection of an objects versions by Id
///
///
/// An Enumerable list of objects
public IEnumerable GetVersions(int id)
{
using (var repository = _repositoryFactory.CreateContentRepository(_uowProvider.GetUnitOfWork()))
{
var versions = repository.GetAllVersions(id);
return versions;
}
}
///
/// Gets a collection of objects by Parent Id
///
/// Id of the Parent to retrieve Children from
/// An Enumerable list of objects
public IEnumerable GetChildren(int id)
{
using (var repository = _repositoryFactory.CreateContentRepository(_uowProvider.GetUnitOfWork()))
{
var query = Query.Builder.Where(x => x.ParentId == id);
var contents = repository.GetByQuery(query);
return contents;
}
}
///
/// 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) && x.Id != content.Id);
var contents = repository.GetByQuery(query);
return contents;
}
}
///
/// Gets the published version of an item
///
/// Id of the to retrieve version from
/// An item
public IContent GetPublishedVersion(int id)
{
var version = GetVersions(id);
return version.FirstOrDefault(x => x.Published == true);
}
///
/// Gets a collection of objects, which reside at the first level / root
///
/// An Enumerable list of objects
public IEnumerable GetRootContent()
{
using (var repository = _repositoryFactory.CreateContentRepository(_uowProvider.GetUnitOfWork()))
{
var query = Query.Builder.Where(x => x.ParentId == -1);
var contents = repository.GetByQuery(query);
return contents;
}
}
///
/// Gets a collection of objects, which has an expiration date less than or equal to today.
///
/// An Enumerable list of objects
public IEnumerable GetContentForExpiration()
{
using (var repository = _repositoryFactory.CreateContentRepository(_uowProvider.GetUnitOfWork()))
{
var query = Query.Builder.Where(x => x.Published == true && x.ExpireDate <= DateTime.Now);
var contents = repository.GetByQuery(query);
return contents;
}
}
///
/// Gets a collection of objects, which has a release date less than or equal to today.
///
/// An Enumerable list of objects
public IEnumerable GetContentForRelease()
{
using (var repository = _repositoryFactory.CreateContentRepository(_uowProvider.GetUnitOfWork()))
{
var query = Query.Builder.Where(x => x.Published == false && x.ReleaseDate <= DateTime.Now);
var contents = repository.GetByQuery(query);
return contents;
}
}
///
/// Gets a collection of an objects, which resides in the Recycle Bin
///
/// An Enumerable list of objects
public IEnumerable GetContentInRecycleBin()
{
using (var repository = _repositoryFactory.CreateContentRepository(_uowProvider.GetUnitOfWork()))
{
var query = Query.Builder.Where(x => x.ParentId == -20);
var contents = repository.GetByQuery(query);
return contents;
}
}
///
/// 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 && x.Trashed == false);
int count = repository.Count(query);
return count > 0;
}
}
///
/// Checks if the passed in can be published based on the anscestors publish state.
///
/// to check if anscestors are published
/// True if the Content can be published, otherwise False
public bool IsPublishable(IContent content)
{
//If the passed in content has yet to be saved we "fallback" to checking the Parent
//because if the Parent is publishable then the current content can be Saved and Published
if (content.HasIdentity == false)
{
IContent parent = GetById(content.ParentId);
return IsPublishable(parent, true);
}
return IsPublishable(content, false);
}
///
/// Re-Publishes all Content
///
/// Optional Id of the User issueing the publishing
/// True if publishing succeeded, otherwise False
public bool RePublishAll(int userId = 0)
{
return RePublishAllDo(false, userId);
}
///
/// 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 = 0)
{
return SaveAndPublishDo(content, false, userId);
}
///
/// 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 = 0)
{
return PublishWithChildrenDo(content, false, userId);
}
///
/// 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 = 0)
{
return UnPublishDo(content, false, userId);
}
///
/// Saves and Publishes a single object
///
/// The to save and publish
/// Optional Id of the User issueing the publishing
/// Optional boolean indicating whether or not to raise save events.
/// True if publishing succeeded, otherwise False
public bool SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true)
{
return SaveAndPublishDo(content, false, userId, raiseEvents);
}
///
/// Saves a single object
///
/// The to save
/// Optional Id of the User saving the Content
/// Optional boolean indicating whether or not to raise events.
public void Save(IContent content, int userId = 0, bool raiseEvents = true)
{
Save(content, true, userId, raiseEvents);
}
///
/// Saves a collection of objects.
///
///
/// If the collection of content contains new objects that references eachother by Id or ParentId,
/// then use the overload Save method with a collection of Lazy .
///
/// Collection of to save
/// Optional Id of the User saving the Content
/// Optional boolean indicating whether or not to raise events.
public void Save(IEnumerable contents, int userId = 0, bool raiseEvents = true)
{
if(raiseEvents)
{
if (Saving.IsRaisedEventCancelled(new SaveEventArgs(contents), this))
return;
}
var containsNew = contents.Any(x => x.HasIdentity == false);
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateContentRepository(uow))
{
if (containsNew)
{
foreach (var content in contents)
{
content.WriterId = userId;
//Only change the publish state if the "previous" version was actually published
if (content.Published)
content.ChangePublishedState(PublishedState.Saved);
repository.AddOrUpdate(content);
uow.Commit();
}
}
else
{
foreach (var content in contents)
{
content.WriterId = userId;
repository.AddOrUpdate(content);
}
uow.Commit();
}
}
if(raiseEvents)
Saved.RaiseEvent(new SaveEventArgs(contents, false), this);
Audit.Add(AuditTypes.Save, "Bulk Save content performed by user", userId == -1 ? 0 : userId, -1);
}
///
/// 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 = 0)
{
using (var uow = _uowProvider.GetUnitOfWork())
{
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);
if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(contents), this))
return;
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);
foreach (var child in children)
{
if (child.ContentType.Id != contentTypeId)
MoveToRecycleBin(child, userId);
}
//Permantly delete the content
Delete(content, userId);
}
}
Audit.Add(AuditTypes.Delete,
string.Format("Delete Content of Type {0} performed by user", contentTypeId),
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 = 0)
{
if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(content), this))
return;
//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);
}
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateContentRepository(uow))
{
repository.Delete(content);
uow.Commit();
}
Deleted.RaiseEvent(new DeleteEventArgs(content, false), this);
Audit.Add(AuditTypes.Delete, "Delete Content performed by user", userId, content.Id);
}
///
/// Permanently deletes versions from an object prior to a specific date.
///
/// Id of the object to delete versions from
/// Latest version date
/// Optional Id of the User deleting versions of a Content object
public void DeleteVersions(int id, DateTime versionDate, int userId = 0)
{
//TODO: We should check if we are going to delete the most recent version because if that happens it means the
// entity is completely deleted and we should raise the normal Deleting/Deleted event
if (DeletingVersions.IsRaisedEventCancelled(new DeleteRevisionsEventArgs(id, dateToRetain: versionDate), this))
return;
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateContentRepository(uow))
{
repository.DeleteVersions(id, versionDate);
uow.Commit();
}
DeletedVersions.RaiseEvent(new DeleteRevisionsEventArgs(id, false, dateToRetain: versionDate), this);
Audit.Add(AuditTypes.Delete, "Delete Content by version date performed by user", userId, -1);
}
///
/// Permanently deletes specific version(s) from an object.
///
/// Id of the object to delete a version from
/// Id of the version to delete
/// Boolean indicating whether to delete versions prior to the versionId
/// Optional Id of the User deleting versions of a Content object
public void DeleteVersion(int id, Guid versionId, bool deletePriorVersions, int userId = 0)
{
//TODO: We should check if we are going to delete the most recent version because if that happens it means the
// entity is completely deleted and we should raise the normal Deleting/Deleted event
if (deletePriorVersions)
{
var content = GetByVersion(versionId);
DeleteVersions(id, content.UpdateDate, userId);
}
if (DeletingVersions.IsRaisedEventCancelled(new DeleteRevisionsEventArgs(id, specificVersion: versionId), this))
return;
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateContentRepository(uow))
{
repository.DeleteVersion(versionId);
uow.Commit();
}
DeletedVersions.RaiseEvent(new DeleteRevisionsEventArgs(id, false, specificVersion:versionId), this);
Audit.Add(AuditTypes.Delete, "Delete Content by version performed by user", userId, -1);
}
///
/// Deletes an object by moving it to the Recycle Bin
///
/// Move an item to the Recycle Bin will result in the item being unpublished
/// The to delete
/// Optional Id of the User deleting the Content
public void MoveToRecycleBin(IContent content, int userId = 0)
{
if (Trashing.IsRaisedEventCancelled(new MoveEventArgs(content, -20), this))
return;
//Make sure that published content is unpublished before being moved to the Recycle Bin
if (HasPublishedVersion(content.Id))
{
UnPublish(content, userId);
}
//Unpublish descendents of the content item that is being moved to trash
var descendants = GetDescendants(content).ToList();
foreach (var descendant in descendants)
{
UnPublish(descendant, userId);
}
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateContentRepository(uow))
{
content.WriterId = userId;
content.ChangeTrashedState(true);
repository.AddOrUpdate(content);
//Loop through descendants to update their trash state, but ensuring structure by keeping the ParentId
foreach (var descendant in descendants)
{
descendant.WriterId = userId;
descendant.ChangeTrashedState(true, descendant.ParentId);
repository.AddOrUpdate(descendant);
}
uow.Commit();
}
Trashed.RaiseEvent(new MoveEventArgs(content, false, -20), this);
Audit.Add(AuditTypes.Move, "Move Content to Recycle Bin performed by user", userId, content.Id);
}
///
/// Moves an object to a new location by changing its parent id.
///
///
/// If the object is already published it will be
/// published after being moved to its new location. Otherwise it'll just
/// be saved with a new parent id.
///
/// The to move
/// Id of the Content's new Parent
/// Optional Id of the User moving the Content
public void Move(IContent content, int parentId, int userId = 0)
{
//This ensures that the correct method is called if this method is used to Move to recycle bin.
if (parentId == -20)
{
MoveToRecycleBin(content, userId);
return;
}
if (Moving.IsRaisedEventCancelled(new MoveEventArgs(content, parentId), this))
return;
content.WriterId = userId;
var parent = GetById(parentId);
content.Path = string.Concat(parent.Path, ",", content.Id);
content.Level = parent.Level + 1;
//If Content is being moved away from Recycle Bin, its state should be un-trashed
if (content.Trashed && parentId != -20)
{
content.ChangeTrashedState(false, parentId);
}
else
{
content.ParentId = parentId;
}
//If Content is published, it should be (re)published from its new location
if (content.Published)
{
//If Content is Publishable its saved and published
//otherwise we save the content without changing the publish state, and generate new xml because the Path, Level and Parent has changed.
if (IsPublishable(content))
{
SaveAndPublish(content, userId);
}
else
{
Save(content, false, userId, true);
using (var uow = _uowProvider.GetUnitOfWork())
{
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));
}
}
}
else
{
Save(content, userId);
}
//Ensure that Path and Level is updated on children
var children = GetChildren(content.Id);
if (children.Any())
{
foreach (var child in children)
{
Move(child, content.Id, userId);
}
}
Moved.RaiseEvent(new MoveEventArgs(content, false, parentId), this);
Audit.Add(AuditTypes.Move, "Move Content performed by user", userId, content.Id);
}
///
/// Empties the Recycle Bin by deleting all that resides in the bin
///
public void EmptyRecycleBin()
{
//TODO: Why don't we have a base class to share between MediaService/ContentService as some of this is exacty the same?
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateContentRepository(uow))
{
var query = Query.Builder.Where(x => x.ParentId == -20);
var contents = repository.GetByQuery(query);
foreach (var content in contents)
{
if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(content), this))
continue;
repository.Delete(content);
Deleted.RaiseEvent(new DeleteEventArgs(content, false), this);
}
uow.Commit();
}
Audit.Add(AuditTypes.Delete, "Empty Recycle Bin performed by user", 0, -20);
}
///
/// 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 = 0)
{
var copy = ((Content)content).Clone();
copy.ParentId = parentId;
// A copy should never be set to published automatically even if the original was.
copy.ChangePublishedState(PublishedState.Unpublished);
if (Copying.IsRaisedEventCancelled(new CopyEventArgs(content, copy, parentId), this))
return null;
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateContentRepository(uow))
{
content.WriterId = userId;
repository.AddOrUpdate(copy);
uow.Commit();
//Special case for the Upload DataType
var uploadDataTypeId = new Guid("5032a6e6-69e3-491d-bb28-cd31cd11086c");
if (content.Properties.Any(x => x.PropertyType.DataTypeId == uploadDataTypeId))
{
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.DataTypeId == uploadDataTypeId
&& 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();
}
}
//Special case for the Tags DataType
var tagsDataTypeId = new Guid("4023e540-92f5-11dd-ad8b-0800200c9a66");
if (content.Properties.Any(x => x.PropertyType.DataTypeId == tagsDataTypeId))
{
var tags = uow.Database.Fetch("WHERE nodeId = @Id", new {Id = content.Id});
foreach (var tag in tags)
{
uow.Database.Insert(new TagRelationshipDto {NodeId = copy.Id, TagId = tag.TagId});
}
}
}
//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);
}
Copied.RaiseEvent(new CopyEventArgs(content, copy, false, parentId), this);
Audit.Add(AuditTypes.Copy, "Copy Content performed by user", content.WriterId, content.Id);
return copy;
}
///
/// 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
internal bool SendToPublication(IContent content, int userId = 0)
{
if (SendingToPublish.IsRaisedEventCancelled(new SendToPublishEventArgs(content), this))
return false;
//TODO: Do some stuff here.. RunActionHandlers
SentToPublish.RaiseEvent(new SendToPublishEventArgs(content, false), this);
Audit.Add(AuditTypes.SendToPublish, "Send to Publish performed by user", content.WriterId, content.Id);
return true;
}
///
/// Rollback an object to a previous version.
/// This will create a new version, which is a copy of all the old data.
///
///
/// The way data is stored actually only allows us to rollback on properties
/// and not data like Name and Alias of the Content.
///
/// Id of the being rolled back
/// Id of the version to rollback to
/// Optional Id of the User issueing the rollback of the Content
/// The newly created object
public IContent Rollback(int id, Guid versionId, int userId = 0)
{
var content = GetByVersion(versionId);
if (RollingBack.IsRaisedEventCancelled(new RollbackEventArgs(content), this))
return content;
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateContentRepository(uow))
{
content.WriterId = userId;
content.CreatorId = userId;
repository.AddOrUpdate(content);
uow.Commit();
}
RolledBack.RaiseEvent(new RollbackEventArgs(content, false), this);
Audit.Add(AuditTypes.RollBack, "Content rollback performed by user", content.WriterId, content.Id);
return content;
}
#region Internal Methods
///
/// Internal method to Re-Publishes all Content for legacy purposes.
///
/// 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 not update the cache.
/// True if publishing succeeded, otherwise False
internal bool RePublishAll(bool omitCacheRefresh = true, int userId = 0)
{
return RePublishAllDo(omitCacheRefresh, userId);
}
///
/// Internal method that Publishes a single object for legacy purposes.
///
/// The to publish
/// Optional boolean to avoid having the cache refreshed when calling this Publish method. By default this method will not update the cache.
/// Optional Id of the User issueing the publishing
/// True if publishing succeeded, otherwise False
internal bool Publish(IContent content, bool omitCacheRefresh = true, int userId = 0)
{
return SaveAndPublishDo(content, omitCacheRefresh, userId);
}
///
/// Internal method that Publishes a object and all its children for legacy purposes.
///
/// The to publish along with its children
/// Optional boolean to avoid having the cache refreshed when calling this Publish method. By default this method will not update the cache.
/// Optional Id of the User issueing the publishing
/// True if publishing succeeded, otherwise False
internal bool PublishWithChildren(IContent content, bool omitCacheRefresh = true, int userId = 0)
{
return PublishWithChildrenDo(content, omitCacheRefresh, userId);
}
///
/// Internal method that UnPublishes a single object for legacy purposes.
///
/// The to publish
/// Optional boolean to avoid having the cache refreshed when calling this Unpublish method. By default this method will not update the cache.
/// Optional Id of the User issueing the publishing
/// True if unpublishing succeeded, otherwise False
internal bool UnPublish(IContent content, bool omitCacheRefresh = true, int userId = 0)
{
return UnPublishDo(content, omitCacheRefresh, userId);
}
///
/// Saves and Publishes a single object
///
/// The to save and publish
/// Optional boolean to avoid having the cache refreshed when calling this Publish method. By default this method will not update the cache.
/// Optional Id of the User issueing the publishing
/// Optional boolean indicating whether or not to raise save events.
/// True if publishing succeeded, otherwise False
internal bool SaveAndPublish(IContent content, bool omitCacheRefresh = true, int userId = 0, bool raiseEvents = true)
{
return SaveAndPublishDo(content, omitCacheRefresh, userId, raiseEvents);
}
///
/// Gets a collection of descendants by the first Parent.
///
/// item to retrieve Descendants from
/// An Enumerable list of objects
internal IEnumerable GetPublishedDescendants(IContent content)
{
using (var repository = _repositoryFactory.CreateContentRepository(_uowProvider.GetUnitOfWork()))
{
var query = Query.Builder.Where(x => x.Id != content.Id && x.Path.StartsWith(content.Path) && x.Published == true && x.Trashed == false);
var contents = repository.GetByQuery(query);
return contents;
}
}
#endregion
#region Private Methods
///
/// 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
private bool RePublishAllDo(bool omitCacheRefresh = false, int userId = 0)
{
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"));
var rootContent = GetRootContent();
foreach (var content in rootContent)
{
if (content.IsValid())
{
list.Add(content);
list.AddRange(GetDescendants(content));
}
}
//Publish and then update the database with new status
var published = _publishingStrategy.PublishWithChildren(list, userId);
if (published)
{
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateContentRepository(uow))
{
//Only loop through content where the Published property has been updated
foreach (var item in list.Where(x => ((ICanBeDirty)x).IsPropertyDirty("Published")))
{
item.WriterId = userId;
repository.AddOrUpdate(item);
updated.Add(item);
}
uow.Commit();
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);
return published;
}
///
/// Publishes a object and all its children
///
/// The to publish along with its children
/// Optional boolean to avoid having the cache refreshed when calling this Publish method. By default this method will update the cache.
/// Optional Id of the User issueing the publishing
/// True if publishing succeeded, otherwise False
private bool PublishWithChildrenDo(IContent content, bool omitCacheRefresh = false, int userId = 0)
{
//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 && IsPublishable(content) == false)
{
LogHelper.Info(
string.Format(
"Content '{0}' with Id '{1}' could not be published because its parent or one of its ancestors is not published.",
content.Name, content.Id));
return false;
}
//Content contains invalid property values and can therefore not be published - fire event?
if (!content.IsValid())
{
LogHelper.Info(
string.Format("Content '{0}' with Id '{1}' could not be published because of invalid properties.",
content.Name, content.Id));
return false;
}
//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(GetDescendants(content));
//Publish and then update the database with new status
var published = _publishingStrategy.PublishWithChildren(list, userId);
if (published)
{
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateContentRepository(uow))
{
//Only loop through content where the Published property has been updated
foreach (var item in list.Where(x => ((ICanBeDirty)x).IsPropertyDirty("Published")))
{
item.WriterId = userId;
repository.AddOrUpdate(item);
updated.Add(item);
}
uow.Commit();
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, content.Id);
}
return published;
}
///
/// UnPublishes a single object
///
/// The to publish
/// Optional boolean to avoid having the cache refreshed when calling this Unpublish method. By default this method will update the cache.
/// Optional Id of the User issueing the publishing
/// True if unpublishing succeeded, otherwise False
private bool UnPublishDo(IContent content, bool omitCacheRefresh = false, int userId = 0)
{
var unpublished = _publishingStrategy.UnPublish(content, userId);
if (unpublished)
{
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateContentRepository(uow))
{
content.WriterId = userId;
repository.AddOrUpdate(content);
//Remove 'published' xml from the cmsContentXml table for the unpublished content
uow.Database.Delete("WHERE nodeId = @Id", new { Id = content.Id });
uow.Commit();
}
//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, content.Id);
}
return unpublished;
}
///
/// Saves and Publishes a single object
///
/// The to save and publish
/// Optional boolean to avoid having the cache refreshed when calling this Publish method. By default this method will update the cache.
/// Optional Id of the User issueing the publishing
/// Optional boolean indicating whether or not to raise save events.
/// True if publishing succeeded, otherwise False
private bool SaveAndPublishDo(IContent content, bool omitCacheRefresh = false, int userId = 0, bool raiseEvents = true)
{
if(raiseEvents)
{
if (Saving.IsRaisedEventCancelled(new SaveEventArgs(content), this))
return false;
}
//Has this content item previously been published? If so, we don't need to refresh the children
var previouslyPublished = HasPublishedVersion(content.Id);
//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 && IsPublishable(content) == false)
{
LogHelper.Info(
string.Format(
"Content '{0}' with Id '{1}' could not be published because its parent is not published.",
content.Name, content.Id));
return false;
}
//Content contains invalid property values and can therefore not be published - fire event?
if (!content.IsValid())
{
LogHelper.Info(
string.Format(
"Content '{0}' with Id '{1}' could not be published because of invalid properties.",
content.Name, content.Id));
return false;
}
//Publish and then update the database with new status
bool published = _publishingStrategy.Publish(content, userId);
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateContentRepository(uow))
{
//Since this is the Save and Publish method, the content should be saved even though the publish fails or isn't allowed
content.WriterId = 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));
}
}
if(raiseEvents)
Saved.RaiseEvent(new SaveEventArgs(content, false), this);
//Save xml to db and call following method to fire event through PublishingStrategy to update cache
if (omitCacheRefresh == false)
_publishingStrategy.PublishingFinalized(content);
//We need to check if children and their publish state to ensure that we 'republish' content that was previously published
if (omitCacheRefresh == false && previouslyPublished == false && HasChildren(content.Id))
{
var descendants = GetPublishedDescendants(content);
_publishingStrategy.PublishingFinalized(descendants, false);
}
Audit.Add(AuditTypes.Publish, "Save and Publish performed by user", userId, content.Id);
return published;
}
///
/// Saves a single object
///
/// The to save
/// Boolean indicating whether or not to change the Published state upon saving
/// Optional Id of the User saving the Content
/// Optional boolean indicating whether or not to raise events.
private void Save(IContent content, bool changeState, int userId = 0, bool raiseEvents = true)
{
if(raiseEvents)
{
if (Saving.IsRaisedEventCancelled(new SaveEventArgs(content), this))
return;
}
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateContentRepository(uow))
{
content.WriterId = userId;
//Only change the publish state if the "previous" version was actually published
if (changeState && content.Published)
content.ChangePublishedState(PublishedState.Saved);
repository.AddOrUpdate(content);
uow.Commit();
}
if(raiseEvents)
Saved.RaiseEvent(new SaveEventArgs(content, false), this);
Audit.Add(AuditTypes.Save, "Save Content performed by user", userId, content.Id);
}
///
/// Checks if the passed in can be published based on the anscestors publish state.
///
///
/// Check current is only used when falling back to checking the Parent of non-saved content, as
/// non-saved content doesn't have a valid path yet.
///
/// to check if anscestors are published
/// Boolean indicating whether the passed in content should also be checked for published versions
/// True if the Content can be published, otherwise False
private bool IsPublishable(IContent content, bool checkCurrent)
{
var ids = content.Path.Split(',').Select(int.Parse).ToList();
foreach (var id in ids)
{
//If Id equals that of the recycle bin we return false because nothing in the bin can be published
if (id == -20)
return false;
//We don't check the System Root, so just continue
if (id == -1) continue;
//If the current id equals that of the passed in content and if current shouldn't be checked we skip it.
if (checkCurrent == false && id == content.Id) continue;
//Check if the content for the current id is published - escape the loop if we encounter content that isn't published
var hasPublishedVersion = HasPublishedVersion(id);
if (hasPublishedVersion == false)
return false;
}
return true;
}
#endregion
#region Event Handlers
///
/// Occurs before Delete
///
public static event TypedEventHandler> Deleting;
///
/// Occurs after Delete
///
public static event TypedEventHandler> Deleted;
///
/// Occurs before Delete Versions
///
public static event TypedEventHandler DeletingVersions;
///
/// Occurs after Delete Versions
///
public static event TypedEventHandler DeletedVersions;
///
/// Occurs before Save
///
public static event TypedEventHandler> Saving;
///
/// Occurs after Save
///
public static event TypedEventHandler> Saved;
///
/// Occurs before Create
///
public static event TypedEventHandler> Creating;
///
/// Occurs after Create
///
///
/// Please note that the Content object has been created, but not saved
/// so it does not have an identity yet (meaning no Id has been set).
///
public static event TypedEventHandler> Created;
///
/// Occurs before Copy
///
public static event TypedEventHandler> Copying;
///
/// Occurs after Copy
///
public static event TypedEventHandler> Copied;
///
/// Occurs before Content is moved to Recycle Bin
///
public static event TypedEventHandler> Trashing;
///
/// Occurs after Content is moved to Recycle Bin
///
public static event TypedEventHandler> Trashed;
///
/// Occurs before Move
///
public static event TypedEventHandler> Moving;
///
/// Occurs after Move
///
public static event TypedEventHandler> Moved;
///
/// Occurs before Rollback
///
public static event TypedEventHandler> RollingBack;
///
/// Occurs after Rollback
///
public static event TypedEventHandler> RolledBack;
///
/// Occurs before Send to Publish
///
public static event TypedEventHandler> SendingToPublish;
///
/// Occurs after Send to Publish
///
public static event TypedEventHandler> SentToPublish;
#endregion
}
}