2012-11-07 13:56:52 -01:00
using System ;
2012-10-03 12:51:32 -02:00
using System.Collections.Generic ;
2013-02-09 11:12:51 -01:00
using System.Globalization ;
2012-10-03 12:51:32 -02:00
using System.Linq ;
2012-12-14 15:19:54 -01:00
using System.Xml.Linq ;
2012-12-15 10:43:03 +05:00
using Umbraco.Core.Events ;
2016-05-18 10:55:19 +02:00
using Umbraco.Core.IO ;
2012-10-29 14:28:16 -01:00
using Umbraco.Core.Logging ;
2012-10-03 12:51:32 -02:00
using Umbraco.Core.Models ;
2013-09-10 13:38:45 +10:00
using Umbraco.Core.Models.Membership ;
2014-08-12 08:15:43 +01:00
using Umbraco.Core.Persistence.DatabaseModelDefinitions ;
2012-10-10 12:13:23 -02:00
using Umbraco.Core.Persistence.Querying ;
2014-09-10 15:07:20 +10:00
using Umbraco.Core.Persistence.Repositories ;
2012-10-10 12:13:23 -02:00
using Umbraco.Core.Persistence.UnitOfWork ;
2015-01-27 14:58:33 +11:00
using Umbraco.Core.Strings ;
2012-10-03 12:51:32 -02:00
2012-11-12 07:40:11 -01:00
namespace Umbraco.Core.Services
2012-10-03 12:51:32 -02:00
{
2013-02-13 04:22:46 +06:00
/// <summary>
2013-04-23 12:49:33 -02:00
/// Represents the Content Service, which is an easy access to operations involving <see cref="IContent"/>
/// </summary>
2015-07-29 15:12:12 +02:00
public class ContentService : RepositoryService , IContentService , IContentServiceOperations
2013-04-23 12:49:33 -02:00
{
2014-04-28 17:28:40 +10:00
private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer ( ) ;
private readonly IDataTypeService _dataTypeService ;
2014-09-30 18:46:02 +10:00
private readonly IUserService _userService ;
2015-01-27 14:58:33 +11:00
private readonly IEnumerable < IUrlSegmentProvider > _urlSegmentProviders ;
2016-05-18 10:55:19 +02:00
private IContentTypeService _contentTypeService ;
2014-04-28 17:28:40 +10:00
2016-05-18 10:55:19 +02:00
#region Constructors
2012-12-11 23:19:07 +05:00
2015-07-23 20:04:40 +02:00
public ContentService (
2015-07-29 15:12:12 +02:00
IDatabaseUnitOfWorkProvider provider ,
2015-07-23 20:04:40 +02:00
ILogger logger ,
IEventMessagesFactory eventMessagesFactory ,
2015-07-29 15:12:12 +02:00
IDataTypeService dataTypeService ,
2015-12-18 13:29:12 +01:00
IUserService userService ,
IEnumerable < IUrlSegmentProvider > urlSegmentProviders )
2016-05-02 12:17:30 +02:00
: base ( provider , logger , eventMessagesFactory )
2014-04-28 17:28:40 +10:00
{
2016-05-18 10:55:19 +02:00
if ( dataTypeService = = null ) throw new ArgumentNullException ( nameof ( dataTypeService ) ) ;
if ( userService = = null ) throw new ArgumentNullException ( nameof ( userService ) ) ;
if ( urlSegmentProviders = = null ) throw new ArgumentNullException ( nameof ( urlSegmentProviders ) ) ;
2014-04-28 17:28:40 +10:00
_dataTypeService = dataTypeService ;
2014-09-30 18:46:02 +10:00
_userService = userService ;
2015-01-27 14:58:33 +11:00
_urlSegmentProviders = urlSegmentProviders ;
2013-04-23 12:49:33 -02:00
}
2012-10-10 12:13:23 -02:00
2016-05-18 10:55:19 +02:00
// don't change or remove this, will need it later
private IContentTypeService ContentTypeService = > _contentTypeService ;
//// handle circular dependencies
//internal IContentTypeService ContentTypeService
//{
// get
// {
// if (_contentTypeService == null)
// throw new InvalidOperationException("ContentService.ContentTypeService has not been initialized.");
// return _contentTypeService;
// }
// set { _contentTypeService = value; }
//}
#endregion
#region Count
2014-10-23 18:31:08 +10:00
public int CountPublished ( string contentTypeAlias = null )
{
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2014-10-23 18:31:08 +10:00
{
2016-05-18 10:55:19 +02:00
uow . ReadLock ( Constants . Locks . ContentTree ) ;
var repo = uow . CreateRepository < IContentRepository > ( ) ;
var count = repo . CountPublished ( ) ;
uow . Complete ( ) ;
return count ;
2014-10-23 18:31:08 +10:00
}
}
2014-08-05 12:37:20 -06:00
public int Count ( string contentTypeAlias = null )
{
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2014-08-20 17:01:12 +02:00
{
2016-05-18 10:55:19 +02:00
uow . ReadLock ( Constants . Locks . ContentTree ) ;
var repo = uow . CreateRepository < IContentRepository > ( ) ;
var count = repo . Count ( contentTypeAlias ) ;
uow . Complete ( ) ;
return count ;
2014-08-05 12:37:20 -06:00
}
}
public int CountChildren ( int parentId , string contentTypeAlias = null )
{
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2014-08-05 12:37:20 -06:00
{
2016-05-18 10:55:19 +02:00
uow . ReadLock ( Constants . Locks . ContentTree ) ;
var repo = uow . CreateRepository < IContentRepository > ( ) ;
var count = repo . CountChildren ( parentId , contentTypeAlias ) ;
uow . Complete ( ) ;
return count ;
2014-08-05 12:37:20 -06:00
}
}
public int CountDescendants ( int parentId , string contentTypeAlias = null )
{
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2014-08-05 12:37:20 -06:00
{
2016-05-18 10:55:19 +02:00
uow . ReadLock ( Constants . Locks . ContentTree ) ;
var repo = uow . CreateRepository < IContentRepository > ( ) ;
var count = repo . CountDescendants ( parentId , contentTypeAlias ) ;
uow . Complete ( ) ;
return count ;
2014-08-05 12:37:20 -06:00
}
}
2016-05-18 10:55:19 +02:00
#endregion
#region Permissions
2014-07-10 12:24:36 +10:00
/// <summary>
/// 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.
/// </summary>
/// <param name="permissionSet"></param>
public void ReplaceContentPermissions ( EntityPermissionSet permissionSet )
{
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2014-07-10 12:24:36 +10:00
{
2016-05-18 10:55:19 +02:00
uow . WriteLock ( Constants . Locks . ContentTree ) ;
var repo = uow . CreateRepository < IContentRepository > ( ) ;
repo . ReplaceContentPermissions ( permissionSet ) ;
uow . Complete ( ) ;
2014-07-10 12:24:36 +10:00
}
}
2014-02-17 15:33:17 +11:00
/// <summary>
/// Assigns a single permission to the current content item for the specified user ids
/// </summary>
/// <param name="entity"></param>
/// <param name="permission"></param>
/// <param name="userIds"></param>
2014-02-21 15:55:56 +11:00
public void AssignContentPermission ( IContent entity , char permission , IEnumerable < int > userIds )
2014-01-08 15:42:49 +11:00
{
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2013-09-10 13:38:45 +10:00
{
2016-05-18 10:55:19 +02:00
uow . WriteLock ( Constants . Locks . ContentTree ) ;
var repo = uow . CreateRepository < IContentRepository > ( ) ;
repo . AssignEntityPermission ( entity , permission , userIds ) ;
uow . Complete ( ) ;
2013-09-10 13:38:45 +10:00
}
}
2014-02-17 15:33:17 +11:00
/// <summary>
/// Gets the list of permissions for the content item
/// </summary>
/// <param name="content"></param>
/// <returns></returns>
public IEnumerable < EntityPermission > GetPermissionsForEntity ( IContent content )
{
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2014-02-17 15:33:17 +11:00
{
2016-05-18 10:55:19 +02:00
uow . ReadLock ( Constants . Locks . ContentTree ) ;
var repo = uow . CreateRepository < IContentRepository > ( ) ;
var perms = repo . GetPermissionsForEntity ( content . Id ) ;
uow . Complete ( ) ;
return perms ;
2014-02-17 15:33:17 +11:00
}
}
2016-05-18 10:55:19 +02:00
#endregion
#region Create
2013-04-23 12:49:33 -02:00
/// <summary>
2016-05-18 10:55:19 +02:00
/// Creates an <see cref="IContent"/> object of a specified content type.
2013-04-23 12:49:33 -02:00
/// </summary>
2016-05-18 10:55:19 +02:00
/// <remarks>This method simply returns a new, non-persisted, IContent without any identity. It
/// is intended as a shortcut to creating new content objects that does not invoke a save
/// operation against the database.
2013-05-27 08:45:07 -02:00
/// </remarks>
2016-05-18 10:55:19 +02:00
/// <param name="name">The name of the content object.</param>
/// <param name="parentId">The identifier of the parent, or -1.</param>
/// <param name="contentTypeAlias">The alias of the content type.</param>
/// <param name="userId">The optional id of the user creating the content.</param>
/// <returns>The content object.</returns>
2013-04-23 12:49:33 -02:00
public IContent CreateContent ( string name , int parentId , string contentTypeAlias , int userId = 0 )
{
2016-05-18 10:55:19 +02:00
var contentType = GetContentType ( contentTypeAlias ) ;
if ( contentType = = null )
throw new ArgumentException ( "No content type with that alias." , nameof ( contentTypeAlias ) ) ;
var parent = parentId > 0 ? GetById ( parentId ) : null ;
if ( parentId > 0 & & parent = = null )
throw new ArgumentException ( "No content with that id." , nameof ( parentId ) ) ;
2015-11-18 15:52:32 +01:00
2016-05-18 10:55:19 +02:00
var content = new Content ( name , parentId , contentType ) ;
CreateContent ( null , content , parent , userId , false ) ;
2012-12-15 11:04:03 -01:00
2016-05-18 10:55:19 +02:00
return content ;
}
2012-12-15 11:04:03 -01:00
2016-05-18 10:55:19 +02:00
/// <summary>
/// Creates an <see cref="IContent"/> object of a specified content type, at root.
/// </summary>
/// <remarks>This method simply returns a new, non-persisted, IContent without any identity. It
/// is intended as a shortcut to creating new content objects that does not invoke a save
/// operation against the database.
/// </remarks>
/// <param name="name">The name of the content object.</param>
/// <param name="contentTypeAlias">The alias of the content type.</param>
/// <param name="userId">The optional id of the user creating the content.</param>
/// <returns>The content object.</returns>
public IContent CreateContent ( string name , string contentTypeAlias , int userId = 0 )
{
// not locking since not saving anything
2012-12-15 11:04:03 -01:00
2016-05-18 10:55:19 +02:00
var contentType = GetContentType ( contentTypeAlias ) ;
if ( contentType = = null )
throw new ArgumentException ( "No content type with that alias." , nameof ( contentTypeAlias ) ) ;
2012-12-21 04:59:51 +05:00
2016-05-18 10:55:19 +02:00
var content = new Content ( name , - 1 , contentType ) ;
CreateContent ( null , content , null , userId , false ) ;
2012-12-15 11:04:03 -01:00
2013-04-23 12:49:33 -02:00
return content ;
}
2012-12-11 23:19:07 +05:00
2013-01-29 12:45:42 -01:00
/// <summary>
2016-05-18 10:55:19 +02:00
/// Creates an <see cref="IContent"/> object of a specified content type, under a parent.
2013-01-29 12:45:42 -01:00
/// </summary>
2016-05-18 10:55:19 +02:00
/// <remarks>This method simply returns a new, non-persisted, IContent without any identity. It
/// is intended as a shortcut to creating new content objects that does not invoke a save
/// operation against the database.
2013-05-27 08:45:07 -02:00
/// </remarks>
2016-05-18 10:55:19 +02:00
/// <param name="name">The name of the content object.</param>
/// <param name="parent">The parent content object.</param>
/// <param name="contentTypeAlias">The alias of the content type.</param>
/// <param name="userId">The optional id of the user creating the content.</param>
/// <returns>The content object.</returns>
2013-01-29 12:45:42 -01:00
public IContent CreateContent ( string name , IContent parent , string contentTypeAlias , int userId = 0 )
{
2016-05-18 10:55:19 +02:00
if ( parent = = null ) throw new ArgumentNullException ( nameof ( parent ) ) ;
2015-11-18 15:52:32 +01:00
2016-05-18 10:55:19 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2013-03-22 12:13:06 -01:00
{
2016-05-18 10:55:19 +02:00
// not locking since not saving anything
2013-01-29 12:45:42 -01:00
2016-05-18 10:55:19 +02:00
var contentType = GetContentType ( contentTypeAlias ) ;
if ( contentType = = null )
throw new ArgumentException ( "No content type with that alias." , nameof ( contentTypeAlias ) ) ; // causes rollback
2013-01-29 12:45:42 -01:00
2016-05-18 10:55:19 +02:00
var content = new Content ( name , parent , contentType ) ;
CreateContent ( uow , content , parent , userId , false ) ;
2013-01-29 12:45:42 -01:00
2016-05-18 10:55:19 +02:00
uow . Complete ( ) ;
return content ;
}
2013-01-29 12:45:42 -01:00
}
2013-05-27 08:45:07 -02:00
/// <summary>
2016-05-18 10:55:19 +02:00
/// Creates an <see cref="IContent"/> object of a specified content type.
2013-05-27 08:45:07 -02:00
/// </summary>
2016-05-18 10:55:19 +02:00
/// <remarks>This method returns a new, persisted, IContent with an identity.</remarks>
/// <param name="name">The name of the content object.</param>
/// <param name="parentId">The identifier of the parent, or -1.</param>
/// <param name="contentTypeAlias">The alias of the content type.</param>
/// <param name="userId">The optional id of the user creating the content.</param>
/// <returns>The content object.</returns>
2013-05-27 08:45:07 -02:00
public IContent CreateContentWithIdentity ( string name , int parentId , string contentTypeAlias , int userId = 0 )
{
2016-05-18 10:55:19 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2013-05-27 08:45:07 -02:00
{
2016-05-18 10:55:19 +02:00
// locking the content tree secures content types too
uow . WriteLock ( Constants . Locks . ContentTree ) ;
2013-05-27 08:45:07 -02:00
2016-05-18 10:55:19 +02:00
var contentType = GetContentType ( contentTypeAlias ) ; // + locks
if ( contentType = = null )
throw new ArgumentException ( "No content type with that alias." , nameof ( contentTypeAlias ) ) ; // causes rollback
2014-03-06 17:35:47 +11:00
2016-05-18 10:55:19 +02:00
var parent = parentId > 0 ? GetById ( parentId ) : null ; // + locks
if ( parentId > 0 & & parent = = null )
throw new ArgumentException ( "No content with that id." , nameof ( parentId ) ) ; // causes rollback
var content = parentId > 0 ? new Content ( name , parent , contentType ) : new Content ( name , parentId , contentType ) ;
CreateContent ( uow , content , parent , userId , true ) ;
2016-05-02 12:12:21 +02:00
uow . Complete ( ) ;
2016-05-18 10:55:19 +02:00
return content ;
2013-05-27 08:45:07 -02:00
}
}
/// <summary>
2016-05-18 10:55:19 +02:00
/// Creates an <see cref="IContent"/> object of a specified content type, under a parent.
2013-05-27 08:45:07 -02:00
/// </summary>
2016-05-18 10:55:19 +02:00
/// <remarks>This method returns a new, persisted, IContent with an identity.</remarks>
/// <param name="name">The name of the content object.</param>
/// <param name="parent">The parent content object.</param>
/// <param name="contentTypeAlias">The alias of the content type.</param>
/// <param name="userId">The optional id of the user creating the content.</param>
/// <returns>The content object.</returns>
2013-05-27 08:45:07 -02:00
public IContent CreateContentWithIdentity ( string name , IContent parent , string contentTypeAlias , int userId = 0 )
{
2016-05-18 10:55:19 +02:00
if ( parent = = null ) throw new ArgumentNullException ( nameof ( parent ) ) ;
2013-05-27 08:45:07 -02:00
2016-05-18 10:55:19 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2013-05-27 08:45:07 -02:00
{
2016-05-18 10:55:19 +02:00
// locking the content tree secures content types too
uow . WriteLock ( Constants . Locks . ContentTree ) ;
var contentType = GetContentType ( contentTypeAlias ) ; // + locks
if ( contentType = = null )
throw new ArgumentException ( "No content type with that alias." , nameof ( contentTypeAlias ) ) ; // causes rollback
var content = new Content ( name , parent , contentType ) ;
CreateContent ( uow , content , parent , userId , true ) ;
uow . Complete ( ) ;
2013-05-27 08:45:07 -02:00
return content ;
}
2016-05-18 10:55:19 +02:00
}
private void CreateContent ( IDatabaseUnitOfWork uow , Content content , IContent parent , int userId , bool withIdentity )
{
// 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 newArgs = parent ! = null
? new NewEventArgs < IContent > ( content , content . ContentType . Alias , parent )
: new NewEventArgs < IContent > ( content , content . ContentType . Alias , - 1 ) ;
2013-05-27 08:45:07 -02:00
2016-05-18 10:55:19 +02:00
if ( Creating . IsRaisedEventCancelled ( newArgs , this ) )
2014-03-06 17:35:47 +11:00
{
content . WasCancelled = true ;
2016-05-18 10:55:19 +02:00
return ;
2014-03-06 17:35:47 +11:00
}
2016-05-02 12:12:21 +02:00
content . CreatorId = userId ;
content . WriterId = userId ;
2016-05-18 10:55:19 +02:00
if ( withIdentity )
2013-05-27 08:45:07 -02:00
{
2016-05-18 10:55:19 +02:00
if ( Saving . IsRaisedEventCancelled ( new SaveEventArgs < IContent > ( content ) , this ) )
{
content . WasCancelled = true ;
return ;
}
var repo = uow . CreateRepository < IContentRepository > ( ) ;
repo . AddOrUpdate ( content ) ;
repo . AddOrUpdatePreviewXml ( content , c = > _entitySerializer . Serialize ( this , _dataTypeService , _userService , _urlSegmentProviders , c ) ) ;
Saved . RaiseEvent ( new SaveEventArgs < IContent > ( content , false ) , this ) ;
2013-05-27 08:45:07 -02:00
}
2016-05-18 10:55:19 +02:00
Created . RaiseEvent ( new NewEventArgs < IContent > ( content , false , content . ContentType . Alias , parent ) , this ) ;
2013-05-27 08:45:07 -02:00
2016-05-18 10:55:19 +02:00
var msg = withIdentity
? "Content '{0}' was created with Id {1}"
: "Content '{0}' was created" ;
Audit ( AuditType . New , string . Format ( msg , content . Name , content . Id ) , content . CreatorId , content . Id ) ;
2013-05-27 08:45:07 -02:00
}
2016-05-18 10:55:19 +02:00
#endregion
#region Get , Has , Is
2013-04-23 12:49:33 -02:00
/// <summary>
/// Gets an <see cref="IContent"/> object by Id
/// </summary>
/// <param name="id">Id of the Content to retrieve</param>
/// <returns><see cref="IContent"/></returns>
public IContent GetById ( int id )
{
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2013-04-23 12:49:33 -02:00
{
2016-05-18 10:55:19 +02:00
uow . ReadLock ( Constants . Locks . ContentTree ) ;
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IContentRepository > ( ) ;
2016-05-18 10:55:19 +02:00
var content = repository . Get ( id ) ;
uow . Complete ( ) ;
return content ;
2013-04-23 12:49:33 -02:00
}
}
2013-06-11 14:43:36 +02:00
/// <summary>
/// Gets an <see cref="IContent"/> object by Id
/// </summary>
/// <param name="ids">Ids of the Content to retrieve</param>
/// <returns><see cref="IContent"/></returns>
2013-10-09 15:21:01 +11:00
public IEnumerable < IContent > GetByIds ( IEnumerable < int > ids )
2013-06-11 14:43:36 +02:00
{
2016-05-18 10:55:19 +02:00
var idsA = ids . ToArray ( ) ;
if ( idsA . Length = = 0 ) return Enumerable . Empty < IContent > ( ) ;
2015-07-24 18:36:07 +02:00
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2013-06-11 14:43:36 +02:00
{
2016-05-18 10:55:19 +02:00
uow . ReadLock ( Constants . Locks . ContentTree ) ;
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IContentRepository > ( ) ;
2016-05-18 10:55:19 +02:00
var content = repository . GetAll ( idsA ) ;
uow . Complete ( ) ;
return content ;
2013-06-11 14:43:36 +02:00
}
}
2013-04-23 12:49:33 -02:00
/// <summary>
/// Gets an <see cref="IContent"/> object by its 'UniqueId'
/// </summary>
/// <param name="key">Guid key of the Content to retrieve</param>
/// <returns><see cref="IContent"/></returns>
public IContent GetById ( Guid key )
{
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2013-04-23 12:49:33 -02:00
{
2016-05-18 10:55:19 +02:00
uow . ReadLock ( Constants . Locks . ContentTree ) ;
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IContentRepository > ( ) ;
2015-02-22 21:36:02 +01:00
var query = repository . Query . Where ( x = > x . Key = = key ) ;
2013-04-23 12:49:33 -02:00
var contents = repository . GetByQuery ( query ) ;
2016-05-18 10:55:19 +02:00
var content = contents . SingleOrDefault ( ) ;
uow . Complete ( ) ;
return content ;
2013-04-23 12:49:33 -02:00
}
}
/// <summary>
/// Gets a collection of <see cref="IContent"/> objects by the Id of the <see cref="IContentType"/>
/// </summary>
/// <param name="id">Id of the <see cref="IContentType"/></param>
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
public IEnumerable < IContent > GetContentOfContentType ( int id )
{
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2013-04-23 12:49:33 -02:00
{
2016-05-18 10:55:19 +02:00
uow . ReadLock ( Constants . Locks . ContentTree ) ;
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IContentRepository > ( ) ;
2015-02-22 21:36:02 +01:00
var query = repository . Query . Where ( x = > x . ContentTypeId = = id ) ;
2016-05-18 10:55:19 +02:00
var content = repository . GetByQuery ( query ) ;
uow . Complete ( ) ;
return content ;
2013-04-23 12:49:33 -02:00
}
}
2013-07-29 15:49:56 +10:00
internal IEnumerable < IContent > GetPublishedContentOfContentType ( int id )
{
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2013-07-29 15:49:56 +10:00
{
2016-05-18 10:55:19 +02:00
uow . ReadLock ( Constants . Locks . ContentTree ) ;
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IContentRepository > ( ) ;
2015-02-22 21:36:02 +01:00
var query = repository . Query . Where ( x = > x . ContentTypeId = = id ) ;
2016-05-18 10:55:19 +02:00
var content = repository . GetByPublishedVersion ( query ) ;
uow . Complete ( ) ;
return content ;
2013-07-29 15:49:56 +10:00
}
}
2013-04-23 12:49:33 -02:00
/// <summary>
/// Gets a collection of <see cref="IContent"/> objects by Level
/// </summary>
/// <param name="level">The level to retrieve Content from</param>
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
2016-05-18 10:55:19 +02:00
/// <remarks>Contrary to most methods, this method filters out trashed content items.</remarks>
2013-04-23 12:49:33 -02:00
public IEnumerable < IContent > GetByLevel ( int level )
{
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2013-04-23 12:49:33 -02:00
{
2016-05-18 10:55:19 +02:00
uow . ReadLock ( Constants . Locks . ContentTree ) ;
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IContentRepository > ( ) ;
2016-05-18 10:55:19 +02:00
var query = repository . Query . Where ( x = > x . Level = = level & & x . Trashed = = false ) ;
var content = repository . GetByQuery ( query ) ;
uow . Complete ( ) ;
return content ;
2013-04-23 12:49:33 -02:00
}
}
2012-12-11 23:19:07 +05:00
2012-12-17 13:37:36 -01:00
/// <summary>
/// Gets a specific version of an <see cref="IContent"/> item.
/// </summary>
/// <param name="versionId">Id of the version to retrieve</param>
/// <returns>An <see cref="IContent"/> item</returns>
public IContent GetByVersion ( Guid versionId )
{
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2012-12-17 13:37:36 -01:00
{
2016-05-18 10:55:19 +02:00
uow . ReadLock ( Constants . Locks . ContentTree ) ;
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IContentRepository > ( ) ;
2016-05-18 10:55:19 +02:00
var content = repository . GetByVersion ( versionId ) ;
uow . Complete ( ) ;
return content ;
2012-12-17 13:37:36 -01:00
}
}
2015-01-19 15:12:34 +11:00
2012-12-17 13:37:36 -01:00
/// <summary>
/// Gets a collection of an <see cref="IContent"/> objects versions by Id
/// </summary>
/// <param name="id"></param>
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
public IEnumerable < IContent > GetVersions ( int id )
{
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2012-12-17 13:37:36 -01:00
{
2016-05-18 10:55:19 +02:00
uow . ReadLock ( Constants . Locks . ContentTree ) ;
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IContentRepository > ( ) ;
2016-05-18 10:55:19 +02:00
var content = repository . GetAllVersions ( id ) ;
uow . Complete ( ) ;
return content ;
2012-12-17 13:37:36 -01:00
}
}
2013-02-09 10:58:21 -01:00
/// <summary>
/// Gets a collection of <see cref="IContent"/> objects, which are ancestors of the current content.
/// </summary>
/// <param name="id">Id of the <see cref="IContent"/> to retrieve ancestors for</param>
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
public IEnumerable < IContent > GetAncestors ( int id )
{
2016-05-18 10:55:19 +02:00
// intentionnaly not locking
2013-02-09 10:58:21 -01:00
var content = GetById ( id ) ;
return GetAncestors ( content ) ;
}
/// <summary>
/// Gets a collection of <see cref="IContent"/> objects, which are ancestors of the current content.
/// </summary>
/// <param name="content"><see cref="IContent"/> to retrieve ancestors for</param>
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
2014-01-09 10:33:35 +11:00
public IEnumerable < IContent > GetAncestors ( IContent content )
{
2015-10-29 12:05:46 +01:00
//null check otherwise we get exceptions
if ( content . Path . IsNullOrWhiteSpace ( ) ) return Enumerable . Empty < IContent > ( ) ;
2016-05-18 10:55:19 +02:00
var rootId = Constants . System . Root . ToInvariantString ( ) ;
var ids = content . Path . Split ( ',' )
. Where ( x = > x ! = rootId & & x ! = content . Id . ToString ( CultureInfo . InvariantCulture ) ) . Select ( int . Parse ) . ToArray ( ) ;
2013-02-09 11:12:51 -01:00
if ( ids . Any ( ) = = false )
return new List < IContent > ( ) ;
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2013-02-09 10:58:21 -01:00
{
2016-05-18 10:55:19 +02:00
uow . ReadLock ( Constants . Locks . ContentTree ) ;
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IContentRepository > ( ) ;
2016-05-18 10:55:19 +02:00
var ancestors = repository . GetAll ( ids ) ;
uow . Complete ( ) ;
return ancestors ;
2013-02-09 10:58:21 -01:00
}
2014-01-09 10:33:35 +11:00
}
2013-02-09 10:58:21 -01:00
2014-01-09 10:33:35 +11:00
/// <summary>
2013-04-23 12:49:33 -02:00
/// Gets a collection of <see cref="IContent"/> objects by Parent Id
/// </summary>
/// <param name="id">Id of the Parent to retrieve Children from</param>
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
public IEnumerable < IContent > GetChildren ( int id )
{
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2013-04-23 12:49:33 -02:00
{
2016-05-18 10:55:19 +02:00
uow . ReadLock ( Constants . Locks . ContentTree ) ;
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IContentRepository > ( ) ;
2015-02-22 21:36:02 +01:00
var query = repository . Query . Where ( x = > x . ParentId = = id ) ;
2016-05-18 10:55:19 +02:00
var children = repository . GetByQuery ( query ) . OrderBy ( x = > x . SortOrder ) ;
uow . Complete ( ) ;
return children ;
}
}
2012-10-10 12:13:23 -02:00
2016-05-18 10:55:19 +02:00
/// <summary>
/// Gets a collection of published <see cref="IContent"/> objects by Parent Id
/// </summary>
/// <param name="id">Id of the Parent to retrieve Children from</param>
/// <returns>An Enumerable list of published <see cref="IContent"/> objects</returns>
public IEnumerable < IContent > GetPublishedChildren ( int id )
{
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
{
uow . ReadLock ( Constants . Locks . ContentTree ) ;
var repository = uow . CreateRepository < IContentRepository > ( ) ;
var query = repository . Query . Where ( x = > x . ParentId = = id & & x . Published ) ;
var children = repository . GetByQuery ( query ) . OrderBy ( x = > x . SortOrder ) ;
uow . Complete ( ) ;
return children ;
2013-04-23 12:49:33 -02:00
}
}
2012-12-11 23:19:07 +05:00
2014-08-12 08:15:43 +01:00
/// <summary>
/// Gets a collection of <see cref="IContent"/> objects by Parent Id
/// </summary>
/// <param name="id">Id of the Parent to retrieve Children from</param>
2014-09-16 23:21:27 +10:00
/// <param name="pageIndex">Page index (zero based)</param>
2014-08-12 08:15:43 +01:00
/// <param name="pageSize">Page size</param>
2014-08-25 16:58:01 +10:00
/// <param name="totalChildren">Total records query would return without paging</param>
2014-08-12 08:15:43 +01:00
/// <param name="orderBy">Field to order by</param>
2014-08-25 16:58:01 +10:00
/// <param name="orderDirection">Direction to order by</param>
2014-08-13 20:02:51 +01:00
/// <param name="filter">Search text filter</param>
2014-08-12 08:15:43 +01:00
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
2015-05-04 11:58:00 +10:00
public IEnumerable < IContent > GetPagedChildren ( int id , long pageIndex , int pageSize , out long totalChildren ,
2014-08-13 20:02:51 +01:00
string orderBy , Direction orderDirection , string filter = "" )
2016-04-06 13:23:39 +02:00
{
return GetPagedChildren ( id , pageIndex , pageSize , out totalChildren , orderBy , orderDirection , true , filter ) ;
}
2014-08-12 08:15:43 +01:00
/// <summary>
/// Gets a collection of <see cref="IContent"/> objects by Parent Id
/// </summary>
/// <param name="id">Id of the Parent to retrieve Children from</param>
2014-09-16 23:21:27 +10:00
/// <param name="pageIndex">Page index (zero based)</param>
2014-08-12 08:15:43 +01:00
/// <param name="pageSize">Page size</param>
2014-08-25 16:58:01 +10:00
/// <param name="totalChildren">Total records query would return without paging</param>
2014-08-12 08:15:43 +01:00
/// <param name="orderBy">Field to order by</param>
2014-08-25 16:58:01 +10:00
/// <param name="orderDirection">Direction to order by</param>
2014-12-13 22:00:32 +01:00
/// <param name="orderBySystemField">Flag to indicate when ordering by system field</param>
2014-08-13 20:02:51 +01:00
/// <param name="filter">Search text filter</param>
2014-08-12 08:15:43 +01:00
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
2015-05-04 11:58:00 +10:00
public IEnumerable < IContent > GetPagedChildren ( int id , long pageIndex , int pageSize , out long totalChildren ,
2016-04-06 13:23:39 +02:00
string orderBy , Direction orderDirection , bool orderBySystemField , string filter )
2014-08-12 08:15:43 +01:00
{
2015-03-24 12:30:28 +11:00
Mandate . ParameterCondition ( pageIndex > = 0 , "pageIndex" ) ;
2014-08-24 23:37:10 +02:00
Mandate . ParameterCondition ( pageSize > 0 , "pageSize" ) ;
2016-05-18 10:55:19 +02:00
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2014-08-12 08:15:43 +01:00
{
2016-05-18 10:55:19 +02:00
uow . ReadLock ( Constants . Locks . ContentTree ) ;
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IContentRepository > ( ) ;
2015-01-19 15:12:34 +11:00
2015-02-22 21:36:02 +01:00
var query = repository . Query ;
2015-01-04 21:02:20 +00:00
//if the id is System Root, then just get all
if ( id ! = Constants . System . Root )
2014-09-30 15:13:10 +10:00
query . Where ( x = > x . ParentId = = id ) ;
2016-05-18 10:55:19 +02:00
var children = repository . GetPagedResultsByQuery ( query , pageIndex , pageSize , out totalChildren , orderBy , orderDirection , orderBySystemField , filter ) ;
uow . Complete ( ) ;
return children ;
2014-09-30 15:13:10 +10:00
}
}
/// <summary>
/// Gets a collection of <see cref="IContent"/> objects by Parent Id
/// </summary>
/// <param name="id">Id of the Parent to retrieve Descendants from</param>
/// <param name="pageIndex">Page number</param>
/// <param name="pageSize">Page size</param>
/// <param name="totalChildren">Total records query would return without paging</param>
/// <param name="orderBy">Field to order by</param>
/// <param name="orderDirection">Direction to order by</param>
/// <param name="filter">Search text filter</param>
2016-05-02 12:12:21 +02:00
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
2015-05-04 11:58:00 +10:00
public IEnumerable < IContent > GetPagedDescendants ( int id , long pageIndex , int pageSize , out long totalChildren , string orderBy = "Path" , Direction orderDirection = Direction . Ascending , string filter = "" )
2016-04-06 13:23:39 +02:00
{
return GetPagedDescendants ( id , pageIndex , pageSize , out totalChildren , orderBy , orderDirection , true , filter ) ;
}
2014-09-30 15:13:10 +10:00
/// <summary>
/// Gets a collection of <see cref="IContent"/> objects by Parent Id
/// </summary>
/// <param name="id">Id of the Parent to retrieve Descendants from</param>
/// <param name="pageIndex">Page number</param>
/// <param name="pageSize">Page size</param>
/// <param name="totalChildren">Total records query would return without paging</param>
/// <param name="orderBy">Field to order by</param>
/// <param name="orderDirection">Direction to order by</param>
2014-12-13 22:00:32 +01:00
/// <param name="orderBySystemField">Flag to indicate when ordering by system field</param>
2014-09-30 15:13:10 +10:00
/// <param name="filter">Search text filter</param>
2016-05-02 12:12:21 +02:00
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
2016-04-06 13:23:39 +02:00
public IEnumerable < IContent > GetPagedDescendants ( int id , long pageIndex , int pageSize , out long totalChildren , string orderBy , Direction orderDirection , bool orderBySystemField , string filter )
2014-09-30 15:13:10 +10:00
{
2016-05-18 10:55:19 +02:00
Mandate . ParameterCondition ( pageIndex > = 0 , nameof ( pageIndex ) ) ;
Mandate . ParameterCondition ( pageSize > 0 , nameof ( pageSize ) ) ;
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2014-09-30 15:13:10 +10:00
{
2016-05-18 10:55:19 +02:00
uow . ReadLock ( Constants . Locks . ContentTree ) ;
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IContentRepository > ( ) ;
2014-09-30 15:13:10 +10:00
2015-02-22 21:36:02 +01:00
var query = repository . Query ;
2015-01-04 21:02:20 +00:00
//if the id is System Root, then just get all
if ( id ! = Constants . System . Root )
2016-05-18 10:55:19 +02:00
query . Where ( x = > x . Path . SqlContains ( $",{id}," , TextColumnType . NVarchar ) ) ;
2014-12-13 22:00:32 +01:00
var contents = repository . GetPagedResultsByQuery ( query , pageIndex , pageSize , out totalChildren , orderBy , orderDirection , orderBySystemField , filter ) ;
2016-05-18 10:55:19 +02:00
uow . Complete ( ) ;
2014-08-12 08:15:43 +01:00
return contents ;
}
}
2012-12-14 15:19:54 -01:00
/// <summary>
/// Gets a collection of <see cref="IContent"/> objects by its name or partial name
/// </summary>
/// <param name="parentId">Id of the Parent to retrieve Children from</param>
/// <param name="name">Full or partial name of the children</param>
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
public IEnumerable < IContent > GetChildrenByName ( int parentId , string name )
{
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2012-12-14 15:19:54 -01:00
{
2016-05-18 10:55:19 +02:00
uow . ReadLock ( Constants . Locks . ContentTree ) ;
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IContentRepository > ( ) ;
2015-02-22 21:36:02 +01:00
var query = repository . Query . Where ( x = > x . ParentId = = parentId & & x . Name . Contains ( name ) ) ;
2016-05-18 10:55:19 +02:00
var children = repository . GetByQuery ( query ) ;
uow . Complete ( ) ;
return children ;
2012-12-14 15:19:54 -01:00
}
}
/// <summary>
/// Gets a collection of <see cref="IContent"/> objects by Parent Id
/// </summary>
/// <param name="id">Id of the Parent to retrieve Descendants from</param>
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
public IEnumerable < IContent > GetDescendants ( int id )
{
2016-05-18 10:55:19 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2014-09-26 11:19:54 +10:00
{
2016-05-18 10:55:19 +02:00
uow . ReadLock ( Constants . Locks . ContentTree ) ;
var repository = uow . CreateRepository < IContentRepository > ( ) ;
var content = GetById ( id ) ;
if ( content = = null )
{
uow . Complete ( ) ; // else causes rollback
return Enumerable . Empty < IContent > ( ) ;
}
var pathMatch = content . Path + "," ;
var query = repository . Query . Where ( x = > x . Id ! = content . Id & & x . Path . StartsWith ( pathMatch ) ) ;
var descendants = repository . GetByQuery ( query ) ;
uow . Complete ( ) ;
return descendants ;
2014-09-26 11:19:54 +10:00
}
2012-12-14 15:19:54 -01:00
}
/// <summary>
/// Gets a collection of <see cref="IContent"/> objects by Parent Id
/// </summary>
/// <param name="content"><see cref="IContent"/> item to retrieve Descendants from</param>
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
public IEnumerable < IContent > GetDescendants ( IContent content )
{
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2012-12-14 15:19:54 -01:00
{
2016-05-18 10:55:19 +02:00
uow . ReadLock ( Constants . Locks . ContentTree ) ;
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IContentRepository > ( ) ;
2014-10-08 17:23:14 +11:00
var pathMatch = content . Path + "," ;
2016-05-18 10:55:19 +02:00
var query = repository . Query . Where ( x = > x . Id ! = content . Id & & x . Path . StartsWith ( pathMatch ) ) ;
var descendants = repository . GetByQuery ( query ) ;
uow . Complete ( ) ;
return descendants ;
2012-12-14 15:19:54 -01:00
}
}
/// <summary>
2013-02-09 10:58:21 -01:00
/// Gets the parent of the current content as an <see cref="IContent"/> item.
/// </summary>
/// <param name="id">Id of the <see cref="IContent"/> to retrieve the parent from</param>
/// <returns>Parent <see cref="IContent"/> object</returns>
public IContent GetParent ( int id )
{
2016-05-18 10:55:19 +02:00
// intentionnaly not locking
2013-02-09 10:58:21 -01:00
var content = GetById ( id ) ;
return GetParent ( content ) ;
}
/// <summary>
/// Gets the parent of the current content as an <see cref="IContent"/> item.
/// </summary>
/// <param name="content"><see cref="IContent"/> to retrieve the parent from</param>
/// <returns>Parent <see cref="IContent"/> object</returns>
public IContent GetParent ( IContent content )
{
2015-01-04 21:02:20 +00:00
if ( content . ParentId = = Constants . System . Root | | content . ParentId = = Constants . System . RecycleBinContent )
2013-02-09 11:12:51 -01:00
return null ;
2013-02-09 10:58:21 -01:00
return GetById ( content . ParentId ) ;
}
2014-01-09 10:33:35 +11:00
/// <summary>
2012-12-14 15:19:54 -01:00
/// Gets the published version of an <see cref="IContent"/> item
/// </summary>
/// <param name="id">Id of the <see cref="IContent"/> to retrieve version from</param>
/// <returns>An <see cref="IContent"/> item</returns>
public IContent GetPublishedVersion ( int id )
{
2012-12-15 11:04:03 -01:00
var version = GetVersions ( id ) ;
2016-05-18 10:55:19 +02:00
return version . FirstOrDefault ( x = > x . Published ) ;
2012-12-14 15:19:54 -01:00
}
2015-02-10 15:28:48 +01:00
/// <summary>
/// Gets the published version of a <see cref="IContent"/> item.
/// </summary>
/// <param name="content">The content item.</param>
/// <returns>The published version, if any; otherwise, null.</returns>
public IContent GetPublishedVersion ( IContent content )
{
2015-02-10 19:15:23 +01:00
if ( content . Published ) return content ;
2015-02-10 15:28:48 +01:00
return content . HasPublishedVersion
? GetByVersion ( content . PublishedVersionGuid )
: null ;
}
2013-04-23 12:49:33 -02:00
/// <summary>
/// Gets a collection of <see cref="IContent"/> objects, which reside at the first level / root
/// </summary>
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
public IEnumerable < IContent > GetRootContent ( )
{
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2013-04-23 12:49:33 -02:00
{
2016-05-18 10:55:19 +02:00
uow . ReadLock ( Constants . Locks . ContentTree ) ;
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IContentRepository > ( ) ;
2015-02-22 21:36:02 +01:00
var query = repository . Query . Where ( x = > x . ParentId = = Constants . System . Root ) ;
2016-05-18 10:55:19 +02:00
var content = repository . GetByQuery ( query ) ;
uow . Complete ( ) ;
return content ;
2013-04-23 12:49:33 -02:00
}
}
2013-07-29 15:49:56 +10:00
/// <summary>
/// Gets all published content items
/// </summary>
/// <returns></returns>
internal IEnumerable < IContent > GetAllPublished ( )
{
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2013-07-29 15:49:56 +10:00
{
2016-05-18 10:55:19 +02:00
uow . ReadLock ( Constants . Locks . ContentTree ) ;
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IContentRepository > ( ) ;
2015-02-22 21:36:02 +01:00
var query = repository . Query . Where ( x = > x . Trashed = = false ) ;
2016-05-18 10:55:19 +02:00
var content = repository . GetByPublishedVersion ( query ) ;
uow . Complete ( ) ;
return content ;
2013-07-29 15:49:56 +10:00
}
}
2013-04-23 12:49:33 -02:00
/// <summary>
/// Gets a collection of <see cref="IContent"/> objects, which has an expiration date less than or equal to today.
/// </summary>
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
public IEnumerable < IContent > GetContentForExpiration ( )
{
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2013-04-23 12:49:33 -02:00
{
2016-05-18 10:55:19 +02:00
uow . ReadLock ( Constants . Locks . ContentTree ) ;
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IContentRepository > ( ) ;
2016-05-18 10:55:19 +02:00
var query = repository . Query . Where ( x = > x . Published & & x . ExpireDate < = DateTime . Now ) ;
var content = repository . GetByQuery ( query ) ;
uow . Complete ( ) ;
return content ;
2013-04-23 12:49:33 -02:00
}
}
/// <summary>
/// Gets a collection of <see cref="IContent"/> objects, which has a release date less than or equal to today.
/// </summary>
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
public IEnumerable < IContent > GetContentForRelease ( )
{
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2013-04-23 12:49:33 -02:00
{
2016-05-18 10:55:19 +02:00
uow . ReadLock ( Constants . Locks . ContentTree ) ;
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IContentRepository > ( ) ;
2015-02-22 21:36:02 +01:00
var query = repository . Query . Where ( x = > x . Published = = false & & x . ReleaseDate < = DateTime . Now ) ;
2016-05-18 10:55:19 +02:00
var content = repository . GetByQuery ( query ) ;
uow . Complete ( ) ;
return content ;
2013-04-23 12:49:33 -02:00
}
}
/// <summary>
/// Gets a collection of an <see cref="IContent"/> objects, which resides in the Recycle Bin
/// </summary>
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
public IEnumerable < IContent > GetContentInRecycleBin ( )
{
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2013-04-23 12:49:33 -02:00
{
2016-05-18 10:55:19 +02:00
uow . ReadLock ( Constants . Locks . ContentTree ) ;
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IContentRepository > ( ) ;
2015-02-22 21:36:02 +01:00
var query = repository . Query . Where ( x = > x . Path . Contains ( Constants . System . RecycleBinContent . ToInvariantString ( ) ) ) ;
2016-05-18 10:55:19 +02:00
var content = repository . GetByQuery ( query ) ;
uow . Complete ( ) ;
return content ;
2013-04-23 12:49:33 -02:00
}
}
2012-12-11 23:19:07 +05:00
2012-12-14 15:19:54 -01:00
/// <summary>
/// Checks whether an <see cref="IContent"/> item has any children
/// </summary>
/// <param name="id">Id of the <see cref="IContent"/></param>
/// <returns>True if the content has any children otherwise False</returns>
public bool HasChildren ( int id )
2013-08-06 18:42:36 +10:00
{
return CountChildren ( id ) > 0 ;
}
2012-12-14 15:19:54 -01:00
/// <summary>
/// Checks whether an <see cref="IContent"/> item has any published versions
/// </summary>
/// <param name="id">Id of the <see cref="IContent"/></param>
/// <returns>True if the content has any published version otherwise False</returns>
public bool HasPublishedVersion ( int id )
{
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2012-12-14 15:19:54 -01:00
{
2016-05-18 10:55:19 +02:00
uow . ReadLock ( Constants . Locks . ContentTree ) ;
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IContentRepository > ( ) ;
2016-05-18 10:55:19 +02:00
var query = repository . Query . Where ( x = > x . Published & & x . Id = = id & & x . Trashed = = false ) ;
var count = repository . Count ( query ) ;
uow . Complete ( ) ;
2012-12-14 15:19:54 -01:00
return count > 0 ;
}
}
2012-12-18 11:46:05 -01:00
/// <summary>
/// Checks if the passed in <see cref="IContent"/> can be published based on the anscestors publish state.
/// </summary>
/// <param name="content"><see cref="IContent"/> to check if anscestors are published</param>
/// <returns>True if the Content can be published, otherwise False</returns>
public bool IsPublishable ( IContent content )
{
2016-05-18 10:55:19 +02:00
// fast
if ( content . ParentId = = Constants . System . Root ) return true ; // root content is always publishable
if ( content . Trashed ) return false ; // trashed content is never publishable
// not trashed and has a parent: publishable if the parent is path-published
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2012-12-18 11:46:05 -01:00
{
2016-05-18 10:55:19 +02:00
uow . ReadLock ( Constants . Locks . ContentTree ) ;
var repo = uow . CreateRepository < IContentRepository > ( ) ;
var parent = repo . Get ( content . ParentId ) ;
if ( parent = = null )
throw new Exception ( "Out of sync." ) ; // causes rollback
var isPublishable = repo . IsPathPublished ( parent ) ;
uow . Complete ( ) ;
return isPublishable ;
2012-12-18 11:46:05 -01:00
}
2016-05-18 10:55:19 +02:00
}
2012-12-18 11:46:05 -01:00
2016-05-18 10:55:19 +02:00
public bool IsPathPublished ( IContent content )
{
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
{
uow . ReadLock ( Constants . Locks . ContentTree ) ;
var repo = uow . CreateRepository < IContentRepository > ( ) ;
var isPathPublished = repo . IsPathPublished ( content ) ;
uow . Complete ( ) ;
return isPathPublished ;
}
2012-12-18 11:46:05 -01:00
}
2016-05-18 10:55:19 +02:00
#endregion
#region Save , Publish , Unpublish
2013-03-19 23:46:13 +06:00
/// <summary>
2016-05-02 12:12:21 +02:00
/// This will rebuild the xml structures for content in the database.
2013-03-20 00:19:36 +06:00
/// </summary>
2013-03-20 20:53:12 +06:00
/// <param name="userId">This is not used for anything</param>
2013-03-20 00:19:36 +06:00
/// <returns>True if publishing succeeded, otherwise False</returns>
2013-03-19 23:46:13 +06:00
/// <remarks>
2016-05-02 12:12:21 +02:00
/// This is used for when a document type alias or a document type property is changed, the xml will need to
2013-03-19 23:46:13 +06:00
/// be regenerated.
/// </remarks>
2013-03-20 00:19:36 +06:00
public bool RePublishAll ( int userId = 0 )
{
2013-03-19 23:46:13 +06:00
try
{
2013-04-25 22:13:05 -10:00
RebuildXmlStructures ( ) ;
2013-03-19 23:46:13 +06:00
return true ;
}
catch ( Exception ex )
{
2015-01-19 18:37:48 +11:00
Logger . Error < ContentService > ( "An error occurred executing RePublishAll" , ex ) ;
2013-03-19 23:46:13 +06:00
return false ;
2014-01-09 10:33:35 +11:00
}
2013-03-20 00:19:36 +06:00
}
2012-12-11 23:19:07 +05:00
2013-03-20 20:53:12 +06:00
/// <summary>
2016-05-02 12:12:21 +02:00
/// This will rebuild the xml structures for content in the database.
2013-03-20 20:53:12 +06:00
/// </summary>
/// <param name="contentTypeIds">
/// If specified will only rebuild the xml for the content type's specified, otherwise will update the structure
/// for all published content.
/// </param>
internal void RePublishAll ( params int [ ] contentTypeIds )
{
try
{
2013-04-25 22:13:05 -10:00
RebuildXmlStructures ( contentTypeIds ) ;
2013-03-20 20:53:12 +06:00
}
catch ( Exception ex )
{
2015-01-19 18:37:48 +11:00
Logger . Error < ContentService > ( "An error occurred executing RePublishAll" , ex ) ;
2014-01-09 10:33:35 +11:00
}
2013-03-20 20:53:12 +06:00
}
/// <summary>
2016-05-18 10:55:19 +02:00
/// Saves a single <see cref="IContent"/> object
2012-12-14 15:19:54 -01:00
/// </summary>
2016-05-18 10:55:19 +02:00
/// <param name="content">The <see cref="IContent"/> to save</param>
/// <param name="userId">Optional Id of the User saving the Content</param>
/// <param name="raiseEvents">Optional boolean indicating whether or not to raise events.</param>
public void Save ( IContent content , int userId = 0 , bool raiseEvents = true )
2012-12-14 15:19:54 -01:00
{
2016-05-18 10:55:19 +02:00
( ( IContentServiceOperations ) this ) . Save ( content , userId , raiseEvents ) ;
2012-12-14 15:19:54 -01:00
}
2015-07-29 15:12:12 +02:00
/// <summary>
2016-05-18 10:55:19 +02:00
/// Saves a single <see cref="IContent"/> object
2015-07-29 15:12:12 +02:00
/// </summary>
2016-05-18 10:55:19 +02:00
/// <param name="content">The <see cref="IContent"/> to save</param>
/// <param name="userId">Optional Id of the User saving the Content</param>
/// <param name="raiseEvents">Optional boolean indicating whether or not to raise events.</param>
Attempt < OperationStatus > IContentServiceOperations . Save ( IContent content , int userId , bool raiseEvents )
2015-07-29 15:12:12 +02:00
{
2016-05-18 10:55:19 +02:00
var evtMsgs = EventMessagesFactory . Get ( ) ;
if ( raiseEvents & & Saving . IsRaisedEventCancelled ( new SaveEventArgs < IContent > ( content , evtMsgs ) , this ) )
return OperationStatus . Attempt . Cancel ( evtMsgs ) ;
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
{
uow . WriteLock ( Constants . Locks . ContentTree ) ;
var repository = uow . CreateRepository < IContentRepository > ( ) ;
if ( content . HasIdentity = = false )
content . CreatorId = userId ;
content . WriterId = userId ;
// saving the Published version => indicate we are .Saving
// saving the Unpublished version => remains .Unpublished
if ( content . Published )
content . ChangePublishedState ( PublishedState . Saving ) ;
repository . AddOrUpdate ( content ) ;
repository . AddOrUpdatePreviewXml ( content , c = > _entitySerializer . Serialize ( this , _dataTypeService , _userService , _urlSegmentProviders , c ) ) ;
uow . Complete ( ) ;
}
if ( raiseEvents )
Saved . RaiseEvent ( new SaveEventArgs < IContent > ( content , false , evtMsgs ) , this ) ;
Audit ( AuditType . Save , "Save Content performed by user" , userId , content . Id ) ;
return OperationStatus . Attempt . Succeed ( evtMsgs ) ;
2015-07-29 15:12:12 +02:00
}
/// <summary>
2016-05-18 10:55:19 +02:00
/// Saves a collection of <see cref="IContent"/> objects.
2015-07-29 15:12:12 +02:00
/// </summary>
2016-05-18 10:55:19 +02:00
/// <remarks>
/// 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 <see cref="IContent"/>.
/// </remarks>
/// <param name="contents">Collection of <see cref="IContent"/> to save</param>
/// <param name="userId">Optional Id of the User saving the Content</param>
/// <param name="raiseEvents">Optional boolean indicating whether or not to raise events.</param>
public void Save ( IEnumerable < IContent > contents , int userId = 0 , bool raiseEvents = true )
2015-07-29 15:12:12 +02:00
{
2016-05-18 10:55:19 +02:00
( ( IContentServiceOperations ) this ) . Save ( contents , userId , raiseEvents ) ;
2015-07-29 15:12:12 +02:00
}
/// <summary>
2016-05-18 10:55:19 +02:00
/// Saves a collection of <see cref="IContent"/> objects.
2015-07-29 15:12:12 +02:00
/// </summary>
2016-05-18 10:55:19 +02:00
/// <param name="contents">Collection of <see cref="IContent"/> to save</param>
/// <param name="userId">Optional Id of the User saving the Content</param>
/// <param name="raiseEvents">Optional boolean indicating whether or not to raise events.</param>
Attempt < OperationStatus > IContentServiceOperations . Save ( IEnumerable < IContent > contents , int userId , bool raiseEvents )
2015-07-29 15:12:12 +02:00
{
2015-08-04 14:52:34 +02:00
var evtMsgs = EventMessagesFactory . Get ( ) ;
2016-05-18 10:55:19 +02:00
var contentsA = contents . ToArray ( ) ;
2015-08-04 14:52:34 +02:00
2016-05-18 10:55:19 +02:00
if ( raiseEvents & & Saving . IsRaisedEventCancelled ( new SaveEventArgs < IContent > ( contentsA , evtMsgs ) , this ) )
return OperationStatus . Attempt . Cancel ( evtMsgs ) ;
2015-07-29 15:12:12 +02:00
2016-05-18 10:55:19 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
{
uow . WriteLock ( Constants . Locks . ContentTree ) ;
var repository = uow . CreateRepository < IContentRepository > ( ) ;
foreach ( var content in contentsA )
2015-07-29 15:12:12 +02:00
{
2016-05-18 10:55:19 +02:00
if ( content . HasIdentity = = false )
content . CreatorId = userId ;
content . WriterId = userId ;
2015-07-29 15:12:12 +02:00
2016-05-18 10:55:19 +02:00
// saving the Published version => indicate we are .Saving
// saving the Unpublished version => remains .Unpublished
if ( content . Published )
content . ChangePublishedState ( PublishedState . Saving ) ;
2016-05-02 12:12:21 +02:00
2015-07-29 15:12:12 +02:00
repository . AddOrUpdate ( content ) ;
2016-05-18 10:55:19 +02:00
repository . AddOrUpdatePreviewXml ( content , c = > _entitySerializer . Serialize ( this , _dataTypeService , _userService , _urlSegmentProviders , c ) ) ;
2015-07-29 15:12:12 +02:00
}
2016-05-18 10:55:19 +02:00
uow . Complete ( ) ;
2015-07-29 15:12:12 +02:00
}
2016-05-18 10:55:19 +02:00
if ( raiseEvents )
Saved . RaiseEvent ( new SaveEventArgs < IContent > ( contentsA , false , evtMsgs ) , this ) ;
Audit ( AuditType . Save , "Bulk Save content performed by user" , userId = = - 1 ? 0 : userId , Constants . System . Root ) ;
return OperationStatus . Attempt . Succeed ( evtMsgs ) ;
2015-07-29 15:12:12 +02:00
}
/// <summary>
2016-05-18 10:55:19 +02:00
/// Saves and Publishes a single <see cref="IContent"/> object
2015-07-29 15:12:12 +02:00
/// </summary>
2016-05-18 10:55:19 +02:00
/// <param name="content">The <see cref="IContent"/> to save and publish</param>
2015-07-29 15:12:12 +02:00
/// <param name="userId">Optional Id of the User issueing the publishing</param>
2016-05-18 10:55:19 +02:00
/// <param name="raiseEvents">Optional boolean indicating whether or not to raise save events.</param>
/// <returns>True if publishing succeeded, otherwise False</returns>
[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 )
2015-07-29 15:12:12 +02:00
{
2016-05-18 10:55:19 +02:00
var result = SaveAndPublishDo ( content , userId , raiseEvents ) ;
return result . Success ;
2015-07-29 15:12:12 +02:00
}
2013-10-10 09:35:23 +11:00
/// <summary>
2016-05-18 10:55:19 +02:00
/// Saves and Publishes a single <see cref="IContent"/> object
2013-10-10 09:35:23 +11:00
/// </summary>
2016-05-18 10:55:19 +02:00
/// <param name="content">The <see cref="IContent"/> to save and publish</param>
2013-10-10 09:35:23 +11:00
/// <param name="userId">Optional Id of the User issueing the publishing</param>
2016-05-18 10:55:19 +02:00
/// <param name="raiseEvents">Optional boolean indicating whether or not to raise save events.</param>
2013-10-10 09:35:23 +11:00
/// <returns>True if publishing succeeded, otherwise False</returns>
2016-05-18 10:55:19 +02:00
Attempt < PublishStatus > IContentServiceOperations . SaveAndPublish ( IContent content , int userId , bool raiseEvents )
2013-10-10 09:35:23 +11:00
{
2016-05-18 10:55:19 +02:00
return SaveAndPublishDo ( content , userId , raiseEvents ) ;
2013-10-10 09:35:23 +11:00
}
2013-04-23 12:49:33 -02:00
/// <summary>
2016-05-18 10:55:19 +02:00
/// Publishes a single <see cref="IContent"/> object
2013-04-23 12:49:33 -02:00
/// </summary>
2016-05-18 10:55:19 +02:00
/// <param name="content">The <see cref="IContent"/> to publish</param>
2013-04-23 12:49:33 -02:00
/// <param name="userId">Optional Id of the User issueing the publishing</param>
/// <returns>True if publishing succeeded, otherwise False</returns>
2016-05-18 10:55:19 +02:00
public bool Publish ( IContent content , int userId = 0 )
2013-04-23 12:49:33 -02:00
{
2016-05-18 10:55:19 +02:00
var result = SaveAndPublishDo ( content , userId ) ;
Logger . Info < ContentService > ( "Call was made to ContentService.Publish, use PublishWithStatus instead since that method will provide more detailed information on the outcome" ) ;
return result . Success ;
2013-10-10 09:35:23 +11:00
}
/// <summary>
2016-05-18 10:55:19 +02:00
/// Publishes a single <see cref="IContent"/> object
2013-10-10 09:35:23 +11:00
/// </summary>
2016-05-18 10:55:19 +02:00
/// <param name="content">The <see cref="IContent"/> to publish</param>
2013-10-10 09:35:23 +11:00
/// <param name="userId">Optional Id of the User issueing the publishing</param>
2016-05-18 10:55:19 +02:00
/// <returns>The published status attempt</returns>
Attempt < PublishStatus > IContentServiceOperations . Publish ( IContent content , int userId )
2013-10-10 09:35:23 +11:00
{
2016-05-18 10:55:19 +02:00
return SaveAndPublishDo ( content , userId ) ;
2013-04-23 12:49:33 -02:00
}
2012-12-11 23:19:07 +05:00
2013-04-23 12:49:33 -02:00
/// <summary>
/// UnPublishes a single <see cref="IContent"/> object
/// </summary>
/// <param name="content">The <see cref="IContent"/> to publish</param>
/// <param name="userId">Optional Id of the User issueing the publishing</param>
/// <returns>True if unpublishing succeeded, otherwise False</returns>
public bool UnPublish ( IContent content , int userId = 0 )
{
2016-02-26 14:30:32 +00:00
return ( ( IContentServiceOperations ) this ) . UnPublish ( content , userId ) . Success ;
2013-04-23 12:49:33 -02:00
}
2012-12-11 23:19:07 +05:00
2013-04-23 12:49:33 -02:00
/// <summary>
2016-05-18 10:55:19 +02:00
/// UnPublishes a single <see cref="IContent"/> object
2013-04-23 12:49:33 -02:00
/// </summary>
2016-05-18 10:55:19 +02:00
/// <param name="content">The <see cref="IContent"/> to publish</param>
2013-04-23 12:49:33 -02:00
/// <param name="userId">Optional Id of the User issueing the publishing</param>
2016-05-18 10:55:19 +02:00
/// <returns>True if unpublishing succeeded, otherwise False</returns>
Attempt < UnPublishStatus > IContentServiceOperations . UnPublish ( IContent content , int userId )
2013-04-23 12:49:33 -02:00
{
2016-05-18 10:55:19 +02:00
return UnPublishDo ( content , false , userId ) ;
2013-04-23 12:49:33 -02:00
}
2012-12-11 12:03:36 +05:00
2013-10-10 09:35:23 +11:00
/// <summary>
/// Saves and Publishes a single <see cref="IContent"/> object
/// </summary>
/// <param name="content">The <see cref="IContent"/> to save and publish</param>
/// <param name="userId">Optional Id of the User issueing the publishing</param>
/// <param name="raiseEvents">Optional boolean indicating whether or not to raise save events.</param>
/// <returns>True if publishing succeeded, otherwise False</returns>
public Attempt < PublishStatus > SaveAndPublishWithStatus ( IContent content , int userId = 0 , bool raiseEvents = true )
{
2015-07-29 15:12:12 +02:00
return ( ( IContentServiceOperations ) this ) . SaveAndPublish ( content , userId , raiseEvents ) ;
2013-10-10 09:35:23 +11:00
}
2013-04-23 12:49:33 -02:00
/// <summary>
2016-05-18 10:55:19 +02:00
/// Publishes a single <see cref="IContent"/> object
2013-04-23 12:49:33 -02:00
/// </summary>
2016-05-18 10:55:19 +02:00
/// <param name="content">The <see cref="IContent"/> to publish</param>
/// <param name="userId">Optional Id of the User issueing the publishing</param>
/// <returns>True if publishing succeeded, otherwise False</returns>
public Attempt < PublishStatus > PublishWithStatus ( IContent content , int userId = 0 )
2013-04-23 12:49:33 -02:00
{
2016-05-18 10:55:19 +02:00
return ( ( IContentServiceOperations ) this ) . Publish ( content , userId ) ;
2013-04-23 12:49:33 -02:00
}
2012-11-14 11:20:21 -01:00
2013-04-23 12:49:33 -02:00
/// <summary>
2016-05-18 10:55:19 +02:00
/// Publishes a <see cref="IContent"/> object and all its children
2016-05-02 12:12:21 +02:00
/// </summary>
2016-05-18 10:55:19 +02:00
/// <param name="content">The <see cref="IContent"/> to publish along with its children</param>
/// <param name="userId">Optional Id of the User issueing the publishing</param>
/// <returns>True if publishing succeeded, otherwise False</returns>
[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 )
2013-04-23 12:49:33 -02:00
{
2016-05-18 10:55:19 +02:00
// 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
2014-04-29 11:11:48 +10:00
2016-05-18 10:55:19 +02:00
var result = PublishWithChildrenDo ( content , userId , true ) ;
2015-08-04 14:52:34 +02:00
2016-05-18 10:55:19 +02:00
// FirstOrDefault() is a pain to use with structs and result contain Attempt structs
// so use this code, which is fast and works - and please ReSharper do NOT suggest otherwise
// ReSharper disable once LoopCanBeConvertedToQuery
foreach ( var r in result )
if ( r . Result . ContentItem . Id = = content . Id ) return r . Success ;
return false ;
}
2012-12-14 15:19:54 -01:00
2016-05-18 10:55:19 +02:00
/// <summary>
/// Publishes a <see cref="IContent"/> object and all its children
/// </summary>
/// <param name="content">The <see cref="IContent"/> to publish along with its children</param>
/// <param name="userId">Optional Id of the User issueing the publishing</param>
/// <param name="includeUnpublished">set to true if you want to also publish children that are currently unpublished</param>
/// <returns>True if publishing succeeded, otherwise False</returns>
public IEnumerable < Attempt < PublishStatus > > PublishWithChildrenWithStatus ( IContent content , int userId = 0 , bool includeUnpublished = false )
{
return ( ( IContentServiceOperations ) this ) . PublishWithChildren ( content , userId , includeUnpublished ) ;
}
2014-04-29 11:11:48 +10:00
2016-05-18 13:10:36 +02:00
/// <summary>
/// Used to perform scheduled publishing/unpublishing
/// </summary>
public IEnumerable < Attempt < PublishStatus > > PerformScheduledPublish ( )
{
//TODO: Do I need to move all of this logic to the repo? Or wrap this all in a unit of work?
foreach ( var d in GetContentForRelease ( ) )
{
d . ReleaseDate = null ;
var result = SaveAndPublishWithStatus ( d , ( int ) d . GetWriterProfile ( _userService ) . Id ) ;
if ( result . Success = = false )
{
if ( result . Exception ! = null )
{
Logger . Error < ContentService > ( "Could not published the document (" + d . Id + ") based on it's scheduled release, status result: " + result . Result . StatusType , result . Exception ) ;
}
else
{
Logger . Warn < ContentService > ( "Could not published the document (" + d . Id + ") based on it's scheduled release. Status result: " + result . Result . StatusType ) ;
}
}
yield return result ;
}
foreach ( var d in GetContentForExpiration ( ) )
{
try
{
d . ExpireDate = null ;
UnPublish ( d , ( int ) d . GetWriterProfile ( _userService ) . Id ) ;
}
catch ( Exception ee )
{
Logger . Error < ContentService > ( $"Error unpublishing node {d.Id}" , ee ) ;
throw ;
}
}
}
2016-05-18 10:55:19 +02:00
/// <summary>
/// Publishes a <see cref="IContent"/> object and all its children
/// </summary>
/// <param name="content">The <see cref="IContent"/> to publish along with its children</param>
/// <param name="userId">Optional Id of the User issueing the publishing</param>
/// <param name="includeUnpublished"></param>
/// <returns>The list of statuses for all published items</returns>
IEnumerable < Attempt < PublishStatus > > IContentServiceOperations . PublishWithChildren ( IContent content , int userId , bool includeUnpublished )
{
return PublishWithChildrenDo ( content , userId , includeUnpublished ) ;
}
2013-04-23 12:49:33 -02:00
2016-05-18 10:55:19 +02:00
#endregion
2015-07-24 11:44:09 +02:00
2016-05-18 10:55:19 +02:00
#region Delete
/// <summary>
/// Permanently deletes an <see cref="IContent"/> object as well as all of its Children.
/// </summary>
/// <remarks>
/// This method will also delete associated media files, child content and possibly associated domains.
/// </remarks>
/// <remarks>Please note that this method will completely remove the Content from the database</remarks>
/// <param name="content">The <see cref="IContent"/> to delete</param>
/// <param name="userId">Optional Id of the User deleting the Content</param>
public void Delete ( IContent content , int userId = 0 )
{
( ( IContentServiceOperations ) this ) . Delete ( content , userId ) ;
2013-04-23 12:49:33 -02:00
}
2015-07-29 15:12:12 +02:00
/// <summary>
/// Permanently deletes an <see cref="IContent"/> object.
/// </summary>
/// <remarks>
/// This method will also delete associated media files, child content and possibly associated domains.
/// </remarks>
/// <remarks>Please note that this method will completely remove the Content from the database</remarks>
/// <param name="content">The <see cref="IContent"/> to delete</param>
/// <param name="userId">Optional Id of the User deleting the Content</param>
Attempt < OperationStatus > IContentServiceOperations . Delete ( IContent content , int userId )
{
2015-08-04 14:52:34 +02:00
var evtMsgs = EventMessagesFactory . Get ( ) ;
2016-05-18 10:55:19 +02:00
if ( Deleting . IsRaisedEventCancelled ( new DeleteEventArgs < IContent > ( content , evtMsgs ) , this ) )
return OperationStatus . Attempt . Cancel ( evtMsgs ) ;
2015-07-29 15:12:12 +02:00
2016-05-18 10:55:19 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
{
uow . WriteLock ( Constants . Locks . ContentTree ) ;
var repository = uow . CreateRepository < IContentRepository > ( ) ;
2015-07-29 15:12:12 +02:00
2016-05-18 10:55:19 +02:00
// if it's not trashed yet, and published, we should unpublish
// but... UnPublishing event makes no sense (not going to cancel?) and no need to save
// just raise the event
if ( content . Trashed = = false & & content . HasPublishedVersion )
UnPublished . RaiseEvent ( new PublishEventArgs < IContent > ( content , false , false ) , this ) ;
2015-07-29 15:12:12 +02:00
2016-05-18 10:55:19 +02:00
DeleteLocked ( repository , content ) ;
uow . Complete ( ) ;
2015-07-29 15:12:12 +02:00
}
2016-05-18 10:55:19 +02:00
Audit ( AuditType . Delete , "Delete Content performed by user" , userId , content . Id ) ;
2015-07-29 15:12:12 +02:00
2016-05-18 10:55:19 +02:00
return OperationStatus . Attempt . Succeed ( evtMsgs ) ;
2015-07-24 11:44:09 +02:00
}
2016-05-18 10:55:19 +02:00
private void DeleteLocked ( IContentRepository repository , IContent content )
2015-07-24 11:44:09 +02:00
{
2016-05-18 10:55:19 +02:00
// then recursively delete descendants, bottom-up
// just repository.Delete + an event
var stack = new Stack < IContent > ( ) ;
stack . Push ( content ) ;
var level = 1 ;
while ( stack . Count > 0 )
2013-04-23 12:49:33 -02:00
{
2016-05-18 10:55:19 +02:00
var c = stack . Peek ( ) ;
IContent [ ] cc ;
if ( c . Level = = level )
while ( ( cc = c . Children ( ) . ToArray ( ) ) . Length > 0 )
2013-04-23 12:49:33 -02:00
{
2016-05-18 10:55:19 +02:00
foreach ( var ci in cc )
stack . Push ( ci ) ;
c = cc [ cc . Length - 1 ] ;
2013-04-23 12:49:33 -02:00
}
2016-05-18 10:55:19 +02:00
c = stack . Pop ( ) ;
level = c . Level ;
2013-04-23 12:49:33 -02:00
2016-05-18 10:55:19 +02:00
repository . Delete ( c ) ;
var args = new DeleteEventArgs < IContent > ( c , false ) ; // raise event & get flagged files
Deleted . RaiseEvent ( args , this ) ;
IOHelper . DeleteFiles ( args . MediaFilesToDelete , // remove flagged files
( file , e ) = > Logger . Error < MemberService > ( "An error occurred while deleting file attached to nodes: " + file , e ) ) ;
2013-01-29 12:45:42 -01:00
}
2013-04-23 12:49:33 -02:00
}
2016-05-18 10:55:19 +02:00
//TODO:
// both DeleteVersions methods below have an issue. Sort of. They do NOT take care of files the way
// Delete does - for a good reason: the file may be referenced by other, non-deleted, versions. BUT,
// if that's not the case, then the file will never be deleted, because when we delete the content,
// the version referencing the file will not be there anymore. SO, we can leak files.
2013-04-23 12:49:33 -02:00
/// <summary>
/// Permanently deletes versions from an <see cref="IContent"/> object prior to a specific date.
2013-09-18 11:49:36 +02:00
/// This method will never delete the latest version of a content item.
2013-04-23 12:49:33 -02:00
/// </summary>
/// <param name="id">Id of the <see cref="IContent"/> object to delete versions from</param>
/// <param name="versionDate">Latest version date</param>
/// <param name="userId">Optional Id of the User deleting versions of a Content object</param>
public void DeleteVersions ( int id , DateTime versionDate , int userId = 0 )
{
2013-03-19 23:46:13 +06:00
if ( DeletingVersions . IsRaisedEventCancelled ( new DeleteRevisionsEventArgs ( id , dateToRetain : versionDate ) , this ) )
return ;
2012-12-15 11:04:03 -01:00
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2013-03-19 23:46:13 +06:00
{
2016-05-18 10:55:19 +02:00
uow . WriteLock ( Constants . Locks . ContentTree ) ;
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IContentRepository > ( ) ;
2013-03-19 23:46:13 +06:00
repository . DeleteVersions ( id , versionDate ) ;
2016-05-02 12:12:21 +02:00
uow . Complete ( ) ;
2013-03-19 23:46:13 +06:00
}
DeletedVersions . RaiseEvent ( new DeleteRevisionsEventArgs ( id , false , dateToRetain : versionDate ) , this ) ;
2012-12-21 04:59:51 +05:00
2015-01-19 15:12:34 +11:00
Audit ( AuditType . Delete , "Delete Content by version date performed by user" , userId , Constants . System . Root ) ;
2013-04-23 12:49:33 -02:00
}
2012-12-11 23:19:07 +05:00
2013-04-23 12:49:33 -02:00
/// <summary>
/// Permanently deletes specific version(s) from an <see cref="IContent"/> object.
2013-09-18 11:49:36 +02:00
/// This method will never delete the latest version of a content item.
2013-04-23 12:49:33 -02:00
/// </summary>
/// <param name="id">Id of the <see cref="IContent"/> object to delete a version from</param>
/// <param name="versionId">Id of the version to delete</param>
/// <param name="deletePriorVersions">Boolean indicating whether to delete versions prior to the versionId</param>
/// <param name="userId">Optional Id of the User deleting versions of a Content object</param>
public void DeleteVersion ( int id , Guid versionId , bool deletePriorVersions , int userId = 0 )
{
2016-05-18 10:55:19 +02:00
if ( DeletingVersions . IsRaisedEventCancelled ( new DeleteRevisionsEventArgs ( id , /*specificVersion:*/ versionId ) , this ) )
return ;
2012-12-21 07:17:27 +05:00
2016-05-18 10:55:19 +02:00
if ( deletePriorVersions )
{
var content = GetByVersion ( versionId ) ;
DeleteVersions ( id , content . UpdateDate , userId ) ;
}
2012-11-11 18:07:52 -01:00
2016-05-18 10:55:19 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
{
uow . WriteLock ( Constants . Locks . ContentTree ) ;
var repository = uow . CreateRepository < IContentRepository > ( ) ;
repository . DeleteVersion ( versionId ) ;
uow . Complete ( ) ;
}
2012-11-14 11:20:21 -01:00
2016-05-18 10:55:19 +02:00
DeletedVersions . RaiseEvent ( new DeleteRevisionsEventArgs ( id , false , /* specificVersion:*/ versionId ) , this ) ;
2012-12-11 23:19:07 +05:00
2016-05-18 10:55:19 +02:00
Audit ( AuditType . Delete , "Delete Content by version performed by user" , userId , Constants . System . Root ) ;
2013-04-23 12:49:33 -02:00
}
2012-12-11 23:19:07 +05:00
2016-05-18 10:55:19 +02:00
#endregion
#region Move , RecycleBin
2013-04-23 12:49:33 -02:00
/// <summary>
/// Deletes an <see cref="IContent"/> object by moving it to the Recycle Bin
/// </summary>
/// <remarks>Move an item to the Recycle Bin will result in the item being unpublished</remarks>
/// <param name="content">The <see cref="IContent"/> to delete</param>
/// <param name="userId">Optional Id of the User deleting the Content</param>
public void MoveToRecycleBin ( IContent content , int userId = 0 )
{
2016-02-26 14:30:32 +00:00
( ( IContentServiceOperations ) this ) . MoveToRecycleBin ( content , userId ) ;
2013-04-23 12:49:33 -02:00
}
2012-12-11 23:19:07 +05:00
2016-05-18 10:55:19 +02:00
/// <summary>
/// Deletes an <see cref="IContent"/> object by moving it to the Recycle Bin
/// </summary>
/// <remarks>Move an item to the Recycle Bin will result in the item being unpublished</remarks>
/// <param name="content">The <see cref="IContent"/> to delete</param>
/// <param name="userId">Optional Id of the User deleting the Content</param>
Attempt < OperationStatus > IContentServiceOperations . MoveToRecycleBin ( IContent content , int userId )
{
var evtMsgs = EventMessagesFactory . Get ( ) ;
var moves = new List < Tuple < IContent , string > > ( ) ;
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
{
uow . WriteLock ( Constants . Locks . ContentTree ) ;
var repository = uow . CreateRepository < IContentRepository > ( ) ;
var originalPath = content . Path ;
if ( Trashing . IsRaisedEventCancelled ( new MoveEventArgs < IContent > ( new MoveEventInfo < IContent > ( content , originalPath , Constants . System . RecycleBinContent ) ) , this ) )
return OperationStatus . Attempt . Cancel ( evtMsgs ) ; // causes rollback
// if it's published we may want to force-unpublish it - that would be backward-compatible... but...
// making a radical decision here: trashing is equivalent to moving under an unpublished node so
// it's NOT unpublishing, only the content is now masked - allowing us to restore it if wanted
//if (content.HasPublishedVersion)
//{ }
PerformMoveLocked ( repository , content , Constants . System . RecycleBinContent , null , userId , moves , true ) ;
uow . Complete ( ) ;
}
var moveInfo = moves
. Select ( x = > new MoveEventInfo < IContent > ( x . Item1 , x . Item2 , x . Item1 . ParentId ) )
. ToArray ( ) ;
Trashed . RaiseEvent ( new MoveEventArgs < IContent > ( false , evtMsgs , moveInfo ) , this ) ;
Audit ( AuditType . Move , "Move Content to Recycle Bin performed by user" , userId , content . Id ) ;
return OperationStatus . Attempt . Succeed ( evtMsgs ) ;
}
2013-04-23 12:49:33 -02:00
/// <summary>
/// Moves an <see cref="IContent"/> object to a new location by changing its parent id.
/// </summary>
/// <remarks>
/// If the <see cref="IContent"/> 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.
/// </remarks>
/// <param name="content">The <see cref="IContent"/> to move</param>
/// <param name="parentId">Id of the Content's new Parent</param>
/// <param name="userId">Optional Id of the User moving the Content</param>
public void Move ( IContent content , int parentId , int userId = 0 )
{
2016-05-18 10:55:19 +02:00
// if moving to the recycle bin then use the proper method
if ( parentId = = Constants . System . RecycleBinContent )
2013-04-23 12:49:33 -02:00
{
2016-05-18 10:55:19 +02:00
MoveToRecycleBin ( content , userId ) ;
return ;
}
var moves = new List < Tuple < IContent , string > > ( ) ;
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
{
uow . WriteLock ( Constants . Locks . ContentTree ) ;
var repository = uow . CreateRepository < IContentRepository > ( ) ;
2014-08-20 17:01:12 +02:00
2016-05-18 10:55:19 +02:00
var parent = parentId = = Constants . System . Root ? null : GetById ( parentId ) ;
if ( parentId ! = Constants . System . Root & & ( parent = = null | | parent . Trashed ) )
throw new InvalidOperationException ( "Parent does not exist or is trashed." ) ; // causes rollback
if ( Moving . IsRaisedEventCancelled ( new MoveEventArgs < IContent > ( new MoveEventInfo < IContent > ( content , content . Path , parentId ) ) , this ) )
return ; // causes rollback
// if content was trashed, and since we're not moving to the recycle bin,
// indicate that the trashed status should be changed to false, else just
// leave it unchanged
var trashed = content . Trashed ? false : ( bool? ) null ;
// if the content was trashed under another content, and so has a published version,
// it cannot move back as published but has to be unpublished first - that's for the
// root content, everything underneath will retain its published status
if ( content . Trashed & & content . HasPublishedVersion )
2013-03-19 23:46:13 +06:00
{
2016-05-18 10:55:19 +02:00
// however, it had been masked when being trashed, so there's no need for
// any special event here - just change its state
content . ChangePublishedState ( PublishedState . Unpublishing ) ;
2013-03-19 23:46:13 +06:00
}
2013-01-21 14:50:58 -01:00
2016-05-18 10:55:19 +02:00
PerformMoveLocked ( repository , content , parentId , parent , userId , moves , trashed ) ;
uow . Complete ( ) ;
}
var moveInfo = moves //changes
. Select ( x = > new MoveEventInfo < IContent > ( x . Item1 , x . Item2 , x . Item1 . ParentId ) )
. ToArray ( ) ;
Moved . RaiseEvent ( new MoveEventArgs < IContent > ( false , moveInfo ) , this ) ;
2013-03-19 23:46:13 +06:00
2016-05-18 10:55:19 +02:00
Audit ( AuditType . Move , "Move Content performed by user" , userId , content . Id ) ;
}
2013-01-18 10:56:14 -01:00
2016-05-18 10:55:19 +02:00
// MUST be called from within WriteLock
// trash indicates whether we are trashing, un-trashing, or not changing anything
private void PerformMoveLocked ( IContentRepository repository ,
IContent content , int parentId , IContent parent , int userId ,
ICollection < Tuple < IContent , string > > moves ,
bool? trash )
{
content . WriterId = userId ;
content . ParentId = parentId ;
2012-12-11 23:19:07 +05:00
2016-05-18 10:55:19 +02:00
// get the level delta (old pos to new pos)
var levelDelta = parent = = null
? 1 - content . Level + ( parentId = = Constants . System . RecycleBinContent ? 1 : 0 )
: parent . Level + 1 - content . Level ;
var paths = new Dictionary < int , string > ( ) ;
moves . Add ( Tuple . Create ( content , content . Path ) ) ; // capture original path
// these will be updated by the repo because we changed parentId
//content.Path = (parent == null ? "-1" : parent.Path) + "," + content.Id;
//content.SortOrder = ((ContentRepository) repository).NextChildSortOrder(parentId);
//content.Level += levelDelta;
PerformMoveContentLocked ( repository , content , userId , trash ) ;
// BUT content.Path will be updated only when the UOW commits, and
// because we want it now, we have to calculate it by ourselves
//paths[content.Id] = content.Path;
paths [ content . Id ] = ( parent = = null ? ( parentId = = Constants . System . RecycleBinContent ? "-1,-20" : "-1" ) : parent . Path ) + "," + content . Id ;
var descendants = GetDescendants ( content ) ;
foreach ( var descendant in descendants )
{
moves . Add ( Tuple . Create ( descendant , descendant . Path ) ) ; // capture original path
// update path and level since we do not update parentId
descendant . Path = paths [ descendant . Id ] = paths [ descendant . ParentId ] + "," + descendant . Id ;
descendant . Level + = levelDelta ;
PerformMoveContentLocked ( repository , descendant , userId , trash ) ;
2013-04-23 12:49:33 -02:00
}
}
2012-12-11 23:19:07 +05:00
2016-05-18 10:55:19 +02:00
private static void PerformMoveContentLocked ( IContentRepository repository , IContent content , int userId ,
bool? trash )
{
if ( trash . HasValue ) ( ( ContentBase ) content ) . Trashed = trash . Value ;
content . WriterId = userId ;
repository . AddOrUpdate ( content ) ;
}
2013-01-18 10:56:14 -01:00
/// <summary>
2013-04-23 12:49:33 -02:00
/// Empties the Recycle Bin by deleting all <see cref="IContent"/> that resides in the bin
/// </summary>
public void EmptyRecycleBin ( )
{
2016-05-18 10:55:19 +02:00
var nodeObjectType = new Guid ( Constants . ObjectTypes . Document ) ;
var deleted = new List < IContent > ( ) ;
var evtMsgs = EventMessagesFactory . Get ( ) ; // todo - and then?
2014-09-10 15:07:20 +10:00
2016-05-18 10:55:19 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
{
uow . WriteLock ( Constants . Locks . ContentTree ) ;
var repository = uow . CreateRepository < IContentRepository > ( ) ;
2012-12-21 07:42:37 +05:00
2016-05-18 10:55:19 +02:00
// v7 EmptyingRecycleBin and EmptiedRecycleBin events are greatly simplified since
// each deleted items will have its own deleting/deleted events. so, files and such
// are managed by Delete, and not here.
2014-09-10 15:07:20 +10:00
2016-05-18 10:55:19 +02:00
// no idea what those events are for, keep a simplified version
if ( EmptyingRecycleBin . IsRaisedEventCancelled ( new RecycleBinEventArgs ( nodeObjectType ) , this ) )
return ; // causes rollback
2015-01-19 15:12:34 +11:00
2016-05-18 10:55:19 +02:00
// emptying the recycle bin means deleting whetever is in there - do it properly!
var query = repository . Query . Where ( x = > x . ParentId = = Constants . System . RecycleBinContent ) ;
var contents = repository . GetByQuery ( query ) . ToArray ( ) ;
foreach ( var content in contents )
{
DeleteLocked ( repository , content ) ;
deleted . Add ( content ) ;
2013-08-14 16:12:13 +02:00
}
2016-05-18 10:55:19 +02:00
EmptiedRecycleBin . RaiseEvent ( new RecycleBinEventArgs ( nodeObjectType , true ) , this ) ;
uow . Complete ( ) ;
2013-08-14 16:12:13 +02:00
}
2016-05-18 10:55:19 +02:00
2015-01-19 15:12:34 +11:00
Audit ( AuditType . Delete , "Empty Content Recycle Bin performed by user" , 0 , Constants . System . RecycleBinContent ) ;
2013-04-23 12:49:33 -02:00
}
2012-12-11 23:19:07 +05:00
2016-05-18 10:55:19 +02:00
#endregion
#region Others
2012-12-14 15:19:54 -01:00
/// <summary>
2016-05-02 12:12:21 +02:00
/// Copies an <see cref="IContent"/> object by creating a new Content object of the same type and copies all data from the current
2015-09-02 16:41:00 +02:00
/// to the new copy which is returned. Recursively copies all children.
2012-12-14 15:19:54 -01:00
/// </summary>
/// <param name="content">The <see cref="IContent"/> to copy</param>
/// <param name="parentId">Id of the Content's new Parent</param>
/// <param name="relateToOriginal">Boolean indicating whether the copy should be related to the original</param>
/// <param name="userId">Optional Id of the User copying the Content</param>
/// <returns>The newly created <see cref="IContent"/> object</returns>
2013-01-25 08:58:21 -01:00
public IContent Copy ( IContent content , int parentId , bool relateToOriginal , int userId = 0 )
2015-09-02 16:41:00 +02:00
{
return Copy ( content , parentId , relateToOriginal , true , userId ) ;
}
/// <summary>
2016-05-02 12:12:21 +02:00
/// Copies an <see cref="IContent"/> object by creating a new Content object of the same type and copies all data from the current
2015-09-02 16:41:00 +02:00
/// to the new copy which is returned.
/// </summary>
/// <param name="content">The <see cref="IContent"/> to copy</param>
/// <param name="parentId">Id of the Content's new Parent</param>
/// <param name="relateToOriginal">Boolean indicating whether the copy should be related to the original</param>
/// <param name="recursive">A value indicating whether to recursively copy children.</param>
/// <param name="userId">Optional Id of the User copying the Content</param>
/// <returns>The newly created <see cref="IContent"/> object</returns>
public IContent Copy ( IContent content , int parentId , bool relateToOriginal , bool recursive , int userId = 0 )
2012-12-27 16:08:24 -01:00
{
2016-05-18 10:55:19 +02:00
var copy = content . DeepCloneWithResetIdentities ( ) ;
copy . ParentId = parentId ;
2012-12-11 23:19:07 +05:00
2016-05-18 10:55:19 +02:00
if ( Copying . IsRaisedEventCancelled ( new CopyEventArgs < IContent > ( content , copy , parentId ) , this ) )
return null ;
2012-12-11 23:19:07 +05:00
2016-05-18 10:55:19 +02:00
// fixme - relateToOriginal is ignored?!
2013-06-18 10:38:08 -04:00
2016-05-18 10:55:19 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
{
uow . WriteLock ( Constants . Locks . ContentTree ) ;
var repository = uow . CreateRepository < IContentRepository > ( ) ;
2012-12-11 23:19:07 +05:00
2016-05-18 10:55:19 +02:00
// a copy is .Saving and will be .Unpublished
if ( copy . Published )
copy . ChangePublishedState ( PublishedState . Saving ) ;
2014-07-10 13:40:22 +10:00
2016-05-18 10:55:19 +02:00
// update the create author and last edit author
copy . CreatorId = userId ;
copy . WriterId = userId ;
2014-08-20 17:01:12 +02:00
2016-05-18 10:55:19 +02:00
// save
repository . AddOrUpdate ( copy ) ;
repository . AddOrUpdatePreviewXml ( copy , c = > _entitySerializer . Serialize ( this , _dataTypeService , _userService , _urlSegmentProviders , c ) ) ;
2016-05-02 12:12:21 +02:00
2016-05-18 10:55:19 +02:00
uow . Flush ( ) ; // ensure copy has an ID - fixme why?
2015-01-19 15:12:34 +11:00
2015-09-02 16:41:00 +02:00
if ( recursive )
2013-03-19 23:46:13 +06:00
{
2016-05-18 10:55:19 +02:00
// process descendants
var copyIds = new Dictionary < int , IContent > ( ) ;
copyIds [ content . Id ] = copy ;
foreach ( var descendant in GetDescendants ( content ) )
2015-09-02 16:41:00 +02:00
{
2016-05-18 10:55:19 +02:00
var dcopy = descendant . DeepCloneWithResetIdentities ( ) ;
//dcopy.ParentId = copyIds[descendant.ParentId];
var descendantParentId = descendant . ParentId ;
( ( Content ) dcopy ) . SetLazyParentId ( new Lazy < int > ( ( ) = > copyIds [ descendantParentId ] . Id ) ) ;
if ( dcopy . Published )
dcopy . ChangePublishedState ( PublishedState . Saving ) ;
dcopy . CreatorId = userId ;
dcopy . WriterId = userId ;
repository . AddOrUpdate ( dcopy ) ;
repository . AddOrUpdatePreviewXml ( dcopy , c = > _entitySerializer . Serialize ( this , _dataTypeService , _userService , _urlSegmentProviders , c ) ) ;
copyIds [ descendant . Id ] = dcopy ;
2015-09-02 16:41:00 +02:00
}
2013-03-19 23:46:13 +06:00
}
2012-12-11 23:19:07 +05:00
2016-05-18 10:55:19 +02:00
// fixme tag & tree issue
// tags code handling has been removed here
// - tags should be handled by the content repository
// - a copy is unpublished and therefore has no impact on tags in DB
2012-12-11 23:19:07 +05:00
2016-05-18 10:55:19 +02:00
uow . Complete ( ) ;
2013-03-19 23:46:13 +06:00
}
2012-12-11 23:19:07 +05:00
2016-05-18 10:55:19 +02:00
Copied . RaiseEvent ( new CopyEventArgs < IContent > ( content , copy , false , parentId , relateToOriginal ) , this ) ;
Audit ( AuditType . Copy , "Copy Content performed by user" , content . WriterId , content . Id ) ;
return copy ;
}
2014-02-17 15:26:38 +11:00
2013-04-23 12:49:33 -02:00
/// <summary>
/// Sends an <see cref="IContent"/> to Publication, which executes handlers and events for the 'Send to Publication' action.
/// </summary>
/// <param name="content">The <see cref="IContent"/> to send to publication</param>
/// <param name="userId">Optional Id of the User issueing the send to publication</param>
/// <returns>True if sending publication was succesfull otherwise false</returns>
2013-10-31 18:28:02 +11:00
public bool SendToPublication ( IContent content , int userId = 0 )
2013-04-23 12:49:33 -02:00
{
if ( SendingToPublish . IsRaisedEventCancelled ( new SendToPublishEventArgs < IContent > ( content ) , this ) )
return false ;
2012-12-11 23:19:07 +05:00
2014-05-28 12:38:11 +10:00
//Save before raising event
Save ( content , userId ) ;
2012-12-11 23:19:07 +05:00
2013-04-23 12:49:33 -02:00
SentToPublish . RaiseEvent ( new SendToPublishEventArgs < IContent > ( content , false ) , this ) ;
2012-12-11 23:19:07 +05:00
2015-01-19 15:12:34 +11:00
Audit ( AuditType . SendToPublish , "Send to Publish performed by user" , content . WriterId , content . Id ) ;
2012-12-11 23:19:07 +05:00
2013-04-23 12:49:33 -02:00
return true ;
}
2012-12-11 23:19:07 +05:00
2013-04-23 12:49:33 -02:00
/// <summary>
/// Rollback an <see cref="IContent"/> object to a previous version.
/// This will create a new version, which is a copy of all the old data.
/// </summary>
/// <remarks>
/// The way data is stored actually only allows us to rollback on properties
/// and not data like Name and Alias of the Content.
/// </remarks>
/// <param name="id">Id of the <see cref="IContent"/>being rolled back</param>
/// <param name="versionId">Id of the version to rollback to</param>
/// <param name="userId">Optional Id of the User issueing the rollback of the Content</param>
/// <returns>The newly created <see cref="IContent"/> object</returns>
public IContent Rollback ( int id , Guid versionId , int userId = 0 )
{
2013-03-19 23:46:13 +06:00
var content = GetByVersion ( versionId ) ;
2012-11-05 14:42:21 -01:00
2013-03-19 23:46:13 +06:00
if ( RollingBack . IsRaisedEventCancelled ( new RollbackEventArgs < IContent > ( content ) , this ) )
return content ;
2012-11-14 11:20:21 -01:00
2016-05-02 12:12:21 +02:00
content . CreatorId = userId ;
2016-05-18 10:55:19 +02:00
// need to make sure that the repository is going to save a new version
// but if we're not changing anything, the repository would not save anything
// so - make sure the property IS dirty, doing a flip-flop with an impossible value
content . WriterId = - 1 ;
content . WriterId = userId ;
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2013-03-19 23:46:13 +06:00
{
2016-05-18 10:55:19 +02:00
uow . WriteLock ( Constants . Locks . ContentTree ) ;
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IContentRepository > ( ) ;
2016-05-18 10:55:19 +02:00
// a rolled back version is .Saving and will be .Unpublished
content . ChangePublishedState ( PublishedState . Saving ) ;
2013-03-19 23:46:13 +06:00
repository . AddOrUpdate ( content ) ;
2015-01-27 14:58:33 +11:00
repository . AddOrUpdatePreviewXml ( content , c = > _entitySerializer . Serialize ( this , _dataTypeService , _userService , _urlSegmentProviders , c ) ) ;
2016-05-02 12:12:21 +02:00
uow . Complete ( ) ;
2013-03-19 23:46:13 +06:00
}
2012-11-14 11:20:21 -01:00
2013-03-19 23:46:13 +06:00
RolledBack . RaiseEvent ( new RollbackEventArgs < IContent > ( content , false ) , this ) ;
2015-01-19 15:12:34 +11:00
Audit ( AuditType . RollBack , "Content rollback performed by user" , content . WriterId , content . Id ) ;
2012-10-10 12:13:23 -02:00
2013-03-19 23:46:13 +06:00
return content ;
2013-04-23 12:49:33 -02:00
}
2012-12-11 23:19:07 +05:00
2013-05-23 10:29:44 -02:00
/// <summary>
/// Sorts a collection of <see cref="IContent"/> objects by updating the SortOrder according
2013-05-29 13:18:03 -02:00
/// to the ordering of items in the passed in <see cref="IEnumerable{T}"/>.
2013-05-23 10:29:44 -02:00
/// </summary>
/// <remarks>
/// Using this method will ensure that the Published-state is maintained upon sorting
/// so the cache is updated accordingly - as needed.
/// </remarks>
/// <param name="items"></param>
/// <param name="userId"></param>
/// <param name="raiseEvents"></param>
/// <returns>True if sorting succeeded, otherwise False</returns>
2013-05-29 13:18:03 -02:00
public bool Sort ( IEnumerable < IContent > items , int userId = 0 , bool raiseEvents = true )
2013-05-23 10:29:44 -02:00
{
2016-05-18 10:55:19 +02:00
var itemsA = items . ToArray ( ) ;
if ( itemsA . Length = = 0 ) return true ;
if ( raiseEvents & & Saving . IsRaisedEventCancelled ( new SaveEventArgs < IContent > ( itemsA ) , this ) )
2013-05-23 10:29:44 -02:00
return false ;
2016-05-18 10:55:19 +02:00
var published = new List < IContent > ( ) ;
var saved = new List < IContent > ( ) ;
2013-05-23 10:29:44 -02:00
2016-05-18 10:55:19 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2013-05-23 10:29:44 -02:00
{
2016-05-18 10:55:19 +02:00
uow . WriteLock ( Constants . Locks . ContentTree ) ;
var repository = uow . CreateRepository < IContentRepository > ( ) ;
var sortOrder = 0 ;
foreach ( var content in itemsA )
2013-05-23 10:29:44 -02:00
{
2016-05-18 10:55:19 +02:00
// 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 = = sortOrder )
2013-05-23 10:29:44 -02:00
{
2016-05-18 10:55:19 +02:00
sortOrder + + ;
continue ;
2013-05-23 10:29:44 -02:00
}
2014-08-20 17:01:12 +02:00
2016-05-18 10:55:19 +02:00
// else update
content . SortOrder = sortOrder + + ;
content . WriterId = userId ;
2014-04-28 17:28:40 +10:00
2016-05-18 10:55:19 +02:00
// if it's published, register it, no point running StrategyPublish
// since we're not really publishing it and it cannot be cancelled etc
if ( content . Published )
published . Add ( content ) ;
else if ( content . HasPublishedVersion )
published . Add ( GetByVersion ( content . PublishedVersionGuid ) ) ;
// save
saved . Add ( content ) ;
repository . AddOrUpdate ( content ) ;
repository . AddOrUpdatePreviewXml ( content , c = > _entitySerializer . Serialize ( this , _dataTypeService , _userService , _urlSegmentProviders , c ) ) ;
2013-05-23 10:29:44 -02:00
}
2016-05-18 10:55:19 +02:00
foreach ( var content in published )
repository . AddOrUpdateContentXml ( content , c = > _entitySerializer . Serialize ( this , _dataTypeService , _userService , _urlSegmentProviders , c ) ) ;
2013-05-23 10:29:44 -02:00
2016-05-18 10:55:19 +02:00
uow . Complete ( ) ;
2015-07-29 15:12:12 +02:00
}
2016-02-26 14:30:32 +00:00
2016-05-18 10:55:19 +02:00
if ( raiseEvents )
Saved . RaiseEvent ( new SaveEventArgs < IContent > ( saved , false ) , this ) ;
if ( raiseEvents & & published . Any ( ) )
Published . RaiseEvent ( new PublishEventArgs < IContent > ( published , false , false ) , this ) ;
2013-05-23 10:29:44 -02:00
2015-01-19 15:12:34 +11:00
Audit ( AuditType . Sort , "Sorting content performed by user" , userId , 0 ) ;
2013-05-23 10:29:44 -02:00
return true ;
}
2016-05-18 10:55:19 +02:00
#endregion
2014-10-23 18:31:08 +10:00
2012-12-15 11:04:03 -01:00
#region Internal Methods
2013-01-22 11:23:33 -01:00
2013-01-30 12:11:10 -01:00
/// <summary>
/// Gets a collection of <see cref="IContent"/> descendants by the first Parent.
/// </summary>
/// <param name="content"><see cref="IContent"/> item to retrieve Descendants from</param>
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
internal IEnumerable < IContent > GetPublishedDescendants ( IContent content )
{
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2013-01-30 12:11:10 -01:00
{
2016-05-18 10:55:19 +02:00
uow . ReadLock ( Constants . Locks . ContentTree ) ;
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IContentRepository > ( ) ;
2016-05-18 10:55:19 +02:00
var descendants = GetPublishedDescendantsLocked ( repository , content ) ;
uow . Complete ( ) ;
return descendants ;
}
}
2013-01-30 12:11:10 -01:00
2016-05-18 10:55:19 +02:00
internal IEnumerable < IContent > GetPublishedDescendantsLocked ( IContentRepository repository , IContent content )
{
var pathMatch = content . Path + "," ;
var query = repository . Query . Where ( x = > x . Id ! = content . Id & & x . Path . StartsWith ( pathMatch ) /*&& x.Trashed == false*/ ) ;
var contents = repository . GetByPublishedVersion ( query ) ;
// beware! contents contains all published version below content
// including those that are not directly published because below an unpublished content
// these must be filtered out here
var parents = new List < int > { content . Id } ;
foreach ( var c in contents )
{
if ( parents . Contains ( c . ParentId ) )
{
yield return c ;
parents . Add ( c . Id ) ;
}
2013-01-30 12:11:10 -01:00
}
}
2012-12-15 11:04:03 -01:00
#endregion
#region Private Methods
2015-01-19 15:12:34 +11:00
private void Audit ( AuditType type , string message , int userId , int objectId )
{
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2015-01-19 15:12:34 +11:00
{
2016-04-28 08:48:59 +02:00
var repo = uow . CreateRepository < IAuditRepository > ( ) ;
repo . AddOrUpdate ( new AuditItem ( objectId , message , type , userId ) ) ;
2016-05-02 12:12:21 +02:00
uow . Complete ( ) ;
2015-01-19 15:12:34 +11:00
}
}
2014-01-09 10:33:35 +11:00
/// <summary>
/// Publishes a <see cref="IContent"/> object and all its children
/// </summary>
/// <param name="content">The <see cref="IContent"/> to publish along with its children</param>
/// <param name="userId">Optional Id of the User issueing the publishing</param>
2016-05-02 12:12:21 +02:00
/// <param name="includeUnpublished">If set to true, this will also publish descendants that are completely unpublished, normally this will only publish children that have previously been published</param>
2014-01-09 10:33:35 +11:00
/// <returns>
/// 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.
/// </returns>
2016-05-18 10:55:19 +02:00
private IEnumerable < Attempt < PublishStatus > > PublishWithChildrenDo ( IContent content , int userId = 0 , bool includeUnpublished = false )
2013-01-22 11:23:33 -01:00
{
2016-05-18 10:55:19 +02:00
if ( content = = null ) throw new ArgumentNullException ( nameof ( content ) ) ;
2013-02-11 03:55:58 +06:00
2015-08-04 14:52:34 +02:00
var evtMsgs = EventMessagesFactory . Get ( ) ;
2016-05-18 10:55:19 +02:00
var publishedItems = new List < IContent > ( ) ; // this is for events
Attempt < PublishStatus > [ ] attempts ;
2015-08-04 14:52:34 +02:00
2016-05-18 10:55:19 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2014-01-09 10:33:35 +11:00
{
2016-05-18 10:55:19 +02:00
uow . WriteLock ( Constants . Locks . ContentTree ) ;
var repository = uow . CreateRepository < IContentRepository > ( ) ;
2013-01-22 11:23:33 -01:00
2016-05-18 10:55:19 +02:00
// fail fast + use in alreadyChecked below to avoid duplicate checks
var attempt = EnsurePublishable ( content , evtMsgs ) ;
if ( attempt . Success )
attempt = StrategyCanPublish ( content , userId , evtMsgs ) ;
if ( attempt . Success = = false )
return new [ ] { attempt } ; // causes rollback
2013-01-22 11:23:33 -01:00
2016-05-18 10:55:19 +02:00
var contents = new List < IContent > { content } ; //include parent item
contents . AddRange ( GetDescendants ( content ) ) ;
2013-02-11 20:07:23 +06:00
2016-05-18 10:55:19 +02:00
// publish using the strategy - for descendants,
// - published w/out changes: nothing to do
// - published w/changes: publish those changes
// - unpublished: publish if includeUnpublished, otherwise ignore
var alreadyChecked = new [ ] { content } ;
attempts = StrategyPublishWithChildren ( contents , alreadyChecked , userId , evtMsgs , includeUnpublished ) . ToArray ( ) ;
2013-03-19 23:46:13 +06:00
2016-05-18 10:55:19 +02:00
foreach ( var status in attempts . Where ( x = > x . Success ) . Select ( x = > x . Result ) )
2013-01-22 11:23:33 -01:00
{
2016-05-18 10:55:19 +02:00
// save them all, even those that are .Success because of (.StatusType == PublishStatusType.SuccessAlreadyPublished)
// so we bump the date etc
var publishedItem = status . ContentItem ;
publishedItem . WriterId = userId ;
repository . AddOrUpdate ( publishedItem ) ;
repository . AddOrUpdatePreviewXml ( publishedItem , c = > _entitySerializer . Serialize ( this , _dataTypeService , _userService , _urlSegmentProviders , c ) ) ;
repository . AddOrUpdateContentXml ( publishedItem , c = > _entitySerializer . Serialize ( this , _dataTypeService , _userService , _urlSegmentProviders , c ) ) ;
publishedItems . Add ( publishedItem ) ;
2013-01-22 11:23:33 -01:00
}
2016-05-18 10:55:19 +02:00
uow . Complete ( ) ;
2014-01-09 10:33:35 +11:00
}
2016-05-18 10:55:19 +02:00
Published . RaiseEvent ( new PublishEventArgs < IContent > ( publishedItems , false , false ) , this ) ;
Audit ( AuditType . Publish , "Publish with Children performed by user" , userId , content . Id ) ;
return attempts ;
2013-01-22 11:23:33 -01:00
}
2016-05-18 10:55:19 +02:00
2013-01-22 11:23:33 -01:00
/// <summary>
/// UnPublishes a single <see cref="IContent"/> object
/// </summary>
/// <param name="content">The <see cref="IContent"/> to publish</param>
/// <param name="omitCacheRefresh">Optional boolean to avoid having the cache refreshed when calling this Unpublish method. By default this method will update the cache.</param>
/// <param name="userId">Optional Id of the User issueing the publishing</param>
/// <returns>True if unpublishing succeeded, otherwise False</returns>
2015-07-29 15:12:12 +02:00
private Attempt < UnPublishStatus > UnPublishDo ( IContent content , bool omitCacheRefresh = false , int userId = 0 )
2013-01-22 11:23:33 -01:00
{
2016-05-18 10:55:19 +02:00
// fixme kill omitCacheRefresh!
2015-08-04 14:52:34 +02:00
var evtMsgs = EventMessagesFactory . Get ( ) ;
2016-02-26 14:30:32 +00:00
2016-05-18 10:55:19 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2013-01-22 11:23:33 -01:00
{
2016-05-18 10:55:19 +02:00
uow . WriteLock ( Constants . Locks . ContentTree ) ;
var repository = uow . CreateRepository < IContentRepository > ( ) ;
2016-02-26 14:30:32 +00:00
2016-05-18 10:55:19 +02:00
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 ;
if ( content . Published = = false & & content . HasPublishedVersion = = false )
{
uow . Complete ( ) ;
return Attempt . Succeed ( new UnPublishStatus ( UnPublishedStatusType . SuccessAlreadyUnPublished , evtMsgs , content ) ) ; // already unpublished
}
2013-01-22 11:23:33 -01:00
2016-05-18 10:55:19 +02:00
// strategy
var attempt = StrategyCanUnPublish ( content , userId , evtMsgs ) ;
if ( attempt = = false ) return attempt ; // causes rollback
attempt = StrategyUnPublish ( content , true , userId , evtMsgs ) ;
if ( attempt = = false ) return attempt ; // causes rollback
2016-05-02 12:12:21 +02:00
2016-05-18 10:55:19 +02:00
content . WriterId = userId ;
2015-07-29 15:12:12 +02:00
repository . AddOrUpdate ( content ) ;
2016-05-18 10:55:19 +02:00
// fixme delete xml from database! was in _publishingStrategy.UnPublishingFinalized(content);
2015-07-29 15:12:12 +02:00
repository . DeleteContentXml ( content ) ;
2016-05-18 10:55:19 +02:00
2016-05-02 12:12:21 +02:00
uow . Complete ( ) ;
2013-04-23 12:49:33 -02:00
}
2015-07-29 15:12:12 +02:00
2016-05-18 10:55:19 +02:00
UnPublished . RaiseEvent ( new PublishEventArgs < IContent > ( content , false , false ) , this ) ;
return Attempt . Succeed ( new UnPublishStatus ( UnPublishedStatusType . Success , evtMsgs , content ) ) ;
2013-01-22 11:23:33 -01:00
}
2013-04-23 12:49:33 -02:00
/// <summary>
/// Saves and Publishes a single <see cref="IContent"/> object
/// </summary>
/// <param name="content">The <see cref="IContent"/> to save and publish</param>
/// <param name="userId">Optional Id of the User issueing the publishing</param>
2013-01-29 12:45:42 -01:00
/// <param name="raiseEvents">Optional boolean indicating whether or not to raise save events.</param>
2013-04-23 12:49:33 -02:00
/// <returns>True if publishing succeeded, otherwise False</returns>
2014-01-09 10:33:35 +11:00
private Attempt < PublishStatus > SaveAndPublishDo ( IContent content , int userId = 0 , bool raiseEvents = true )
2013-01-22 11:23:33 -01:00
{
2015-08-04 14:52:34 +02:00
var evtMsgs = EventMessagesFactory . Get ( ) ;
2016-05-18 10:55:19 +02:00
if ( raiseEvents & & Saving . IsRaisedEventCancelled ( new SaveEventArgs < IContent > ( content ) , this ) )
return Attempt . Fail ( new PublishStatus ( PublishStatusType . FailedCancelledByEvent , evtMsgs , content ) ) ;
2015-07-24 11:44:09 +02:00
2016-05-18 10:55:19 +02:00
var isNew = content . IsNewEntity ( ) ;
var previouslyPublished = content . HasIdentity & & content . HasPublishedVersion ;
var status = default ( Attempt < PublishStatus > ) ;
2012-12-11 23:19:07 +05:00
2016-05-18 10:55:19 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2012-12-18 11:46:05 -01:00
{
2016-05-18 10:55:19 +02:00
uow . WriteLock ( Constants . Locks . ContentTree ) ;
var repository = uow . CreateRepository < IContentRepository > ( ) ;
2012-12-18 11:46:05 -01:00
2016-05-18 10:55:19 +02:00
// fixme - EnsurePublishable vs StrategyCanPublish?
// EnsurePublishable ensures that path published is ok
// StrategyCanPublish ensures other things including valid properties
// should we merge or?!
2012-12-18 11:46:05 -01:00
2016-05-18 10:55:19 +02:00
// ensure content is publishable, and try to publish
status = EnsurePublishable ( content , evtMsgs ) ;
if ( status . Success )
{
// strategy handles events, and various business rules eg release & expire
// dates, trashed status...
status = StrategyPublish ( content , false , userId , evtMsgs ) ;
}
2012-12-18 11:46:05 -01:00
2016-05-18 10:55:19 +02:00
// save - always, even if not publishing (this is SaveAndPublish)
if ( content . HasIdentity = = false )
content . CreatorId = userId ;
content . WriterId = userId ;
2012-12-18 11:46:05 -01:00
2016-05-18 10:55:19 +02:00
repository . AddOrUpdate ( content ) ;
repository . AddOrUpdatePreviewXml ( content , c = > _entitySerializer . Serialize ( this , _dataTypeService , _userService , _urlSegmentProviders , c ) ) ;
if ( content . Published )
repository . AddOrUpdateContentXml ( content , c = > _entitySerializer . Serialize ( this , _dataTypeService , _userService , _urlSegmentProviders , c ) ) ;
2012-12-18 11:46:05 -01:00
2016-05-18 10:55:19 +02:00
uow . Complete ( ) ;
2015-10-29 15:21:15 +00:00
}
2016-05-18 10:55:19 +02:00
if ( status . Success = = false )
2015-10-29 15:21:15 +00:00
{
2016-05-18 10:55:19 +02:00
// fixme what about the saved event?
return status ;
2015-10-29 15:21:15 +00:00
}
2013-05-23 10:29:44 -02:00
2016-05-18 10:55:19 +02:00
Published . RaiseEvent ( new PublishEventArgs < IContent > ( content , false , false ) , this ) ;
2013-05-23 10:29:44 -02:00
2016-05-18 10:55:19 +02:00
// if was not published and now is... descendants that were 'published' (but
// had an unpublished ancestor) are 're-published' ie not explicitely published
// but back as 'published' nevertheless
if ( isNew = = false & & previouslyPublished = = false )
2013-05-23 10:29:44 -02:00
{
2016-05-18 10:55:19 +02:00
if ( HasChildren ( content . Id ) )
{
var descendants = GetPublishedDescendants ( content ) . ToArray ( ) ;
Published . RaiseEvent ( new PublishEventArgs < IContent > ( descendants , false , false ) , this ) ;
}
2013-05-23 10:29:44 -02:00
}
2016-05-18 10:55:19 +02:00
Audit ( AuditType . Publish , "Save and Publish performed by user" , userId , content . Id ) ;
return status ;
2014-08-20 17:01:12 +02:00
}
2013-05-23 10:29:44 -02:00
2016-05-18 10:55:19 +02:00
private Attempt < PublishStatus > EnsurePublishable ( IContent content , EventMessages evtMsgs )
2013-03-20 13:51:46 -01:00
{
2016-05-18 10:55:19 +02:00
// root content can be published
var checkParents = content . ParentId = = Constants . System . Root ;
2013-03-20 13:51:46 -01:00
2016-05-18 10:55:19 +02:00
// trashed content cannot be published
if ( checkParents = = false & & content . ParentId ! = Constants . System . RecycleBinContent )
{
// ensure all ancestors are published
// because content may be new its Path may be null - start with parent
var path = content . Path ? ? content . Parent ( ) . Path ;
if ( path ! = null ) // if parent is also null, give up
{
var ancestorIds = path . Split ( ',' )
. Skip ( 1 ) // remove leading "-1"
. Reverse ( )
. Select ( int . Parse ) ;
if ( content . Path ! = null )
ancestorIds = ancestorIds . Skip ( 1 ) ; // remove trailing content.Id
if ( ancestorIds . All ( HasPublishedVersion ) )
checkParents = true ;
}
2013-03-20 13:51:46 -01:00
}
2016-05-18 10:55:19 +02:00
if ( checkParents = = false )
{
Logger . Info < ContentService > ( $"Content '{content.Name}' with Id '{content.Id}' could not be published because its parent is not published." ) ;
return Attempt . Fail ( new PublishStatus ( PublishStatusType . FailedPathNotPublished , evtMsgs , content ) ) ;
}
2012-12-15 11:04:03 -01:00
2016-05-18 10:55:19 +02:00
// fixme - should we do it - are we doing it for descendants too?
if ( content . IsValid ( ) = = false )
{
Logger . Info < ContentService > ( $"Content '{content.Name}' with Id '{content.Id}' could not be published because of invalid properties." ) ;
return Attempt . Fail ( new PublishStatus ( PublishStatusType . FailedContentInvalid , evtMsgs , content )
{
InvalidProperties = ( ( ContentBase ) content ) . LastInvalidProperties
} ) ;
}
2013-04-04 07:16:33 -02:00
2016-05-18 10:55:19 +02:00
return Attempt . Succeed ( new PublishStatus ( PublishStatusType . Success , evtMsgs , content ) ) ;
2013-04-04 07:16:33 -02:00
}
#endregion
2012-12-15 11:04:03 -01:00
#region Event Handlers
2016-05-18 10:55:19 +02:00
2012-12-15 11:04:03 -01:00
/// <summary>
2013-04-23 12:49:33 -02:00
/// Occurs before Delete
2016-05-02 12:12:21 +02:00
/// </summary>
2013-04-23 12:49:33 -02:00
public static event TypedEventHandler < IContentService , DeleteEventArgs < IContent > > Deleting ;
/// <summary>
/// Occurs after Delete
/// </summary>
public static event TypedEventHandler < IContentService , DeleteEventArgs < IContent > > Deleted ;
/// <summary>
/// Occurs before Delete Versions
2016-05-02 12:12:21 +02:00
/// </summary>
2013-04-23 12:49:33 -02:00
public static event TypedEventHandler < IContentService , DeleteRevisionsEventArgs > DeletingVersions ;
/// <summary>
/// Occurs after Delete Versions
/// </summary>
public static event TypedEventHandler < IContentService , DeleteRevisionsEventArgs > DeletedVersions ;
/// <summary>
/// Occurs before Save
/// </summary>
public static event TypedEventHandler < IContentService , SaveEventArgs < IContent > > Saving ;
/// <summary>
/// Occurs after Save
/// </summary>
public static event TypedEventHandler < IContentService , SaveEventArgs < IContent > > Saved ;
/// <summary>
/// Occurs before Create
/// </summary>
2014-02-25 01:45:41 +11:00
[Obsolete("Use the Created event instead, the Creating and Created events both offer the same functionality, Creating event has been deprecated.")]
2013-04-23 12:49:33 -02:00
public static event TypedEventHandler < IContentService , NewEventArgs < IContent > > Creating ;
/// <summary>
/// Occurs after Create
/// </summary>
2012-12-15 11:17:32 -01:00
/// <remarks>
2013-08-14 16:12:13 +02:00
/// Please note that the Content object has been created, but might not have been saved
2012-12-15 11:17:32 -01:00
/// so it does not have an identity yet (meaning no Id has been set).
/// </remarks>
2013-04-23 12:49:33 -02:00
public static event TypedEventHandler < IContentService , NewEventArgs < IContent > > Created ;
/// <summary>
/// Occurs before Copy
/// </summary>
public static event TypedEventHandler < IContentService , CopyEventArgs < IContent > > Copying ;
/// <summary>
/// Occurs after Copy
/// </summary>
public static event TypedEventHandler < IContentService , CopyEventArgs < IContent > > Copied ;
/// <summary>
/// Occurs before Content is moved to Recycle Bin
/// </summary>
public static event TypedEventHandler < IContentService , MoveEventArgs < IContent > > Trashing ;
/// <summary>
/// Occurs after Content is moved to Recycle Bin
/// </summary>
public static event TypedEventHandler < IContentService , MoveEventArgs < IContent > > Trashed ;
/// <summary>
/// Occurs before Move
/// </summary>
public static event TypedEventHandler < IContentService , MoveEventArgs < IContent > > Moving ;
/// <summary>
/// Occurs after Move
/// </summary>
public static event TypedEventHandler < IContentService , MoveEventArgs < IContent > > Moved ;
/// <summary>
/// Occurs before Rollback
/// </summary>
public static event TypedEventHandler < IContentService , RollbackEventArgs < IContent > > RollingBack ;
/// <summary>
/// Occurs after Rollback
/// </summary>
public static event TypedEventHandler < IContentService , RollbackEventArgs < IContent > > RolledBack ;
/// <summary>
/// Occurs before Send to Publish
/// </summary>
public static event TypedEventHandler < IContentService , SendToPublishEventArgs < IContent > > SendingToPublish ;
/// <summary>
/// Occurs after Send to Publish
/// </summary>
public static event TypedEventHandler < IContentService , SendToPublishEventArgs < IContent > > SentToPublish ;
2013-08-14 16:12:13 +02:00
/// <summary>
/// Occurs before the Recycle Bin is emptied
/// </summary>
public static event TypedEventHandler < IContentService , RecycleBinEventArgs > EmptyingRecycleBin ;
/// <summary>
/// Occurs after the Recycle Bin has been Emptied
/// </summary>
public static event TypedEventHandler < IContentService , RecycleBinEventArgs > EmptiedRecycleBin ;
2016-05-18 10:55:19 +02:00
/// <summary>
/// Occurs before publish
/// </summary>
public static event TypedEventHandler < IContentService , PublishEventArgs < IContent > > Publishing ;
/// <summary>
/// Occurs after publish
/// </summary>
public static event TypedEventHandler < IContentService , PublishEventArgs < IContent > > Published ;
/// <summary>
/// Occurs before unpublish
/// </summary>
public static event TypedEventHandler < IContentService , PublishEventArgs < IContent > > UnPublishing ;
/// <summary>
/// Occurs after unpublish
/// </summary>
public static event TypedEventHandler < IContentService , PublishEventArgs < IContent > > UnPublished ;
#endregion
#region Publishing Strategies
// prob. want to find nicer names?
internal Attempt < PublishStatus > StrategyCanPublish ( IContent content , int userId , EventMessages evtMsgs )
{
if ( Publishing . IsRaisedEventCancelled ( new PublishEventArgs < IContent > ( content , evtMsgs ) , this ) )
{
Logger . Info < ContentService > ( $"Content '{content.Name}' with Id '{content.Id}' will not be published, the event was cancelled." ) ;
return Attempt . Fail ( new PublishStatus ( PublishStatusType . FailedCancelledByEvent , evtMsgs , content ) ) ;
}
// check if the content is valid
if ( content . IsValid ( ) = = false )
{
Logger . Info < ContentService > ( $"Content '{content.Name}' with Id '{content.Id}' could not be published because of invalid properties." ) ;
return Attempt . Fail ( new PublishStatus ( PublishStatusType . FailedContentInvalid , evtMsgs , content )
{
InvalidProperties = ( ( ContentBase ) content ) . LastInvalidProperties
} ) ;
}
// check if the Content is Expired
if ( content . Status = = ContentStatus . Expired )
{
Logger . Info < ContentService > ( $"Content '{content.Name}' with Id '{content.Id}' has expired and could not be published." ) ;
return Attempt . Fail ( new PublishStatus ( PublishStatusType . FailedHasExpired , evtMsgs , content ) ) ;
}
// check if the Content is Awaiting Release
if ( content . Status = = ContentStatus . AwaitingRelease )
{
Logger . Info < ContentService > ( $"Content '{content.Name}' with Id '{content.Id}' is awaiting release and could not be published." ) ;
return Attempt . Fail ( new PublishStatus ( PublishStatusType . FailedAwaitingRelease , evtMsgs , content ) ) ;
}
// check if the Content is Trashed
if ( content . Status = = ContentStatus . Trashed )
{
Logger . Info < ContentService > ( $"Content '{content.Name}' with Id '{content.Id}' is trashed and could not be published." ) ;
return Attempt . Fail ( new PublishStatus ( PublishStatusType . FailedIsTrashed , evtMsgs , content ) ) ;
}
return Attempt . Succeed ( new PublishStatus ( content , evtMsgs ) ) ;
}
internal Attempt < PublishStatus > StrategyPublish ( IContent content , bool alreadyCheckedCanPublish , int userId , EventMessages evtMsgs )
{
var attempt = alreadyCheckedCanPublish
? Attempt . Succeed ( new PublishStatus ( content , evtMsgs ) ) // already know we can
: StrategyCanPublish ( content , userId , evtMsgs ) ; // else check
if ( attempt . Success = = false )
return attempt ;
// change state to publishing
content . ChangePublishedState ( PublishedState . Publishing ) ;
Logger . Info < ContentService > ( $"Content '{content.Name}' with Id '{content.Id}' has been published." ) ;
return attempt ;
}
/// <summary>
/// Publishes a list of content items
/// </summary>
/// <param name="contents">Contents, ordered by level ASC</param>
/// <param name="alreadyChecked">Contents for which we've already checked CanPublish</param>
/// <param name="userId"></param>
/// <param name="evtMsgs"></param>
/// <param name="includeUnpublished">Indicates whether to publish content that is completely unpublished (has no published
/// version). If false, will only publish already published content with changes. Also impacts what happens if publishing
/// fails (see remarks).</param>
/// <returns></returns>
/// <remarks>
/// Navigate content & descendants top-down and for each,
/// - if it is published
/// - and unchanged, do nothing
/// - else (has changes), publish those changes
/// - if it is not published
/// - and at top-level, publish
/// - or includeUnpublished is true, publish
/// - else do nothing & skip the underlying branch
///
/// When publishing fails
/// - if content has no published version, skip the underlying branch
/// - else (has published version),
/// - if includeUnpublished is true, process the underlying branch
/// - else, do not process the underlying branch
/// </remarks>
internal IEnumerable < Attempt < PublishStatus > > StrategyPublishWithChildren ( IEnumerable < IContent > contents , IEnumerable < IContent > alreadyChecked , int userId , EventMessages evtMsgs , bool includeUnpublished = true )
{
var statuses = new List < Attempt < PublishStatus > > ( ) ;
var alreadyCheckedA = ( alreadyChecked ? ? Enumerable . Empty < IContent > ( ) ) . ToArray ( ) ;
// list of ids that we exclude because they could not be published
var excude = new List < int > ( ) ;
var topLevel = - 1 ;
foreach ( var content in contents )
{
// initialize - content is ordered by level ASC
if ( topLevel < 0 )
topLevel = content . Level ;
if ( excude . Contains ( content . ParentId ) )
{
// parent is excluded, so exclude content too
Logger . Info < ContentService > ( $"Content '{content.Name}' with Id '{content.Id}' will not be published because it's parent's publishing action failed or was cancelled." ) ;
excude . Add ( content . Id ) ;
// status has been reported for an ancestor and that one is excluded => no status
continue ;
}
if ( content . Published & & content . Level > topLevel ) // topLevel we DO want to (re)publish
{
// newest is published already
statuses . Add ( Attempt . Succeed ( new PublishStatus ( PublishStatusType . SuccessAlreadyPublished , evtMsgs , content ) ) ) ;
continue ;
}
if ( content . HasPublishedVersion )
{
// newest is published already but we are topLevel, or
// newest is not published, but another version is - publish newest
var r = StrategyPublish ( content , alreadyCheckedA . Contains ( content ) , userId , evtMsgs ) ;
if ( r . Success = = false )
{
// we tried to publish and it failed, but it already had / still has a published version,
// the rule in remarks says that we should skip the underlying branch if includeUnpublished
// is false, else process it - not that it makes much sense, but keep it like that for now
if ( includeUnpublished = = false )
excude . Add ( content . Id ) ;
}
statuses . Add ( r ) ;
continue ;
}
if ( content . Level = = topLevel | | includeUnpublished )
{
// content has no published version, and we want to publish it, either
// because it is top-level or because we include unpublished.
// if publishing fails, and because content does not have a published
// version at all, ensure we do not process its descendants
var r = StrategyPublish ( content , alreadyCheckedA . Contains ( content ) , userId , evtMsgs ) ;
if ( r . Success = = false )
excude . Add ( content . Id ) ;
statuses . Add ( r ) ;
continue ;
}
// content has no published version, and we don't want to publish it
excude . Add ( content . Id ) ; // ignore everything below it
// content is not even considered, really => no status
}
return statuses ;
}
internal Attempt < UnPublishStatus > StrategyCanUnPublish ( IContent content , int userId , EventMessages evtMsgs )
{
// fire UnPublishing event
if ( UnPublishing . IsRaisedEventCancelled ( new PublishEventArgs < IContent > ( content , evtMsgs ) , this ) )
{
Logger . Info < ContentService > ( $"Content '{content.Name}' with Id '{content.Id}' will not be unpublished, the event was cancelled." ) ;
return Attempt . Fail ( new UnPublishStatus ( UnPublishedStatusType . FailedCancelledByEvent , evtMsgs , content ) ) ;
}
return Attempt . Succeed ( new UnPublishStatus ( content , evtMsgs ) ) ;
}
internal Attempt < UnPublishStatus > StrategyUnPublish ( IContent content , bool alreadyCheckedCanUnPublish , int userId , EventMessages evtMsgs )
{
// content should (is assumed to) be the newest version, which may not be published,
// don't know how to test this, so it's not verified
var attempt = alreadyCheckedCanUnPublish
? Attempt . Succeed ( new UnPublishStatus ( content , evtMsgs ) ) // already know we can
: StrategyCanUnPublish ( content , userId , evtMsgs ) ;
if ( attempt . Success = = false )
return attempt ;
// if Content has a release date set to before now, it should be removed so it doesn't interrupt an unpublish
// otherwise it would remain released == published
if ( content . ReleaseDate . HasValue & & content . ReleaseDate . Value < = DateTime . Now )
{
content . ReleaseDate = null ;
Logger . Info < ContentService > ( $"Content '{content.Name}' with Id '{content.Id}' had its release date removed, because it was unpublished." ) ;
}
// version is published or unpublished, but content is published
// change state to unpublishing
content . ChangePublishedState ( PublishedState . Unpublishing ) ;
Logger . Info < ContentService > ( $"Content '{content.Name}' with Id '{content.Id}' has been unpublished." ) ;
return attempt ;
}
internal IEnumerable < Attempt < UnPublishStatus > > StrategyUnPublish ( IEnumerable < IContent > content , int userId , EventMessages evtMsgs )
{
return content . Select ( x = > StrategyUnPublish ( x , false , userId , evtMsgs ) ) ;
}
#endregion
#region Content Types
/// <summary>
/// Deletes all content of specified type. All children of deleted content is moved to Recycle Bin.
/// </summary>
/// <remarks>This needs extra care and attention as its potentially a dangerous and extensive operation</remarks>
/// <param name="contentTypeId">Id of the <see cref="IContentType"/></param>
/// <param name="userId">Optional Id of the user issueing the delete operation</param>
public void DeleteContentOfType ( int contentTypeId , int userId = 0 )
{
//TODO: This currently this is called from the ContentTypeService but that needs to change,
// if we are deleting a content type, we should just delete the data and do this operation slightly differently.
// This method will recursively go lookup every content item, check if any of it's descendants are
// of a different type, move them to the recycle bin, then permanently delete the content items.
// The main problem with this is that for every content item being deleted, events are raised...
// which we need for many things like keeping caches in sync, but we can surely do this MUCH better.
var moves = new List < Tuple < IContent , string > > ( ) ;
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
{
uow . WriteLock ( Constants . Locks . ContentTree ) ;
var repository = uow . CreateRepository < IContentRepository > ( ) ;
// fixme what about content that has the contenttype as part of its composition?
var query = repository . Query . Where ( x = > x . ContentTypeId = = contentTypeId ) ;
var contents = repository . GetByQuery ( query ) . ToArray ( ) ;
if ( Deleting . IsRaisedEventCancelled ( new DeleteEventArgs < IContent > ( contents ) , this ) )
return ; // causes rollback
// order by level, descending, so deepest first - that way, we cannot move
// a content of the deleted type, to the recycle bin (and then delete it...)
foreach ( var content in contents . OrderByDescending ( x = > x . ParentId ) )
{
// if it's not trashed yet, and published, we should unpublish
// but... UnPublishing event makes no sense (not going to cancel?) and no need to save
// just raise the event
if ( content . Trashed = = false & & content . HasPublishedVersion )
UnPublished . RaiseEvent ( new PublishEventArgs < IContent > ( content , false , false ) , this ) ;
// if current content has children, move them to trash
var c = content ;
var childQuery = repository . Query . Where ( x = > x . Path . StartsWith ( c . Path ) ) ;
var children = repository . GetByQuery ( childQuery ) ;
foreach ( var child in children . Where ( x = > x . ContentTypeId ! = contentTypeId ) )
{
// see MoveToRecycleBin
PerformMoveLocked ( repository , child , Constants . System . RecycleBinContent , null , userId , moves , true ) ;
}
// delete content
// triggers the deleted event (and handles the files)
DeleteLocked ( repository , content ) ;
}
uow . Complete ( ) ;
}
var moveInfos = moves
. Select ( x = > new MoveEventInfo < IContent > ( x . Item1 , x . Item2 , x . Item1 . ParentId ) )
. ToArray ( ) ;
if ( moveInfos . Length > 0 )
Trashed . RaiseEvent ( new MoveEventArgs < IContent > ( false , moveInfos ) , this ) ;
Audit ( AuditType . Delete , $"Delete Content of Type {contentTypeId} performed by user" , userId , Constants . System . Root ) ;
}
private IContentType GetContentType ( string contentTypeAlias )
{
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
{
uow . ReadLock ( Constants . Locks . ContentTree ) ;
var repository = uow . CreateRepository < IContentTypeRepository > ( ) ;
var query = repository . Query . Where ( x = > x . Alias = = contentTypeAlias ) ;
var contentType = repository . GetByQuery ( query ) . FirstOrDefault ( ) ;
if ( contentType = = null )
throw new Exception ( $"No ContentType matching the passed in Alias: '{contentTypeAlias}' was found" ) ; // causes rollback
uow . Complete ( ) ;
return contentType ;
}
}
#endregion
#region Xml - Shoud Move !
/// <summary>
/// Returns the persisted content's XML structure
/// </summary>
/// <param name="contentId"></param>
/// <returns></returns>
public XElement GetContentXml ( int contentId )
{
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
{
uow . ReadLock ( Constants . Locks . ContentTree ) ;
var repository = uow . CreateRepository < IContentRepository > ( ) ;
var elt = repository . GetContentXml ( contentId ) ;
uow . Complete ( ) ;
return elt ;
}
}
/// <summary>
/// Returns the persisted content's preview XML structure
/// </summary>
/// <param name="contentId"></param>
/// <param name="version"></param>
/// <returns></returns>
public XElement GetContentPreviewXml ( int contentId , Guid version )
{
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
{
uow . ReadLock ( Constants . Locks . ContentTree ) ;
var repository = uow . CreateRepository < IContentRepository > ( ) ;
var elt = repository . GetContentPreviewXml ( contentId , version ) ;
uow . Complete ( ) ;
return elt ;
}
}
/// <summary>
/// Rebuilds all xml content in the cmsContentXml table for all documents
/// </summary>
/// <param name="contentTypeIds">
/// Only rebuild the xml structures for the content type ids passed in, if none then rebuilds the structures
/// for all content
/// </param>
public void RebuildXmlStructures ( params int [ ] contentTypeIds )
{
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
{
uow . WriteLock ( Constants . Locks . ContentTree ) ;
var repository = uow . CreateRepository < IContentRepository > ( ) ;
repository . RebuildXmlStructures (
content = > _entitySerializer . Serialize ( this , _dataTypeService , _userService , _urlSegmentProviders , content ) ,
contentTypeIds : contentTypeIds . Length = = 0 ? null : contentTypeIds ) ;
uow . Complete ( ) ;
}
Audit ( AuditType . Publish , "ContentService.RebuildXmlStructures completed, the xml has been regenerated in the database" , 0 , Constants . System . Root ) ;
}
2013-04-23 12:49:33 -02:00
#endregion
}
2012-10-03 12:51:32 -02:00
}