2021-09-14 22:13:39 +02:00
using System.Globalization ;
2020-09-17 09:42:55 +02:00
using Microsoft.Extensions.Logging ;
2017-12-07 16:45:25 +01:00
using NPoco ;
2021-03-05 15:36:27 +01:00
using Umbraco.Cms.Core ;
2021-02-09 10:22:42 +01:00
using Umbraco.Cms.Core.Cache ;
2021-04-20 12:17:11 +02:00
using Umbraco.Cms.Core.Events ;
2021-02-09 10:22:42 +01:00
using Umbraco.Cms.Core.Models ;
using Umbraco.Cms.Core.Models.Membership ;
2021-05-11 14:33:49 +02:00
using Umbraco.Cms.Core.Notifications ;
2021-02-09 10:22:42 +01:00
using Umbraco.Cms.Core.Persistence ;
using Umbraco.Cms.Core.Persistence.Querying ;
using Umbraco.Cms.Core.Persistence.Repositories ;
using Umbraco.Cms.Core.PropertyEditors ;
using Umbraco.Cms.Core.Serialization ;
using Umbraco.Cms.Core.Services ;
2021-02-12 13:36:50 +01:00
using Umbraco.Cms.Infrastructure.Persistence.Dtos ;
using Umbraco.Cms.Infrastructure.Persistence.Factories ;
using Umbraco.Cms.Infrastructure.Persistence.Querying ;
using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax ;
2022-01-13 17:44:11 +00:00
using Umbraco.Cms.Infrastructure.Scoping ;
2021-02-09 11:26:22 +01:00
using Umbraco.Extensions ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement ;
/// <summary>
/// Represents a repository for doing CRUD operations for <see cref="IContent" />.
/// </summary>
public class DocumentRepository : ContentRepositoryBase < int , IContent , DocumentRepository > , IDocumentRepository
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
private readonly AppCaches _appCaches ;
private readonly ContentByGuidReadRepository _contentByGuidReadRepository ;
private readonly IContentTypeRepository _contentTypeRepository ;
private readonly ILoggerFactory _loggerFactory ;
private readonly IScopeAccessor _scopeAccessor ;
private readonly IJsonSerializer _serializer ;
private readonly ITagRepository _tagRepository ;
private readonly ITemplateRepository _templateRepository ;
private PermissionRepository < IContent > ? _permissionRepository ;
2017-12-07 16:45:25 +01:00
/// <summary>
2022-06-02 08:18:31 +02:00
/// Constructor
2017-12-07 16:45:25 +01:00
/// </summary>
2022-06-02 08:18:31 +02:00
/// <param name="scopeAccessor"></param>
/// <param name="appCaches"></param>
/// <param name="logger"></param>
/// <param name="loggerFactory"></param>
/// <param name="contentTypeRepository"></param>
/// <param name="templateRepository"></param>
/// <param name="tagRepository"></param>
/// <param name="languageRepository"></param>
/// <param name="relationRepository"></param>
/// <param name="relationTypeRepository"></param>
/// <param name="dataValueReferenceFactories"></param>
/// <param name="dataTypeService"></param>
/// <param name="serializer"></param>
/// <param name="eventAggregator"></param>
/// <param name="propertyEditors">
/// Lazy property value collection - must be lazy because we have a circular dependency since some property editors
/// require services, yet these services require property editors
/// </param>
public DocumentRepository (
IScopeAccessor scopeAccessor ,
AppCaches appCaches ,
ILogger < DocumentRepository > logger ,
ILoggerFactory loggerFactory ,
IContentTypeRepository contentTypeRepository ,
ITemplateRepository templateRepository ,
ITagRepository tagRepository ,
ILanguageRepository languageRepository ,
IRelationRepository relationRepository ,
IRelationTypeRepository relationTypeRepository ,
PropertyEditorCollection propertyEditors ,
DataValueReferenceFactoryCollection dataValueReferenceFactories ,
IDataTypeService dataTypeService ,
IJsonSerializer serializer ,
IEventAggregator eventAggregator )
: base ( scopeAccessor , appCaches , logger , languageRepository , relationRepository , relationTypeRepository ,
propertyEditors , dataValueReferenceFactories , dataTypeService , eventAggregator )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
_contentTypeRepository =
contentTypeRepository ? ? throw new ArgumentNullException ( nameof ( contentTypeRepository ) ) ;
_templateRepository = templateRepository ? ? throw new ArgumentNullException ( nameof ( templateRepository ) ) ;
_tagRepository = tagRepository ? ? throw new ArgumentNullException ( nameof ( tagRepository ) ) ;
_serializer = serializer ;
_appCaches = appCaches ;
_loggerFactory = loggerFactory ;
_scopeAccessor = scopeAccessor ;
_contentByGuidReadRepository = new ContentByGuidReadRepository ( this , scopeAccessor , appCaches ,
loggerFactory . CreateLogger < ContentByGuidReadRepository > ( ) ) ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
protected override DocumentRepository This = > this ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
/// <summary>
/// Default is to always ensure all documents have unique names
/// </summary>
protected virtual bool EnsureUniqueNaming { get ; } = true ;
// note: is ok to 'new' the repo here as it's a sub-repo really
private PermissionRepository < IContent > PermissionRepository = > _permissionRepository
? ? ( _permissionRepository =
new PermissionRepository < IContent > (
_scopeAccessor , _appCaches ,
_loggerFactory
. CreateLogger <
PermissionRepository < IContent > > ( ) ) ) ;
/// <inheritdoc />
public ContentScheduleCollection GetContentSchedule ( int contentId )
{
var result = new ContentScheduleCollection ( ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
List < ContentScheduleDto > ? scheduleDtos = Database . Fetch < ContentScheduleDto > ( Sql ( )
. Select < ContentScheduleDto > ( )
. From < ContentScheduleDto > ( )
. Where < ContentScheduleDto > ( x = > x . NodeId = = contentId ) ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
foreach ( ContentScheduleDto ? scheduleDto in scheduleDtos )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
result . Add ( new ContentSchedule ( scheduleDto . Id ,
LanguageRepository . GetIsoCodeById ( scheduleDto . LanguageId ) ? ? string . Empty ,
scheduleDto . Date ,
scheduleDto . Action = = ContentScheduleAction . Release . ToString ( )
? ContentScheduleAction . Release
: ContentScheduleAction . Expire ) ) ;
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
return result ;
}
protected override string ApplySystemOrdering ( ref Sql < ISqlContext > sql , Ordering ordering )
{
// note: 'updater' is the user who created the latest draft version,
// we don't have an 'updater' per culture (should we?)
if ( ordering . OrderBy . InvariantEquals ( "updater" ) )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
Sql < ISqlContext > joins = Sql ( )
. InnerJoin < UserDto > ( "updaterUser" )
. On < ContentVersionDto , UserDto > ( ( version , user ) = > version . UserId = = user . Id ,
aliasRight : "updaterUser" ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// see notes in ApplyOrdering: the field MUST be selected + aliased
sql = Sql (
InsertBefore ( sql , "FROM" ,
", " + SqlSyntax . GetFieldName < UserDto > ( x = > x . UserName , "updaterUser" ) + " AS ordering " ) ,
sql . Arguments ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
sql = InsertJoins ( sql , joins ) ;
return "ordering" ;
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
if ( ordering . OrderBy . InvariantEquals ( "published" ) )
2017-12-07 16:45:25 +01:00
{
2022-11-25 11:37:46 +01:00
// no culture, assume invariant and simply order by published.
2022-06-02 08:18:31 +02:00
if ( ordering . Culture . IsNullOrWhiteSpace ( ) )
{
2022-11-25 11:37:46 +01:00
return SqlSyntax . GetFieldName < DocumentDto > ( x = > x . Published ) ;
2022-06-02 08:18:31 +02:00
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// invariant: left join will yield NULL and we must use pcv to determine published
// variant: left join may yield NULL or something, and that determines published
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
Sql < ISqlContext > joins = Sql ( )
. InnerJoin < ContentTypeDto > ( "ctype" ) . On < ContentDto , ContentTypeDto > (
( content , contentType ) = > content . ContentTypeId = = contentType . NodeId , aliasRight : "ctype" )
// left join on optional culture variation
//the magic "[[[ISOCODE]]]" parameter value will be replaced in ContentRepositoryBase.GetPage() by the actual ISO code
. LeftJoin < ContentVersionCultureVariationDto > ( nested = >
nested . InnerJoin < LanguageDto > ( "langp" ) . On < ContentVersionCultureVariationDto , LanguageDto > (
( ccv , lang ) = > ccv . LanguageId = = lang . Id & & lang . IsoCode = = "[[[ISOCODE]]]" , "ccvp" ,
"langp" ) ,
"ccvp" )
. On < ContentVersionDto , ContentVersionCultureVariationDto > ( ( version , ccv ) = > version . Id = = ccv . VersionId ,
"pcv" , "ccvp" ) ;
2018-11-08 16:33:19 +01:00
2022-06-02 08:18:31 +02:00
sql = InsertJoins ( sql , joins ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// see notes in ApplyOrdering: the field MUST be selected + aliased, and we cannot have
// the whole CASE fragment in ORDER BY due to it not being detected by NPoco
var sqlText = InsertBefore ( sql . SQL , "FROM" ,
2022-11-25 11:37:46 +01:00
// when invariant, ie 'variations' does not have the culture flag (value 1), it should be safe to simply use the published flag on umbracoDocument,
2022-06-02 08:18:31 +02:00
// otherwise check if there's a version culture variation for the lang, via ccv.id
2022-11-25 11:37:46 +01:00
$", (CASE WHEN (ctype.variations & 1) = 0 THEN ({SqlSyntax.GetFieldName<DocumentDto>(x => x.Published)}) ELSE (CASE WHEN ccvp.id IS NULL THEN 0 ELSE 1 END) END) AS ordering " ) ; // trailing space is important!
2022-06-02 08:18:31 +02:00
sql = Sql ( sqlText , sql . Arguments ) ;
return "ordering" ;
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
return base . ApplySystemOrdering ( ref sql , ordering ) ;
}
private IEnumerable < IContent > MapDtosToContent ( List < DocumentDto > dtos ,
bool withCache = false ,
bool loadProperties = true ,
bool loadTemplates = true ,
bool loadVariants = true )
{
var temps = new List < TempContent < Content > > ( ) ;
var contentTypes = new Dictionary < int , IContentType ? > ( ) ;
var templateIds = new List < int > ( ) ;
2018-10-18 14:16:54 +02:00
2022-06-02 08:18:31 +02:00
var content = new Content [ dtos . Count ] ;
for ( var i = 0 ; i < dtos . Count ; i + + )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
DocumentDto dto = dtos [ i ] ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
if ( withCache )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
// if the cache contains the (proper version of the) item, use it
IContent ? cached =
IsolatedCache . GetCacheItem < IContent > ( RepositoryCacheKeys . GetKey < IContent , int > ( dto . NodeId ) ) ;
if ( cached ! = null & & cached . VersionId = = dto . DocumentVersionDto . ContentVersionDto . Id )
{
content [ i ] = ( Content ) cached ;
continue ;
}
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
// else, need to build it
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// get the content type - the repository is full cache *but* still deep-clones
// whatever comes out of it, so use our own local index here to avoid this
var contentTypeId = dto . ContentDto . ContentTypeId ;
if ( contentTypes . TryGetValue ( contentTypeId , out IContentType ? contentType ) = = false )
{
contentTypes [ contentTypeId ] = contentType = _contentTypeRepository . Get ( contentTypeId ) ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
Content c = content [ i ] = ContentBaseFactory . BuildEntity ( dto , contentType ) ;
2018-10-04 13:15:01 +02:00
2022-06-02 08:18:31 +02:00
if ( loadTemplates )
{
// need templates
var templateId = dto . DocumentVersionDto . TemplateId ;
if ( templateId . HasValue )
{
templateIds . Add ( templateId . Value ) ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
if ( dto . Published )
{
templateId = dto . PublishedVersionDto ! . TemplateId ;
if ( templateId . HasValue )
{
templateIds . Add ( templateId . Value ) ;
}
}
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// need temps, for properties, templates and variations
var versionId = dto . DocumentVersionDto . Id ;
var publishedVersionId = dto . Published ? dto . PublishedVersionDto ! . Id : 0 ;
var temp = new TempContent < Content > ( dto . NodeId , versionId , publishedVersionId , contentType , c )
{
Template1Id = dto . DocumentVersionDto . TemplateId
} ;
if ( dto . Published )
{
temp . Template2Id = dto . PublishedVersionDto ! . TemplateId ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
temps . Add ( temp ) ;
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
Dictionary < int , ITemplate > ? templates = null ;
if ( loadTemplates )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
// load all required templates in 1 query, and index
templates = _templateRepository . GetMany ( templateIds . ToArray ( ) ) ?
. ToDictionary ( x = > x . Id , x = > x ) ;
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
IDictionary < int , PropertyCollection > ? properties = null ;
if ( loadProperties )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
// load all properties for all documents from database in 1 query - indexed by version id
properties = GetPropertyCollections ( temps ) ;
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
// assign templates and properties
foreach ( TempContent < Content > temp in temps )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
if ( loadTemplates )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
// set the template ID if it matches an existing template
if ( temp . Template1Id . HasValue & & ( templates ? . ContainsKey ( temp . Template1Id . Value ) ? ? false ) )
{
temp . Content ! . TemplateId = temp . Template1Id ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
if ( temp . Template2Id . HasValue & & ( templates ? . ContainsKey ( temp . Template2Id . Value ) ? ? false ) )
{
temp . Content ! . PublishTemplateId = temp . Template2Id ;
}
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// set properties
if ( loadProperties )
{
if ( properties ? . ContainsKey ( temp . VersionId ) ? ? false )
{
temp . Content ! . Properties = properties [ temp . VersionId ] ;
}
else
{
throw new InvalidOperationException ( $"No property data found for version: '{temp.VersionId}'." ) ;
}
}
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
if ( loadVariants )
2018-10-22 08:45:30 +02:00
{
2022-06-02 08:18:31 +02:00
// set variations, if varying
temps = temps . Where ( x = > x . ContentType ? . VariesByCulture ( ) ? ? false ) . ToList ( ) ;
if ( temps . Count > 0 )
{
// load all variations for all documents from database, in one query
IDictionary < int , List < ContentVariation > > contentVariations = GetContentVariations ( temps ) ;
IDictionary < int , List < DocumentVariation > > documentVariations = GetDocumentVariations ( temps ) ;
foreach ( TempContent < Content > temp in temps )
{
SetVariations ( temp . Content , contentVariations , documentVariations ) ;
}
}
}
2019-03-22 13:55:46 +01:00
2021-10-28 10:02:04 +02:00
2022-06-02 08:18:31 +02:00
foreach ( Content c in content )
{
c . ResetDirtyProperties ( false ) ; // reset dirty initial properties (U4-1946)
2018-10-22 08:45:30 +02:00
}
2022-06-02 08:18:31 +02:00
return content ;
}
private IContent MapDtoToContent ( DocumentDto dto )
{
IContentType ? contentType = _contentTypeRepository . Get ( dto . ContentDto . ContentTypeId ) ;
Content content = ContentBaseFactory . BuildEntity ( dto , contentType ) ;
try
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
content . DisableChangeTracking ( ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// get template
if ( dto . DocumentVersionDto . TemplateId . HasValue )
{
content . TemplateId = dto . DocumentVersionDto . TemplateId ;
}
// get properties - indexed by version id
var versionId = dto . DocumentVersionDto . Id ;
// TODO: shall we get published properties or not?
//var publishedVersionId = dto.Published ? dto.PublishedVersionDto.Id : 0;
var publishedVersionId = dto . PublishedVersionDto ? . Id ? ? 0 ;
var temp = new TempContent < Content > ( dto . NodeId , versionId , publishedVersionId , contentType ) ;
var ltemp = new List < TempContent < Content > > { temp } ;
IDictionary < int , PropertyCollection > properties = GetPropertyCollections ( ltemp ) ;
content . Properties = properties [ dto . DocumentVersionDto . Id ] ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// set variations, if varying
if ( contentType ? . VariesByCulture ( ) ? ? false )
{
IDictionary < int , List < ContentVariation > > contentVariations = GetContentVariations ( ltemp ) ;
IDictionary < int , List < DocumentVariation > > documentVariations = GetDocumentVariations ( ltemp ) ;
SetVariations ( content , contentVariations , documentVariations ) ;
}
// reset dirty initial properties (U4-1946)
content . ResetDirtyProperties ( false ) ;
return content ;
}
finally
2019-10-01 22:31:42 +01:00
{
2022-06-02 08:18:31 +02:00
content . EnableChangeTracking ( ) ;
2019-10-01 22:31:42 +01:00
}
2022-06-02 08:18:31 +02:00
}
2019-10-01 22:31:42 +01:00
2022-06-02 08:18:31 +02:00
private void SetVariations ( Content ? content , IDictionary < int , List < ContentVariation > > contentVariations ,
IDictionary < int , List < DocumentVariation > > documentVariations )
{
if ( content is null )
2019-10-01 22:31:42 +01:00
{
2022-06-02 08:18:31 +02:00
return ;
2019-10-01 22:31:42 +01:00
}
2022-06-02 08:18:31 +02:00
if ( contentVariations . TryGetValue ( content . VersionId , out List < ContentVariation > ? contentVariation ) )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
foreach ( ContentVariation v in contentVariation )
{
content . SetCultureInfo ( v . Culture , v . Name , v . Date ) ;
}
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
if ( content . PublishedVersionId > 0 & &
contentVariations . TryGetValue ( content . PublishedVersionId , out contentVariation ) )
{
foreach ( ContentVariation v in contentVariation )
{
content . SetPublishInfo ( v . Culture , v . Name , v . Date ) ;
}
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
if ( documentVariations . TryGetValue ( content . Id , out List < DocumentVariation > ? documentVariation ) )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
content . SetCultureEdited ( documentVariation . Where ( x = > x . Edited ) . Select ( x = > x . Culture ) ) ;
}
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
private IDictionary < int , List < ContentVariation > > GetContentVariations < T > ( List < TempContent < T > > temps )
where T : class , IContentBase
{
var versions = new List < int > ( ) ;
foreach ( TempContent < T > temp in temps )
{
versions . Add ( temp . VersionId ) ;
if ( temp . PublishedVersionId > 0 )
{
versions . Add ( temp . PublishedVersionId ) ;
}
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
if ( versions . Count = = 0 )
{
return new Dictionary < int , List < ContentVariation > > ( ) ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
IEnumerable < ContentVersionCultureVariationDto > dtos =
Database . FetchByGroups < ContentVersionCultureVariationDto , int > ( versions , Constants . Sql . MaxParameterCount ,
batch
= > Sql ( )
. Select < ContentVersionCultureVariationDto > ( )
. From < ContentVersionCultureVariationDto > ( )
. WhereIn < ContentVersionCultureVariationDto > ( x = > x . VersionId , batch ) ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
var variations = new Dictionary < int , List < ContentVariation > > ( ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
foreach ( ContentVersionCultureVariationDto dto in dtos )
{
if ( ! variations . TryGetValue ( dto . VersionId , out List < ContentVariation > ? variation ) )
{
variations [ dto . VersionId ] = variation = new List < ContentVariation > ( ) ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
variation . Add ( new ContentVariation
{
Culture = LanguageRepository . GetIsoCodeById ( dto . LanguageId ) , Name = dto . Name , Date = dto . UpdateDate
} ) ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
return variations ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
private IDictionary < int , List < DocumentVariation > > GetDocumentVariations < T > ( List < TempContent < T > > temps )
where T : class , IContentBase
{
IEnumerable < int > ids = temps . Select ( x = > x . Id ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
IEnumerable < DocumentCultureVariationDto > dtos = Database . FetchByGroups < DocumentCultureVariationDto , int > ( ids ,
Constants . Sql . MaxParameterCount , batch = >
Sql ( )
. Select < DocumentCultureVariationDto > ( )
. From < DocumentCultureVariationDto > ( )
. WhereIn < DocumentCultureVariationDto > ( x = > x . NodeId , batch ) ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
var variations = new Dictionary < int , List < DocumentVariation > > ( ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
foreach ( DocumentCultureVariationDto dto in dtos )
{
if ( ! variations . TryGetValue ( dto . NodeId , out List < DocumentVariation > ? variation ) )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
variations [ dto . NodeId ] = variation = new List < DocumentVariation > ( ) ;
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
variation . Add ( new DocumentVariation
2021-07-12 16:26:19 -06:00
{
2022-06-02 08:18:31 +02:00
Culture = LanguageRepository . GetIsoCodeById ( dto . LanguageId ) , Edited = dto . Edited
} ) ;
}
2021-07-12 16:26:19 -06:00
2022-06-02 08:18:31 +02:00
return variations ;
}
2018-04-23 12:53:17 +02:00
2022-06-02 08:18:31 +02:00
private IEnumerable < ContentVersionCultureVariationDto > GetContentVariationDtos ( IContent content , bool publishing )
{
if ( content . CultureInfos is not null )
{
// create dtos for the 'current' (non-published) version, all cultures
// ReSharper disable once UseDeconstruction
foreach ( ContentCultureInfos cultureInfo in content . CultureInfos )
2021-07-12 16:26:19 -06:00
{
2022-06-02 08:18:31 +02:00
yield return new ContentVersionCultureVariationDto
{
VersionId = content . VersionId ,
LanguageId =
LanguageRepository . GetIdByIsoCode ( cultureInfo . Culture ) ? ?
throw new InvalidOperationException ( "Not a valid culture." ) ,
Culture = cultureInfo . Culture ,
Name = cultureInfo . Name ,
UpdateDate =
content . GetUpdateDate ( cultureInfo . Culture ) ? ? DateTime . MinValue // we *know* there is a value
} ;
2021-07-12 16:26:19 -06:00
}
2022-06-02 08:18:31 +02:00
}
2021-07-12 16:26:19 -06:00
2022-06-02 08:18:31 +02:00
// if not publishing, we're just updating the 'current' (non-published) version,
// so there are no DTOs to create for the 'published' version which remains unchanged
if ( ! publishing )
{
yield break ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
if ( content . PublishCultureInfos is not null )
{
// create dtos for the 'published' version, for published cultures (those having a name)
// ReSharper disable once UseDeconstruction
foreach ( ContentCultureInfos cultureInfo in content . PublishCultureInfos )
2018-04-12 22:53:04 +02:00
{
2022-06-02 08:18:31 +02:00
yield return new ContentVersionCultureVariationDto
2021-07-12 16:26:19 -06:00
{
2022-06-02 08:18:31 +02:00
VersionId = content . PublishedVersionId ,
LanguageId =
LanguageRepository . GetIdByIsoCode ( cultureInfo . Culture ) ? ?
throw new InvalidOperationException ( "Not a valid culture." ) ,
Culture = cultureInfo . Culture ,
Name = cultureInfo . Name ,
UpdateDate =
content . GetPublishDate ( cultureInfo . Culture ) ? ? DateTime . MinValue // we *know* there is a value
} ;
}
}
}
2021-07-13 09:52:31 -06:00
2022-06-02 08:18:31 +02:00
private IEnumerable < DocumentCultureVariationDto > GetDocumentVariationDtos ( IContent content ,
HashSet < string > editedCultures )
{
IEnumerable < string >
allCultures = content . AvailableCultures . Union ( content . PublishedCultures ) ; // union = distinct
foreach ( var culture in allCultures )
{
var dto = new DocumentCultureVariationDto
{
NodeId = content . Id ,
LanguageId =
LanguageRepository . GetIdByIsoCode ( culture ) ? ?
throw new InvalidOperationException ( "Not a valid culture." ) ,
Culture = culture ,
Name = content . GetCultureName ( culture ) ? ? content . GetPublishName ( culture ) ,
Available = content . IsCultureAvailable ( culture ) ,
Published = content . IsCulturePublished ( culture ) ,
// note: can't use IsCultureEdited at that point - hasn't been updated yet - see PersistUpdatedItem
Edited = content . IsCultureAvailable ( culture ) & &
( ! content . IsCulturePublished ( culture ) | |
( editedCultures ! = null & & editedCultures . Contains ( culture ) ) )
} ;
2021-07-13 09:52:31 -06:00
2022-06-02 08:18:31 +02:00
yield return dto ;
}
}
2018-04-23 12:53:17 +02:00
2022-06-02 08:18:31 +02:00
private class ContentVariation
{
public string? Culture { get ; set ; }
public string? Name { get ; set ; }
public DateTime Date { get ; set ; }
}
2018-04-12 22:53:04 +02:00
2022-06-02 08:18:31 +02:00
private class DocumentVariation
{
public string? Culture { get ; set ; }
public bool Edited { get ; set ; }
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
#region Repository Base
2018-02-01 14:14:45 +01:00
2022-06-02 08:18:31 +02:00
protected override Guid NodeObjectTypeId = > Constants . ObjectTypes . Document ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
protected override IContent ? PerformGet ( int id )
{
Sql < ISqlContext > sql = GetBaseQuery ( QueryType . Single )
. Where < NodeDto > ( x = > x . NodeId = = id )
. SelectTop ( 1 ) ;
DocumentDto ? dto = Database . Fetch < DocumentDto > ( sql ) . FirstOrDefault ( ) ;
return dto = = null
? null
: MapDtoToContent ( dto ) ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
protected override IEnumerable < IContent > PerformGetAll ( params int [ ] ? ids )
{
Sql < ISqlContext > sql = GetBaseQuery ( QueryType . Many ) ;
2019-10-25 14:17:18 +11:00
2022-06-02 08:18:31 +02:00
if ( ids ? . Any ( ) ? ? false )
{
sql . WhereIn < NodeDto > ( x = > x . NodeId , ids ) ;
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
return MapDtosToContent ( Database . Fetch < DocumentDto > ( sql ) ) ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
protected override IEnumerable < IContent > PerformGetByQuery ( IQuery < IContent > query )
{
Sql < ISqlContext > sqlClause = GetBaseQuery ( QueryType . Many ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
var translator = new SqlTranslator < IContent > ( sqlClause , query ) ;
Sql < ISqlContext > sql = translator . Translate ( ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
AddGetByQueryOrderBy ( sql ) ;
2019-06-28 09:19:11 +02:00
2022-06-02 08:18:31 +02:00
return MapDtosToContent ( Database . Fetch < DocumentDto > ( sql ) ) ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
private void AddGetByQueryOrderBy ( Sql < ISqlContext > sql ) = >
sql
. OrderBy < NodeDto > ( x = > x . Level )
. OrderBy < NodeDto > ( x = > x . SortOrder ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
protected override Sql < ISqlContext > GetBaseQuery ( QueryType queryType ) = > GetBaseQuery ( queryType , true ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// gets the COALESCE expression for variant/invariant name
private string VariantNameSqlExpression
= > SqlContext . VisitDto < ContentVersionCultureVariationDto , NodeDto > ( ( ccv , node ) = > ccv . Name ? ? node . Text , "ccv" )
. Sql ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
protected Sql < ISqlContext > GetBaseQuery ( QueryType queryType , bool current )
{
Sql < ISqlContext > sql = SqlContext . Sql ( ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
switch ( queryType )
{
case QueryType . Count :
sql = sql . SelectCount ( ) ;
break ;
case QueryType . Ids :
sql = sql . Select < DocumentDto > ( x = > x . NodeId ) ;
break ;
case QueryType . Single :
case QueryType . Many :
// R# may flag this ambiguous and red-squiggle it, but it is not
sql = sql . Select < DocumentDto > ( r = >
r . Select ( documentDto = > documentDto . ContentDto , r1 = >
r1 . Select ( contentDto = > contentDto . NodeDto ) )
. Select ( documentDto = > documentDto . DocumentVersionDto , r1 = >
r1 . Select ( documentVersionDto = > documentVersionDto . ContentVersionDto ) )
. Select ( documentDto = > documentDto . PublishedVersionDto , "pdv" , r1 = >
r1 . Select ( documentVersionDto = > documentVersionDto ! . ContentVersionDto , "pcv" ) ) )
// select the variant name, coalesce to the invariant name, as "variantName"
. AndSelect ( VariantNameSqlExpression + " AS variantName" ) ;
break ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
sql
. From < DocumentDto > ( )
. InnerJoin < ContentDto > ( ) . On < DocumentDto , ContentDto > ( left = > left . NodeId , right = > right . NodeId )
. InnerJoin < NodeDto > ( ) . On < ContentDto , NodeDto > ( left = > left . NodeId , right = > right . NodeId )
// inner join on mandatory edited version
. InnerJoin < ContentVersionDto > ( )
. On < DocumentDto , ContentVersionDto > ( ( left , right ) = > left . NodeId = = right . NodeId )
. InnerJoin < DocumentVersionDto > ( )
. On < ContentVersionDto , DocumentVersionDto > ( ( left , right ) = > left . Id = = right . Id )
// left join on optional published version
. LeftJoin < ContentVersionDto > ( nested = >
nested . InnerJoin < DocumentVersionDto > ( "pdv" )
. On < ContentVersionDto , DocumentVersionDto > ( ( left , right ) = > left . Id = = right . Id & & right . Published ,
"pcv" , "pdv" ) , "pcv" )
. On < DocumentDto , ContentVersionDto > ( ( left , right ) = > left . NodeId = = right . NodeId , aliasRight : "pcv" )
// TODO: should we be joining this when the query type is not single/many?
// left join on optional culture variation
//the magic "[[[ISOCODE]]]" parameter value will be replaced in ContentRepositoryBase.GetPage() by the actual ISO code
. LeftJoin < ContentVersionCultureVariationDto > ( nested = >
nested . InnerJoin < LanguageDto > ( "lang" ) . On < ContentVersionCultureVariationDto , LanguageDto > (
( ccv , lang ) = > ccv . LanguageId = = lang . Id & & lang . IsoCode = = "[[[ISOCODE]]]" , "ccv" , "lang" ) , "ccv" )
. On < ContentVersionDto , ContentVersionCultureVariationDto > ( ( version , ccv ) = > version . Id = = ccv . VersionId ,
aliasRight : "ccv" ) ;
sql
. Where < NodeDto > ( x = > x . NodeObjectType = = NodeObjectTypeId ) ;
// this would ensure we don't get the published version - keep for reference
//sql
// .WhereAny(
// x => x.Where<ContentVersionDto, ContentVersionDto>((x1, x2) => x1.Id != x2.Id, alias2: "pcv"),
// x => x.WhereNull<ContentVersionDto>(x1 => x1.Id, "pcv")
// );
if ( current )
{
sql . Where < ContentVersionDto > ( x = > x . Current ) ; // always get the current version
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
return sql ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
protected override Sql < ISqlContext > GetBaseQuery ( bool isCount ) = >
GetBaseQuery ( isCount ? QueryType . Count : QueryType . Single ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// ah maybe not, that what's used for eg Exists in base repo
protected override string GetBaseWhereClause ( ) = > $"{Constants.DatabaseSchema.Tables.Node}.id = @id" ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
protected override IEnumerable < string > GetDeleteClauses ( )
{
var list = new List < string >
{
"DELETE FROM " + Constants . DatabaseSchema . Tables . ContentSchedule + " WHERE nodeId = @id" ,
"DELETE FROM " + Constants . DatabaseSchema . Tables . RedirectUrl +
" WHERE contentKey IN (SELECT uniqueId FROM " + Constants . DatabaseSchema . Tables . Node +
" WHERE id = @id)" ,
"DELETE FROM " + Constants . DatabaseSchema . Tables . User2NodeNotify + " WHERE nodeId = @id" ,
"DELETE FROM " + Constants . DatabaseSchema . Tables . UserGroup2Node + " WHERE nodeId = @id" ,
"DELETE FROM " + Constants . DatabaseSchema . Tables . UserGroup2NodePermission + " WHERE nodeId = @id" ,
"DELETE FROM " + Constants . DatabaseSchema . Tables . UserStartNode + " WHERE startNode = @id" ,
"UPDATE " + Constants . DatabaseSchema . Tables . UserGroup +
" SET startContentId = NULL WHERE startContentId = @id" ,
"DELETE FROM " + Constants . DatabaseSchema . Tables . Relation + " WHERE parentId = @id" ,
"DELETE FROM " + Constants . DatabaseSchema . Tables . Relation + " WHERE childId = @id" ,
"DELETE FROM " + Constants . DatabaseSchema . Tables . TagRelationship + " WHERE nodeId = @id" ,
"DELETE FROM " + Constants . DatabaseSchema . Tables . Domain + " WHERE domainRootStructureID = @id" ,
"DELETE FROM " + Constants . DatabaseSchema . Tables . Document + " WHERE nodeId = @id" ,
"DELETE FROM " + Constants . DatabaseSchema . Tables . DocumentCultureVariation + " WHERE nodeId = @id" ,
"DELETE FROM " + Constants . DatabaseSchema . Tables . DocumentVersion + " WHERE id IN (SELECT id FROM " +
Constants . DatabaseSchema . Tables . ContentVersion + " WHERE nodeId = @id)" ,
"DELETE FROM " + Constants . DatabaseSchema . Tables . PropertyData + " WHERE versionId IN (SELECT id FROM " +
Constants . DatabaseSchema . Tables . ContentVersion + " WHERE nodeId = @id)" ,
"DELETE FROM " + Constants . DatabaseSchema . Tables . ContentVersionCultureVariation +
" WHERE versionId IN (SELECT id FROM " + Constants . DatabaseSchema . Tables . ContentVersion +
" WHERE nodeId = @id)" ,
"DELETE FROM " + Constants . DatabaseSchema . Tables . ContentVersion + " WHERE nodeId = @id" ,
"DELETE FROM " + Constants . DatabaseSchema . Tables . Content + " WHERE nodeId = @id" ,
"DELETE FROM " + Constants . DatabaseSchema . Tables . AccessRule + " WHERE accessId IN (SELECT id FROM " +
Constants . DatabaseSchema . Tables . Access +
" WHERE nodeId = @id OR loginNodeId = @id OR noAccessNodeId = @id)" ,
"DELETE FROM " + Constants . DatabaseSchema . Tables . Access + " WHERE nodeId = @id" ,
"DELETE FROM " + Constants . DatabaseSchema . Tables . Access + " WHERE loginNodeId = @id" ,
"DELETE FROM " + Constants . DatabaseSchema . Tables . Access + " WHERE noAccessNodeId = @id" ,
"DELETE FROM " + Constants . DatabaseSchema . Tables . Node + " WHERE id = @id"
} ;
return list ;
}
2021-11-16 13:04:31 +01:00
2022-06-02 08:18:31 +02:00
#endregion
2021-11-16 13:04:31 +01:00
2022-06-02 08:18:31 +02:00
#region Versions
2018-04-23 12:53:17 +02:00
2022-06-02 08:18:31 +02:00
public override IEnumerable < IContent > GetAllVersions ( int nodeId )
{
Sql < ISqlContext > sql = GetBaseQuery ( QueryType . Many , false )
. Where < NodeDto > ( x = > x . NodeId = = nodeId )
. OrderByDescending < ContentVersionDto > ( x = > x . Current )
. AndByDescending < ContentVersionDto > ( x = > x . VersionDate ) ;
2018-10-17 17:27:39 +02:00
2022-06-02 08:18:31 +02:00
return MapDtosToContent ( Database . Fetch < DocumentDto > ( sql ) , true ) ;
}
2018-04-12 22:53:04 +02:00
2022-06-02 08:18:31 +02:00
// TODO: This method needs to return a readonly version of IContent! The content returned
// from this method does not contain all of the data required to re-persist it and if that
// is attempted some odd things will occur.
// Either we create an IContentReadOnly (which ultimately we should for vNext so we can
// differentiate between methods that return entities that can be re-persisted or not), or
// in the meantime to not break API compatibility, we can add a property to IContentBase
// (or go further and have it on IUmbracoEntity): "IsReadOnly" and if that is true we throw
// an exception if that entity is passed to a Save method.
// Ideally we return "Slim" versions of content for all sorts of methods here and in ContentService.
// Perhaps another non-breaking alternative is to have new services like IContentServiceReadOnly
// which can return IContentReadOnly.
// We have the ability with `MapDtosToContent` to reduce the amount of data looked up for a
// content item. Ideally for paged data that populates list views, these would be ultra slim
// content items, there's no reason to populate those with really anything apart from property data,
// but until we do something like the above, we can't do that since it would be breaking and unclear.
public override IEnumerable < IContent > GetAllVersionsSlim ( int nodeId , int skip , int take )
{
Sql < ISqlContext > sql = GetBaseQuery ( QueryType . Many , false )
. Where < NodeDto > ( x = > x . NodeId = = nodeId )
. OrderByDescending < ContentVersionDto > ( x = > x . Current )
. AndByDescending < ContentVersionDto > ( x = > x . VersionDate ) ;
2018-09-25 18:05:14 +02:00
2022-06-02 08:18:31 +02:00
var pageIndex = skip / take ;
2021-07-12 16:26:19 -06:00
2022-06-02 08:18:31 +02:00
return MapDtosToContent ( Database . Page < DocumentDto > ( pageIndex + 1 , take , sql ) . Items , true ,
// load bare minimum, need variants though since this is used to rollback with variants
false , false ) ;
}
2021-07-12 16:26:19 -06:00
2022-06-02 08:18:31 +02:00
public override IContent ? GetVersion ( int versionId )
{
Sql < ISqlContext > sql = GetBaseQuery ( QueryType . Single , false )
. Where < ContentVersionDto > ( x = > x . Id = = versionId ) ;
2020-04-07 15:02:08 +10:00
2022-06-02 08:18:31 +02:00
DocumentDto ? dto = Database . Fetch < DocumentDto > ( sql ) . FirstOrDefault ( ) ;
return dto = = null ? null : MapDtoToContent ( dto ) ;
}
2021-10-12 09:18:42 +01:00
2022-06-02 08:18:31 +02:00
// deletes a specific version
public override void DeleteVersion ( int versionId )
{
// TODO: test object node type?
2020-04-07 15:02:08 +10:00
2022-06-02 08:18:31 +02:00
// get the version we want to delete
SqlTemplate template = SqlContext . Templates . Get ( "Umbraco.Core.DocumentRepository.GetVersion" , tsql = >
tsql . Select < ContentVersionDto > ( )
. AndSelect < DocumentVersionDto > ( )
. From < ContentVersionDto > ( )
. InnerJoin < DocumentVersionDto > ( )
. On < ContentVersionDto , DocumentVersionDto > ( ( c , d ) = > c . Id = = d . Id )
. Where < ContentVersionDto > ( x = > x . Id = = SqlTemplate . Arg < int > ( "versionId" ) )
) ;
DocumentVersionDto ? versionDto =
Database . Fetch < DocumentVersionDto > ( template . Sql ( new { versionId } ) ) . FirstOrDefault ( ) ;
// nothing to delete
if ( versionDto = = null )
{
return ;
}
2021-07-13 09:52:31 -06:00
2022-06-02 08:18:31 +02:00
// don't delete the current or published version
if ( versionDto . ContentVersionDto . Current )
{
throw new InvalidOperationException ( "Cannot delete the current version." ) ;
}
2021-07-13 09:52:31 -06:00
2022-06-02 08:18:31 +02:00
if ( versionDto . Published )
{
throw new InvalidOperationException ( "Cannot delete the published version." ) ;
}
2020-04-07 15:02:08 +10:00
2022-06-02 08:18:31 +02:00
PerformDeleteVersion ( versionDto . ContentVersionDto . NodeId , versionId ) ;
}
2020-04-07 15:02:08 +10:00
2022-06-02 08:18:31 +02:00
// deletes all versions of an entity, older than a date.
public override void DeleteVersions ( int nodeId , DateTime versionDate )
{
// TODO: test object node type?
2020-04-07 15:02:08 +10:00
2022-06-02 08:18:31 +02:00
// get the versions we want to delete, excluding the current one
SqlTemplate template = SqlContext . Templates . Get ( "Umbraco.Core.DocumentRepository.GetVersions" , tsql = >
tsql . Select < ContentVersionDto > ( )
. From < ContentVersionDto > ( )
. InnerJoin < DocumentVersionDto > ( )
. On < ContentVersionDto , DocumentVersionDto > ( ( c , d ) = > c . Id = = d . Id )
. Where < ContentVersionDto > ( x = >
x . NodeId = = SqlTemplate . Arg < int > ( "nodeId" ) & & ! x . Current & &
x . VersionDate < SqlTemplate . Arg < DateTime > ( "versionDate" ) )
. Where < DocumentVersionDto > ( x = > ! x . Published )
) ;
List < ContentVersionDto > ? versionDtos =
Database . Fetch < ContentVersionDto > ( template . Sql ( new { nodeId , versionDate } ) ) ;
foreach ( ContentVersionDto ? versionDto in versionDtos )
{
PerformDeleteVersion ( versionDto . NodeId , versionDto . Id ) ;
}
}
2020-04-07 15:02:08 +10:00
2022-06-02 08:18:31 +02:00
protected override void PerformDeleteVersion ( int id , int versionId )
{
Database . Delete < PropertyDataDto > ( "WHERE versionId = @versionId" , new { versionId } ) ;
Database . Delete < ContentVersionCultureVariationDto > ( "WHERE versionId = @versionId" , new { versionId } ) ;
Database . Delete < DocumentVersionDto > ( "WHERE id = @versionId" , new { versionId } ) ;
Database . Delete < ContentVersionDto > ( "WHERE id = @versionId" , new { versionId } ) ;
}
2018-04-23 12:53:17 +02:00
2022-06-02 08:18:31 +02:00
#endregion
2021-07-12 16:26:19 -06:00
2022-06-02 08:18:31 +02:00
#region Persist
2020-04-07 15:02:08 +10:00
2022-06-02 08:18:31 +02:00
protected override void PersistNewItem ( IContent entity )
{
entity . AddingEntity ( ) ;
2018-04-12 22:53:04 +02:00
2022-06-02 08:18:31 +02:00
var publishing = entity . PublishedState = = PublishedState . Publishing ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// ensure that the default template is assigned
if ( entity . TemplateId . HasValue = = false )
{
entity . TemplateId = entity . ContentType . DefaultTemplate ? . Id ;
}
2018-02-01 14:14:45 +01:00
2022-06-02 08:18:31 +02:00
// sanitize names
SanitizeNames ( entity , publishing ) ;
2018-02-01 14:14:45 +01:00
2022-06-02 08:18:31 +02:00
// ensure that strings don't contain characters that are invalid in xml
// TODO: do we really want to keep doing this here?
entity . SanitizeEntityPropertiesForXmlStorage ( ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// create the dto
DocumentDto dto = ContentBaseFactory . BuildDto ( entity , NodeObjectTypeId ) ;
2019-10-25 14:17:18 +11:00
2022-06-02 08:18:31 +02:00
// derive path and level from parent
NodeDto parent = GetParentNodeDto ( entity . ParentId ) ;
var level = parent . Level + 1 ;
2024-11-21 09:07:22 +01:00
var calculateSortOrder = ( entity is { HasIdentity : false , SortOrder : 0 } & & entity . IsPropertyDirty ( nameof ( entity . SortOrder ) ) is false ) // SortOrder was not updated from it's default value
| | SortorderExists ( entity . ParentId , entity . SortOrder ) ;
2023-01-08 16:10:56 +01:00
// if the sortorder of the entity already exists get a new one, else use the sortOrder of the entity
2024-11-21 09:07:22 +01:00
var sortOrder = calculateSortOrder ? GetNewChildSortOrder ( entity . ParentId , 0 ) : entity . SortOrder ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// persist the node dto
NodeDto nodeDto = dto . ContentDto . NodeDto ;
nodeDto . Path = parent . Path ;
nodeDto . Level = Convert . ToInt16 ( level ) ;
nodeDto . SortOrder = sortOrder ;
// see if there's a reserved identifier for this unique id
// and then either update or insert the node dto
var id = GetReservedId ( nodeDto . UniqueId ) ;
if ( id > 0 )
{
nodeDto . NodeId = id ;
}
else
{
Database . Insert ( nodeDto ) ;
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
nodeDto . Path = string . Concat ( parent . Path , "," , nodeDto . NodeId ) ;
nodeDto . ValidatePathWithException ( ) ;
Database . Update ( nodeDto ) ;
// update entity
entity . Id = nodeDto . NodeId ;
entity . Path = nodeDto . Path ;
entity . SortOrder = sortOrder ;
entity . Level = level ;
// persist the content dto
ContentDto contentDto = dto . ContentDto ;
contentDto . NodeId = nodeDto . NodeId ;
Database . Insert ( contentDto ) ;
// persist the content version dto
ContentVersionDto contentVersionDto = dto . DocumentVersionDto . ContentVersionDto ;
contentVersionDto . NodeId = nodeDto . NodeId ;
contentVersionDto . Current = ! publishing ;
Database . Insert ( contentVersionDto ) ;
entity . VersionId = contentVersionDto . Id ;
// persist the document version dto
DocumentVersionDto documentVersionDto = dto . DocumentVersionDto ;
documentVersionDto . Id = entity . VersionId ;
if ( publishing )
2018-11-14 13:59:53 +01:00
{
2022-06-02 08:18:31 +02:00
documentVersionDto . Published = true ;
}
2018-11-14 13:59:53 +01:00
2022-06-02 08:18:31 +02:00
Database . Insert ( documentVersionDto ) ;
2018-11-14 13:59:53 +01:00
2022-06-02 08:18:31 +02:00
// and again in case we're publishing immediately
if ( publishing )
{
entity . PublishedVersionId = entity . VersionId ;
contentVersionDto . Id = 0 ;
contentVersionDto . Current = true ;
contentVersionDto . Text = entity . Name ;
Database . Insert ( contentVersionDto ) ;
entity . VersionId = contentVersionDto . Id ;
2021-10-19 11:29:25 +01:00
2022-06-02 08:18:31 +02:00
documentVersionDto . Id = entity . VersionId ;
documentVersionDto . Published = false ;
Database . Insert ( documentVersionDto ) ;
}
2021-10-19 11:29:25 +01:00
2022-06-02 08:18:31 +02:00
// persist the property data
IEnumerable < PropertyDataDto > propertyDataDtos = PropertyFactory . BuildDtos ( entity . ContentType . Variations ,
entity . VersionId , entity . PublishedVersionId , entity . Properties , LanguageRepository , out var edited ,
out HashSet < string > ? editedCultures ) ;
foreach ( PropertyDataDto propertyDataDto in propertyDataDtos )
{
Database . Insert ( propertyDataDto ) ;
2018-11-14 13:59:53 +01:00
}
2022-06-02 08:18:31 +02:00
// if !publishing, we may have a new name != current publish name,
// also impacts 'edited'
if ( ! publishing & & entity . PublishName ! = entity . Name )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
edited = true ;
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
// persist the document dto
// at that point, when publishing, the entity still has its old Published value
// so we need to explicitly update the dto to persist the correct value
if ( entity . PublishedState = = PublishedState . Publishing )
{
dto . Published = true ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
dto . NodeId = nodeDto . NodeId ;
entity . Edited = dto . Edited = ! dto . Published | | edited ; // if not published, always edited
Database . Insert ( dto ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// persist the variations
if ( entity . ContentType . VariesByCulture ( ) )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
// names also impact 'edited'
// ReSharper disable once UseDeconstruction
foreach ( ContentCultureInfos cultureInfo in entity . CultureInfos ! )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
if ( cultureInfo . Name ! = entity . GetPublishName ( cultureInfo . Culture ) )
{
( editedCultures ? ? = new HashSet < string > ( StringComparer . OrdinalIgnoreCase ) ) . Add ( cultureInfo . Culture ) ;
}
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
// refresh content
entity . SetCultureEdited ( editedCultures ! ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// bump dates to align cultures to version
entity . AdjustDates ( contentVersionDto . VersionDate , publishing ) ;
// insert content variations
Database . BulkInsertRecords ( GetContentVariationDtos ( entity , publishing ) ) ;
// insert document variations
Database . BulkInsertRecords ( GetDocumentVariationDtos ( entity , editedCultures ! ) ) ;
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
// trigger here, before we reset Published etc
OnUowRefreshedEntity ( new ContentRefreshNotification ( entity , new EventMessages ( ) ) ) ;
// flip the entity's published property
// this also flips its published state
// note: what depends on variations (eg PublishNames) is managed directly by the content
if ( entity . PublishedState = = PublishedState . Publishing )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
entity . Published = true ;
entity . PublishTemplateId = entity . TemplateId ;
entity . PublisherId = entity . WriterId ;
entity . PublishName = entity . Name ;
entity . PublishDate = entity . UpdateDate ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
SetEntityTags ( entity , _tagRepository , _serializer ) ;
}
else if ( entity . PublishedState = = PublishedState . Unpublishing )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
entity . Published = false ;
entity . PublishTemplateId = null ;
entity . PublisherId = null ;
entity . PublishName = null ;
entity . PublishDate = null ;
ClearEntityTags ( entity , _tagRepository ) ;
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
PersistRelations ( entity ) ;
entity . ResetDirtyProperties ( ) ;
// troubleshooting
//if (Database.ExecuteScalar<int>($"SELECT COUNT(*) FROM {Constants.DatabaseSchema.Tables.DocumentVersion} JOIN {Constants.DatabaseSchema.Tables.ContentVersion} ON {Constants.DatabaseSchema.Tables.DocumentVersion}.id={Constants.DatabaseSchema.Tables.ContentVersion}.id WHERE published=1 AND nodeId=" + content.Id) > 1)
//{
// Debugger.Break();
// throw new Exception("oops");
//}
//if (Database.ExecuteScalar<int>($"SELECT COUNT(*) FROM {Constants.DatabaseSchema.Tables.DocumentVersion} JOIN {Constants.DatabaseSchema.Tables.ContentVersion} ON {Constants.DatabaseSchema.Tables.DocumentVersion}.id={Constants.DatabaseSchema.Tables.ContentVersion}.id WHERE [current]=1 AND nodeId=" + content.Id) > 1)
//{
// Debugger.Break();
// throw new Exception("oops");
//}
}
protected override void PersistUpdatedItem ( IContent entity )
{
var isEntityDirty = entity . IsDirty ( ) ;
var editedSnapshot = entity . Edited ;
// check if we need to make any database changes at all
if ( ( entity . PublishedState = = PublishedState . Published | | entity . PublishedState = = PublishedState . Unpublished )
& & ! isEntityDirty & & ! entity . IsAnyUserPropertyDirty ( ) )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
return ; // no change to save, do nothing, don't even update dates
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
// whatever we do, we must check that we are saving the current version
ContentVersionDto ? version = Database . Fetch < ContentVersionDto > ( SqlContext . Sql ( ) . Select < ContentVersionDto > ( )
. From < ContentVersionDto > ( ) . Where < ContentVersionDto > ( x = > x . Id = = entity . VersionId ) ) . FirstOrDefault ( ) ;
if ( version = = null | | ! version . Current )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
throw new InvalidOperationException ( "Cannot save a non-current version." ) ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// update
entity . UpdatingEntity ( ) ;
2018-10-18 14:16:54 +02:00
2022-06-02 08:18:31 +02:00
// Check if this entity is being moved as a descendant as part of a bulk moving operations.
// In this case we can bypass a lot of the below operations which will make this whole operation go much faster.
// When moving we don't need to create new versions, etc... because we cannot roll this operation back anyways.
var isMoving = entity . IsMoving ( ) ;
// TODO: I'm sure we can also detect a "Copy" (of a descendant) operation and probably perform similar checks below.
// There is probably more stuff that would be required for copying but I'm sure not all of this logic would be, we could more than likely boost
// copy performance by 95% just like we did for Move
2018-10-18 14:16:54 +02:00
2022-06-02 08:18:31 +02:00
var publishing = entity . PublishedState = = PublishedState . Publishing ;
2018-10-18 14:16:54 +02:00
2022-06-02 08:18:31 +02:00
if ( ! isMoving )
{
// check if we need to create a new version
if ( publishing & & entity . PublishedVersionId > 0 )
{
// published version is not published anymore
Database . Execute ( Sql ( ) . Update < DocumentVersionDto > ( u = > u . Set ( x = > x . Published , false ) )
. Where < DocumentVersionDto > ( x = > x . Id = = entity . PublishedVersionId ) ) ;
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
// sanitize names
SanitizeNames ( entity , publishing ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// ensure that strings don't contain characters that are invalid in xml
// TODO: do we really want to keep doing this here?
entity . SanitizeEntityPropertiesForXmlStorage ( ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// if parent has changed, get path, level and sort order
if ( entity . IsPropertyDirty ( "ParentId" ) )
{
NodeDto parent = GetParentNodeDto ( entity . ParentId ) ;
entity . Path = string . Concat ( parent . Path , "," , entity . Id ) ;
entity . Level = parent . Level + 1 ;
entity . SortOrder = GetNewChildSortOrder ( entity . ParentId , 0 ) ;
}
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// create the dto
DocumentDto dto = ContentBaseFactory . BuildDto ( entity , NodeObjectTypeId ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// update the node dto
NodeDto nodeDto = dto . ContentDto . NodeDto ;
nodeDto . ValidatePathWithException ( ) ;
Database . Update ( nodeDto ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
if ( ! isMoving )
{
// update the content dto
Database . Update ( dto . ContentDto ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// update the content & document version dtos
ContentVersionDto contentVersionDto = dto . DocumentVersionDto . ContentVersionDto ;
DocumentVersionDto documentVersionDto = dto . DocumentVersionDto ;
if ( publishing )
{
documentVersionDto . Published = true ; // now published
contentVersionDto . Current = false ; // no more current
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// Ensure existing version retains current preventCleanup flag (both saving and publishing).
contentVersionDto . PreventCleanup = version . PreventCleanup ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
Database . Update ( contentVersionDto ) ;
Database . Update ( documentVersionDto ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// and, if publishing, insert new content & document version dtos
if ( publishing )
{
entity . PublishedVersionId = entity . VersionId ;
2021-03-25 12:54:46 +01:00
2022-06-02 08:18:31 +02:00
contentVersionDto . Id = 0 ; // want a new id
contentVersionDto . Current = true ; // current version
contentVersionDto . Text = entity . Name ;
contentVersionDto . PreventCleanup = false ; // new draft version disregards prevent cleanup flag
Database . Insert ( contentVersionDto ) ;
entity . VersionId = documentVersionDto . Id = contentVersionDto . Id ; // get the new id
2021-03-25 12:54:46 +01:00
2022-06-02 08:18:31 +02:00
documentVersionDto . Published = false ; // non-published version
Database . Insert ( documentVersionDto ) ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// replace the property data (rather than updating)
// only need to delete for the version that existed, the new version (if any) has no property data yet
var versionToDelete = publishing ? entity . PublishedVersionId : entity . VersionId ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// insert property data
ReplacePropertyValues ( entity , versionToDelete , publishing ? entity . PublishedVersionId : 0 , out var edited ,
out HashSet < string > ? editedCultures ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// if !publishing, we may have a new name != current publish name,
// also impacts 'edited'
if ( ! publishing & & entity . PublishName ! = entity . Name )
{
edited = true ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// To establish the new value of "edited" we compare all properties publishedValue to editedValue and look
// for differences.
//
// If we SaveAndPublish but the publish fails (e.g. already scheduled for release)
// we have lost the publishedValue on IContent (in memory vs database) so we cannot correctly make that comparison.
//
// This is a slight change to behaviour, historically a publish, followed by change & save, followed by undo change & save
// would change edited back to false.
if ( ! publishing & & editedSnapshot )
{
edited = true ;
}
if ( entity . ContentType . VariesByCulture ( ) )
{
// names also impact 'edited'
// ReSharper disable once UseDeconstruction
foreach ( ContentCultureInfos cultureInfo in entity . CultureInfos ! )
{
if ( cultureInfo . Name ! = entity . GetPublishName ( cultureInfo . Culture ) )
{
edited = true ;
( editedCultures ? ? = new HashSet < string > ( StringComparer . OrdinalIgnoreCase ) ) . Add ( cultureInfo
. Culture ) ;
// TODO: change tracking
// at the moment, we don't do any dirty tracking on property values, so we don't know whether the
// culture has just been edited or not, so we don't update its update date - that date only changes
// when the name is set, and it all works because the controller does it - but, if someone uses a
// service to change a property value and save (without setting name), the update date does not change.
}
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// refresh content
entity . SetCultureEdited ( editedCultures ! ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// bump dates to align cultures to version
entity . AdjustDates ( contentVersionDto . VersionDate , publishing ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// replace the content version variations (rather than updating)
// only need to delete for the version that existed, the new version (if any) has no property data yet
Sql < ISqlContext > deleteContentVariations = Sql ( ) . Delete < ContentVersionCultureVariationDto > ( )
. Where < ContentVersionCultureVariationDto > ( x = > x . VersionId = = versionToDelete ) ;
Database . Execute ( deleteContentVariations ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// replace the document version variations (rather than updating)
Sql < ISqlContext > deleteDocumentVariations = Sql ( ) . Delete < DocumentCultureVariationDto > ( )
. Where < DocumentCultureVariationDto > ( x = > x . NodeId = = entity . Id ) ;
Database . Execute ( deleteDocumentVariations ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// TODO: NPoco InsertBulk issue?
// we should use the native NPoco InsertBulk here but it causes problems (not sure exactly all scenarios)
// but by using SQL Server and updating a variants name will cause: Unable to cast object of type
// 'Umbraco.Core.Persistence.FaultHandling.RetryDbConnection' to type 'System.Data.SqlClient.SqlConnection'.
// (same in PersistNewItem above)
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// insert content variations
Database . BulkInsertRecords ( GetContentVariationDtos ( entity , publishing ) ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// insert document variations
Database . BulkInsertRecords ( GetDocumentVariationDtos ( entity , editedCultures ! ) ) ;
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
// update the document dto
// at that point, when un/publishing, the entity still has its old Published value
// so we need to explicitly update the dto to persist the correct value
if ( entity . PublishedState = = PublishedState . Publishing )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
dto . Published = true ;
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
else if ( entity . PublishedState = = PublishedState . Unpublishing )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
dto . Published = false ;
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
entity . Edited = dto . Edited = ! dto . Published | | edited ; // if not published, always edited
Database . Update ( dto ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// if entity is publishing, update tags, else leave tags there
// means that implicitly unpublished, or trashed, entities *still* have tags in db
if ( entity . PublishedState = = PublishedState . Publishing )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
SetEntityTags ( entity , _tagRepository , _serializer ) ;
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// trigger here, before we reset Published etc
OnUowRefreshedEntity ( new ContentRefreshNotification ( entity , new EventMessages ( ) ) ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
if ( ! isMoving )
{
// flip the entity's published property
// this also flips its published state
if ( entity . PublishedState = = PublishedState . Publishing )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
entity . Published = true ;
entity . PublishTemplateId = entity . TemplateId ;
entity . PublisherId = entity . WriterId ;
entity . PublishName = entity . Name ;
entity . PublishDate = entity . UpdateDate ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
SetEntityTags ( entity , _tagRepository , _serializer ) ;
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
else if ( entity . PublishedState = = PublishedState . Unpublishing )
{
entity . Published = false ;
entity . PublishTemplateId = null ;
entity . PublisherId = null ;
entity . PublishName = null ;
entity . PublishDate = null ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
ClearEntityTags ( entity , _tagRepository ) ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
PersistRelations ( entity ) ;
2018-11-05 13:59:55 +11:00
2022-06-02 08:18:31 +02:00
// TODO: note re. tags: explicitly unpublished entities have cleared tags, but masked or trashed entities *still* have tags in the db - so what?
2018-11-13 17:51:59 +11:00
}
2022-06-02 08:18:31 +02:00
entity . ResetDirtyProperties ( ) ;
// troubleshooting
//if (Database.ExecuteScalar<int>($"SELECT COUNT(*) FROM {Constants.DatabaseSchema.Tables.DocumentVersion} JOIN {Constants.DatabaseSchema.Tables.ContentVersion} ON {Constants.DatabaseSchema.Tables.DocumentVersion}.id={Constants.DatabaseSchema.Tables.ContentVersion}.id WHERE published=1 AND nodeId=" + content.Id) > 1)
//{
// Debugger.Break();
// throw new Exception("oops");
//}
//if (Database.ExecuteScalar<int>($"SELECT COUNT(*) FROM {Constants.DatabaseSchema.Tables.DocumentVersion} JOIN {Constants.DatabaseSchema.Tables.ContentVersion} ON {Constants.DatabaseSchema.Tables.DocumentVersion}.id={Constants.DatabaseSchema.Tables.ContentVersion}.id WHERE [current]=1 AND nodeId=" + content.Id) > 1)
//{
// Debugger.Break();
// throw new Exception("oops");
//}
}
2020-07-08 18:16:12 +10:00
2022-06-02 08:18:31 +02:00
/// <inheritdoc />
public void PersistContentSchedule ( IContent content , ContentScheduleCollection contentSchedule )
{
if ( content = = null )
2020-07-08 18:16:12 +10:00
{
2022-06-02 08:18:31 +02:00
throw new ArgumentNullException ( nameof ( content ) ) ;
2020-07-08 18:16:12 +10:00
}
2022-06-02 08:18:31 +02:00
if ( contentSchedule = = null )
2020-07-08 18:16:12 +10:00
{
2022-06-02 08:18:31 +02:00
throw new ArgumentNullException ( nameof ( contentSchedule ) ) ;
2020-07-08 18:16:12 +10:00
}
2022-06-02 08:18:31 +02:00
var schedules = ContentBaseFactory . BuildScheduleDto ( content , contentSchedule , LanguageRepository ) . ToList ( ) ;
//remove any that no longer exist
IEnumerable < Guid > ids = schedules . Where ( x = > x . Model . Id ! = Guid . Empty ) . Select ( x = > x . Model . Id ) . Distinct ( ) ;
Database . Execute ( Sql ( )
. Delete < ContentScheduleDto > ( )
. Where < ContentScheduleDto > ( x = > x . NodeId = = content . Id )
. WhereNotIn < ContentScheduleDto > ( x = > x . Id , ids ) ) ;
//add/update the rest
foreach ( ( ContentSchedule Model , ContentScheduleDto Dto ) schedule in schedules )
2020-07-08 18:16:12 +10:00
{
2022-06-02 08:18:31 +02:00
if ( schedule . Model . Id = = Guid . Empty )
{
schedule . Model . Id = schedule . Dto . Id = Guid . NewGuid ( ) ;
Database . Insert ( schedule . Dto ) ;
}
else
{
Database . Update ( schedule . Dto ) ;
}
2020-07-08 18:16:12 +10:00
}
2022-06-02 08:18:31 +02:00
}
2020-07-08 18:16:12 +10:00
2022-06-02 08:18:31 +02:00
protected override void PersistDeletedItem ( IContent entity )
{
// Raise event first else potential FK issues
OnUowRemovingEntity ( entity ) ;
//We need to clear out all access rules but we need to do this in a manual way since
// nothing in that table is joined to a content id
Sql < ISqlContext > subQuery = SqlContext . Sql ( )
. Select < AccessRuleDto > ( x = > x . AccessId )
. From < AccessRuleDto > ( )
. InnerJoin < AccessDto > ( )
. On < AccessRuleDto , AccessDto > ( left = > left . AccessId , right = > right . Id )
. Where < AccessDto > ( dto = > dto . NodeId = = entity . Id ) ;
Database . Execute ( SqlContext . SqlSyntax . GetDeleteSubquery ( "umbracoAccessRule" , "accessId" , subQuery ) ) ;
//now let the normal delete clauses take care of everything else
base . PersistDeletedItem ( entity ) ;
}
2018-11-08 16:33:19 +01:00
2022-06-02 08:18:31 +02:00
#endregion
2018-11-14 11:59:16 +01:00
2022-06-02 08:18:31 +02:00
#region Content Repository
2018-11-08 16:33:19 +01:00
2022-06-02 08:18:31 +02:00
public int CountPublished ( string? contentTypeAlias = null )
{
Sql < ISqlContext > sql = SqlContext . Sql ( ) ;
if ( contentTypeAlias . IsNullOrWhiteSpace ( ) )
{
sql . SelectCount ( )
. From < NodeDto > ( )
. InnerJoin < DocumentDto > ( )
. On < NodeDto , DocumentDto > ( left = > left . NodeId , right = > right . NodeId )
. Where < NodeDto > ( x = > x . NodeObjectType = = NodeObjectTypeId & & x . Trashed = = false )
. Where < DocumentDto > ( x = > x . Published ) ;
2018-11-05 13:59:55 +11:00
}
2022-06-02 08:18:31 +02:00
else
2018-11-05 13:59:55 +11:00
{
2022-06-02 08:18:31 +02:00
sql . SelectCount ( )
. From < NodeDto > ( )
. InnerJoin < ContentDto > ( )
. On < NodeDto , ContentDto > ( left = > left . NodeId , right = > right . NodeId )
. InnerJoin < DocumentDto > ( )
. On < NodeDto , DocumentDto > ( left = > left . NodeId , right = > right . NodeId )
. InnerJoin < ContentTypeDto > ( )
. On < ContentTypeDto , ContentDto > ( left = > left . NodeId , right = > right . ContentTypeId )
. Where < NodeDto > ( x = > x . NodeObjectType = = NodeObjectTypeId & & x . Trashed = = false )
. Where < ContentTypeDto > ( x = > x . Alias = = contentTypeAlias )
. Where < DocumentDto > ( x = > x . Published ) ;
}
2018-11-08 16:33:19 +01:00
2022-06-02 08:18:31 +02:00
return Database . ExecuteScalar < int > ( sql ) ;
}
2018-11-08 16:33:19 +01:00
2022-06-02 08:18:31 +02:00
public void ReplaceContentPermissions ( EntityPermissionSet permissionSet ) = >
PermissionRepository . ReplaceEntityPermissions ( permissionSet ) ;
2018-11-08 16:33:19 +01:00
2022-06-02 08:18:31 +02:00
/// <summary>
/// Assigns a single permission to the current content item for the specified group ids
/// </summary>
/// <param name="entity"></param>
/// <param name="permission"></param>
/// <param name="groupIds"></param>
public void AssignEntityPermission ( IContent entity , char permission , IEnumerable < int > groupIds ) = >
PermissionRepository . AssignEntityPermission ( entity , permission , groupIds ) ;
2018-11-05 13:59:55 +11:00
2022-06-02 08:18:31 +02:00
public EntityPermissionCollection GetPermissionsForEntity ( int entityId ) = >
PermissionRepository . GetPermissionsForEntity ( entityId ) ;
2018-11-05 13:59:55 +11:00
2022-06-02 08:18:31 +02:00
/// <summary>
/// Used to add/update a permission for a content item
/// </summary>
/// <param name="permission"></param>
public void AddOrUpdatePermissions ( ContentPermissionSet permission ) = > PermissionRepository . Save ( permission ) ;
2018-09-18 11:53:33 +02:00
2022-06-02 08:18:31 +02:00
/// <inheritdoc />
public override IEnumerable < IContent > GetPage ( IQuery < IContent > ? query ,
long pageIndex , int pageSize , out long totalRecords ,
IQuery < IContent > ? filter , Ordering ? ordering )
{
Sql < ISqlContext > ? filterSql = null ;
2018-09-19 17:50:43 +02:00
2022-06-02 08:18:31 +02:00
// if we have a filter, map its clauses to an Sql statement
if ( filter ! = null )
{
// if the clause works on "name", we need to swap the field and use the variantName instead,
// so that querying also works on variant content (for instance when searching a listview).
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// figure out how the "name" field is going to look like - so we can look for it
var nameField = SqlContext . VisitModelField < IContent > ( x = > x . Name ) ;
2018-09-18 11:53:33 +02:00
2022-06-02 08:18:31 +02:00
filterSql = Sql ( ) ;
foreach ( Tuple < string , object [ ] > filterClause in filter . GetWhereClauses ( ) )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
var clauseSql = filterClause . Item1 ;
var clauseArgs = filterClause . Item2 ;
2018-09-18 11:53:33 +02:00
2022-06-02 08:18:31 +02:00
// replace the name field
// we cannot reference an aliased field in a WHERE clause, so have to repeat the expression here
clauseSql = clauseSql . Replace ( nameField , VariantNameSqlExpression ) ;
2019-03-18 14:52:50 +01:00
2022-06-02 08:18:31 +02:00
// append the clause
filterSql . Append ( $"AND ({clauseSql})" , clauseArgs ) ;
}
}
2018-09-18 11:53:33 +02:00
2022-06-02 08:18:31 +02:00
return GetPage < DocumentDto > ( query , pageIndex , pageSize , out totalRecords ,
x = > MapDtosToContent ( x ) ,
filterSql ,
ordering ) ;
}
2018-09-18 11:53:33 +02:00
2022-06-02 08:18:31 +02:00
public bool IsPathPublished ( IContent ? content )
{
// fail fast
if ( content ? . Path . StartsWith ( "-1,-20," ) ? ? false )
{
return false ;
}
2018-09-18 11:53:33 +02:00
2022-06-02 08:18:31 +02:00
// succeed fast
if ( content ? . ParentId = = - 1 )
{
return content . Published ;
}
2018-09-18 11:53:33 +02:00
2022-06-02 08:18:31 +02:00
IEnumerable < int > ? ids = content ? . Path . Split ( Constants . CharArrays . Comma ) . Skip ( 1 )
. Select ( s = > int . Parse ( s , CultureInfo . InvariantCulture ) ) ;
2018-09-18 11:53:33 +02:00
2022-06-02 08:18:31 +02:00
Sql < ISqlContext > sql = SqlContext . Sql ( )
. SelectCount < NodeDto > ( x = > x . NodeId )
. From < NodeDto > ( )
. InnerJoin < DocumentDto > ( ) . On < NodeDto , DocumentDto > ( ( n , d ) = > n . NodeId = = d . NodeId & & d . Published )
. WhereIn < NodeDto > ( x = > x . NodeId , ids ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
var count = Database . ExecuteScalar < int > ( sql ) ;
return count = = content ? . Level ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
#endregion
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
#region Recycle Bin
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
public override int RecycleBinId = > Constants . System . RecycleBinContent ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
public bool RecycleBinSmells ( )
{
IAppPolicyCache cache = _appCaches . RuntimeCache ;
var cacheKey = CacheKeys . ContentRecycleBinCacheKey ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// always cache either true or false
return cache . GetCacheItem ( cacheKey , ( ) = > CountChildren ( RecycleBinId ) > 0 ) ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
#endregion
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
#region Read Repository implementation for Guid keys
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
public IContent ? Get ( Guid id ) = > _contentByGuidReadRepository . Get ( id ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
IEnumerable < IContent > IReadRepository < Guid , IContent > . GetMany ( params Guid [ ] ? ids ) = >
_contentByGuidReadRepository . GetMany ( ids ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
public bool Exists ( Guid id ) = > _contentByGuidReadRepository . Exists ( id ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// reading repository purely for looking up by GUID
// TODO: ugly and to fix we need to decouple the IRepositoryQueryable -> IRepository -> IReadRepository which should all be separate things!
// This sub-repository pattern is super old and totally unecessary anymore, caching can be handled in much nicer ways without this
private class ContentByGuidReadRepository : EntityRepositoryBase < Guid , IContent >
{
private readonly DocumentRepository _outerRepo ;
2019-12-11 16:31:03 +01:00
2022-06-02 08:18:31 +02:00
public ContentByGuidReadRepository ( DocumentRepository outerRepo , IScopeAccessor scopeAccessor , AppCaches cache ,
ILogger < ContentByGuidReadRepository > logger )
: base ( scopeAccessor , cache , logger ) = >
_outerRepo = outerRepo ;
2018-10-22 17:04:58 +11:00
2022-06-02 08:18:31 +02:00
protected override IContent ? PerformGet ( Guid id )
{
Sql < ISqlContext > sql = _outerRepo . GetBaseQuery ( QueryType . Single )
. Where < NodeDto > ( x = > x . UniqueId = = id ) ;
2018-10-22 17:04:58 +11:00
2022-06-02 08:18:31 +02:00
DocumentDto ? dto = Database . Fetch < DocumentDto > ( sql . SelectTop ( 1 ) ) . FirstOrDefault ( ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
if ( dto = = null )
2018-04-12 22:53:04 +02:00
{
2022-06-02 08:18:31 +02:00
return null ;
2018-04-12 22:53:04 +02:00
}
2022-06-02 08:18:31 +02:00
IContent content = _outerRepo . MapDtoToContent ( dto ) ;
2018-11-07 21:32:12 +11:00
2017-12-07 16:45:25 +01:00
return content ;
}
2022-06-02 08:18:31 +02:00
protected override IEnumerable < IContent > PerformGetAll ( params Guid [ ] ? ids )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
Sql < ISqlContext > sql = _outerRepo . GetBaseQuery ( QueryType . Many ) ;
if ( ids ? . Length > 0 )
2018-11-05 17:20:26 +11:00
{
2022-06-02 08:18:31 +02:00
sql . WhereIn < NodeDto > ( x = > x . UniqueId , ids ) ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
return _outerRepo . MapDtosToContent ( Database . Fetch < DocumentDto > ( sql ) ) ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
protected override IEnumerable < IContent > PerformGetByQuery ( IQuery < IContent > query ) = >
throw new InvalidOperationException ( "This method won't be implemented." ) ;
2018-11-05 17:20:26 +11:00
2022-06-02 08:18:31 +02:00
protected override IEnumerable < string > GetDeleteClauses ( ) = >
throw new InvalidOperationException ( "This method won't be implemented." ) ;
2018-11-05 17:20:26 +11:00
2022-06-02 08:18:31 +02:00
protected override void PersistNewItem ( IContent entity ) = >
throw new InvalidOperationException ( "This method won't be implemented." ) ;
2018-04-12 22:53:04 +02:00
2022-06-02 08:18:31 +02:00
protected override void PersistUpdatedItem ( IContent entity ) = >
throw new InvalidOperationException ( "This method won't be implemented." ) ;
2018-11-08 16:33:19 +01:00
2022-06-02 08:18:31 +02:00
protected override Sql < ISqlContext > GetBaseQuery ( bool isCount ) = >
throw new InvalidOperationException ( "This method won't be implemented." ) ;
2018-11-08 16:33:19 +01:00
2022-06-02 08:18:31 +02:00
protected override string GetBaseWhereClause ( ) = >
throw new InvalidOperationException ( "This method won't be implemented." ) ;
}
2018-11-08 16:33:19 +01:00
2022-06-02 08:18:31 +02:00
#endregion
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
#region Schedule
2019-02-06 17:28:48 +01:00
2022-06-02 08:18:31 +02:00
/// <inheritdoc />
public void ClearSchedule ( DateTime date )
{
Sql < ISqlContext > sql = Sql ( ) . Delete < ContentScheduleDto > ( ) . Where < ContentScheduleDto > ( x = > x . Date < = date ) ;
Database . Execute ( sql ) ;
}
2019-02-06 17:28:48 +01:00
2022-06-02 08:18:31 +02:00
/// <inheritdoc />
public void ClearSchedule ( DateTime date , ContentScheduleAction action )
{
var a = action . ToString ( ) ;
Sql < ISqlContext > sql = Sql ( ) . Delete < ContentScheduleDto > ( )
. Where < ContentScheduleDto > ( x = > x . Date < = date & & x . Action = = a ) ;
Database . Execute ( sql ) ;
}
2018-04-12 22:53:04 +02:00
2022-06-02 08:18:31 +02:00
private Sql GetSqlForHasScheduling ( ContentScheduleAction action , DateTime date )
{
SqlTemplate template = SqlContext . Templates . Get ( "Umbraco.Core.DocumentRepository.GetSqlForHasScheduling" ,
tsql = > tsql
. SelectCount ( )
. From < ContentScheduleDto > ( )
. Where < ContentScheduleDto > ( x = >
x . Action = = SqlTemplate . Arg < string > ( "action" ) & & x . Date < = SqlTemplate . Arg < DateTime > ( "date" ) ) ) ;
2018-04-12 22:53:04 +02:00
2022-06-02 08:18:31 +02:00
Sql < ISqlContext > sql = template . Sql ( action . ToString ( ) , date ) ;
return sql ;
}
2018-04-12 22:53:04 +02:00
2022-06-02 08:18:31 +02:00
public bool HasContentForExpiration ( DateTime date )
{
Sql sql = GetSqlForHasScheduling ( ContentScheduleAction . Expire , date ) ;
return Database . ExecuteScalar < int > ( sql ) > 0 ;
}
2018-04-12 22:53:04 +02:00
2022-06-02 08:18:31 +02:00
public bool HasContentForRelease ( DateTime date )
{
Sql sql = GetSqlForHasScheduling ( ContentScheduleAction . Release , date ) ;
return Database . ExecuteScalar < int > ( sql ) > 0 ;
}
2018-04-12 22:53:04 +02:00
2022-06-02 08:18:31 +02:00
/// <inheritdoc />
public IEnumerable < IContent > GetContentForRelease ( DateTime date )
{
var action = ContentScheduleAction . Release . ToString ( ) ;
2018-04-23 12:53:17 +02:00
2022-06-02 08:18:31 +02:00
Sql < ISqlContext > sql = GetBaseQuery ( QueryType . Many )
. WhereIn < NodeDto > ( x = > x . NodeId , Sql ( )
. Select < ContentScheduleDto > ( x = > x . NodeId )
. From < ContentScheduleDto > ( )
. Where < ContentScheduleDto > ( x = > x . Action = = action & & x . Date < = date ) ) ;
2018-04-23 12:53:17 +02:00
2022-06-02 08:18:31 +02:00
AddGetByQueryOrderBy ( sql ) ;
2018-04-23 12:53:17 +02:00
2022-06-02 08:18:31 +02:00
return MapDtosToContent ( Database . Fetch < DocumentDto > ( sql ) ) ;
}
2018-04-23 12:53:17 +02:00
2022-06-02 08:18:31 +02:00
/// <inheritdoc />
public IEnumerable < IContent > GetContentForExpiration ( DateTime date )
{
var action = ContentScheduleAction . Expire . ToString ( ) ;
2018-04-23 12:53:17 +02:00
2022-06-02 08:18:31 +02:00
Sql < ISqlContext > sql = GetBaseQuery ( QueryType . Many )
. WhereIn < NodeDto > ( x = > x . NodeId , Sql ( )
. Select < ContentScheduleDto > ( x = > x . NodeId )
. From < ContentScheduleDto > ( )
. Where < ContentScheduleDto > ( x = > x . Action = = action & & x . Date < = date ) ) ;
2018-04-23 12:53:17 +02:00
2022-06-02 08:18:31 +02:00
AddGetByQueryOrderBy ( sql ) ;
2018-04-12 22:53:04 +02:00
2022-06-02 08:18:31 +02:00
return MapDtosToContent ( Database . Fetch < DocumentDto > ( sql ) ) ;
}
2018-04-12 22:53:04 +02:00
2022-06-02 08:18:31 +02:00
#endregion
2018-04-12 22:53:04 +02:00
2022-06-02 08:18:31 +02:00
#region Utilities
2018-04-12 22:53:04 +02:00
2022-06-02 08:18:31 +02:00
private void SanitizeNames ( IContent content , bool publishing )
{
// a content item *must* have an invariant name, and invariant published name
// else we just cannot write the invariant rows (node, content version...) to the database
// ensure that we have an invariant name
// invariant content = must be there already, else throw
// variant content = update with default culture or anything really
EnsureInvariantNameExists ( content ) ;
// ensure that invariant name is unique
EnsureInvariantNameIsUnique ( content ) ;
// and finally,
// ensure that each culture has a unique node name
// no published name = not published
// else, it needs to be unique
EnsureVariantNamesAreUnique ( content , publishing ) ;
}
2018-04-12 22:53:04 +02:00
2022-06-02 08:18:31 +02:00
private void EnsureInvariantNameExists ( IContent content )
{
if ( content . ContentType . VariesByCulture ( ) )
2018-04-23 12:53:17 +02:00
{
2022-06-02 08:18:31 +02:00
// content varies by culture
// then it must have at least a variant name, else it makes no sense
if ( content . CultureInfos ? . Count = = 0 )
2019-07-31 18:30:34 +10:00
{
2022-06-02 08:18:31 +02:00
throw new InvalidOperationException ( "Cannot save content with an empty name." ) ;
2019-07-31 18:30:34 +10:00
}
2019-11-05 12:54:22 +01:00
2022-06-02 08:18:31 +02:00
// and then, we need to set the invariant name implicitly,
// using the default culture if it has a name, otherwise anything we can
var defaultCulture = LanguageRepository . GetDefaultIsoCode ( ) ;
content . Name = defaultCulture ! = null & &
( content . CultureInfos ? . TryGetValue ( defaultCulture , out ContentCultureInfos cultureName ) ? ?
false )
? cultureName . Name !
: content . CultureInfos ! [ 0 ] . Name ! ;
2018-04-23 12:53:17 +02:00
}
2022-06-02 08:18:31 +02:00
else
2018-06-20 14:18:57 +02:00
{
2022-06-02 08:18:31 +02:00
// content is invariant, and invariant content must have an explicit invariant name
if ( string . IsNullOrWhiteSpace ( content . Name ) )
2018-05-02 17:44:14 +02:00
{
2022-06-02 08:18:31 +02:00
throw new InvalidOperationException ( "Cannot save content with an empty name." ) ;
2018-05-02 14:52:00 +10:00
}
}
2022-06-02 08:18:31 +02:00
}
2018-05-02 14:52:00 +10:00
2022-06-02 08:18:31 +02:00
private void EnsureInvariantNameIsUnique ( IContent content ) = >
content . Name = EnsureUniqueNodeName ( content . ParentId , content . Name , content . Id ) ;
2018-06-20 14:18:57 +02:00
2022-06-02 08:18:31 +02:00
protected override string? EnsureUniqueNodeName ( int parentId , string? nodeName , int id = 0 ) = >
EnsureUniqueNaming = = false ? nodeName : base . EnsureUniqueNodeName ( parentId , nodeName , id ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
private SqlTemplate SqlEnsureVariantNamesAreUnique = > SqlContext . Templates . Get (
"Umbraco.Core.DomainRepository.EnsureVariantNamesAreUnique" , tsql = > tsql
2018-06-05 16:01:48 +02:00
. Select < ContentVersionCultureVariationDto > ( x = > x . Id , x = > x . Name , x = > x . LanguageId )
. From < ContentVersionCultureVariationDto > ( )
2022-06-02 08:18:31 +02:00
. InnerJoin < ContentVersionDto > ( )
. On < ContentVersionDto , ContentVersionCultureVariationDto > ( x = > x . Id , x = > x . VersionId )
2018-06-20 14:18:57 +02:00
. InnerJoin < NodeDto > ( ) . On < NodeDto , ContentVersionDto > ( x = > x . NodeId , x = > x . NodeId )
2018-06-05 16:01:48 +02:00
. Where < ContentVersionDto > ( x = > x . Current = = SqlTemplate . Arg < bool > ( "current" ) )
. Where < NodeDto > ( x = > x . NodeObjectType = = SqlTemplate . Arg < Guid > ( "nodeObjectType" ) & &
x . ParentId = = SqlTemplate . Arg < int > ( "parentId" ) & &
x . NodeId ! = SqlTemplate . Arg < int > ( "id" ) )
. OrderBy < ContentVersionCultureVariationDto > ( x = > x . LanguageId ) ) ;
2022-06-02 08:18:31 +02:00
private void EnsureVariantNamesAreUnique ( IContent content , bool publishing )
{
if ( ! EnsureUniqueNaming | | ! content . ContentType . VariesByCulture ( ) | | content . CultureInfos ? . Count = = 0 )
2018-06-01 15:20:16 +10:00
{
2022-06-02 08:18:31 +02:00
return ;
}
2018-06-01 15:20:16 +10:00
2022-06-02 08:18:31 +02:00
// get names per culture, at same level (ie all siblings)
Sql < ISqlContext > sql = SqlEnsureVariantNamesAreUnique . Sql ( true , NodeObjectTypeId , content . ParentId , content . Id ) ;
var names = Database . Fetch < CultureNodeName > ( sql )
. GroupBy ( x = > x . LanguageId )
. ToDictionary ( x = > x . Key , x = > x ) ;
2018-06-01 15:20:16 +10:00
2022-06-02 08:18:31 +02:00
if ( names . Count = = 0 )
{
return ;
}
2018-06-01 15:20:16 +10:00
2022-06-02 08:18:31 +02:00
// note: the code below means we are going to unique-ify every culture names, regardless
// of whether the name has changed (ie the culture has been updated) - some saving culture
// fr-FR could cause culture en-UK name to change - not sure that is clean
if ( content . CultureInfos is null )
{
return ;
}
2018-07-04 10:41:08 +02:00
2022-06-02 08:18:31 +02:00
foreach ( ContentCultureInfos cultureInfo in content . CultureInfos )
{
var langId = LanguageRepository . GetIdByIsoCode ( cultureInfo . Culture ) ;
if ( ! langId . HasValue )
2022-02-24 09:24:56 +01:00
{
2022-06-02 08:18:31 +02:00
continue ;
2022-02-24 09:24:56 +01:00
}
2018-06-20 14:18:57 +02:00
2022-06-02 08:18:31 +02:00
if ( ! names . TryGetValue ( langId . Value , out IGrouping < int , CultureNodeName > ? cultureNames ) )
{
continue ;
}
2018-06-20 14:18:57 +02:00
2022-06-02 08:18:31 +02:00
// get a unique name
IEnumerable < SimilarNodeName > otherNames =
cultureNames . Select ( x = > new SimilarNodeName { Id = x . Id , Name = x . Name } ) ;
var uniqueName = SimilarNodeName . GetUniqueName ( otherNames , 0 , cultureInfo . Name ) ;
2018-06-20 14:18:57 +02:00
2022-06-02 08:18:31 +02:00
if ( uniqueName = = content . GetCultureName ( cultureInfo . Culture ) )
{
continue ;
2018-06-01 15:20:16 +10:00
}
2022-06-02 08:18:31 +02:00
// update the name, and the publish name if published
content . SetCultureName ( uniqueName , cultureInfo . Culture ) ;
if ( publishing & & ( content . PublishCultureInfos ? . ContainsKey ( cultureInfo . Culture ) ? ? false ) )
{
content . SetPublishInfo ( cultureInfo . Culture , uniqueName ,
DateTime . Now ) ; //TODO: This is weird, this call will have already been made in the SetCultureName
}
2018-06-01 15:20:16 +10:00
}
2022-06-02 08:18:31 +02:00
}
2018-06-01 15:20:16 +10:00
2022-06-02 08:18:31 +02:00
// ReSharper disable once ClassNeverInstantiated.Local
private class CultureNodeName
{
public int Id { get ; set ; }
public string? Name { get ; set ; }
public int LanguageId { get ; set ; }
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
#endregion
2017-12-07 16:45:25 +01:00
}