2019-02-05 14:13:03 +11:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using Umbraco.Core.Exceptions ;
namespace Umbraco.Core.Models
{
/// <summary>
/// Extension methods used to manipulate content variations by the document repository
/// </summary>
internal static class ContentRepositoryExtensions
{
2019-02-06 16:10:20 +11:00
/// <summary>
/// Gets the cultures that have been flagged for unpublishing.
/// </summary>
/// <remarks>Gets cultures for which content.UnpublishCulture() has been invoked.</remarks>
public static IReadOnlyList < string > GetCulturesUnpublishing ( this IContent content )
{
if ( ! content . Published | | ! content . ContentType . VariesByCulture ( ) | | ! content . IsPropertyDirty ( "PublishCultureInfos" ) )
return Array . Empty < string > ( ) ;
var culturesUnpublishing = content . CultureInfos . Values
2019-02-06 17:28:48 +01:00
. Where ( x = > content . IsPropertyDirty ( ContentBase . ChangeTrackingPrefix . UnpublishedCulture + x . Culture ) )
2019-02-06 16:10:20 +11:00
. Select ( x = > x . Culture ) ;
return culturesUnpublishing . ToList ( ) ;
}
/// <summary>
/// Copies values from another document.
/// </summary>
public static void CopyFrom ( this IContent content , IContent other , string culture = "*" )
{
if ( other . ContentTypeId ! = content . ContentTypeId )
throw new InvalidOperationException ( "Cannot copy values from a different content type." ) ;
culture = culture ? . ToLowerInvariant ( ) . NullOrWhiteSpaceAsNull ( ) ;
// the variation should be supported by the content type properties
// if the content type is invariant, only '*' and 'null' is ok
// if the content type varies, everything is ok because some properties may be invariant
if ( ! content . ContentType . SupportsPropertyVariation ( culture , "*" , true ) )
throw new NotSupportedException ( $"Culture \" { culture } \ " is not supported by content type \"{content.ContentType.Alias}\" with variation \"{content.ContentType.Variations}\"." ) ;
// copying from the same Id and VersionPk
var copyingFromSelf = content . Id = = other . Id & & content . VersionId = = other . VersionId ;
var published = copyingFromSelf ;
// note: use property.SetValue(), don't assign pvalue.EditValue, else change tracking fails
// clear all existing properties for the specified culture
foreach ( var property in content . Properties )
{
// each property type may or may not support the variation
if ( ! property . PropertyType . SupportsVariation ( culture , "*" , wildcards : true ) )
continue ;
foreach ( var pvalue in property . Values )
if ( property . PropertyType . SupportsVariation ( pvalue . Culture , pvalue . Segment , wildcards : true ) & &
( culture = = "*" | | pvalue . Culture . InvariantEquals ( culture ) ) )
{
property . SetValue ( null , pvalue . Culture , pvalue . Segment ) ;
}
}
// copy properties from 'other'
var otherProperties = other . Properties ;
foreach ( var otherProperty in otherProperties )
{
if ( ! otherProperty . PropertyType . SupportsVariation ( culture , "*" , wildcards : true ) )
continue ;
var alias = otherProperty . PropertyType . Alias ;
foreach ( var pvalue in otherProperty . Values )
{
if ( otherProperty . PropertyType . SupportsVariation ( pvalue . Culture , pvalue . Segment , wildcards : true ) & &
( culture = = "*" | | pvalue . Culture . InvariantEquals ( culture ) ) )
{
var value = published ? pvalue . PublishedValue : pvalue . EditedValue ;
content . SetValue ( alias , value , pvalue . Culture , pvalue . Segment ) ;
}
}
}
// copy names, too
if ( culture = = "*" )
{
content . CultureInfos . Clear ( ) ;
content . CultureInfos = null ;
2019-02-06 17:28:48 +01:00
}
2019-02-06 16:10:20 +11:00
if ( culture = = null | | culture = = "*" )
content . Name = other . Name ;
// ReSharper disable once UseDeconstruction
foreach ( var cultureInfo in other . CultureInfos )
{
if ( culture = = "*" | | culture = = cultureInfo . Culture )
content . SetCultureName ( cultureInfo . Name , cultureInfo . Culture ) ;
}
}
2019-02-05 14:13:03 +11:00
public static void SetPublishInfo ( this IContent content , string culture , string name , DateTime date )
{
if ( string . IsNullOrWhiteSpace ( name ) )
throw new ArgumentNullOrEmptyException ( nameof ( name ) ) ;
if ( culture . IsNullOrWhiteSpace ( ) )
throw new ArgumentNullOrEmptyException ( nameof ( culture ) ) ;
content . PublishCultureInfos . AddOrUpdate ( culture , name , date ) ;
}
2019-02-06 23:51:12 +11:00
/// <summary>
/// Used to synchronize all culture dates to the same date if they've been modified
/// </summary>
/// <param name="content"></param>
/// <param name="date"></param>
/// <remarks>
/// This is so that in an operation where (for example) 2 languages are updates like french and english, it is possible that
/// these dates assigned to them differ by a couple of Ticks, but we need to ensure they are persisted at the exact same time.
/// </remarks>
2019-02-05 14:13:03 +11:00
public static void AdjustDates ( this IContent content , DateTime date )
{
foreach ( var culture in content . PublishedCultures . ToList ( ) )
{
if ( ! content . PublishCultureInfos . TryGetValue ( culture , out var publishInfos ) )
continue ;
2019-02-06 17:28:48 +01:00
// if it's not dirty, it means it hasn't changed so there's nothing to adjust
2019-02-06 23:51:12 +11:00
if ( ! publishInfos . IsDirty ( ) )
2019-02-06 17:28:48 +01:00
continue ;
2019-02-06 23:51:12 +11:00
2019-02-05 14:13:03 +11:00
content . PublishCultureInfos . AddOrUpdate ( culture , publishInfos . Name , date ) ;
if ( content . CultureInfos . TryGetValue ( culture , out var infos ) )
SetCultureInfo ( content , culture , infos . Name , date ) ;
}
}
// sets the edited cultures on the content
public static void SetCultureEdited ( this IContent content , IEnumerable < string > cultures )
{
if ( cultures = = null )
content . EditedCultures = null ;
else
{
var editedCultures = new HashSet < string > ( cultures . Where ( x = > ! x . IsNullOrWhiteSpace ( ) ) , StringComparer . OrdinalIgnoreCase ) ;
content . EditedCultures = editedCultures . Count > 0 ? editedCultures : null ;
}
}
public static void SetCultureInfo ( this IContentBase content , string culture , string name , DateTime date )
{
if ( name . IsNullOrWhiteSpace ( ) )
throw new ArgumentNullOrEmptyException ( nameof ( name ) ) ;
if ( culture . IsNullOrWhiteSpace ( ) )
throw new ArgumentNullOrEmptyException ( nameof ( culture ) ) ;
content . CultureInfos . AddOrUpdate ( culture , name , date ) ;
}
2019-02-21 14:46:14 +11:00
/// <summary>
2019-02-21 14:13:37 +01:00
/// Sets the publishing values for names and properties.
2019-02-21 14:46:14 +11:00
/// </summary>
/// <param name="content"></param>
/// <param name="culture"></param>
2019-02-21 14:13:37 +01:00
/// <returns>A value indicating whether it was possible to publish the names and values for the specified
/// culture(s). The method may fail if required names are not set, but it does NOT validate property data</returns>
2019-03-27 16:31:53 +11:00
public static bool PublishCulture ( this IContent content , CultureType culture )
2019-02-20 16:05:42 +01:00
{
2019-03-27 16:31:53 +11:00
if ( culture = = null ) throw new ArgumentNullException ( nameof ( culture ) ) ;
2019-02-05 14:13:03 +11:00
// the variation should be supported by the content type properties
// if the content type is invariant, only '*' and 'null' is ok
// if the content type varies, everything is ok because some properties may be invariant
2019-03-27 12:41:02 +11:00
if ( ! content . ContentType . SupportsPropertyVariation ( culture . Culture , "*" , true ) )
2019-02-05 14:13:03 +11:00
throw new NotSupportedException ( $"Culture \" { culture } \ " is not supported by content type \"{content.ContentType.Alias}\" with variation \"{content.ContentType.Variations}\"." ) ;
2019-03-27 12:41:02 +11:00
switch ( culture . CultureBehavior )
2019-02-05 14:13:03 +11:00
{
2019-03-28 23:59:49 +11:00
case CultureType . Behavior . AllCultures :
2019-02-05 14:13:03 +11:00
{
2019-03-27 12:41:02 +11:00
foreach ( var c in content . AvailableCultures )
{
var name = content . GetCultureName ( c ) ;
if ( string . IsNullOrWhiteSpace ( name ) )
return false ;
content . SetPublishInfo ( c , name , DateTime . Now ) ;
}
break ;
}
2019-03-28 23:59:49 +11:00
case CultureType . Behavior . InvariantCulture :
2019-03-27 12:41:02 +11:00
{
if ( string . IsNullOrWhiteSpace ( content . Name ) )
return false ;
// PublishName set by repository - nothing to do here
break ;
}
2019-03-28 23:59:49 +11:00
case CultureType . Behavior . ExplicitCulture :
2019-03-27 12:41:02 +11:00
{
var name = content . GetCultureName ( culture . Culture ) ;
2019-02-05 14:13:03 +11:00
if ( string . IsNullOrWhiteSpace ( name ) )
return false ;
2019-03-27 12:41:02 +11:00
content . SetPublishInfo ( culture . Culture , name , DateTime . Now ) ;
break ;
2019-02-05 14:13:03 +11:00
}
}
// property.PublishValues only publishes what is valid, variation-wise
foreach ( var property in content . Properties )
{
2019-03-27 12:41:02 +11:00
property . PublishValues ( culture . Culture ) ;
2019-03-28 23:59:49 +11:00
if ( culture . CultureBehavior . HasFlag ( CultureType . Behavior . InvariantProperties ) )
2019-02-05 14:13:03 +11:00
property . PublishValues ( null ) ;
}
content . PublishedState = PublishedState . Publishing ;
return true ;
}
public static void UnpublishCulture ( this IContent content , string culture = "*" )
{
culture = culture . NullOrWhiteSpaceAsNull ( ) ;
// the variation should be supported by the content type properties
if ( ! content . ContentType . SupportsPropertyVariation ( culture , "*" , true ) )
throw new NotSupportedException ( $"Culture \" { culture } \ " is not supported by content type \"{content.ContentType.Alias}\" with variation \"{content.ContentType.Variations}\"." ) ;
if ( culture = = "*" ) // all cultures
content . ClearPublishInfos ( ) ;
else // one single culture
content . ClearPublishInfo ( culture ) ;
// property.PublishValues only publishes what is valid, variation-wise
foreach ( var property in content . Properties )
property . UnpublishValues ( culture ) ;
content . PublishedState = PublishedState . Publishing ;
}
public static void ClearPublishInfos ( this IContent content )
{
content . PublishCultureInfos = null ;
}
public static void ClearPublishInfo ( this IContent content , string culture )
{
if ( culture . IsNullOrWhiteSpace ( ) )
throw new ArgumentNullOrEmptyException ( nameof ( culture ) ) ;
content . PublishCultureInfos . Remove ( culture ) ;
// set the culture to be dirty - it's been modified
2019-02-06 16:10:20 +11:00
content . TouchCulture ( culture ) ;
2019-02-05 14:13:03 +11:00
}
2019-02-06 16:10:20 +11:00
/// <summary>
/// Updates a culture date, if the culture exists.
/// </summary>
2019-02-06 23:51:12 +11:00
public static void TouchCulture ( this IContentBase content , string culture )
2019-02-05 14:13:03 +11:00
{
2019-02-07 00:15:47 +11:00
if ( culture . IsNullOrWhiteSpace ( ) ) return ;
2019-02-05 14:13:03 +11:00
if ( ! content . CultureInfos . TryGetValue ( culture , out var infos ) ) return ;
content . CultureInfos . AddOrUpdate ( culture , infos . Name , DateTime . Now ) ;
}
}
}