diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs
index a1e7c9955e..cf49e892dd 100644
--- a/src/Umbraco.Core/Services/ContentService.cs
+++ b/src/Umbraco.Core/Services/ContentService.cs
@@ -1,2904 +1,2939 @@
-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;
-
-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(UowProvider.ScopeProvider, eventMessagesFactory, logger);
- _dataTypeService = dataTypeService;
- _userService = userService;
- }
-
- #region Static Queries
-
- private IQuery _notTrashedQuery;
-
- #endregion
-
- public int CountPublished(string contentTypeAlias = null)
- {
- using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
- {
- var repository = RepositoryFactory.CreateContentRepository(uow);
- return repository.CountPublished(contentTypeAlias);
- }
- }
-
- public int Count(string contentTypeAlias = null)
- {
- using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
- {
- var repository = RepositoryFactory.CreateContentRepository(uow);
- return repository.Count(contentTypeAlias);
- }
- }
-
- public int CountChildren(int parentId, string contentTypeAlias = null)
- {
- using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
- {
- var repository = RepositoryFactory.CreateContentRepository(uow);
- return repository.CountChildren(parentId, contentTypeAlias);
- }
- }
-
- public int CountDescendants(int parentId, string contentTypeAlias = null)
- {
- using (var uow = UowProvider.GetUnitOfWork(readOnly: 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 group ids
- ///
- ///
- ///
- ///
- public void AssignContentPermission(IContent entity, char permission, IEnumerable groupIds)
- {
- using (var uow = UowProvider.GetUnitOfWork())
- {
- var repository = RepositoryFactory.CreateContentRepository(uow);
- repository.AssignEntityPermission(entity, permission, groupIds);
- uow.Commit();
- }
- }
-
- ///
- /// Returns implicit/inherited permissions assigned to the content item for all user groups
- ///
- ///
- ///
- public EntityPermissionCollection GetPermissionsForEntity(IContent content)
- {
- using (var uow = UowProvider.GetUnitOfWork(readOnly: 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, Guid parentId, string contentTypeAlias, int userId = 0)
- {
- var parent = GetById(parentId);
- return CreateContent(name, parent, contentTypeAlias, userId);
- }
-
- ///
- /// 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);
-
- using (var uow = UowProvider.GetUnitOfWork())
- {
- var newEventArgs = new NewEventArgs(content, contentTypeAlias, parentId);
- if (uow.Events.DispatchCancelable(Creating, this, newEventArgs))
- {
- uow.Commit();
- content.WasCancelled = true;
- return content;
- }
-
- content.CreatorId = userId;
- content.WriterId = userId;
- newEventArgs.CanCancel = false;
- uow.Events.Dispatch(Created, this, newEventArgs);
-
- uow.Commit();
- }
-
- 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())
- {
- var newEventArgs = new NewEventArgs(content, contentTypeAlias, parent);
- if (uow.Events.DispatchCancelable(Creating, this, newEventArgs))
- {
- uow.Commit();
- content.WasCancelled = true;
- return content;
- }
-
- content.CreatorId = userId;
- content.WriterId = userId;
- newEventArgs.CanCancel = false;
- uow.Events.Dispatch(Created, this, newEventArgs);
-
- uow.Commit();
- }
-
- 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.
- var newEventArgs = new NewEventArgs(content, contentTypeAlias, parentId);
- if (uow.Events.DispatchCancelable(Creating, this, newEventArgs))
- {
- uow.Commit();
- content.WasCancelled = true;
- return content;
- }
-
- var saveEventArgs = new SaveEventArgs(content);
- if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
- {
- uow.Commit();
- content.WasCancelled = true;
- return content;
- }
-
- var repository = RepositoryFactory.CreateContentRepository(uow);
- content.CreatorId = userId;
- content.WriterId = userId;
-
- repository.AddOrUpdate(content);
- repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c));
- saveEventArgs.CanCancel = false;
- uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved");
- newEventArgs.CanCancel = false;
- uow.Events.Dispatch(Created, this, newEventArgs);
-
- Audit(uow, AuditType.New, string.Format("Content '{0}' was created with Id {1}", name, content.Id), content.CreatorId, content.Id);
- uow.Commit();
- }
-
- 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.
- var newEventArgs = new NewEventArgs(content, contentTypeAlias, parent);
- if (uow.Events.DispatchCancelable(Creating, this, newEventArgs))
- {
- uow.Commit();
- content.WasCancelled = true;
- return content;
- }
-
- var saveEventArgs = new SaveEventArgs(content);
- if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
- {
- uow.Commit();
- content.WasCancelled = true;
- return content;
- }
-
- var repository = RepositoryFactory.CreateContentRepository(uow);
- content.CreatorId = userId;
- content.WriterId = userId;
-
- repository.AddOrUpdate(content);
- repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c));
- saveEventArgs.CanCancel = false;
- uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved");
- newEventArgs.CanCancel = false;
- uow.Events.Dispatch(Created, this, newEventArgs);
-
- Audit(uow, AuditType.New, string.Format("Content '{0}' was created with Id {1}", name, content.Id), content.CreatorId, content.Id);
- uow.Commit();
- }
-
- return content;
- }
-
- ///
- /// Gets an object by Id
- ///
- /// Id of the Content to retrieve
- ///
- public IContent GetById(int id)
- {
- using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
- {
- var repository = RepositoryFactory.CreateContentRepository(uow);
- return repository.Get(id);
- }
- }
-
- ///
- /// Gets objects by Ids
- ///
- /// 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(readOnly: true))
- {
- 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();
-
- return sortedResult;
- }
- }
-
- ///
- /// Gets objects by Ids
- ///
- /// 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(readOnly: true))
- {
- 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.Key, x => x);
-
- var sortedResult = idsArray.Select(x =>
- {
- IContent c;
- return content.TryGetValue(x, out c) ? c : null;
- }).WhereNotNull();
-
- 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(readOnly: true))
- {
- var repository = RepositoryFactory.CreateContentRepository(uow);
- return repository.Get(key);
- }
- }
-
- ///
- /// 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(readOnly: 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(readOnly: 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(readOnly: 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(readOnly: 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(readOnly: 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(readOnly: 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(readOnly: 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(readOnly: 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(readOnly: true))
- {
- var repository = RepositoryFactory.CreateContentRepository(uow);
-
- var query = Query.Builder;
- // always check for a parent - else it will also get decendants (and then you should use the GetPagedDescendants method)
- 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(readOnly: true))
- {
- var repository = RepositoryFactory.CreateContentRepository(uow);
-
- // get query - if the id is System Root, then just get all
- var query = Query.Builder;
- if (id != Constants.System.Root)
- {
- var entityRepository = RepositoryFactory.CreateEntityRepository(uow);
- var contentPath = entityRepository.GetAllPaths(Constants.ObjectTypes.DocumentGuid, id).ToArray();
- if (contentPath.Length == 0)
- {
- totalChildren = 0;
- return Enumerable.Empty();
- }
- query.Where(x => x.Path.SqlStartsWith(string.Format("{0},", contentPath[0].Path), TextColumnType.NVarchar));
- }
-
-
- // get filter
- 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(readOnly: true))
- {
- var repository = RepositoryFactory.CreateContentRepository(uow);
-
- // get query - if the id is System Root, then just get all
- var query = Query.Builder;
- if (id != Constants.System.Root)
- {
- var entityRepository = RepositoryFactory.CreateEntityRepository(uow);
- var contentPath = entityRepository.GetAllPaths(Constants.ObjectTypes.DocumentGuid, id).ToArray();
- if (contentPath.Length == 0)
- {
- totalChildren = 0;
- return Enumerable.Empty();
- }
- query.Where(x => x.Path.SqlStartsWith(string.Format("{0},", contentPath[0].Path), 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(readOnly: 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);
- return content == null ? Enumerable.Empty() : 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(readOnly: 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);
- }
-
- ///
- /// 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(readOnly: 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(readOnly: 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(readOnly: true))
- {
- var repository = RepositoryFactory.CreateContentRepository(uow);
- var query = Query.Builder.Where(x => x.ExpireDate <= DateTime.Now);
- return repository.GetByQuery(query).Where(x => x.HasPublishedVersion);
- }
- }
-
- ///
- /// 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(readOnly: 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(readOnly: true))
- {
- var repository = RepositoryFactory.CreateContentRepository(uow);
- var query = Query.Builder.Where(x => x.Path.StartsWith(Constants.System.RecycleBinContentPathPrefix));
- 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(readOnly: 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(readOnly: 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)
- {
- int[] ids;
- if (content.HasIdentity)
- {
- // get ids from path (we have identity)
- // skip the first one that has to be -1 - and we don't care
- // skip the last one that has to be "this" - and it's ok to stop at the parent
- ids = content.Path.Split(',').Skip(1).SkipLast().Select(int.Parse).ToArray();
- }
- else
- {
- // no path yet (no identity), have to move up to parent
- // skip the first one that has to be -1 - and we don't care
- // don't skip the last one that is "parent"
- var parent = GetById(content.ParentId);
- if (parent == null) return false;
- ids = parent.Path.Split(',').Skip(1).Select(int.Parse).ToArray();
- }
- if (ids.Length == 0)
- return false;
-
- // if the first one is recycle bin, fail fast
- if (ids[0] == Constants.System.RecycleBinContent)
- return false;
-
- // fixme - move to repository?
- using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
- {
- var sql = new Sql(@"
- SELECT id
- FROM umbracoNode
- JOIN cmsDocument ON umbracoNode.id=cmsDocument.nodeId AND cmsDocument.published=@0
- WHERE umbracoNode.trashed=@1 AND umbracoNode.id IN (@2)",
- true, false, ids);
- var x = uow.Database.Fetch(sql);
- return ids.Length == x.Count;
- }
- }
-
- ///
- /// 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)
- {
- return MoveToRecycleBinDo(content, userId, false);
- }
-
- ///
- /// 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
- ///
- /// A boolean indicating to ignore this item's descendant list from also being moved to the recycle bin. This is required for the DeleteContentOfTypes method
- /// because it has already looked up all descendant nodes that will need to be recycled
- /// TODO: Fix all of this, it will require a reasonable refactor and most of this stuff should be done at the repo level instead of service sub operations
- ///
- private Attempt MoveToRecycleBinDo(IContent content, int userId, bool ignoreDescendants)
- {
- 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;
- var moveEventInfo = new MoveEventInfo(content, originalPath, Constants.System.RecycleBinContent);
- var moveEventArgs = new MoveEventArgs(evtMsgs, moveEventInfo);
- if (uow.Events.DispatchCancelable(Trashing, this, moveEventArgs, "Trashing"))
- {
- uow.Commit();
- return OperationStatus.Cancelled(evtMsgs);
- }
- var moveInfo = new List>
- {
- moveEventInfo
- };
-
- //get descendents to process of the content item that is being moved to trash - must be done before changing the state below
- //must be processed with shallowest levels first
- var descendants = ignoreDescendants ? Enumerable.Empty() : GetDescendants(content).OrderBy(x => x.Level);
-
- //Do the updates for this item
- var repository = RepositoryFactory.CreateContentRepository(uow);
- //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);
- }
- 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)
- {
- //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);
- descendant.WriterId = userId;
- descendant.ChangeTrashedState(true, descendant.ParentId);
- repository.AddOrUpdate(descendant);
-
- moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId));
- }
-
- moveEventArgs.CanCancel = false;
- moveEventArgs.MoveInfoCollection = moveInfo;
- uow.Events.Dispatch(Trashed, this, moveEventArgs, "Trashed");
-
- Audit(uow, AuditType.Move, "Move Content to Recycle Bin performed by user", userId, content.Id);
- uow.Commit();
- }
-
- 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)
- {
- var attempt = ((IContentServiceOperations)this).UnPublish(content, userId);
- LogHelper.Debug(string.Format("Result of unpublish attempt: {0}", attempt.Result.StatusType));
- return attempt.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);
- }
-
- public IContent GetBlueprintById(int id)
- {
- using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
- {
- var repository = RepositoryFactory.CreateContentBlueprintRepository(uow);
- var blueprint = repository.Get(id);
- if (blueprint != null)
- ((Content) blueprint).IsBlueprint = true;
- return blueprint;
- }
- }
-
- public IContent GetBlueprintById(Guid id)
- {
- using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
- {
- var repository = RepositoryFactory.CreateContentBlueprintRepository(uow);
- var blueprint = repository.Get(id);
- if (blueprint != null)
- ((Content)blueprint).IsBlueprint = true;
- return blueprint;
- }
- }
-
- public void SaveBlueprint(IContent content, int userId = 0)
- {
- //always ensure the blueprint is at the root
- if (content.ParentId != -1)
- content.ParentId = -1;
-
- ((Content) content).IsBlueprint = true;
-
- using (new WriteLock(Locker))
- {
- using (var uow = UowProvider.GetUnitOfWork())
- {
- if (string.IsNullOrWhiteSpace(content.Name))
- {
- throw new ArgumentException("Cannot save content blueprint with empty name.");
- }
-
- var repository = RepositoryFactory.CreateContentBlueprintRepository(uow);
-
- if (content.HasIdentity == false)
- {
- content.CreatorId = userId;
- }
- content.WriterId = userId;
-
- repository.AddOrUpdate(content);
-
- uow.Events.Dispatch(SavedBlueprint, this, new SaveEventArgs(content), "SavedBlueprint");
-
- uow.Commit();
- }
- }
- }
-
- public void DeleteBlueprint(IContent content, int userId = 0)
- {
- using (new WriteLock(Locker))
- {
- using (var uow = UowProvider.GetUnitOfWork())
- {
- var repository = RepositoryFactory.CreateContentBlueprintRepository(uow);
- repository.Delete(content);
- uow.Events.Dispatch(DeletedBlueprint, this, new DeleteEventArgs(content), "DeletedBlueprint");
- uow.Commit();
- }
- }
- }
-
- public IContent CreateContentFromBlueprint(IContent blueprint, string name, int userId = 0)
- {
- if (blueprint == null) throw new ArgumentNullException("blueprint");
-
- var contentType = blueprint.ContentType;
- var content = new Content(name, -1, contentType);
- content.Path = string.Concat(content.ParentId.ToString(), ",", content.Id);
-
- content.CreatorId = userId;
- content.WriterId = userId;
-
- foreach (var property in blueprint.Properties)
- content.SetValue(property.Alias, property.Value);
-
- return content;
- }
-
- public void DeleteBlueprintsOfTypes(IEnumerable contentTypeIds, int userId = 0)
- {
- using (new WriteLock(Locker))
- using (var uow = UowProvider.GetUnitOfWork())
- {
- var repository = RepositoryFactory.CreateContentBlueprintRepository(uow);
-
- var contentTypeIdsA = contentTypeIds.ToArray();
- var query = new Query();
- if (contentTypeIdsA.Length > 0)
- {
- query.Where(x => contentTypeIdsA.Contains(x.ContentTypeId));
- }
- var blueprints = repository.GetByQuery(query).Select(x =>
- {
- ((Content) x).IsBlueprint = true;
- return x;
- }).ToArray();
-
- foreach (var blueprint in blueprints)
- {
- repository.Delete(blueprint);
- }
-
- uow.Events.Dispatch(DeletedBlueprint, this, new DeleteEventArgs(blueprints), "DeletedBlueprint");
- uow.Commit();
- }
- }
-
- public void DeleteBlueprintsOfType(int contentTypeId, int userId = 0)
- {
- DeleteBlueprintsOfTypes(new[] {contentTypeId}, 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.
- 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())
- {
- var saveEventArgs = new SaveEventArgs(asArray, evtMsgs);
- if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
- {
- uow.Commit();
- return OperationStatus.Cancelled(evtMsgs);
- }
-
- // todo - understand what's a lock in a scope?
- // (though, these locks are refactored in v8)
- 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));
- }
- }
- }
-
- if (raiseEvents)
- {
- saveEventArgs.CanCancel = false;
- uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved");
- }
-
- Audit(uow, AuditType.Save, "Bulk Save content performed by user", userId == -1 ? 0 : userId, Constants.System.Root);
- uow.Commit();
-
- 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())
- {
- var deleteEventArgs = new DeleteEventArgs(content, evtMsgs);
- if (uow.Events.DispatchCancelable(Deleting, this, deleteEventArgs, "Deleting"))
- {
- uow.Commit();
- 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);
-
- deleteEventArgs.CanCancel = false;
- uow.Events.Dispatch(Deleted, this, deleteEventArgs, "Deleted");
-
- Audit(uow, AuditType.Delete, "Delete Content performed by user", userId, content.Id);
- uow.Commit();
- }
-
- 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 the specified types. All Descendants of deleted content that is not of these types is moved to Recycle Bin.
- ///
- /// Id of the
- /// Optional Id of the user issueing the delete operation
- public void DeleteContentOfTypes(IEnumerable contentTypeIds, int userId = 0)
- {
- using (new WriteLock(Locker))
- using (var uow = UowProvider.GetUnitOfWork())
- {
- var repository = RepositoryFactory.CreateContentRepository(uow);
-
- //track the 'root' items of the collection of nodes discovered to delete, we need to use
- //these items to lookup descendants that are not of this doc type so they can be transfered
- //to the recycle bin
- IDictionary rootItems;
- var contentToDelete = this.TrackDeletionsForDeleteContentOfTypes(contentTypeIds, repository, out rootItems).ToArray();
-
- if (uow.Events.DispatchCancelable(Deleting, this, new DeleteEventArgs(contentToDelete), "Deleting"))
- {
- uow.Commit();
- return;
- }
-
- //Determine the items that will need to be recycled (that are children of these content items but not of these content types)
- var contentToRecycle = this.TrackTrashedForDeleteContentOfTypes(contentTypeIds, rootItems, repository);
-
- //move each item to the bin starting with the deepest items
- foreach (var child in contentToRecycle.OrderByDescending(x => x.Level))
- {
- MoveToRecycleBinDo(child, userId, true);
- }
-
- foreach (var content in contentToDelete)
- {
- Delete(content, userId);
- }
-
- Audit(uow, AuditType.Delete,
- string.Format("Delete Content of Types {0} performed by user", string.Join(",", contentTypeIds)),
- userId, Constants.System.Root);
- uow.Commit();
- }
- }
-
- ///
- /// 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)
- {
- DeleteContentOfTypes(new[] {contentTypeId}, userId);
- }
-
- ///
- /// 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())
- {
- var deleteRevisionsEventArgs = new DeleteRevisionsEventArgs(id, dateToRetain: versionDate);
- if (uow.Events.DispatchCancelable(DeletingVersions, this, deleteRevisionsEventArgs, "DeletingVersions"))
- {
- uow.Commit();
- return;
- }
-
- var repository = RepositoryFactory.CreateContentRepository(uow);
- repository.DeleteVersions(id, versionDate);
- deleteRevisionsEventArgs.CanCancel = false;
- uow.Events.Dispatch(DeletedVersions, this, deleteRevisionsEventArgs, "DeletedVersions");
-
- Audit(uow, AuditType.Delete, "Delete Content by version date performed by user", userId, Constants.System.Root);
- uow.Commit();
- }
- }
-
- ///
- /// 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"))
- {
- uow.Commit();
- return;
- }
-
- if (deletePriorVersions)
- {
- var content = GetByVersion(versionId);
- DeleteVersions(id, content.UpdateDate, userId);
- }
-
- var repository = RepositoryFactory.CreateContentRepository(uow);
- repository.DeleteVersion(versionId);
-
- uow.Events.Dispatch(DeletedVersions, this, new DeleteRevisionsEventArgs(id, false, specificVersion: versionId), "DeletedVersions");
-
- Audit(uow, AuditType.Delete, "Delete Content by version performed by user", userId, Constants.System.Root);
- uow.Commit();
- }
- }
- }
-
- ///
- /// 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;
- }
-
- using (var uow = UowProvider.GetUnitOfWork())
- {
- var moveEventInfo = new MoveEventInfo(content, content.Path, parentId);
- var moveEventArgs = new MoveEventArgs(moveEventInfo);
- if (uow.Events.DispatchCancelable(Moving, this, moveEventArgs, "Moving"))
- {
- uow.Commit();
- 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);
-
- moveEventArgs.MoveInfoCollection = moveInfo;
- moveEventArgs.CanCancel = false;
- uow.Events.Dispatch(Moved, this, moveEventArgs, "Moved");
-
- Audit(uow, AuditType.Move, "Move Content performed by user", userId, content.Id);
- uow.Commit();
- }
- }
- }
-
- ///
- /// Empties the Recycle Bin by deleting all that resides in the bin
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- [Obsolete("Use EmptyRecycleBin with explicit indication of user ID instead")]
- public void EmptyRecycleBin() => EmptyRecycleBin(0);
-
- ///
- /// Empties the Recycle Bin by deleting all that resides in the bin
- ///
- /// Optional Id of the User emptying the Recycle Bin
- public void EmptyRecycleBin(int userId = 0)
- {
- using (new WriteLock(Locker))
- {
- var nodeObjectType = Constants.ObjectTypes.DocumentGuid;
-
- using (var uow = UowProvider.GetUnitOfWork())
- {
- var repository = RepositoryFactory.CreateContentRepository(uow);
-
- //Create a dictionary of ids -> dictionary of property aliases + values
- var entities = repository.GetEntitiesInRecycleBin()
- .ToDictionary(
- key => key.Id,
- val => (IEnumerable)val.Properties);
-
- var files = ((ContentRepository)repository).GetFilesInRecycleBinForUploadField();
-
- var recycleBinEventArgs = new RecycleBinEventArgs(nodeObjectType, entities, files);
- if (uow.Events.DispatchCancelable(EmptyingRecycleBin, this, recycleBinEventArgs))
- {
- uow.Commit();
- return;
- }
-
- var success = repository.EmptyRecycleBin();
- recycleBinEventArgs.CanCancel = false;
- recycleBinEventArgs.RecycleBinEmptiedSuccessfully = success;
- uow.Events.Dispatch(EmptiedRecycleBin, this, recycleBinEventArgs);
-
- Audit(uow, AuditType.Delete, "Empty Content Recycle Bin performed by user", userId, Constants.System.RecycleBinContent);
- uow.Commit();
- }
- }
- }
-
- ///
- /// 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);
-
- using (var uow = UowProvider.GetUnitOfWork())
- {
- var copyEventArgs = new CopyEventArgs(content, copy, true, parentId, relateToOriginal);
- if (uow.Events.DispatchCancelable(Copying, this, copyEventArgs))
- {
- uow.Commit();
- return null;
- }
-
- var repository = RepositoryFactory.CreateContentRepository(uow);
-
- // Update the create author and last edit author
- copy.CreatorId = userId;
- copy.WriterId = userId;
-
- //get the current permissions, if there are any explicit ones they need to be copied
- var currentPermissions = GetPermissionsForEntity(content);
- currentPermissions.RemoveWhere(p => p.IsDefaultPermissions);
-
- repository.AddOrUpdate(copy);
- repository.AddOrUpdatePreviewXml(copy, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c));
-
- //add permissions
- if (currentPermissions.Count > 0)
- {
- var permissionSet = new ContentPermissionSet(copy, currentPermissions);
- repository.AddOrUpdatePermissions(permissionSet);
- }
-
- uow.Commit(); // todo - this should flush, not 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(); // todo - this should flush, not 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);
- }
- }
- copyEventArgs.CanCancel = false;
- uow.Events.Dispatch(Copied, this, copyEventArgs);
- Audit(uow, AuditType.Copy, "Copy Content performed by user", userId, content.Id);
- uow.Commit();
- }
-
- 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())
- {
- var sendToPublishEventArgs = new SendToPublishEventArgs(content);
- if (uow.Events.DispatchCancelable(SendingToPublish, this, sendToPublishEventArgs))
- {
- uow.Commit();
- return false;
- }
-
- //Save before raising event
- Save(content, userId);
- sendToPublishEventArgs.CanCancel = false;
- uow.Events.Dispatch(SentToPublish, this, sendToPublishEventArgs);
-
- Audit(uow, AuditType.SendToPublish, "Send to Publish performed by user", content.WriterId, content.Id);
- uow.Commit();
-
- 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())
- {
- var rollbackEventArgs = new RollbackEventArgs(content);
- if (uow.Events.DispatchCancelable(RollingBack, this, rollbackEventArgs))
- {
- uow.Commit();
- return content;
- }
-
- var repository = RepositoryFactory.CreateContentRepository(uow);
-
- content.WriterId = userId;
- content.CreatorId = userId;
- content.ChangePublishedState(PublishedState.Unpublished);
-
- repository.AddOrUpdate(content);
- repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c));
- rollbackEventArgs.CanCancel = false;
- uow.Events.Dispatch(RolledBack, this, rollbackEventArgs);
-
- Audit(uow, AuditType.RollBack, "Content rollback performed by user", content.WriterId, content.Id);
- uow.Commit();
- }
-
- 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();
- var saveEventArgs = new SaveEventArgs(asArray);
- if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
- {
- uow.Commit();
- return false;
- }
-
- var repository = RepositoryFactory.CreateContentRepository(uow);
-
- var 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));
- }
-
- if (raiseEvents)
- {
- saveEventArgs.CanCancel = false;
- uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved");
- }
-
- 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);
- }
-
- Audit(uow, AuditType.Sort, "Sorting content performed by user", userId, 0);
- uow.Commit();
- }
- }
-
- return true;
- }
-
- ///
- /// Sorts a collection of objects by updating the SortOrder according
- /// to the ordering of node Ids 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(int[] ids, int userId = 0, bool raiseEvents = true)
- {
- var shouldBePublished = new List();
- var shouldBeSaved = new List();
-
- using (new WriteLock(Locker))
- {
- var allContent = GetByIds(ids).ToDictionary(x => x.Id, x => x);
- if (allContent.Any() == false)
- {
- return false;
- }
- var items = ids.Select(x => allContent[x]).ToArray();
-
- using (var uow = UowProvider.GetUnitOfWork())
- {
- var saveEventArgs = new SaveEventArgs(items);
- if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
- {
- uow.Commit();
- return false;
- }
-
- var repository = RepositoryFactory.CreateContentRepository(uow);
-
- var i = 0;
- foreach (var content in items)
- {
- //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));
- }
-
- if (raiseEvents)
- {
- saveEventArgs.CanCancel = false;
- uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved");
- }
-
- 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);
- }
-
- Audit(uow, AuditType.Sort, "Sort child items performed by user", userId, items.First().ParentId);
- uow.Commit();
- }
- }
-
- return true;
- }
-
- public IEnumerable GetBlueprintsForContentTypes(params int[] documentTypeIds)
- {
- using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
- {
- var repository = RepositoryFactory.CreateContentBlueprintRepository(uow);
-
- var query = new Query();
- if (documentTypeIds.Length > 0)
- {
- query.Where(x => documentTypeIds.Contains(x.ContentTypeId));
- }
- return repository.GetByQuery(query).Select(x =>
- {
- ((Content) x).IsBlueprint = true;
- return x;
- });
- }
- }
-
- ///
- /// 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");
-
- using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
- {
- 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;
- }
- }
-
- public XmlDocument BuildPreviewXmlCache()
- {
- using (var uow = UowProvider.GetUnitOfWork())
- {
- var repository = RepositoryFactory.CreateContentRepository(uow);
- var result = repository.BuildPreviewXmlCache();
- 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);
-
- Audit(uow, AuditType.Publish, "ContentService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, Constants.System.Root);
- uow.Commit();
- }
- }
-
- #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(readOnly: 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(IScopeUnitOfWork uow, AuditType type, string message, int userId, int objectId)
- {
- var auditRepo = RepositoryFactory.CreateAuditRepository(uow);
- auditRepo.AddOrUpdate(new AuditItem(objectId, message, type, userId));
- }
-
- //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);
- }
-
- //Save xml to db and call following method to fire event:
- _publishingStrategy.PublishingFinalized(uow, updated, false);
-
- Audit(uow, AuditType.Publish, "Publish with Children performed by user", userId, content.Id);
- uow.Commit();
-
- 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);
-
- //Delete xml from db? and call following method to fire event through PublishingStrategy to update cache
- if (omitCacheRefresh == false)
- _publishingStrategy.UnPublishingFinalized(uow, content);
-
- Audit(uow, AuditType.UnPublish, "UnPublish performed by user", userId, content.Id);
- uow.Commit();
- }
-
- 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())
- {
- var saveEventArgs = new SaveEventArgs(content, evtMsgs);
- if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
- {
- uow.Commit();
- 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);
- repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c));
-
- if (published)
- {
- //Content Xml
- repository.AddOrUpdateContentXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c));
- }
-
- if (raiseEvents)
- {
- saveEventArgs.CanCancel = false;
- uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved");
- }
-
- //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))
- {
- //TODO: Horrible for performance if there are lots of descendents! We should page if anything but this is crazy
- var descendants = GetPublishedDescendants(content);
- _publishingStrategy.PublishingFinalized(uow, descendants, false);
- }
-
- uow.Commit();
-
- if (publishStatus.StatusType == PublishStatusType.Success)
- {
- Audit(uow, AuditType.Publish, "Save and Publish performed by user", userId, content.Id);
- }
- else
- {
- Audit(uow, AuditType.Save, "Save performed by user", userId, content.Id);
- }
- uow.Commit();
-
- 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())
- {
- var saveEventArgs = new SaveEventArgs(content, evtMsgs);
- if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
- {
- uow.Commit();
- 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));
-
- if (raiseEvents)
- {
- saveEventArgs.CanCancel = false;
- uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved");
- }
-
- Audit(uow, AuditType.Save, "Save Content performed by user", userId, content.Id);
- uow.Commit();
- }
-
- return OperationStatus.Success(evtMsgs);
- }
- }
-
- 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;
- }
-
- 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;
- }
-
- 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(readOnly: 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;
-
- ///
- /// Occurs after a blueprint has been saved.
- ///
- public static event TypedEventHandler> SavedBlueprint;
-
- ///
- /// Occurs after a blueprint has been deleted.
- ///
- public static event TypedEventHandler> DeletedBlueprint;
-
- #endregion
- }
-}
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+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;
+
+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(UowProvider.ScopeProvider, eventMessagesFactory, logger);
+ _dataTypeService = dataTypeService;
+ _userService = userService;
+ }
+
+ #region Static Queries
+
+ private IQuery _notTrashedQuery;
+
+ #endregion
+
+ public int CountPublished(string contentTypeAlias = null)
+ {
+ using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
+ {
+ var repository = RepositoryFactory.CreateContentRepository(uow);
+ return repository.CountPublished(contentTypeAlias);
+ }
+ }
+
+ public int Count(string contentTypeAlias = null)
+ {
+ using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
+ {
+ var repository = RepositoryFactory.CreateContentRepository(uow);
+ return repository.Count(contentTypeAlias);
+ }
+ }
+
+ public int CountChildren(int parentId, string contentTypeAlias = null)
+ {
+ using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
+ {
+ var repository = RepositoryFactory.CreateContentRepository(uow);
+ return repository.CountChildren(parentId, contentTypeAlias);
+ }
+ }
+
+ public int CountDescendants(int parentId, string contentTypeAlias = null)
+ {
+ using (var uow = UowProvider.GetUnitOfWork(readOnly: 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 group ids
+ ///
+ ///
+ ///
+ ///
+ public void AssignContentPermission(IContent entity, char permission, IEnumerable groupIds)
+ {
+ using (var uow = UowProvider.GetUnitOfWork())
+ {
+ var repository = RepositoryFactory.CreateContentRepository(uow);
+ repository.AssignEntityPermission(entity, permission, groupIds);
+ uow.Commit();
+ }
+ }
+
+ ///
+ /// Returns implicit/inherited permissions assigned to the content item for all user groups
+ ///
+ ///
+ ///
+ public EntityPermissionCollection GetPermissionsForEntity(IContent content)
+ {
+ using (var uow = UowProvider.GetUnitOfWork(readOnly: 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, Guid parentId, string contentTypeAlias, int userId = 0)
+ {
+ var parent = GetById(parentId);
+ return CreateContent(name, parent, contentTypeAlias, userId);
+ }
+
+ ///
+ /// 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);
+
+ using (var uow = UowProvider.GetUnitOfWork())
+ {
+ var newEventArgs = new NewEventArgs(content, contentTypeAlias, parentId);
+ if (uow.Events.DispatchCancelable(Creating, this, newEventArgs))
+ {
+ uow.Commit();
+ content.WasCancelled = true;
+ return content;
+ }
+
+ content.CreatorId = userId;
+ content.WriterId = userId;
+ newEventArgs.CanCancel = false;
+ uow.Events.Dispatch(Created, this, newEventArgs);
+
+ uow.Commit();
+ }
+
+ 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())
+ {
+ var newEventArgs = new NewEventArgs(content, contentTypeAlias, parent);
+ if (uow.Events.DispatchCancelable(Creating, this, newEventArgs))
+ {
+ uow.Commit();
+ content.WasCancelled = true;
+ return content;
+ }
+
+ content.CreatorId = userId;
+ content.WriterId = userId;
+ newEventArgs.CanCancel = false;
+ uow.Events.Dispatch(Created, this, newEventArgs);
+
+ uow.Commit();
+ }
+
+ 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.
+ var newEventArgs = new NewEventArgs(content, contentTypeAlias, parentId);
+ if (uow.Events.DispatchCancelable(Creating, this, newEventArgs))
+ {
+ uow.Commit();
+ content.WasCancelled = true;
+ return content;
+ }
+
+ var saveEventArgs = new SaveEventArgs(content);
+ if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
+ {
+ uow.Commit();
+ content.WasCancelled = true;
+ return content;
+ }
+
+ var repository = RepositoryFactory.CreateContentRepository(uow);
+ content.CreatorId = userId;
+ content.WriterId = userId;
+
+ repository.AddOrUpdate(content);
+ repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c));
+ saveEventArgs.CanCancel = false;
+ uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved");
+ newEventArgs.CanCancel = false;
+ uow.Events.Dispatch(Created, this, newEventArgs);
+
+ Audit(uow, AuditType.New, string.Format("Content '{0}' was created with Id {1}", name, content.Id), content.CreatorId, content.Id);
+ uow.Commit();
+ }
+
+ return content;
+ }
+
+ public IList GetAnchorValuesFromRTEs(int id)
+ {
+ var result = new List();
+
+ var content = GetById(id);
+
+ foreach (var contentProperty in content.Properties)
+ {
+ if (string.Equals(contentProperty.PropertyType.PropertyEditorAlias, Constants.PropertyEditors.TinyMCEAlias))
+ {
+ var value = contentProperty.Value.ToString();
+
+ result.AddRange(GetAnchorValuesFromRTEContent(value));
+ }
+ }
+
+
+ return result;
+ }
+
+ public IList GetAnchorValuesFromRTEContent(string rteContent)
+ {
+ var result = new List();
+ var regex = new Regex("");
+ var matches = regex.Matches(rteContent);
+
+ foreach (Match match in matches)
+ {
+ result.Add(match.Value.Split('\"')[1]);
+ }
+
+ return result;
+ }
+
+ ///
+ /// 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.
+ var newEventArgs = new NewEventArgs(content, contentTypeAlias, parent);
+ if (uow.Events.DispatchCancelable(Creating, this, newEventArgs))
+ {
+ uow.Commit();
+ content.WasCancelled = true;
+ return content;
+ }
+
+ var saveEventArgs = new SaveEventArgs(content);
+ if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
+ {
+ uow.Commit();
+ content.WasCancelled = true;
+ return content;
+ }
+
+ var repository = RepositoryFactory.CreateContentRepository(uow);
+ content.CreatorId = userId;
+ content.WriterId = userId;
+
+ repository.AddOrUpdate(content);
+ repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c));
+ saveEventArgs.CanCancel = false;
+ uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved");
+ newEventArgs.CanCancel = false;
+ uow.Events.Dispatch(Created, this, newEventArgs);
+
+ Audit(uow, AuditType.New, string.Format("Content '{0}' was created with Id {1}", name, content.Id), content.CreatorId, content.Id);
+ uow.Commit();
+ }
+
+ return content;
+ }
+
+ ///
+ /// Gets an object by Id
+ ///
+ /// Id of the Content to retrieve
+ ///
+ public IContent GetById(int id)
+ {
+ using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
+ {
+ var repository = RepositoryFactory.CreateContentRepository(uow);
+ return repository.Get(id);
+ }
+ }
+
+ ///
+ /// Gets objects by Ids
+ ///
+ /// 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(readOnly: true))
+ {
+ 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();
+
+ return sortedResult;
+ }
+ }
+
+ ///
+ /// Gets objects by Ids
+ ///
+ /// 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(readOnly: true))
+ {
+ 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.Key, x => x);
+
+ var sortedResult = idsArray.Select(x =>
+ {
+ IContent c;
+ return content.TryGetValue(x, out c) ? c : null;
+ }).WhereNotNull();
+
+ 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(readOnly: true))
+ {
+ var repository = RepositoryFactory.CreateContentRepository(uow);
+ return repository.Get(key);
+ }
+ }
+
+ ///
+ /// 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(readOnly: 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(readOnly: 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(readOnly: 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(readOnly: 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(readOnly: 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(readOnly: 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(readOnly: 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(readOnly: 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(readOnly: true))
+ {
+ var repository = RepositoryFactory.CreateContentRepository(uow);
+
+ var query = Query.Builder;
+ // always check for a parent - else it will also get decendants (and then you should use the GetPagedDescendants method)
+ 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(readOnly: true))
+ {
+ var repository = RepositoryFactory.CreateContentRepository(uow);
+
+ // get query - if the id is System Root, then just get all
+ var query = Query.Builder;
+ if (id != Constants.System.Root)
+ {
+ var entityRepository = RepositoryFactory.CreateEntityRepository(uow);
+ var contentPath = entityRepository.GetAllPaths(Constants.ObjectTypes.DocumentGuid, id).ToArray();
+ if (contentPath.Length == 0)
+ {
+ totalChildren = 0;
+ return Enumerable.Empty();
+ }
+ query.Where(x => x.Path.SqlStartsWith(string.Format("{0},", contentPath[0].Path), TextColumnType.NVarchar));
+ }
+
+
+ // get filter
+ 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(readOnly: true))
+ {
+ var repository = RepositoryFactory.CreateContentRepository(uow);
+
+ // get query - if the id is System Root, then just get all
+ var query = Query.Builder;
+ if (id != Constants.System.Root)
+ {
+ var entityRepository = RepositoryFactory.CreateEntityRepository(uow);
+ var contentPath = entityRepository.GetAllPaths(Constants.ObjectTypes.DocumentGuid, id).ToArray();
+ if (contentPath.Length == 0)
+ {
+ totalChildren = 0;
+ return Enumerable.Empty();
+ }
+ query.Where(x => x.Path.SqlStartsWith(string.Format("{0},", contentPath[0].Path), 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(readOnly: 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);
+ return content == null ? Enumerable.Empty() : 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(readOnly: 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);
+ }
+
+ ///
+ /// 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(readOnly: 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(readOnly: 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(readOnly: true))
+ {
+ var repository = RepositoryFactory.CreateContentRepository(uow);
+ var query = Query.Builder.Where(x => x.ExpireDate <= DateTime.Now);
+ return repository.GetByQuery(query).Where(x => x.HasPublishedVersion);
+ }
+ }
+
+ ///
+ /// 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(readOnly: 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(readOnly: true))
+ {
+ var repository = RepositoryFactory.CreateContentRepository(uow);
+ var query = Query.Builder.Where(x => x.Path.StartsWith(Constants.System.RecycleBinContentPathPrefix));
+ 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(readOnly: 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(readOnly: 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)
+ {
+ int[] ids;
+ if (content.HasIdentity)
+ {
+ // get ids from path (we have identity)
+ // skip the first one that has to be -1 - and we don't care
+ // skip the last one that has to be "this" - and it's ok to stop at the parent
+ ids = content.Path.Split(',').Skip(1).SkipLast().Select(int.Parse).ToArray();
+ }
+ else
+ {
+ // no path yet (no identity), have to move up to parent
+ // skip the first one that has to be -1 - and we don't care
+ // don't skip the last one that is "parent"
+ var parent = GetById(content.ParentId);
+ if (parent == null) return false;
+ ids = parent.Path.Split(',').Skip(1).Select(int.Parse).ToArray();
+ }
+ if (ids.Length == 0)
+ return false;
+
+ // if the first one is recycle bin, fail fast
+ if (ids[0] == Constants.System.RecycleBinContent)
+ return false;
+
+ // fixme - move to repository?
+ using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
+ {
+ var sql = new Sql(@"
+ SELECT id
+ FROM umbracoNode
+ JOIN cmsDocument ON umbracoNode.id=cmsDocument.nodeId AND cmsDocument.published=@0
+ WHERE umbracoNode.trashed=@1 AND umbracoNode.id IN (@2)",
+ true, false, ids);
+ var x = uow.Database.Fetch(sql);
+ return ids.Length == x.Count;
+ }
+ }
+
+ ///
+ /// 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)
+ {
+ return MoveToRecycleBinDo(content, userId, false);
+ }
+
+ ///
+ /// 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
+ ///
+ /// A boolean indicating to ignore this item's descendant list from also being moved to the recycle bin. This is required for the DeleteContentOfTypes method
+ /// because it has already looked up all descendant nodes that will need to be recycled
+ /// TODO: Fix all of this, it will require a reasonable refactor and most of this stuff should be done at the repo level instead of service sub operations
+ ///
+ private Attempt MoveToRecycleBinDo(IContent content, int userId, bool ignoreDescendants)
+ {
+ 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;
+ var moveEventInfo = new MoveEventInfo(content, originalPath, Constants.System.RecycleBinContent);
+ var moveEventArgs = new MoveEventArgs(evtMsgs, moveEventInfo);
+ if (uow.Events.DispatchCancelable(Trashing, this, moveEventArgs, "Trashing"))
+ {
+ uow.Commit();
+ return OperationStatus.Cancelled(evtMsgs);
+ }
+ var moveInfo = new List>
+ {
+ moveEventInfo
+ };
+
+ //get descendents to process of the content item that is being moved to trash - must be done before changing the state below
+ //must be processed with shallowest levels first
+ var descendants = ignoreDescendants ? Enumerable.Empty() : GetDescendants(content).OrderBy(x => x.Level);
+
+ //Do the updates for this item
+ var repository = RepositoryFactory.CreateContentRepository(uow);
+ //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);
+ }
+ 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)
+ {
+ //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);
+ descendant.WriterId = userId;
+ descendant.ChangeTrashedState(true, descendant.ParentId);
+ repository.AddOrUpdate(descendant);
+
+ moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId));
+ }
+
+ moveEventArgs.CanCancel = false;
+ moveEventArgs.MoveInfoCollection = moveInfo;
+ uow.Events.Dispatch(Trashed, this, moveEventArgs, "Trashed");
+
+ Audit(uow, AuditType.Move, "Move Content to Recycle Bin performed by user", userId, content.Id);
+ uow.Commit();
+ }
+
+ 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)
+ {
+ var attempt = ((IContentServiceOperations)this).UnPublish(content, userId);
+ LogHelper.Debug(string.Format("Result of unpublish attempt: {0}", attempt.Result.StatusType));
+ return attempt.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);
+ }
+
+ public IContent GetBlueprintById(int id)
+ {
+ using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
+ {
+ var repository = RepositoryFactory.CreateContentBlueprintRepository(uow);
+ var blueprint = repository.Get(id);
+ if (blueprint != null)
+ ((Content) blueprint).IsBlueprint = true;
+ return blueprint;
+ }
+ }
+
+ public IContent GetBlueprintById(Guid id)
+ {
+ using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
+ {
+ var repository = RepositoryFactory.CreateContentBlueprintRepository(uow);
+ var blueprint = repository.Get(id);
+ if (blueprint != null)
+ ((Content)blueprint).IsBlueprint = true;
+ return blueprint;
+ }
+ }
+
+ public void SaveBlueprint(IContent content, int userId = 0)
+ {
+ //always ensure the blueprint is at the root
+ if (content.ParentId != -1)
+ content.ParentId = -1;
+
+ ((Content) content).IsBlueprint = true;
+
+ using (new WriteLock(Locker))
+ {
+ using (var uow = UowProvider.GetUnitOfWork())
+ {
+ if (string.IsNullOrWhiteSpace(content.Name))
+ {
+ throw new ArgumentException("Cannot save content blueprint with empty name.");
+ }
+
+ var repository = RepositoryFactory.CreateContentBlueprintRepository(uow);
+
+ if (content.HasIdentity == false)
+ {
+ content.CreatorId = userId;
+ }
+ content.WriterId = userId;
+
+ repository.AddOrUpdate(content);
+
+ uow.Events.Dispatch(SavedBlueprint, this, new SaveEventArgs(content), "SavedBlueprint");
+
+ uow.Commit();
+ }
+ }
+ }
+
+ public void DeleteBlueprint(IContent content, int userId = 0)
+ {
+ using (new WriteLock(Locker))
+ {
+ using (var uow = UowProvider.GetUnitOfWork())
+ {
+ var repository = RepositoryFactory.CreateContentBlueprintRepository(uow);
+ repository.Delete(content);
+ uow.Events.Dispatch(DeletedBlueprint, this, new DeleteEventArgs(content), "DeletedBlueprint");
+ uow.Commit();
+ }
+ }
+ }
+
+ public IContent CreateContentFromBlueprint(IContent blueprint, string name, int userId = 0)
+ {
+ if (blueprint == null) throw new ArgumentNullException("blueprint");
+
+ var contentType = blueprint.ContentType;
+ var content = new Content(name, -1, contentType);
+ content.Path = string.Concat(content.ParentId.ToString(), ",", content.Id);
+
+ content.CreatorId = userId;
+ content.WriterId = userId;
+
+ foreach (var property in blueprint.Properties)
+ content.SetValue(property.Alias, property.Value);
+
+ return content;
+ }
+
+ public void DeleteBlueprintsOfTypes(IEnumerable contentTypeIds, int userId = 0)
+ {
+ using (new WriteLock(Locker))
+ using (var uow = UowProvider.GetUnitOfWork())
+ {
+ var repository = RepositoryFactory.CreateContentBlueprintRepository(uow);
+
+ var contentTypeIdsA = contentTypeIds.ToArray();
+ var query = new Query();
+ if (contentTypeIdsA.Length > 0)
+ {
+ query.Where(x => contentTypeIdsA.Contains(x.ContentTypeId));
+ }
+ var blueprints = repository.GetByQuery(query).Select(x =>
+ {
+ ((Content) x).IsBlueprint = true;
+ return x;
+ }).ToArray();
+
+ foreach (var blueprint in blueprints)
+ {
+ repository.Delete(blueprint);
+ }
+
+ uow.Events.Dispatch(DeletedBlueprint, this, new DeleteEventArgs(blueprints), "DeletedBlueprint");
+ uow.Commit();
+ }
+ }
+
+ public void DeleteBlueprintsOfType(int contentTypeId, int userId = 0)
+ {
+ DeleteBlueprintsOfTypes(new[] {contentTypeId}, userId);
+ }
+
+ ///