using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Xml;
using System.Xml.Linq;
using Umbraco.Core.Events;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.UnitOfWork;
using Umbraco.Core.Publishing;
using Umbraco.Core.Scoping;
namespace Umbraco.Core.Services
{
///
/// Represents the Content Service, which is an easy access to operations involving
///
public class ContentService : ScopeRepositoryService, IContentService, IContentServiceOperations
{
private readonly IPublishingStrategy2 _publishingStrategy;
private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer();
private readonly IDataTypeService _dataTypeService;
private readonly IUserService _userService;
//Support recursive locks because some of the methods that require locking call other methods that require locking.
//for example, the Move method needs to be locked but this calls the Save method which also needs to be locked.
private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
public ContentService(
IDatabaseUnitOfWorkProvider provider,
RepositoryFactory repositoryFactory,
ILogger logger,
IEventMessagesFactory eventMessagesFactory,
IDataTypeService dataTypeService,
IUserService userService)
: base(provider, repositoryFactory, logger, eventMessagesFactory)
{
if (dataTypeService == null) throw new ArgumentNullException("dataTypeService");
if (userService == null) throw new ArgumentNullException("userService");
_publishingStrategy = new PublishingStrategy(eventMessagesFactory, logger);
_dataTypeService = dataTypeService;
_userService = userService;
}
#region Static Queries
private IQuery _notTrashedQuery;
#endregion
public int CountPublished(string contentTypeAlias = null)
{
using (var uow = UowProvider.GetUnitOfWork(commit: true))
{
var repository = RepositoryFactory.CreateContentRepository(uow);
return repository.CountPublished();
}
}
public int Count(string contentTypeAlias = null)
{
using (var uow = UowProvider.GetUnitOfWork(commit: true))
{
var repository = RepositoryFactory.CreateContentRepository(uow);
return repository.Count(contentTypeAlias);
}
}
public int CountChildren(int parentId, string contentTypeAlias = null)
{
using (var uow = UowProvider.GetUnitOfWork(commit: true))
{
var repository = RepositoryFactory.CreateContentRepository(uow);
return repository.CountChildren(parentId, contentTypeAlias);
}
}
public int CountDescendants(int parentId, string contentTypeAlias = null)
{
using (var uow = UowProvider.GetUnitOfWork(commit: true))
{
var repository = RepositoryFactory.CreateContentRepository(uow);
return repository.CountDescendants(parentId, contentTypeAlias);
}
}
///
/// Used to bulk update the permissions set for a content item. This will replace all permissions
/// assigned to an entity with a list of user id & permission pairs.
///
///
public void ReplaceContentPermissions(EntityPermissionSet permissionSet)
{
using (var uow = UowProvider.GetUnitOfWork())
{
var repository = RepositoryFactory.CreateContentRepository(uow);
repository.ReplaceContentPermissions(permissionSet);
uow.Commit();
}
}
///
/// Assigns a single permission to the current content item for the specified user ids
///
///
///
///
public void AssignContentPermission(IContent entity, char permission, IEnumerable userIds)
{
using (var uow = UowProvider.GetUnitOfWork())
{
var repository = RepositoryFactory.CreateContentRepository(uow);
repository.AssignEntityPermission(entity, permission, userIds);
uow.Commit();
}
}
///
/// Gets the list of permissions for the content item
///
///
///
public IEnumerable GetPermissionsForEntity(IContent content)
{
using (var uow = UowProvider.GetUnitOfWork(commit: true))
{
var repository = RepositoryFactory.CreateContentRepository(uow);
return repository.GetPermissionsForEntity(content.Id);
}
}
///
/// Creates an object using the alias of the
/// that this Content should based on.
///
///
/// Note that using this method will simply return a new IContent without any identity
/// as it has not yet been persisted. It is intended as a shortcut to creating new content objects
/// that does not invoke a save operation against the database.
///
/// 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)
{
var contentType = FindContentTypeByAlias(contentTypeAlias);
var content = new Content(name, parentId, contentType);
var parent = GetById(content.ParentId);
content.Path = string.Concat(parent.IfNotNull(x => x.Path, content.ParentId.ToString()), ",", content.Id);
// fixme - why are we creating a UOW here and not a SCOPE FFS!
using (var uow = UowProvider.GetUnitOfWork(/*commit: true*/)) // FIXME + we are WRITING audit info FFS!
{
// fixme
if (uow.Events.DispatchCancelable(Creating, this, new NewEventArgs(content, contentTypeAlias, parentId)))
//if (Creating.IsRaisedEventCancelled(new NewEventArgs(content, contentTypeAlias, parentId), this, uow.Events))
{
content.WasCancelled = true;
return content;
}
content.CreatorId = userId;
content.WriterId = userId;
// fixme
uow.Events.Dispatch(Created, this, new NewEventArgs(content, false, contentTypeAlias, parentId));
//Created.RaiseEvent(new NewEventArgs(content, false, contentTypeAlias, parentId), this, uow.Events);
// fixme
var auditRepo = RepositoryFactory.CreateAuditRepository(uow);
auditRepo.AddOrUpdate(new AuditItem(content.Id, string.Format("Content '{0}' was created", name), AuditType.New, content.CreatorId));
uow.Commit();
}
// fixme duplicate + NOOOOO not another UOW!
//Audit(AuditType.New, string.Format("Content '{0}' was created", name), content.CreatorId, content.Id);
return content;
}
///
/// Creates an object using the alias of the
/// that this Content should based on.
///
///
/// Note that using this method will simply return a new IContent without any identity
/// as it has not yet been persisted. It is intended as a shortcut to creating new content objects
/// that does not invoke a save operation against the database.
///
/// 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)
{
if (parent == null) throw new ArgumentNullException("parent");
var contentType = FindContentTypeByAlias(contentTypeAlias);
var content = new Content(name, parent, contentType);
content.Path = string.Concat(parent.Path, ",", content.Id);
using (var uow = UowProvider.GetUnitOfWork())
{
if (uow.Events.DispatchCancelable(Creating, this, new NewEventArgs(content, contentTypeAlias, parent)))
{
content.WasCancelled = true;
return content;
}
content.CreatorId = userId;
content.WriterId = userId;
uow.Events.Dispatch(Created, this, new NewEventArgs(content, false, contentTypeAlias, parent));
}
// MOVE into the UOW + COMMIT the f*cking thing
Audit(AuditType.New, string.Format("Content '{0}' was created", name), content.CreatorId, content.Id);
return content;
}
///
/// Creates and saves an object using the alias of the
/// that this Content should based on.
///
///
/// This method returns an object that has been persisted to the database
/// and therefor has an identity.
///
/// 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 CreateContentWithIdentity(string name, int parentId, string contentTypeAlias, int userId = 0)
{
var contentType = FindContentTypeByAlias(contentTypeAlias);
var content = new Content(name, parentId, contentType);
using (var uow = UowProvider.GetUnitOfWork())
{
//NOTE: I really hate the notion of these Creating/Created events - they are so inconsistent, I've only just found
// out that in these 'WithIdentity' methods, the Saving/Saved events were not fired, wtf. Anyways, they're added now.
if (uow.Events.DispatchCancelable(Creating, this, new NewEventArgs(content, contentTypeAlias, parentId)))
{
content.WasCancelled = true;
return content;
}
if (uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs(content)))
{
content.WasCancelled = true;
return content;
}
var repository = RepositoryFactory.CreateContentRepository(uow);
content.CreatorId = userId;
content.WriterId = userId;
repository.AddOrUpdate(content);
//Generate a new preview
repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c));
uow.Commit();
uow.Events.Dispatch(Saved, this, new SaveEventArgs(content, false));
uow.Events.Dispatch(Created, this, new NewEventArgs(content, false, contentTypeAlias, parentId));
}
Audit(AuditType.New, string.Format("Content '{0}' was created with Id {1}", name, content.Id), content.CreatorId, content.Id);
return content;
}
///
/// Creates and saves an object using the alias of the
/// that this Content should based on.
///
///
/// This method returns an object that has been persisted to the database
/// and therefor has an identity.
///
/// Name of the Content object
/// Parent object for the new Content
/// Alias of the
/// Optional id of the user creating the content
///
public IContent CreateContentWithIdentity(string name, IContent parent, string contentTypeAlias, int userId = 0)
{
if (parent == null) throw new ArgumentNullException("parent");
var contentType = FindContentTypeByAlias(contentTypeAlias);
var content = new Content(name, parent, contentType);
using (var uow = UowProvider.GetUnitOfWork())
{
//NOTE: I really hate the notion of these Creating/Created events - they are so inconsistent, I've only just found
// out that in these 'WithIdentity' methods, the Saving/Saved events were not fired, wtf. Anyways, they're added now.
if (uow.Events.DispatchCancelable(Creating, this, new NewEventArgs(content, contentTypeAlias, parent)))
{
content.WasCancelled = true;
return content;
}
if (uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs(content)))
{
content.WasCancelled = true;
return content;
}
var repository = RepositoryFactory.CreateContentRepository(uow);
content.CreatorId = userId;
content.WriterId = userId;
repository.AddOrUpdate(content);
//Generate a new preview
repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c));
uow.Commit();
uow.Events.Dispatch(Saved, this, new SaveEventArgs(content, false));
uow.Events.Dispatch(Created, this, new NewEventArgs(content, false, contentTypeAlias, parent));
}
Audit(AuditType.New, string.Format("Content '{0}' was created with Id {1}", name, content.Id), content.CreatorId, content.Id);
return content;
}
///
/// Gets an object by Id
///
/// Id of the Content to retrieve
///
public IContent GetById(int id)
{
using (var uow = UowProvider.GetUnitOfWork(commit: true))
{
var repository = RepositoryFactory.CreateContentRepository(uow);
return repository.Get(id);
}
}
///
/// Gets an object by Id
///
/// Ids of the Content to retrieve
///
public IEnumerable GetByIds(IEnumerable ids)
{
var idsArray = ids.ToArray();
if (idsArray.Length == 0) return Enumerable.Empty();
using (var uow = UowProvider.GetUnitOfWork())
{
var repository = RepositoryFactory.CreateContentRepository(uow);
//ensure that the result has the order based on the ids passed in
var result = repository.GetAll(idsArray);
var content = result.ToDictionary(x => x.Id, x => x);
var sortedResult = idsArray.Select(x =>
{
IContent c;
return content.TryGetValue(x, out c) ? c : null;
}).WhereNotNull();
uow.Commit();
return sortedResult;
}
}
///
/// Gets an object by its 'UniqueId'
///
/// Guid key of the Content to retrieve
///
public IContent GetById(Guid key)
{
using (var uow = UowProvider.GetUnitOfWork(commit: true))
{
var repository = RepositoryFactory.CreateContentRepository(uow);
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 uow = UowProvider.GetUnitOfWork(commit: true))
{
var repository = RepositoryFactory.CreateContentRepository(uow);
var query = Query.Builder.Where(x => x.ContentTypeId == id);
return repository.GetByQuery(query);
}
}
internal IEnumerable GetPublishedContentOfContentType(int id)
{
using (var uow = UowProvider.GetUnitOfWork(commit: true))
{
var repository = RepositoryFactory.CreateContentRepository(uow);
var query = Query.Builder.Where(x => x.ContentTypeId == id);
return repository.GetByPublishedVersion(query);
}
}
///
/// 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 uow = UowProvider.GetUnitOfWork(commit: true))
{
var repository = RepositoryFactory.CreateContentRepository(uow);
var query = Query.Builder.Where(x => x.Level == level && x.Path.StartsWith(Constants.System.RecycleBinContent.ToInvariantString()) == false);
return repository.GetByQuery(query);
}
}
///
/// Gets a specific version of an item.
///
/// Id of the version to retrieve
/// An item
public IContent GetByVersion(Guid versionId)
{
using (var uow = UowProvider.GetUnitOfWork(commit: true))
{
var repository = RepositoryFactory.CreateContentRepository(uow);
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 uow = UowProvider.GetUnitOfWork(commit: true))
{
var repository = RepositoryFactory.CreateContentRepository(uow);
return repository.GetAllVersions(id);
}
}
///
/// Gets a list of all version Ids for the given content item ordered so latest is first
///
///
/// The maximum number of rows to return
///
public IEnumerable GetVersionIds(int id, int maxRows)
{
using (var uow = UowProvider.GetUnitOfWork(commit: true))
{
var repository = RepositoryFactory.CreateContentRepository(uow);
return repository.GetVersionIds(id, maxRows);
}
}
///
/// Gets a collection of objects, which are ancestors of the current content.
///
/// Id of the to retrieve ancestors for
/// An Enumerable list of objects
public IEnumerable GetAncestors(int id)
{
var content = GetById(id);
return GetAncestors(content);
}
///
/// Gets a collection of objects, which are ancestors of the current content.
///
/// to retrieve ancestors for
/// An Enumerable list of objects
public IEnumerable GetAncestors(IContent content)
{
//null check otherwise we get exceptions
if (content.Path.IsNullOrWhiteSpace()) return Enumerable.Empty();
var ids = content.Path.Split(',').Where(x => x != Constants.System.Root.ToInvariantString() && x != content.Id.ToString(CultureInfo.InvariantCulture)).Select(int.Parse).ToArray();
if (ids.Any() == false)
return new List();
using (var uow = UowProvider.GetUnitOfWork(commit: true))
{
var repository = RepositoryFactory.CreateContentRepository(uow);
return repository.GetAll(ids);
}
}
///
/// 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 uow = UowProvider.GetUnitOfWork(commit: true))
{
var repository = RepositoryFactory.CreateContentRepository(uow);
var query = Query.Builder.Where(x => x.ParentId == id);
return repository.GetByQuery(query).OrderBy(x => x.SortOrder);
}
}
[Obsolete("Use the overload with 'long' parameter types instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
public IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalChildren,
string orderBy, Direction orderDirection, string filter = "")
{
long total;
var result = GetPagedChildren(id, Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, true, filter);
totalChildren = Convert.ToInt32(total);
return result;
}
///
/// Gets a collection of objects by Parent Id
///
/// Id of the Parent to retrieve Children from
/// Page index (zero based)
/// Page size
/// Total records query would return without paging
/// Field to order by
/// Direction to order by
/// Search text filter
/// An Enumerable list of objects
public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren,
string orderBy, Direction orderDirection, string filter = "")
{
return GetPagedChildren(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filter);
}
///
/// Gets a collection of objects by Parent Id
///
/// Id of the Parent to retrieve Children from
/// Page index (zero based)
/// Page size
/// Total records query would return without paging
/// Field to order by
/// Direction to order by
/// Flag to indicate when ordering by system field
/// Search text filter
/// An Enumerable list of objects
public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren,
string orderBy, Direction orderDirection, bool orderBySystemField, string filter)
{
Mandate.ParameterCondition(pageIndex >= 0, "pageIndex");
Mandate.ParameterCondition(pageSize > 0, "pageSize");
using (var uow = UowProvider.GetUnitOfWork(commit: true))
{
var repository = RepositoryFactory.CreateContentRepository(uow);
var query = Query.Builder;
//if the id is System Root, then just get all
if (id != Constants.System.Root)
{
query.Where(x => x.ParentId == id);
}
IQuery filterQuery = null;
if (filter.IsNullOrWhiteSpace() == false)
{
filterQuery = Query.Builder.Where(x => x.Name.Contains(filter));
}
return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filterQuery);
}
}
[Obsolete("Use the overload with 'long' parameter types instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "")
{
long total;
var result = GetPagedDescendants(id, Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, true, filter);
totalChildren = Convert.ToInt32(total);
return result;
}
///
/// Gets a collection of objects by Parent Id
///
/// Id of the Parent to retrieve Descendants from
/// Page number
/// Page size
/// Total records query would return without paging
/// Field to order by
/// Direction to order by
/// Search text filter
/// An Enumerable list of objects
public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "")
{
return GetPagedDescendants(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filter);
}
///
/// Gets a collection of objects by Parent Id
///
/// Id of the Parent to retrieve Descendants from
/// Page number
/// Page size
/// Total records query would return without paging
/// Field to order by
/// Direction to order by
/// Flag to indicate when ordering by system field
/// Search text filter
/// An Enumerable list of objects
public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, string filter)
{
Mandate.ParameterCondition(pageIndex >= 0, "pageIndex");
Mandate.ParameterCondition(pageSize > 0, "pageSize");
using (var uow = UowProvider.GetUnitOfWork(commit: true))
{
var repository = RepositoryFactory.CreateContentRepository(uow);
var query = Query.Builder;
//if the id is System Root, then just get all
if (id != Constants.System.Root)
{
query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar));
}
IQuery filterQuery = null;
if (filter.IsNullOrWhiteSpace() == false)
{
filterQuery = Query.Builder.Where(x => x.Name.Contains(filter));
}
return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filterQuery);
}
}
///
/// Gets a collection of objects by Parent Id
///
/// Id of the Parent to retrieve Descendants from
/// Page number
/// Page size
/// Total records query would return without paging
/// Field to order by
/// Direction to order by
/// Flag to indicate when ordering by system field
/// Search filter
/// An Enumerable list of objects
public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter)
{
Mandate.ParameterCondition(pageIndex >= 0, "pageIndex");
Mandate.ParameterCondition(pageSize > 0, "pageSize");
using (var uow = UowProvider.GetUnitOfWork(commit: true))
{
var repository = RepositoryFactory.CreateContentRepository(uow);
var query = Query.Builder;
//if the id is System Root, then just get all
if (id != Constants.System.Root)
{
query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar));
}
return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter);
}
}
///
/// 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 uow = UowProvider.GetUnitOfWork(commit: true))
{
var repository = RepositoryFactory.CreateContentRepository(uow);
var query = Query.Builder.Where(x => x.ParentId == parentId && x.Name.Contains(name));
return repository.GetByQuery(query);
}
}
///
/// Gets a collection of objects by Parent Id
///
/// Id of the Parent to retrieve Descendants from
/// An Enumerable list of objects
public IEnumerable GetDescendants(int id)
{
var content = GetById(id);
if (content == null)
{
return Enumerable.Empty();
}
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)
{
//This is a check to ensure that the path is correct for this entity to avoid problems like: http://issues.umbraco.org/issue/U4-9336 due to data corruption
if (content.ValidatePath() == false)
throw new InvalidDataException(string.Format("The content item {0} has an invalid path: {1} with parentID: {2}", content.Id, content.Path, content.ParentId));
using (var uow = UowProvider.GetUnitOfWork(commit: true))
{
var repository = RepositoryFactory.CreateContentRepository(uow);
var pathMatch = content.Path + ",";
var query = Query.Builder.Where(x => x.Path.StartsWith(pathMatch) && x.Id != content.Id);
return repository.GetByQuery(query);
}
}
///
/// Gets the parent of the current content as an item.
///
/// Id of the to retrieve the parent from
/// Parent object
public IContent GetParent(int id)
{
var content = GetById(id);
return GetParent(content);
}
///
/// Gets the parent of the current content as an item.
///
/// to retrieve the parent from
/// Parent object
public IContent GetParent(IContent content)
{
if (content.ParentId == Constants.System.Root || content.ParentId == Constants.System.RecycleBinContent)
return null;
return GetById(content.ParentId);
}
///
/// 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 the published version of a item.
///
/// The content item.
/// The published version, if any; otherwise, null.
public IContent GetPublishedVersion(IContent content)
{
if (content.Published) return content;
return content.HasPublishedVersion
? GetByVersion(content.PublishedVersionGuid)
: null;
}
///
/// Gets a collection of objects, which reside at the first level / root
///
/// An Enumerable list of objects
public IEnumerable GetRootContent()
{
using (var uow = UowProvider.GetUnitOfWork(commit: true))
{
var repository = RepositoryFactory.CreateContentRepository(uow);
var query = Query.Builder.Where(x => x.ParentId == Constants.System.Root);
return repository.GetByQuery(query);
}
}
///
/// Gets all published content items
///
///
internal IEnumerable GetAllPublished()
{
//create it once if it is needed (no need for locking here)
if (_notTrashedQuery == null)
{
_notTrashedQuery = Query.Builder.Where(x => x.Trashed == false);
}
using (var uow = UowProvider.GetUnitOfWork(commit: true))
{
var repository = RepositoryFactory.CreateContentRepository(uow);
return repository.GetByPublishedVersion(_notTrashedQuery);
}
}
///
/// 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 uow = UowProvider.GetUnitOfWork(commit: true))
{
var repository = RepositoryFactory.CreateContentRepository(uow);
var query = Query.Builder.Where(x => x.Published && x.ExpireDate <= DateTime.Now);
return repository.GetByQuery(query);
}
}
///
/// 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 uow = UowProvider.GetUnitOfWork(commit: true))
{
var repository = RepositoryFactory.CreateContentRepository(uow);
var query = Query.Builder.Where(x => x.Published == false && x.ReleaseDate <= DateTime.Now);
return repository.GetByQuery(query);
}
}
///
/// Gets a collection of an objects, which resides in the Recycle Bin
///
/// An Enumerable list of objects
public IEnumerable GetContentInRecycleBin()
{
using (var uow = UowProvider.GetUnitOfWork(commit: true))
{
var repository = RepositoryFactory.CreateContentRepository(uow);
var query = Query.Builder.Where(x => x.Path.Contains(Constants.System.RecycleBinContent.ToInvariantString()));
return repository.GetByQuery(query);
}
}
///
/// Checks whether an item has any children
///
/// Id of the
/// True if the content has any children otherwise False
public bool HasChildren(int id)
{
return CountChildren(id) > 0;
}
internal int CountChildren(int id)
{
using (var uow = UowProvider.GetUnitOfWork(commit: true))
{
var repository = RepositoryFactory.CreateContentRepository(uow);
var query = Query.Builder.Where(x => x.ParentId == id);
return repository.Count(query);
}
}
///
/// 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 uow = UowProvider.GetUnitOfWork(commit: true))
{
var repository = RepositoryFactory.CreateContentRepository(uow);
var query = Query.Builder.Where(x => x.Published == true && x.Id == id && x.Trashed == false);
return repository.Count(query) > 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);
}
///
/// This will rebuild the xml structures for content in the database.
///
/// This is not used for anything
/// True if publishing succeeded, otherwise False
///
/// This is used for when a document type alias or a document type property is changed, the xml will need to
/// be regenerated.
///
public bool RePublishAll(int userId = 0)
{
try
{
RebuildXmlStructures();
return true;
}
catch (Exception ex)
{
Logger.Error("An error occurred executing RePublishAll", ex);
return false;
}
}
///
/// This will rebuild the xml structures for content in the database.
///
///
/// If specified will only rebuild the xml for the content type's specified, otherwise will update the structure
/// for all published content.
///
internal void RePublishAll(params int[] contentTypeIds)
{
try
{
RebuildXmlStructures(contentTypeIds);
}
catch (Exception ex)
{
Logger.Error("An error occurred executing RePublishAll", ex);
}
}
///
/// 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)
{
var result = SaveAndPublishDo(content, userId);
Logger.Info("Call was made to ContentService.Publish, use PublishWithStatus instead since that method will provide more detailed information on the outcome");
return result.Success;
}
///
/// Publishes a object and all its children
///
/// The to publish along with its children
/// Optional Id of the User issueing the publishing
///
/// The list of statuses for all published items
IEnumerable> IContentServiceOperations.PublishWithChildren(IContent content, int userId, bool includeUnpublished)
{
return PublishWithChildrenDo(content, userId, includeUnpublished);
}
///
/// 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
Attempt IContentServiceOperations.SaveAndPublish(IContent content, int userId, bool raiseEvents)
{
return SaveAndPublishDo(content, userId, raiseEvents);
}
///
/// 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
Attempt IContentServiceOperations.MoveToRecycleBin(IContent content, int userId)
{
var evtMsgs = EventMessagesFactory.Get();
using (new WriteLock(Locker))
{
using (var uow = UowProvider.GetUnitOfWork())
{
//Hack: this ensures that the entity's path is valid and if not it fixes/persists it
//see: http://issues.umbraco.org/issue/U4-9336
content.EnsureValidPath(Logger, entity => GetById(entity.ParentId), QuickUpdate);
var originalPath = content.Path;
if (uow.Events.DispatchCancelable(Trashing, this, new MoveEventArgs(evtMsgs, new MoveEventInfo(content, originalPath, Constants.System.RecycleBinContent)), "Trashing"))
{
return OperationStatus.Cancelled(evtMsgs);
}
var moveInfo = new List>
{
new MoveEventInfo(content, originalPath, Constants.System.RecycleBinContent)
};
//Make sure that published content is unpublished before being moved to the Recycle Bin
if (HasPublishedVersion(content.Id))
{
//TODO: this shouldn't be a 'sub operation', and if it needs to be it cannot raise events and cannot be cancelled!
UnPublish(content, userId);
}
//Unpublish descendents of the content item that is being moved to trash
var descendants = GetDescendants(content).OrderBy(x => x.Level).ToList();
foreach (var descendant in descendants)
{
//TODO: this shouldn't be a 'sub operation', and if it needs to be it cannot raise events and cannot be cancelled!
UnPublish(descendant, userId);
}
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)
{
moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId));
descendant.WriterId = userId;
descendant.ChangeTrashedState(true, descendant.ParentId);
repository.AddOrUpdate(descendant);
}
uow.Commit();
uow.Events.Dispatch(Trashed, this, new MoveEventArgs(false, evtMsgs, moveInfo.ToArray()), "Trashed");
// fixme just writing to DB, no need for an event, ok here
Audit(AuditType.Move, "Move Content to Recycle Bin performed by user", userId, content.Id);
}
// fixme the WHOLE thing should move to the UOW!
//Audit(AuditType.Move, "Move Content to Recycle Bin performed by user", userId, content.Id);
return OperationStatus.Success(evtMsgs);
}
}
///
/// UnPublishes a single object
///
/// The to publish
/// Optional Id of the User issueing the publishing
/// True if unpublishing succeeded, otherwise False
Attempt IContentServiceOperations.UnPublish(IContent content, int userId)
{
return UnPublishDo(content, false, userId);
}
///
/// Publishes a single object
///
/// The to publish
/// Optional Id of the User issueing the publishing
/// True if publishing succeeded, otherwise False
public Attempt PublishWithStatus(IContent content, int userId = 0)
{
return ((IContentServiceOperations)this).Publish(content, 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
[Obsolete("Use PublishWithChildrenWithStatus instead, that method will provide more detailed information on the outcome and also allows the includeUnpublished flag")]
public bool PublishWithChildren(IContent content, int userId = 0)
{
var result = PublishWithChildrenDo(content, userId, true);
//This used to just return false only when the parent content failed, otherwise would always return true so we'll
// do the same thing for the moment
if (result.All(x => x.Result.ContentItem.Id != content.Id))
return false;
return result.Single(x => x.Result.ContentItem.Id == content.Id).Success;
}
///
/// Publishes a object and all its children
///
/// The to publish along with its children
/// Optional Id of the User issueing the publishing
/// set to true if you want to also publish children that are currently unpublished
/// True if publishing succeeded, otherwise False
public IEnumerable> PublishWithChildrenWithStatus(IContent content, int userId = 0, bool includeUnpublished = false)
{
return ((IContentServiceOperations)this).PublishWithChildren(content, userId, includeUnpublished);
}
///
/// 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 ((IContentServiceOperations)this).UnPublish(content, userId).Success;
}
///
/// 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
[Obsolete("Use SaveAndPublishWithStatus instead, that method will provide more detailed information on the outcome")]
public bool SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true)
{
var result = SaveAndPublishDo(content, userId, raiseEvents);
return result.Success;
}
///
/// 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 Attempt SaveAndPublishWithStatus(IContent content, int userId = 0, bool raiseEvents = true)
{
return ((IContentServiceOperations)this).SaveAndPublish(content, 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)
{
((IContentServiceOperations)this).Save(content, userId, raiseEvents);
}
///
/// Saves a collection of objects.
///
/// Collection of to save
/// Optional Id of the User saving the Content
/// Optional boolean indicating whether or not to raise events.
Attempt IContentServiceOperations.Save(IEnumerable contents, int userId, bool raiseEvents)
{
var asArray = contents.ToArray();
var evtMsgs = EventMessagesFactory.Get();
using (var uow = UowProvider.GetUnitOfWork())
{
if (raiseEvents)
{
if (uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs(asArray, evtMsgs)))
{
return OperationStatus.Cancelled(evtMsgs);
}
}
using (new WriteLock(Locker))
{
var containsNew = asArray.Any(x => x.HasIdentity == false);
var repository = RepositoryFactory.CreateContentRepository(uow);
if (containsNew)
{
foreach (var content in asArray)
{
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);
//add or update preview
repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c));
}
}
else
{
foreach (var content in asArray)
{
content.WriterId = userId;
repository.AddOrUpdate(content);
//add or update preview
repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c));
}
}
uow.Commit();
}
if (raiseEvents)
uow.Events.Dispatch(Saved, this, new SaveEventArgs(asArray, false, evtMsgs));
Audit(AuditType.Save, "Bulk Save content performed by user", userId == -1 ? 0 : userId, Constants.System.Root);
return OperationStatus.Success(evtMsgs);
}
}
///
/// 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
Attempt IContentServiceOperations.Delete(IContent content, int userId)
{
var evtMsgs = EventMessagesFactory.Get();
using (new WriteLock(Locker))
{
using (var uow = UowProvider.GetUnitOfWork())
{
if (uow.Events.DispatchCancelable(Deleting, this, new DeleteEventArgs(content, evtMsgs), "Deleting"))
{
return OperationStatus.Cancelled(evtMsgs);
}
//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 repository = RepositoryFactory.CreateContentRepository(uow);
repository.Delete(content);
uow.Commit();
var args = new DeleteEventArgs(content, false, evtMsgs);
uow.Events.Dispatch(Deleted, this, args, "Deleted"); // fixme why the event name?!
//remove any flagged media files
repository.DeleteMediaFiles(args.MediaFilesToDelete);
}
Audit(AuditType.Delete, "Delete Content performed by user", userId, content.Id);
return OperationStatus.Success(evtMsgs);
}
}
///
/// Publishes a single object
///
/// The to publish
/// Optional Id of the User issueing the publishing
/// The published status attempt
Attempt IContentServiceOperations.Publish(IContent content, int userId)
{
return SaveAndPublishDo(content, userId);
}
///
/// Saves a single object
///
/// The to save
/// Optional Id of the User saving the Content
/// Optional boolean indicating whether or not to raise events.
Attempt IContentServiceOperations.Save(IContent content, int userId, bool raiseEvents)
{
return 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)
{
((IContentServiceOperations)this).Save(contents, userId, raiseEvents);
}
///
/// 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)
{
//TODO: This currently this is called from the ContentTypeService but that needs to change,
// if we are deleting a content type, we should just delete the data and do this operation slightly differently.
// This method will recursively go lookup every content item, check if any of it's descendants are
// of a different type, move them to the recycle bin, then permanently delete the content items.
// The main problem with this is that for every content item being deleted, events are raised...
// which we need for many things like keeping caches in sync, but we can surely do this MUCH better.
// FIXME this is all totally entirely completely fucking WRONG
// keeping Shan's code as-is for the time being but compare to 7.5, it's all borked
var childList = new List();
var contentList = new List();
using (new WriteLock(Locker))
{
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).ToArray();
if (uow.Events.DispatchCancelable(Deleting, this, new DeleteEventArgs(contents), "Deleting"))
{
uow.Commit();
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)
{
//track children for moving to the bin
childList.Add(child);
}
//track content for deletion
contentList.Add(content);
}
// do it INSIDE the UOW because nested UOW kinda should work
// fixme - and then we probably don't need the whole mess?
// nesting UOW works, it's just that the outer one NEEDS to be flushed beforehand
// nevertheless, it would be nicer to create a global scope and inner uow
foreach (var child in childList)
{
if (child.ContentType.Id != contentTypeId)
MoveToRecycleBin(child, userId);
}
foreach (var content in contentList)
{
//Permantly delete the content
Delete(content, userId);
}
uow.Commit();
Audit(AuditType.Delete,
string.Format("Delete Content of Type {0} performed by user", contentTypeId),
userId, Constants.System.Root);
}
// FIXME this is borked I don't even...
//We need to do this outside of the uow because otherwise we'll have nested uow's
//TODO: this is a problem because we are doing all of this logic in the Service when it should
//all be happening in the repository
//foreach (var child in childList)
//{
// if (child.ContentType.Id != contentTypeId)
// MoveToRecycleBin(child, userId);
//}
//foreach (var content in contentList)
//{
// //Permantly delete the content
// Delete(content, userId);
//}
//Audit(AuditType.Delete,
// string.Format("Delete Content of Type {0} performed by user", contentTypeId),
// userId, Constants.System.Root);
}
}
///
/// Permanently deletes an object as well as all of its Children.
///
///
/// 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)
{
((IContentServiceOperations)this).Delete(content, userId);
}
///
/// Permanently deletes versions from an object prior to a specific date.
/// This method will never delete the latest version of a content item.
///
/// 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)
{
using (var uow = UowProvider.GetUnitOfWork())
{
if (uow.Events.DispatchCancelable(DeletingVersions, this, new DeleteRevisionsEventArgs(id, dateToRetain: versionDate), "DeletingVersions"))
{
// fixme - not consistent, shall we commit or not when cancelling?!
uow.Commit();
return;
}
var repository = RepositoryFactory.CreateContentRepository(uow);
repository.DeleteVersions(id, versionDate);
uow.Commit();
uow.Events.Dispatch(DeletedVersions, this, new DeleteRevisionsEventArgs(id, false, dateToRetain: versionDate), "DeletedVersions");
Audit(AuditType.Delete, "Delete Content by version date performed by user", userId, Constants.System.Root);
}
}
///
/// Permanently deletes specific version(s) from an object.
/// This method will never delete the latest version of a content item.
///
/// 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)
{
using (new WriteLock(Locker))
{
using (var uow = UowProvider.GetUnitOfWork())
{
if (uow.Events.DispatchCancelable(DeletingVersions, this, new DeleteRevisionsEventArgs(id, specificVersion: versionId), "DeletingVersions"))
return;
if (deletePriorVersions)
{
var content = GetByVersion(versionId);
DeleteVersions(id, content.UpdateDate, userId);
}
var repository = RepositoryFactory.CreateContentRepository(uow);
repository.DeleteVersion(versionId);
uow.Commit();
uow.Events.Dispatch(DeletedVersions, this, new DeleteRevisionsEventArgs(id, false, specificVersion: versionId), "DeletedVersions");
Audit(AuditType.Delete, "Delete Content by version performed by user", userId, Constants.System.Root);
}
}
}
///
/// 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)
{
((IContentServiceOperations)this).MoveToRecycleBin(content, userId);
}
///
/// 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)
{
using (new WriteLock(Locker))
{
//This ensures that the correct method is called if this method is used to Move to recycle bin.
if (parentId == Constants.System.RecycleBinContent)
{
MoveToRecycleBin(content, userId);
return;
}
// fixme - we NEED something for the events
// so even if we don't create a true unit of work (no need) we can create a scope!
// what a spectacular mess
using (IScope scope = UowProvider.ScopeProvider.CreateScope())
{
if (scope.Events.DispatchCancelable(Moving, this, new MoveEventArgs(new MoveEventInfo(content, content.Path, parentId)), "Moving"))
return;
//used to track all the moved entities to be given to the event
var moveInfo = new List>();
//call private method that does the recursive moving
PerformMove(content, parentId, userId, moveInfo);
scope.Events.Dispatch(Moved, this, new MoveEventArgs(false, moveInfo.ToArray()), "Moved");
scope.Complete();
}
Audit(AuditType.Move, "Move Content performed by user", userId, content.Id);
}
}
///
/// Empties the Recycle Bin by deleting all that resides in the bin
///
public void EmptyRecycleBin()
{
using (new WriteLock(Locker))
{
Dictionary> entities;
List files;
bool success;
var nodeObjectType = new Guid(Constants.ObjectTypes.Document);
using (var uow = UowProvider.GetUnitOfWork())
{
var repository = RepositoryFactory.CreateContentRepository(uow);
//Create a dictionary of ids -> dictionary of property aliases + values
entities = repository.GetEntitiesInRecycleBin()
.ToDictionary(
key => key.Id,
val => (IEnumerable)val.Properties);
files = ((ContentRepository)repository).GetFilesInRecycleBinForUploadField();
if (uow.Events.DispatchCancelable(EmptyingRecycleBin, this, new RecycleBinEventArgs(nodeObjectType, entities, files)))
{
uow.Commit(); // fixme not consistent! // fixme not consistent!
return;
}
success = repository.EmptyRecycleBin();
if (success)
repository.DeleteMediaFiles(files);
uow.Events.Dispatch(EmptiedRecycleBin, this, new RecycleBinEventArgs(nodeObjectType, entities, files, success));
uow.Commit();
}
}
// fixme outside the uow!
Audit(AuditType.Delete, "Empty Content Recycle Bin performed by user", 0, Constants.System.RecycleBinContent);
}
///
/// 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. Recursively copies all children.
///
/// 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)
{
return Copy(content, parentId, relateToOriginal, true, userId);
}
///
/// 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
/// A value indicating whether to recursively copy children.
/// Optional Id of the User copying the Content
/// The newly created object
public IContent Copy(IContent content, int parentId, bool relateToOriginal, bool recursive, int userId = 0)
{
//TODO: This all needs to be managed correctly so that the logic is submitted in one
// transaction, the CRUD needs to be moved to the repo
using (new WriteLock(Locker))
{
var copy = content.DeepCloneWithResetIdentities();
copy.ParentId = parentId;
// A copy should never be set to published automatically even if the original was.
copy.ChangePublishedState(PublishedState.Unpublished);
// fixme mess!
using (var scope = UowProvider.ScopeProvider.CreateScope())
{
using (var uow = UowProvider.GetUnitOfWork())
{
if (uow.Events.DispatchCancelable(Copying, this, new CopyEventArgs(content, copy, parentId)))
{
uow.Commit();
return null;
}
var repository = RepositoryFactory.CreateContentRepository(uow);
// Update the create author and last edit author
copy.CreatorId = userId;
copy.WriterId = userId;
repository.AddOrUpdate(copy);
//add or update a preview
repository.AddOrUpdatePreviewXml(copy, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c));
uow.Commit();
//Special case for the associated tags
//TODO: Move this to the repository layer in a single transaction!
//don't copy tags data in tags table if the item is in the recycle bin
if (parentId != Constants.System.RecycleBinContent)
{
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, PropertyTypeId = tag.PropertyTypeId });
}
}
uow.Commit();
}
if (recursive)
{
//Look for children and copy those as well
var children = GetChildren(content.Id);
foreach (var child in children)
{
//TODO: This shouldn't recurse back to this method, it should be done in a private method
// that doesn't have a nested lock and so we can perform the entire operation in one commit.
Copy(child, copy.Id, relateToOriginal, true, userId);
}
}
scope.Events.Dispatch(Copied, this, new CopyEventArgs(content, copy, false, parentId, relateToOriginal));
Audit(AuditType.Copy, "Copy Content performed by user", content.WriterId, content.Id);
scope.Complete();
}
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
public bool SendToPublication(IContent content, int userId = 0)
{
using (var uow = UowProvider.GetUnitOfWork())
{
if (uow.Events.DispatchCancelable(SendingToPublish, this, new SendToPublishEventArgs(content)))
return false;
//Save before raising event
Save(content, userId);
uow.Events.Dispatch(SentToPublish, this, new SendToPublishEventArgs(content, false));
Audit(AuditType.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);
using (var uow = UowProvider.GetUnitOfWork())
{
if (uow.Events.DispatchCancelable(RollingBack, this, new RollbackEventArgs(content)))
{
uow.Commit();
return content;
}
var repository = RepositoryFactory.CreateContentRepository(uow);
content.WriterId = userId;
content.CreatorId = userId;
content.ChangePublishedState(PublishedState.Unpublished);
repository.AddOrUpdate(content);
//add or update a preview
repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c));
uow.Commit();
uow.Events.Dispatch(RolledBack, this, new RollbackEventArgs(content, false));
Audit(AuditType.RollBack, "Content rollback performed by user", content.WriterId, content.Id);
}
return content;
}
///
/// Sorts a collection of objects by updating the SortOrder according
/// to the ordering of items in the passed in .
///
///
/// Using this method will ensure that the Published-state is maintained upon sorting
/// so the cache is updated accordingly - as needed.
///
///
///
///
/// True if sorting succeeded, otherwise False
public bool Sort(IEnumerable items, int userId = 0, bool raiseEvents = true)
{
var shouldBePublished = new List();
var shouldBeSaved = new List();
using (new WriteLock(Locker))
{
using (var uow = UowProvider.GetUnitOfWork())
{
var asArray = items.ToArray();
if (raiseEvents)
{
if (uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs(asArray)))
{
uow.Commit();
return false;
}
}
var repository = RepositoryFactory.CreateContentRepository(uow);
int i = 0;
foreach (var content in asArray)
{
//If the current sort order equals that of the content
//we don't need to update it, so just increment the sort order
//and continue.
if (content.SortOrder == i)
{
i++;
continue;
}
content.SortOrder = i;
content.WriterId = userId;
i++;
if (content.Published)
{
//TODO: This should not be an inner operation, but if we do this, it cannot raise events and cannot be cancellable!
var published = _publishingStrategy.Publish(uow, content, userId).Success;
shouldBePublished.Add(content);
}
else
shouldBeSaved.Add(content);
repository.AddOrUpdate(content);
//add or update a preview
repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c));
}
foreach (var content in shouldBePublished)
{
//Create and Save ContentXml DTO
repository.AddOrUpdateContentXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c));
}
uow.Commit();
if (raiseEvents)
uow.Events.Dispatch(Saved, this, new SaveEventArgs(asArray, false));
if (shouldBePublished.Any())
{
//TODO: This should not be an inner operation, but if we do this, it cannot raise events and cannot be cancellable!
_publishingStrategy.PublishingFinalized(uow, shouldBePublished, false);
}
}
}
// fixme - out of uow?
Audit(AuditType.Sort, "Sorting content performed by user", userId, 0);
return true;
}
///
/// Gets paged content descendants as XML by path
///
/// Path starts with
/// Page number
/// Page size
/// Total records the query would return without paging
/// A paged enumerable of XML entries of content items
public IEnumerable GetPagedXmlEntries(string path, long pageIndex, int pageSize, out long totalRecords)
{
Mandate.ParameterCondition(pageIndex >= 0, "pageIndex");
Mandate.ParameterCondition(pageSize > 0, "pageSize");
var uow = UowProvider.GetUnitOfWork();
using (var repository = RepositoryFactory.CreateContentRepository(uow))
{
var contents = repository.GetPagedXmlEntriesByPath(path, pageIndex, pageSize,
//This order by is VERY important! This allows us to figure out what is implicitly not published, see ContentRepository.BuildXmlCache and
// UmbracoContentIndexer.PerformIndexAll which uses the logic based on this sort order
new[] { "level", "parentID", "sortOrder" },
out totalRecords);
return contents;
}
}
///
/// This builds the Xml document used for the XML cache
///
///
public XmlDocument BuildXmlCache()
{
using (var uow = UowProvider.GetUnitOfWork())
{
var repository = RepositoryFactory.CreateContentRepository(uow);
var result = repository.BuildXmlCache();
uow.Commit();
return result;
}
}
///
/// Rebuilds all xml content in the cmsContentXml table for all documents
///
///
/// Only rebuild the xml structures for the content type ids passed in, if none then rebuilds the structures
/// for all content
///
public void RebuildXmlStructures(params int[] contentTypeIds)
{
using (var uow = UowProvider.GetUnitOfWork())
{
var repository = RepositoryFactory.CreateContentRepository(uow);
repository.RebuildXmlStructures(
content => _entitySerializer.Serialize(this, _dataTypeService, _userService, content),
contentTypeIds: contentTypeIds.Length == 0 ? null : contentTypeIds);
uow.Commit();
}
Audit(AuditType.Publish, "ContentService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, Constants.System.Root);
}
#region Internal Methods
///
/// 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 uow = UowProvider.GetUnitOfWork(commit: true))
{
var repository = RepositoryFactory.CreateContentRepository(uow);
var query = Query.Builder.Where(x => x.Id != content.Id && x.Path.StartsWith(content.Path) && x.Trashed == false);
return repository.GetByPublishedVersion(query);
}
}
#endregion
#region Private Methods
///
/// Hack: This is used to fix some data if an entity's properties are invalid/corrupt
///
///
private void QuickUpdate(IContent content)
{
if (content == null) throw new ArgumentNullException("content");
if (content.HasIdentity == false) throw new InvalidOperationException("Cannot update an entity without an Identity");
using (var uow = UowProvider.GetUnitOfWork())
{
var repository = RepositoryFactory.CreateContentRepository(uow);
repository.AddOrUpdate(content);
uow.Commit();
}
}
private void Audit(AuditType type, string message, int userId, int objectId)
{
using (var uow = UowProvider.GetUnitOfWork())
{
var auditRepo = RepositoryFactory.CreateAuditRepository(uow);
auditRepo.AddOrUpdate(new AuditItem(objectId, message, type, userId));
uow.Commit();
}
}
//TODO: All of this needs to be moved to the repository
private void PerformMove(IContent content, int parentId, int userId, ICollection> moveInfo)
{
//add a tracking item to use in the Moved event
moveInfo.Add(new MoveEventInfo(content, content.Path, parentId));
content.WriterId = userId;
if (parentId == Constants.System.Root)
{
content.Path = string.Concat(Constants.System.Root, ",", content.Id);
content.Level = 1;
}
else
{
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 != Constants.System.RecycleBinContent)
{
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))
{
//TODO: This is raising events, probably not desirable as this costs performance for event listeners like Examine
SaveAndPublish(content, userId);
}
else
{
//TODO: This is raising events, probably not desirable as this costs performance for event listeners like Examine
Save(content, false, userId);
//TODO: This shouldn't be here! This needs to be part of the repository logic but in order to fix this we need to
// change how this method calls "Save" as it needs to save using an internal method
using (var uow = UowProvider.GetUnitOfWork())
{
var xml = _entitySerializer.Serialize(this, _dataTypeService, _userService, content);
var poco = new ContentXmlDto { NodeId = content.Id, Xml = xml.ToDataString() };
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));
uow.Commit();
}
}
}
else
{
//TODO: This is raising events, probably not desirable as this costs performance for event listeners like Examine
Save(content, userId);
}
//Ensure that Path and Level is updated on children
var children = GetChildren(content.Id).ToArray();
if (children.Any())
{
foreach (var child in children)
{
PerformMove(child, content.Id, userId, moveInfo);
}
}
}
///
/// Publishes a object and all its children
///
/// The to publish along with its children
/// Optional Id of the User issueing the publishing
/// If set to true, this will also publish descendants that are completely unpublished, normally this will only publish children that have previously been published
///
/// A list of publish statues. If the parent document is not valid or cannot be published because it's parent(s) is not published
/// then the list will only contain one status item, otherwise it will contain status items for it and all of it's descendants that
/// are to be published.
///
private IEnumerable> PublishWithChildrenDo(
IContent content, int userId = 0, bool includeUnpublished = false)
{
if (content == null) throw new ArgumentNullException("content");
var evtMsgs = EventMessagesFactory.Get();
using (new WriteLock(Locker))
{
//Hack: this ensures that the entity's path is valid and if not it fixes/persists it
//see: http://issues.umbraco.org/issue/U4-9336
content.EnsureValidPath(Logger, entity => GetById(entity.ParentId), QuickUpdate);
var result = new List>();
//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 != Constants.System.Root && content.ParentId != Constants.System.RecycleBinContent && IsPublishable(content) == false)
{
Logger.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));
result.Add(Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedPathNotPublished, evtMsgs)));
return result;
}
//Content contains invalid property values and can therefore not be published - fire event?
if (!content.IsValid())
{
Logger.Info(
string.Format("Content '{0}' with Id '{1}' could not be published because of invalid properties.",
content.Name, content.Id));
result.Add(
Attempt.Fail(
new PublishStatus(content, PublishStatusType.FailedContentInvalid, evtMsgs)
{
InvalidProperties = ((ContentBase)content).LastInvalidProperties
}));
return result;
}
//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); //include parent item
list.AddRange(GetDescendants(content));
var internalStrategy = _publishingStrategy;
using (var uow = UowProvider.GetUnitOfWork())
{
//Publish and then update the database with new status
var publishedOutcome = internalStrategy.PublishWithChildren(uow, list, userId, includeUnpublished).ToArray();
var published = publishedOutcome
.Where(x => x.Success || x.Result.StatusType == PublishStatusType.SuccessAlreadyPublished)
// ensure proper order (for events) - cannot publish a child before its parent!
.OrderBy(x => x.Result.ContentItem.Level)
.ThenBy(x => x.Result.ContentItem.SortOrder);
var repository = RepositoryFactory.CreateContentRepository(uow);
//NOTE The Publish with subpages-dialog was used more as a republish-type-thing, so we'll have to include PublishStatusType.SuccessAlreadyPublished
//in the updated-list, so the Published event is triggered with the expected set of pages and the xml is updated.
foreach (var item in published)
{
item.Result.ContentItem.WriterId = userId;
repository.AddOrUpdate(item.Result.ContentItem);
//add or update a preview
repository.AddOrUpdatePreviewXml(item.Result.ContentItem, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c));
//add or update the published xml
repository.AddOrUpdateContentXml(item.Result.ContentItem, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c));
updated.Add(item.Result.ContentItem);
}
uow.Commit();
//Save xml to db and call following method to fire event:
_publishingStrategy.PublishingFinalized(uow, updated, false);
Audit(AuditType.Publish, "Publish with Children performed by user", userId, content.Id);
return publishedOutcome;
}
}
}
///
/// 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 Attempt UnPublishDo(IContent content, bool omitCacheRefresh = false, int userId = 0)
{
var newest = GetById(content.Id); // ensure we have the newest version
if (content.Version != newest.Version) // but use the original object if it's already the newest version
content = newest;
var evtMsgs = EventMessagesFactory.Get();
var published = content.Published ? content : GetPublishedVersion(content.Id); // get the published version
if (published == null)
{
return Attempt.Succeed(new UnPublishStatus(content, UnPublishedStatusType.SuccessAlreadyUnPublished, evtMsgs)); // already unpublished
}
using (var uow = UowProvider.GetUnitOfWork())
{
var unpublished = _publishingStrategy.UnPublish(uow, content, userId);
if (unpublished == false)
{
uow.Commit();
return Attempt.Fail(new UnPublishStatus(content, UnPublishedStatusType.FailedCancelledByEvent, evtMsgs));
}
var repository = RepositoryFactory.CreateContentRepository(uow);
content.WriterId = userId;
repository.AddOrUpdate(content);
// is published is not newest, reset the published flag on published version
if (published.Version != content.Version)
repository.ClearPublished(published);
repository.DeleteContentXml(content);
uow.Commit();
//Delete xml from db? and call following method to fire event through PublishingStrategy to update cache
if (omitCacheRefresh == false)
_publishingStrategy.UnPublishingFinalized(uow, content);
}
Audit(AuditType.UnPublish, "UnPublish performed by user", userId, content.Id);
return Attempt.Succeed(new UnPublishStatus(content, UnPublishedStatusType.Success, evtMsgs));
}
///
/// 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
private Attempt SaveAndPublishDo(IContent content, int userId = 0, bool raiseEvents = true)
{
var evtMsgs = EventMessagesFactory.Get();
using (new WriteLock(Locker))
{
using (var uow = UowProvider.GetUnitOfWork())
{
if (raiseEvents)
{
if (uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs(content, evtMsgs)))
{
return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedCancelledByEvent, evtMsgs));
}
}
//Has this content item previously been published? If so, we don't need to refresh the children
var previouslyPublished = content.HasIdentity && HasPublishedVersion(content.Id); //content might not have an id
var publishStatus = new PublishStatus(content, PublishStatusType.Success, evtMsgs); //initially set to success
//Check if parent is published (although not if its a root node) - if parent isn't published this Content cannot be published
publishStatus.StatusType = CheckAndLogIsPublishable(content);
//if it is not successful, then check if the props are valid
if ((int)publishStatus.StatusType < 10)
{
//Content contains invalid property values and can therefore not be published - fire event?
publishStatus.StatusType = CheckAndLogIsValid(content);
//set the invalid properties (if there are any)
publishStatus.InvalidProperties = ((ContentBase)content).LastInvalidProperties;
}
//if we're still successful, then publish using the strategy
if (publishStatus.StatusType == PublishStatusType.Success)
{
//Publish and then update the database with new status
var publishResult = _publishingStrategy.Publish(uow, content, userId);
//set the status type to the publish result
publishStatus.StatusType = publishResult.Result.StatusType;
}
//we are successfully published if our publishStatus is still Successful
bool published = publishStatus.StatusType == PublishStatusType.Success;
var repository = RepositoryFactory.CreateContentRepository(uow);
if (published == false)
{
content.ChangePublishedState(PublishedState.Saved);
}
//Since this is the Save and Publish method, the content should be saved even though the publish fails or isn't allowed
if (content.HasIdentity == false)
{
content.CreatorId = userId;
}
content.WriterId = userId;
repository.AddOrUpdate(content);
//Generate a new preview
repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c));
if (published)
{
//Content Xml
repository.AddOrUpdateContentXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c));
}
uow.Commit();
if (raiseEvents)
uow.Events.Dispatch(Saved, this, new SaveEventArgs(content, false, evtMsgs));
//Save xml to db and call following method to fire event through PublishingStrategy to update cache
if (published)
{
_publishingStrategy.PublishingFinalized(uow, content);
}
//We need to check if children and their publish state to ensure that we 'republish' content that was previously published
if (published && previouslyPublished == false && HasChildren(content.Id))
{
var descendants = GetPublishedDescendants(content);
_publishingStrategy.PublishingFinalized(uow, descendants, false);
}
Audit(AuditType.Publish, "Save and Publish performed by user", userId, content.Id);
return Attempt.If(publishStatus.StatusType == PublishStatusType.Success, publishStatus);
}
}
}
///
/// 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 Attempt Save(IContent content, bool changeState, int userId = 0, bool raiseEvents = true)
{
var evtMsgs = EventMessagesFactory.Get();
using (new WriteLock(Locker))
{
using (var uow = UowProvider.GetUnitOfWork())
{
if (raiseEvents)
{
if (uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs(content, evtMsgs)))
{
return OperationStatus.Cancelled(evtMsgs);
}
}
if (string.IsNullOrWhiteSpace(content.Name))
{
throw new ArgumentException("Cannot save content with empty name.");
}
var repository = RepositoryFactory.CreateContentRepository(uow);
if (content.HasIdentity == false)
{
content.CreatorId = userId;
}
content.WriterId = userId;
//Only change the publish state if the "previous" version was actually published or marked as unpublished
if (changeState && (content.Published || ((Content)content).PublishedState == PublishedState.Unpublished))
content.ChangePublishedState(PublishedState.Saved);
repository.AddOrUpdate(content);
//Generate a new preview
repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c));
uow.Commit();
if (raiseEvents)
uow.Events.Dispatch(Saved, this, new SaveEventArgs(content, false, evtMsgs));
Audit(AuditType.Save, "Save Content performed by user", userId, content.Id);
}
return OperationStatus.Success(evtMsgs);
}
}
///
/// 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 == Constants.System.RecycleBinContent)
return false;
//We don't check the System Root, so just continue
if (id == Constants.System.Root) 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;
}
private PublishStatusType CheckAndLogIsPublishable(IContent content)
{
//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 != Constants.System.Root && content.ParentId != Constants.System.RecycleBinContent && IsPublishable(content) == false)
{
Logger.Info(
string.Format(
"Content '{0}' with Id '{1}' could not be published because its parent is not published.",
content.Name, content.Id));
return PublishStatusType.FailedPathNotPublished;
}
else if (content.ExpireDate.HasValue && content.ExpireDate.Value > DateTime.MinValue && DateTime.Now > content.ExpireDate.Value)
{
Logger.Info(
string.Format(
"Content '{0}' with Id '{1}' has expired and could not be published.",
content.Name, content.Id));
return PublishStatusType.FailedHasExpired;
}
else if (content.ReleaseDate.HasValue && content.ReleaseDate.Value > DateTime.MinValue && content.ReleaseDate.Value > DateTime.Now)
{
Logger.Info(
string.Format(
"Content '{0}' with Id '{1}' is awaiting release and could not be published.",
content.Name, content.Id));
return PublishStatusType.FailedAwaitingRelease;
}
return PublishStatusType.Success;
}
private PublishStatusType CheckAndLogIsValid(IContent content)
{
//Content contains invalid property values and can therefore not be published - fire event?
if (content.IsValid() == false)
{
Logger.Info(
string.Format(
"Content '{0}' with Id '{1}' could not be published because of invalid properties.",
content.Name, content.Id));
return PublishStatusType.FailedContentInvalid;
}
return PublishStatusType.Success;
}
private IContentType FindContentTypeByAlias(string contentTypeAlias)
{
using (var uow = UowProvider.GetUnitOfWork(commit: true))
{
var repository = RepositoryFactory.CreateContentTypeRepository(uow);
var query = Query.Builder.Where(x => x.Alias == contentTypeAlias);
var types = repository.GetByQuery(query);
if (types.Any() == false)
throw new Exception(
string.Format("No ContentType matching the passed in Alias: '{0}' was found",
contentTypeAlias));
var contentType = types.First();
if (contentType == null)
throw new Exception(string.Format("ContentType matching the passed in Alias: '{0}' was null",
contentTypeAlias));
return contentType;
}
}
#endregion
#region Proxy Event Handlers
///
/// Occurs before publish.
///
/// Proxy to the real event on the
public static event TypedEventHandler> Publishing
{
add { PublishingStrategy.Publishing += value; }
remove { PublishingStrategy.Publishing -= value; }
}
///
/// Occurs after publish.
///
/// Proxy to the real event on the
public static event TypedEventHandler> Published
{
add { PublishingStrategy.Published += value; }
remove { PublishingStrategy.Published -= value; }
}
///
/// Occurs before unpublish.
///
/// Proxy to the real event on the
public static event TypedEventHandler> UnPublishing
{
add { PublishingStrategy.UnPublishing += value; }
remove { PublishingStrategy.UnPublishing -= value; }
}
///
/// Occurs after unpublish.
///
/// Proxy to the real event on the
public static event TypedEventHandler> UnPublished
{
add { PublishingStrategy.UnPublished += value; }
remove { PublishingStrategy.UnPublished -= value; }
}
#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
///
[Obsolete("Use the Created event instead, the Creating and Created events both offer the same functionality, Creating event has been deprecated.")]
public static event TypedEventHandler> Creating;
///
/// Occurs after Create
///
///
/// Please note that the Content object has been created, but might not have been 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;
///
/// Occurs before the Recycle Bin is emptied
///
public static event TypedEventHandler EmptyingRecycleBin;
///
/// Occurs after the Recycle Bin has been Emptied
///
public static event TypedEventHandler EmptiedRecycleBin;
#endregion
}
}