Compare commits

...

56 Commits

Author SHA1 Message Date
Steve Megson 88349c6b66 Sorting of images in multiple media picker
(cherry picked from commit ed5effd190)
2019-08-08 16:02:35 +02:00
Sebastiaan Janssen 046ed8ec85 Bump version to 8.1.2 2019-08-08 12:23:57 +02:00
Steve Megson aabe6defb2 Fixes #5864 - migrate prevalues for Umbraco.DropDown.Flexible
(cherry picked from commit 4e0dd728a8)
2019-08-08 10:56:02 +02:00
Bjarke Berg 3245c82ea6 Missing file in project file
(cherry picked from commit 3062f40ef5)
2019-08-08 10:45:34 +02:00
Kenn Jacobsen beacc5bba8 Fix broken button to open content type from create dialog
(cherry picked from commit 57e58c6cf7)
2019-08-08 10:05:33 +02:00
leekelleher 7ffe0398f9 DocType Editor sorting - vertical direction
In the DocType Editor, when sorting the groups and properties,
the draggable direction is freeform (horizontal, vertical or even
diagonal).

We can restrict the direction, by setting the `axis` property in
the sort options.
2019-08-07 20:55:35 +02:00
Kenn Jacobsen ce5b3531e0 Make content types with description look ok in the create dialog (#5752)
(cherry picked from commit b3497011e8)
2019-08-07 20:50:08 +02:00
Sebastiaan Janssen a9a5d00916 Merge pull request #5999 from kjac/v8-fix-allow-remove-user-start-nodes
V8: Allow removing user start nodes
(cherry picked from commit 2cf3a7831c)
2019-08-07 20:25:44 +02:00
Gopakumar R 62a71f8052 Fix for issue #5816 (#5836)
(cherry picked from commit 0138091a6c)
2019-08-07 20:16:50 +02:00
Kenn Jacobsen 36eccb472c Fix the create context menu overflows introduced with 405e9a79 (#5754)
(cherry picked from commit 6c0fb2f849)
2019-08-07 19:58:13 +02:00
Benjamin Carleski 289d50d170 Fix FIPS compliance, add FIPS tests (#5978)
(cherry picked from commit e495faf83c)
2019-08-07 19:34:13 +02:00
Linus Elander 0517f29134 Adjusts conditions for which indexes should be affected when a node is unpublished
(cherry picked from commit 7557c584d8)
2019-08-07 16:38:13 +02:00
Shannon 467cf8d762 Adds comments/notes 2019-08-07 15:40:27 +10:00
Bjarke Berg 2cdb6d86ad Merge pull request #6019 from umbraco/v8/bugfix/pending-changes-issue
v8 Changing a property type from invariant to variant and back again causes issues with reporting that a document is edited or not
2019-08-06 09:09:12 +02:00
Bjarke Berg d474f60465 Merge pull request #5992 from umbraco/v8/bugfix/5554-unpublish-state
V8/bugfix/5554 unpublish state
2019-08-05 11:54:49 +02:00
Bjarke Berg caf4d1fe34 Merge pull request #6038 from umbraco/v8/bugfix/examine-startup
Fixes how examine starts up to make it easier for custom index developers
2019-08-05 10:39:29 +02:00
Bjarke Berg e4f346620e Added runtime level to ExamineFinalComposer 2019-08-05 10:38:35 +02:00
Shannon be4536cf16 Refactors ugly statics and nested classes 2019-08-05 17:14:29 +10:00
Shannon 89429bd65a fixes ctor 2019-08-05 16:28:23 +10:00
Shannon 6d3faf91d8 fixes spacing with ext login providers 2019-08-05 13:50:50 +10:00
Shannon 345e6091d2 fixes null check, was on the wrong element 2019-08-02 13:01:02 +10:00
Shannon 86a97c30d1 fixes up LuceneIndexDiagnostics 2019-08-02 12:40:45 +10:00
Shannon 14915e9da7 oops 2019-08-02 01:16:37 +10:00
Shannon 366058581a Adds LuceneIndexDiagnostics to make it easier for devs and custom indexes 2019-08-02 01:15:08 +10:00
Shannon 9f6a7dec2e Fixes how examine starts up to make it easier for custom index developers 2019-08-02 00:28:48 +10:00
Shannon be30bb0ad0 adds test 2019-08-01 21:41:43 +10:00
Shannon 600a29514f more asserts 2019-08-01 21:27:31 +10:00
Shannon 9efec9244b removes commented out code 2019-08-01 19:09:19 +10:00
Shannon ded1a22e45 fixes validation check 2019-08-01 19:01:41 +10:00
Shannon 2e83ac9282 Cleans up some tests 2019-08-01 18:54:19 +10:00
Shannon 9538981730 Adds more assertions and tests and validates variations 2019-08-01 18:49:05 +10:00
Shannon 791222c3ce Merge remote-tracking branch 'origin/v8/8.1' into v8/bugfix/5554-unpublish-state 2019-08-01 16:11:16 +10:00
Shannon 1095815878 removes notes 2019-08-01 16:09:52 +10:00
Shannon a8bdab8f5c Merge remote-tracking branch 'origin/v8/8.1' into v8/bugfix/pending-changes-issue 2019-08-01 15:56:24 +10:00
Shannon ff952a6df1 Passes test! Now to add more tests/assertions and then the logic to renormalize based on name changes. 2019-08-01 15:54:44 +10:00
Shannon 87e7cec02e WIP - commiting what i have. Solved part of the problem but there are others. 2019-07-31 18:30:34 +10:00
Shannon 5c8cd60275 Writes up a test to show the issue with edited culture flags, this shows that the current fix works but there's still an issue 2019-07-31 00:13:40 +10:00
Bjarke Berg e2483dc874 Merge pull request #6020 from umbraco/v8/bugfix/panic-exception
Adds PanicException with details instead of throwing "panic" strings
2019-07-30 15:22:05 +02:00
Shannon 6482d849e3 Moves test to different fixture 2019-07-30 22:59:33 +10:00
Shannon 34f137c8cf Merge branch 'v8/bugfix/panic-exception' into v8/bugfix/5554-unpublish-state 2019-07-30 22:50:06 +10:00
Shannon 16d8f8846e Adds PanicException with details instead of throwing "panic" strings 2019-07-30 22:40:15 +10:00
Shannon 108416638c removes commented out code 2019-07-30 22:28:06 +10:00
Shannon 20d28ea215 removes commented out code 2019-07-30 22:20:28 +10:00
Shannon 27bc309c55 Removes unused code 2019-07-30 22:20:28 +10:00
Shannon b5b4ee79b4 Adds notes, fixes logic with a double save 2019-07-30 22:20:28 +10:00
Shannon 45227357fd adds notes on why this is failing 2019-07-30 22:20:28 +10:00
Shannon a2ba0f6a4c more tests - failing that need fixing 2019-07-30 22:20:27 +10:00
Shannon 415916c527 Fixes an issue with the content service + test 2019-07-30 22:20:27 +10:00
Shannon 40a55cb9df Fixes issue/tests 2019-07-30 22:20:27 +10:00
Shannon 5a9ca8d09f wip added more tests and notes 2019-07-30 22:20:27 +10:00
Shannon 1bfcc9053e Fixes issues: we never properly used SuccessUnpublishMandatoryCulture, fixed issue of unpublishing last culture with a new status 2019-07-30 22:20:27 +10:00
Shannon 3930ae244c Created unit test to show the problem. 2019-07-30 22:20:27 +10:00
Shannon 365ad2981d Fixing issue when changing a property type from invariant to variant and back again and the document always reporting pending changes. 2019-07-30 19:02:03 +10:00
Rasmus John Pedersen ca56655d07 Fix exception when stored media url is already absolute
(cherry picked from commit 49b3351c72)
2019-07-29 14:31:04 +02:00
Shannon 4612c03657 Fixes #5789 - PublishedContentQuery Search always searches on culture 2019-07-29 15:43:03 +10:00
Shannon 41093e5b8b Value Set builder shouldn't stop indexing when one field is empty in value sets 2019-07-29 15:38:08 +10:00
93 changed files with 2345 additions and 1050 deletions
+2 -2
View File
@@ -18,5 +18,5 @@ using System.Resources;
[assembly: AssemblyVersion("8.0.0")]
// these are FYI and changed automatically
[assembly: AssemblyFileVersion("8.1.1")]
[assembly: AssemblyInformationalVersion("8.1.1")]
[assembly: AssemblyFileVersion("8.1.2")]
[assembly: AssemblyInformationalVersion("8.1.2")]
+49 -18
View File
@@ -12,31 +12,62 @@
/// or MS.DI, PerDependency in Autofac.</remarks>
Transient,
// TODO: We need to fix this up, currently LightInject is the only one that behaves differently from all other containers.
// ... the simple fix would be to map this to PerScopeLifetime in LI but need to wait on a response here https://github.com/seesharper/LightInject/issues/494#issuecomment-518942625
//
// we use it for controllers, httpContextBase and other request scoped objects: MembershpHelper, TagQuery, UmbracoTreeSearcher and ISearchableTree
// - so that they are automatically disposed at the end of the scope (ie request)
// - not sure they should not be simply 'scoped'?
/// <summary>
/// One unique instance per request.
/// </summary>
// TODO: review lifetimes for LightInject vs other containers
// currently, corresponds to 'Request' in LightInject which is 'Transient + disposed by Scope'
// but NOT (in LightInject) a per-web-request lifetime, more a TransientScoped
//
// we use it for controllers, httpContextBase and umbracoContext
// - so that they are automatically disposed at the end of the scope (ie request)
// - not sure they should not be simply 'scoped'?
//
// Castle has an extra PerWebRequest something, and others use scope
// what about Request before first request ie during application startup?
// see http://blog.ploeh.dk/2009/11/17/UsingCastleWindsor'sPerWebRequestlifestylewithASP.NETMVConIIS7/
// Castle ends up requiring a special scope manager too
// see https://groups.google.com/forum/#!topic/castle-project-users/1E2W9LVIYR4
//
// but maybe also - why are we requiring scoped services at startup?
/// <remarks>
/// <para>
/// Any instance created with this lifetime will be disposed at the end of a request.
/// </para>
/// Corresponds to
/// <para>
/// PerRequestLifeTime in LightInject - means transient but disposed at the end of the current web request.
/// see: https://github.com/seesharper/LightInject/issues/494#issuecomment-518493262
/// </para>
/// <para>
/// Scoped in MS.DI - means one per web request.
/// see https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2#service-lifetimes</para>
/// <para>
/// InstancePerRequest in Autofac - means one per web request.
/// see https://autofaccn.readthedocs.io/en/latest/lifetime/instance-scope.html#instance-per-request
/// But "Behind the scenes, though, its still just instance per matching lifetime scope."
/// </para>
/// <para>
/// LifestylePerWebRequest in Castle Windsor - means one per web request.
/// see https://github.com/castleproject/Windsor/blob/master/docs/mvc-tutorial-part-7-lifestyles.md#the-perwebrequest-lifestyle
/// </para>
/// </remarks>
Request,
/// <summary>
/// One unique instance per container scope.
/// One unique instance per scope.
/// </summary>
/// <remarks>Corresponds to Scope in LightInject, Scoped in MS.DI
/// or Castle Windsor, PerLifetimeScope in Autofac.</remarks>
/// <remarks>
/// <para>
/// Any instance created with this lifetime will be disposed at the end of the current scope.
/// </para>
/// Corresponds to
/// <para>PerScopeLifetime in LightInject (when in a request, means one per web request)</para>
/// <para>
/// Scoped in MS.DI (when in a request, means one per web request)
/// see https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2#service-lifetimes</para>
/// <para>
/// InstancePerLifetimeScope in Autofac (when in a request, means one per web request)
/// see https://autofaccn.readthedocs.io/en/latest/lifetime/instance-scope.html#instance-per-lifetime-scope
/// Also note that Autofac's InstancePerRequest is the same as this, see https://autofaccn.readthedocs.io/en/latest/lifetime/instance-scope.html#instance-per-request
/// it says "Behind the scenes, though, its still just instance per matching lifetime scope."
/// </para>
/// <para>
/// LifestyleScoped in Castle Windsor
/// </para>
/// </remarks>
Scope,
/// <summary>
@@ -0,0 +1,28 @@
using System;
using System.Runtime.Serialization;
namespace Umbraco.Core.Exceptions
{
/// <summary>
/// Internal exception that in theory should never ben thrown, it is only thrown in circumstances that should never happen
/// </summary>
[Serializable]
internal class PanicException : Exception
{
public PanicException()
{
}
public PanicException(string message) : base(message)
{
}
public PanicException(string message, Exception innerException) : base(message, innerException)
{
}
protected PanicException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
}
+3 -2
View File
@@ -3,6 +3,7 @@ using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Exceptions;
namespace Umbraco.Core.Mapping
{
@@ -259,7 +260,7 @@ namespace Umbraco.Core.Mapping
if (typeof(TTarget).IsArray)
{
var elementType = typeof(TTarget).GetElementType();
if (elementType == null) throw new Exception("panic");
if (elementType == null) throw new PanicException("elementType == null which should never occur");
var targetArray = Array.CreateInstance(elementType, targetList.Count);
targetList.CopyTo(targetArray, 0);
target = targetArray;
@@ -382,7 +383,7 @@ namespace Umbraco.Core.Mapping
{
if (type.IsArray) return type.GetElementType();
if (type.IsGenericType) return type.GenericTypeArguments[0];
throw new Exception("panic");
throw new PanicException($"Could not get enumerable or array type from {type}");
}
/// <summary>
@@ -0,0 +1,32 @@
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
{
class DropDownFlexiblePreValueMigrator : IPreValueMigrator
{
public bool CanMigrate(string editorAlias)
=> editorAlias == "Umbraco.DropDown.Flexible";
public virtual string GetNewAlias(string editorAlias)
=> null;
public object GetConfiguration(int dataTypeId, string editorAlias, Dictionary<string, PreValueDto> preValues)
{
var config = new DropDownFlexibleConfiguration();
foreach (var preValue in preValues.Values)
{
if (preValue.Alias == "multiple")
{
config.Multiple = (preValue.Value == "1");
}
else
{
config.Items.Add(new ValueListConfiguration.ValueListItem { Id = preValue.Id, Value = preValue.Value });
}
}
return config;
}
}
}
@@ -19,6 +19,7 @@ public class PreValueMigratorComposer : ICoreComposer
.Append<NestedContentPreValueMigrator>()
.Append<DecimalPreValueMigrator>()
.Append<ListViewPreValueMigrator>()
.Append<DropDownFlexiblePreValueMigrator>()
.Append<ValueListPreValueMigrator>();
}
}
@@ -1,5 +1,6 @@
using System;
using System.Linq;
using Umbraco.Core.Exceptions;
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
{
@@ -20,7 +21,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
case "Umbraco.NoEdit":
return Constants.PropertyEditors.Aliases.Label;
default:
throw new Exception("panic");
throw new PanicException($"The alias {editorAlias} is not supported");
}
}
}
@@ -222,7 +222,13 @@ namespace Umbraco.Core.Models
return true;
}
public static void UnpublishCulture(this IContent content, string culture = "*")
/// <summary>
/// Returns false if the culture is already unpublished
/// </summary>
/// <param name="content"></param>
/// <param name="culture"></param>
/// <returns></returns>
public static bool UnpublishCulture(this IContent content, string culture = "*")
{
culture = culture.NullOrWhiteSpaceAsNull();
@@ -230,16 +236,31 @@ namespace Umbraco.Core.Models
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
var keepProcessing = true;
if (culture == "*")
{
// all cultures
content.ClearPublishInfos();
else // one single culture
content.ClearPublishInfo(culture);
}
else
{
// one single culture
keepProcessing = content.ClearPublishInfo(culture);
}
// property.PublishValues only publishes what is valid, variation-wise
foreach (var property in content.Properties)
property.UnpublishValues(culture);
if (keepProcessing)
{
// property.PublishValues only publishes what is valid, variation-wise
foreach (var property in content.Properties)
property.UnpublishValues(culture);
content.PublishedState = PublishedState.Publishing;
content.PublishedState = PublishedState.Publishing;
}
return keepProcessing;
}
public static void ClearPublishInfos(this IContent content)
@@ -247,15 +268,24 @@ namespace Umbraco.Core.Models
content.PublishCultureInfos = null;
}
public static void ClearPublishInfo(this IContent content, string culture)
/// <summary>
/// Returns false if the culture is already unpublished
/// </summary>
/// <param name="content"></param>
/// <param name="culture"></param>
/// <returns></returns>
public static bool 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
content.TouchCulture(culture);
var removed = content.PublishCultureInfos.Remove(culture);
if (removed)
{
// set the culture to be dirty - it's been modified
content.TouchCulture(culture);
}
return removed;
}
/// <summary>
+1 -1
View File
@@ -101,7 +101,7 @@ namespace Umbraco.Core.Models
PropertyGroupCollection PropertyGroups { get; set; }
/// <summary>
/// Gets all local property types belonging to a group, across all local property groups.
/// Gets all local property types all local property groups or ungrouped.
/// </summary>
IEnumerable<PropertyType> PropertyTypes { get; }
@@ -75,7 +75,7 @@ namespace Umbraco.Core.Models.PublishedContent
return type;
var def = type.GetGenericTypeDefinition();
if (def == null)
throw new InvalidOperationException("panic");
throw new PanicException($"The type {type} has not generic type definition");
var args = type.GetGenericArguments().Select(x => Map(x, modelTypes, true)).ToArray();
return def.MakeGenericType(args);
@@ -114,7 +114,7 @@ namespace Umbraco.Core.Models.PublishedContent
return type.FullName;
var def = type.GetGenericTypeDefinition();
if (def == null)
throw new InvalidOperationException("panic");
throw new PanicException($"The type {type} has not generic type definition");
var args = type.GetGenericArguments().Select(x => MapToName(x, map, true)).ToArray();
var defFullName = def.FullName.Substring(0, def.FullName.IndexOf('`'));
@@ -12,7 +12,7 @@ namespace Umbraco.Core.Persistence.Factories
public static IEnumerable<Property> BuildEntities(PropertyType[] propertyTypes, IReadOnlyCollection<PropertyDataDto> dtos, int publishedVersionId, ILanguageRepository languageRepository)
{
var properties = new List<Property>();
var xdtos = dtos.GroupBy(x => x.PropertyTypeId).ToDictionary(x => x.Key, x => (IEnumerable<PropertyDataDto>) x);
var xdtos = dtos.GroupBy(x => x.PropertyTypeId).ToDictionary(x => x.Key, x => (IEnumerable<PropertyDataDto>)x);
foreach (var propertyType in propertyTypes)
{
@@ -104,10 +104,14 @@ namespace Umbraco.Core.Persistence.Factories
/// <param name="properties">The properties to map</param>
/// <param name="languageRepository"></param>
/// <param name="edited">out parameter indicating that one or more properties have been edited</param>
/// <param name="editedCultures">out parameter containing a collection of edited cultures when the contentVariation varies by culture</param>
/// <param name="editedCultures">
/// Out parameter containing a collection of edited cultures when the contentVariation varies by culture.
/// The value of this will be used to populate the edited cultures in the umbracoDocumentCultureVariation table.
/// </param>
/// <returns></returns>
public static IEnumerable<PropertyDataDto> BuildDtos(ContentVariation contentVariation, int currentVersionId, int publishedVersionId, IEnumerable<Property> properties,
ILanguageRepository languageRepository, out bool edited, out HashSet<string> editedCultures)
ILanguageRepository languageRepository, out bool edited,
out HashSet<string> editedCultures)
{
var propertyDataDtos = new List<PropertyDataDto>();
edited = false;
@@ -130,6 +134,9 @@ namespace Umbraco.Core.Persistence.Factories
// publishing = deal with edit and published values
foreach (var propertyValue in property.Values)
{
var isInvariantValue = propertyValue.Culture == null;
var isCultureValue = propertyValue.Culture != null && propertyValue.Segment == null;
// deal with published value
if (propertyValue.PublishedValue != null && publishedVersionId > 0)
propertyDataDtos.Add(BuildDto(publishedVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.PublishedValue));
@@ -138,26 +145,36 @@ namespace Umbraco.Core.Persistence.Factories
if (propertyValue.EditedValue != null)
propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue));
// property.Values will contain ALL of it's values, both variant and invariant which will be populated if the
// administrator has previously changed the property type to be variant vs invariant.
// We need to check for this scenario here because otherwise the editedCultures and edited flags
// will end up incorrectly set in the umbracoDocumentCultureVariation table so here we need to
// only process edited cultures based on the current value type and how the property varies.
// The above logic will still persist the currently saved property value for each culture in case the admin
// decides to swap the property's variance again, in which case the edited flag will be recalculated.
if (property.PropertyType.VariesByCulture() && isInvariantValue || !property.PropertyType.VariesByCulture() && isCultureValue)
continue;
// use explicit equals here, else object comparison fails at comparing eg strings
var sameValues = propertyValue.PublishedValue == null ? propertyValue.EditedValue == null : propertyValue.PublishedValue.Equals(propertyValue.EditedValue);
edited |= !sameValues;
if (entityVariesByCulture // cultures can be edited, ie CultureNeutral is supported
&& propertyValue.Culture != null && propertyValue.Segment == null // and value is CultureNeutral
&& !sameValues) // and edited and published are different
if (entityVariesByCulture && !sameValues)
{
editedCultures.Add(propertyValue.Culture); // report culture as edited
}
if (isCultureValue)
{
editedCultures.Add(propertyValue.Culture); // report culture as edited
}
else if (isInvariantValue)
{
// flag culture as edited if it contains an edited invariant property
if (defaultCulture == null)
defaultCulture = languageRepository.GetDefaultIsoCode();
// flag culture as edited if it contains an edited invariant property
if (propertyValue.Culture == null //invariant property
&& !sameValues // and edited and published are different
&& entityVariesByCulture) //only when the entity is variant
{
if (defaultCulture == null)
defaultCulture = languageRepository.GetDefaultIsoCode();
editedCultures.Add(defaultCulture);
editedCultures.Add(defaultCulture);
}
}
}
}
@@ -167,7 +184,7 @@ namespace Umbraco.Core.Persistence.Factories
{
// not publishing = only deal with edit values
if (propertyValue.EditedValue != null)
propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue));
propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue));
}
edited = true;
}
@@ -512,31 +512,16 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
foreach (var a in allPropertyDataDtos)
a.PropertyTypeDto = indexedPropertyTypeDtos[a.PropertyTypeId];
// prefetch configuration for tag properties
var tagEditors = new Dictionary<string, TagConfiguration>();
foreach (var propertyTypeDto in indexedPropertyTypeDtos.Values)
{
var editorAlias = propertyTypeDto.DataTypeDto.EditorAlias;
var editorAttribute = PropertyEditors[editorAlias].GetTagAttribute();
if (editorAttribute == null) continue;
var tagConfigurationSource = propertyTypeDto.DataTypeDto.Configuration;
var tagConfiguration = string.IsNullOrWhiteSpace(tagConfigurationSource)
? new TagConfiguration()
: JsonConvert.DeserializeObject<TagConfiguration>(tagConfigurationSource);
if (tagConfiguration.Delimiter == default) tagConfiguration.Delimiter = editorAttribute.Delimiter;
tagEditors[editorAlias] = tagConfiguration;
}
// now we have
// - the definitions
// - all property data dtos
// - tag editors
// - tag editors (Actually ... no we don't since i removed that code, but we don't need them anyways it seems)
// and we need to build the proper property collections
return GetPropertyCollections(temps, allPropertyDataDtos, tagEditors);
return GetPropertyCollections(temps, allPropertyDataDtos);
}
private IDictionary<int, PropertyCollection> GetPropertyCollections<T>(List<TempContent<T>> temps, IEnumerable<PropertyDataDto> allPropertyDataDtos, Dictionary<string, TagConfiguration> tagConfigurations)
private IDictionary<int, PropertyCollection> GetPropertyCollections<T>(List<TempContent<T>> temps, IEnumerable<PropertyDataDto> allPropertyDataDtos)
where T : class, IContentBase
{
var result = new Dictionary<int, PropertyCollection>();
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using NPoco;
using Umbraco.Core.Cache;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
@@ -90,7 +91,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
contentType = ContentTypeFactory.BuildContentTypeEntity(contentTypeDto);
else if (contentTypeDto.NodeDto.NodeObjectType == Constants.ObjectTypes.MemberType)
contentType = ContentTypeFactory.BuildMemberTypeEntity(contentTypeDto);
else throw new Exception("panic");
else throw new PanicException($"The node object type {contentTypeDto.NodeDto.NodeObjectType} is not supported");
contentTypes.Add(contentType.Id, contentType);
// map allowed content types
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using NPoco;
using Umbraco.Core.Cache;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
@@ -18,8 +19,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
/// </summary>
internal class ContentTypeRepository : ContentTypeRepositoryBase<IContentType>, IContentTypeRepository
{
public ContentTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
: base(scopeAccessor, cache, logger, commonRepository)
public ContentTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository)
: base(scopeAccessor, cache, logger, commonRepository, languageRepository)
{ }
protected override bool SupportsPublishing => ContentType.SupportsPublishingConst;
@@ -56,7 +57,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
// the cache policy will always want everything
// even GetMany(ids) gets everything and filters afterwards
if (ids.Any()) throw new Exception("panic");
if (ids.Any()) throw new PanicException("There can be no ids specified");
return CommonRepository.GetAllTypes().OfType<IContentType>();
}
@@ -15,6 +15,7 @@ using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics;
namespace Umbraco.Core.Persistence.Repositories.Implement
{
@@ -26,14 +27,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
internal abstract class ContentTypeRepositoryBase<TEntity> : NPocoRepositoryBase<int, TEntity>, IReadRepository<Guid, TEntity>
where TEntity : class, IContentTypeComposition
{
protected ContentTypeRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
protected ContentTypeRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository)
: base(scopeAccessor, cache, logger)
{
CommonRepository = commonRepository;
LanguageRepository = languageRepository;
}
protected IContentTypeCommonRepository CommonRepository { get; }
protected ILanguageRepository LanguageRepository { get; }
protected abstract bool SupportsPublishing { get; }
public IEnumerable<MoveEventInfo<TEntity>> Move(TEntity moving, EntityContainer container)
@@ -98,6 +100,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected void PersistNewBaseContentType(IContentTypeComposition entity)
{
ValidateVariations(entity);
var dto = ContentTypeFactory.BuildContentTypeDto(entity);
//Cannot add a duplicate content type
@@ -163,11 +167,11 @@ AND umbracoNode.nodeObjectType = @objectType",
foreach (var allowedContentType in entity.AllowedContentTypes)
{
Database.Insert(new ContentTypeAllowedContentTypeDto
{
Id = entity.Id,
AllowedId = allowedContentType.Id.Value,
SortOrder = allowedContentType.SortOrder
});
{
Id = entity.Id,
AllowedId = allowedContentType.Id.Value,
SortOrder = allowedContentType.SortOrder
});
}
@@ -214,6 +218,8 @@ AND umbracoNode.nodeObjectType = @objectType",
protected void PersistUpdatedBaseContentType(IContentTypeComposition entity)
{
ValidateVariations(entity);
var dto = ContentTypeFactory.BuildContentTypeDto(entity);
// ensure the alias is not used already
@@ -370,7 +376,7 @@ AND umbracoNode.id <> @id",
foreach (var propertyGroup in entity.PropertyGroups)
{
// insert or update group
var groupDto = PropertyGroupFactory.BuildGroupDto(propertyGroup,entity.Id);
var groupDto = PropertyGroupFactory.BuildGroupDto(propertyGroup, entity.Id);
var groupId = propertyGroup.HasIdentity
? Database.Update(groupDto)
: Convert.ToInt32(Database.Insert(groupDto));
@@ -388,7 +394,7 @@ AND umbracoNode.id <> @id",
//check if the content type variation has been changed
var contentTypeVariationDirty = entity.IsPropertyDirty("Variations");
var oldContentTypeVariation = (ContentVariation) dtoPk.Variations;
var oldContentTypeVariation = (ContentVariation)dtoPk.Variations;
var newContentTypeVariation = entity.Variations;
var contentTypeVariationChanging = contentTypeVariationDirty && oldContentTypeVariation != newContentTypeVariation;
if (contentTypeVariationChanging)
@@ -449,7 +455,7 @@ AND umbracoNode.id <> @id",
// via composition, with their original variations (ie not filtered by this
// content type variations - we need this true value to make decisions.
foreach (var propertyType in ((ContentTypeCompositionBase) entity).RawComposedPropertyTypes)
foreach (var propertyType in ((ContentTypeCompositionBase)entity).RawComposedPropertyTypes)
{
if (propertyType.VariesBySegment() || newContentTypeVariation.VariesBySegment())
throw new NotSupportedException(); // TODO: support this
@@ -518,6 +524,25 @@ AND umbracoNode.id <> @id",
CommonRepository.ClearCache(); // always
}
/// <summary>
/// Ensures that no property types are flagged for a variance that is not supported by the content type itself
/// </summary>
/// <param name="entity"></param>
private void ValidateVariations(IContentTypeComposition entity)
{
//if the entity does not vary at all, then the property cannot have a variance value greater than it
if (entity.Variations == ContentVariation.Nothing)
{
foreach (var prop in entity.PropertyTypes)
{
if (prop.IsPropertyDirty(nameof(prop.Variations)) && prop.Variations > entity.Variations)
throw new InvalidOperationException($"The property {prop.Alias} cannot have variations of {prop.Variations} with the content type variations of {entity.Variations}");
}
}
}
private IEnumerable<IContentTypeComposition> GetImpactedContentTypes(IContentTypeComposition contentType, IEnumerable<IContentTypeComposition> all)
{
var impact = new List<IContentTypeComposition>();
@@ -525,12 +550,12 @@ AND umbracoNode.id <> @id",
var tree = new Dictionary<int, List<IContentTypeComposition>>();
foreach (var x in all)
foreach (var y in x.ContentTypeComposition)
{
if (!tree.TryGetValue(y.Id, out var list))
list = tree[y.Id] = new List<IContentTypeComposition>();
list.Add(x);
}
foreach (var y in x.ContentTypeComposition)
{
if (!tree.TryGetValue(y.Id, out var list))
list = tree[y.Id] = new List<IContentTypeComposition>();
list.Add(x);
}
var nset = new List<IContentTypeComposition>();
do
@@ -572,7 +597,7 @@ AND umbracoNode.id <> @id",
// new property type, ignore
if (!oldVariations.TryGetValue(propertyType.Id, out var oldVariationB))
continue;
var oldVariation = (ContentVariation) oldVariationB; // NPoco cannot fetch directly
var oldVariation = (ContentVariation)oldVariationB; // NPoco cannot fetch directly
// only those property types that *actually* changed
var newVariation = propertyType.Variations;
@@ -636,7 +661,7 @@ AND umbracoNode.id <> @id",
var impactedL = impacted.Select(x => x.Id).ToList();
//Group by the "To" variation so we can bulk update in the correct batches
foreach(var grouping in propertyTypeChanges.GroupBy(x => x.Value.ToVariation))
foreach (var grouping in propertyTypeChanges.GroupBy(x => x.Value.ToVariation))
{
var propertyTypeIds = grouping.Select(x => x.Key).ToList();
var toVariation = grouping.Key;
@@ -646,10 +671,12 @@ AND umbracoNode.id <> @id",
case ContentVariation.Culture:
CopyPropertyData(null, defaultLanguageId, propertyTypeIds, impactedL);
CopyTagData(null, defaultLanguageId, propertyTypeIds, impactedL);
RenormalizeDocumentEditedFlags(propertyTypeIds, impactedL);
break;
case ContentVariation.Nothing:
CopyPropertyData(defaultLanguageId, null, propertyTypeIds, impactedL);
CopyTagData(defaultLanguageId, null, propertyTypeIds, impactedL);
RenormalizeDocumentEditedFlags(propertyTypeIds, impactedL);
break;
case ContentVariation.CultureAndSegment:
case ContentVariation.Segment:
@@ -963,6 +990,205 @@ AND umbracoNode.id <> @id",
Database.Execute(sqlDelete);
}
}
/// <summary>
/// Re-normalizes the edited value in the umbracoDocumentCultureVariation and umbracoDocument table when variations are changed
/// </summary>
/// <param name="propertyTypeIds"></param>
/// <param name="contentTypeIds"></param>
/// <remarks>
/// If this is not done, then in some cases the "edited" value for a particular culture for a document will remain true when it should be false
/// if the property was changed to invariant. In order to do this we need to recalculate this value based on the values stored for each
/// property, culture and current/published version.
/// </remarks>
private void RenormalizeDocumentEditedFlags(IReadOnlyCollection<int> propertyTypeIds, IReadOnlyCollection<int> contentTypeIds = null)
{
var defaultLang = LanguageRepository.GetDefaultId();
//This will build up a query to get the property values of both the current and the published version so that we can check
//based on the current variance of each item to see if it's 'edited' value should be true/false.
var whereInArgsCount = propertyTypeIds.Count + (contentTypeIds?.Count ?? 0);
if (whereInArgsCount > 2000)
throw new NotSupportedException("Too many property/content types.");
var propertySql = Sql()
.Select<PropertyDataDto>()
.AndSelect<ContentVersionDto>(x => x.NodeId, x => x.Current)
.AndSelect<DocumentVersionDto>(x => x.Published)
.AndSelect<PropertyTypeDto>(x => x.Variations)
.From<PropertyDataDto>()
.InnerJoin<ContentVersionDto>().On<ContentVersionDto, PropertyDataDto>((left, right) => left.Id == right.VersionId)
.InnerJoin<PropertyTypeDto>().On<PropertyTypeDto, PropertyDataDto>((left, right) => left.Id == right.PropertyTypeId);
if (contentTypeIds != null)
{
propertySql.InnerJoin<ContentDto>().On<ContentDto, ContentVersionDto>((c, cversion) => c.NodeId == cversion.NodeId);
}
propertySql.LeftJoin<DocumentVersionDto>().On<DocumentVersionDto, ContentVersionDto>((docversion, cversion) => cversion.Id == docversion.Id)
.Where<DocumentVersionDto, ContentVersionDto>((docversion, cversion) => cversion.Current || docversion.Published)
.WhereIn<PropertyDataDto>(x => x.PropertyTypeId, propertyTypeIds);
if (contentTypeIds != null)
{
propertySql.WhereIn<ContentDto>(x => x.ContentTypeId, contentTypeIds);
}
propertySql
.OrderBy<ContentVersionDto>(x => x.NodeId)
.OrderBy<PropertyDataDto>(x => x.PropertyTypeId, x => x.LanguageId, x => x.VersionId);
//keep track of this node/lang to mark or unmark a culture as edited
var editedLanguageVersions = new Dictionary<(int nodeId, int? langId), bool>();
//keep track of which node to mark or unmark as edited
var editedDocument = new Dictionary<int, bool>();
var nodeId = -1;
var propertyTypeId = -1;
PropertyValueVersionDto pubRow = null;
//This is a reader (Query), we are not fetching this all into memory so we cannot make any changes during this iteration, we are just collecting data.
//Published data will always come before Current data based on the version id sort.
//There will only be one published row (max) and one current row per property.
foreach (var row in Database.Query<PropertyValueVersionDto>(propertySql))
{
//make sure to reset on each node/property change
if (nodeId != row.NodeId || propertyTypeId != row.PropertyTypeId)
{
nodeId = row.NodeId;
propertyTypeId = row.PropertyTypeId;
pubRow = null;
}
if (row.Published)
pubRow = row;
if (row.Current)
{
var propVariations = (ContentVariation)row.Variations;
//if this prop doesn't vary but the row has a lang assigned or vice versa, flag this as not edited
if (!propVariations.VariesByCulture() && row.LanguageId.HasValue
|| propVariations.VariesByCulture() && !row.LanguageId.HasValue)
{
//Flag this as not edited for this node/lang if the key doesn't exist
if (!editedLanguageVersions.TryGetValue((row.NodeId, row.LanguageId), out _))
editedLanguageVersions.Add((row.NodeId, row.LanguageId), false);
//mark as false if the item doesn't exist, else coerce to true
editedDocument[row.NodeId] = editedDocument.TryGetValue(row.NodeId, out var edited) ? (edited |= false) : false;
}
else if (pubRow == null)
{
//this would mean that that this property is 'edited' since there is no published version
editedLanguageVersions[(row.NodeId, row.LanguageId)] = true;
editedDocument[row.NodeId] = true;
}
//compare the property values, if they differ from versions then flag the current version as edited
else if (IsPropertyValueChanged(pubRow, row))
{
//Here we would check if the property is invariant, in which case the edited language should be indicated by the default lang
editedLanguageVersions[(row.NodeId, !propVariations.VariesByCulture() ? defaultLang : row.LanguageId)] = true;
editedDocument[row.NodeId] = true;
}
//reset
pubRow = null;
}
}
//lookup all matching rows in umbracoDocumentCultureVariation
var docCultureVariationsToUpdate = editedLanguageVersions.InGroupsOf(2000)
.SelectMany(_ => Database.Fetch<DocumentCultureVariationDto>(
Sql().Select<DocumentCultureVariationDto>().From<DocumentCultureVariationDto>()
.WhereIn<DocumentCultureVariationDto>(x => x.LanguageId, editedLanguageVersions.Keys.Select(x => x.langId).ToList())
.WhereIn<DocumentCultureVariationDto>(x => x.NodeId, editedLanguageVersions.Keys.Select(x => x.nodeId))))
//convert to dictionary with the same key type
.ToDictionary(x => (x.NodeId, (int?)x.LanguageId), x => x);
var toUpdate = new List<DocumentCultureVariationDto>();
foreach (var ev in editedLanguageVersions)
{
if (docCultureVariationsToUpdate.TryGetValue(ev.Key, out var docVariations))
{
//check if it needs updating
if (docVariations.Edited != ev.Value)
{
docVariations.Edited = ev.Value;
toUpdate.Add(docVariations);
}
}
else if (ev.Key.langId.HasValue)
{
//This should never happen! If a property culture is flagged as edited then the culture must exist at the document level
throw new PanicException($"The existing DocumentCultureVariationDto was not found for node {ev.Key.nodeId} and language {ev.Key.langId}");
}
}
//Now bulk update the table DocumentCultureVariationDto, once for edited = true, another for edited = false
foreach (var editValue in toUpdate.GroupBy(x => x.Edited))
{
Database.Execute(Sql().Update<DocumentCultureVariationDto>(u => u.Set(x => x.Edited, editValue.Key))
.WhereIn<DocumentCultureVariationDto>(x => x.Id, editValue.Select(x => x.Id)));
}
//Now bulk update the umbracoDocument table
foreach (var editValue in editedDocument.GroupBy(x => x.Value))
{
Database.Execute(Sql().Update<DocumentDto>(u => u.Set(x => x.Edited, editValue.Key))
.WhereIn<DocumentDto>(x => x.NodeId, editValue.Select(x => x.Key)));
}
}
private static bool IsPropertyValueChanged(PropertyValueVersionDto pubRow, PropertyValueVersionDto row)
{
return !pubRow.TextValue.IsNullOrWhiteSpace() && pubRow.TextValue != row.TextValue
|| !pubRow.VarcharValue.IsNullOrWhiteSpace() && pubRow.VarcharValue != row.VarcharValue
|| pubRow.DateValue.HasValue && pubRow.DateValue != row.DateValue
|| pubRow.DecimalValue.HasValue && pubRow.DecimalValue != row.DecimalValue
|| pubRow.IntValue.HasValue && pubRow.IntValue != row.IntValue;
}
private class NameCompareDto
{
public int NodeId { get; set; }
public int CurrentVersion { get; set; }
public int LanguageId { get; set; }
public string CurrentName { get; set; }
public string PublishedName { get; set; }
public int? PublishedVersion { get; set; }
public int Id { get; set; } // the Id of the DocumentCultureVariationDto
public bool Edited { get; set; }
}
private class PropertyValueVersionDto
{
public int VersionId { get; set; }
public int PropertyTypeId { get; set; }
public int? LanguageId { get; set; }
public string Segment { get; set; }
public int? IntValue { get; set; }
private decimal? _decimalValue;
[Column("decimalValue")]
public decimal? DecimalValue
{
get => _decimalValue;
set => _decimalValue = value?.Normalize();
}
public DateTime? DateValue { get; set; }
public string VarcharValue { get; set; }
public string TextValue { get; set; }
public int NodeId { get; set; }
public bool Current { get; set; }
public bool Published { get; set; }
public byte Variations { get; set; }
}
private void DeletePropertyType(int contentTypeId, int propertyTypeId)
@@ -386,7 +386,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
Database.BulkInsertRecords(GetContentVariationDtos(entity, publishing));
// insert document variations
Database.BulkInsertRecords(GetDocumentVariationDtos(entity, publishing, editedCultures));
Database.BulkInsertRecords(GetDocumentVariationDtos(entity, editedCultures));
}
// refresh content
@@ -511,7 +511,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
entity.VersionId = documentVersionDto.Id = contentVersionDto.Id; // get the new id
documentVersionDto.Published = false; // non-published version
Database.Insert(documentVersionDto);
Database.Insert(documentVersionDto);
}
// replace the property data (rather than updating)
@@ -571,7 +571,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
Database.BulkInsertRecords(GetContentVariationDtos(entity, publishing));
// insert document variations
Database.BulkInsertRecords(GetDocumentVariationDtos(entity, publishing, editedCultures));
Database.BulkInsertRecords(GetDocumentVariationDtos(entity, editedCultures));
}
// refresh content
@@ -1297,25 +1297,28 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
};
}
private IEnumerable<DocumentCultureVariationDto> GetDocumentVariationDtos(IContent content, bool publishing, HashSet<string> editedCultures)
private IEnumerable<DocumentCultureVariationDto> GetDocumentVariationDtos(IContent content, HashSet<string> editedCultures)
{
var allCultures = content.AvailableCultures.Union(content.PublishedCultures); // union = distinct
foreach (var culture in allCultures)
yield return new DocumentCultureVariationDto
{
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),
// note: can't use IsCultureEdited at that point - hasn't been updated yet - see PersistUpdatedItem
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)))
};
yield return dto;
}
}
private class ContentVariation
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using NPoco;
using Umbraco.Core.Cache;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
@@ -17,8 +18,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
/// </summary>
internal class MediaTypeRepository : ContentTypeRepositoryBase<IMediaType>, IMediaTypeRepository
{
public MediaTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
: base(scopeAccessor, cache, logger, commonRepository)
public MediaTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository)
: base(scopeAccessor, cache, logger, commonRepository, languageRepository)
{ }
protected override bool SupportsPublishing => MediaType.SupportsPublishingConst;
@@ -50,7 +51,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
// the cache policy will always want everything
// even GetMany(ids) gets everything and filters afterwards
if (ids.Any()) throw new Exception("panic");
if (ids.Any()) throw new PanicException("There can be no ids specified");
return CommonRepository.GetAllTypes().OfType<IMediaType>();
}
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using NPoco;
using Umbraco.Core.Cache;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
@@ -18,8 +19,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
/// </summary>
internal class MemberTypeRepository : ContentTypeRepositoryBase<IMemberType>, IMemberTypeRepository
{
public MemberTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
: base(scopeAccessor, cache, logger, commonRepository)
public MemberTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository)
: base(scopeAccessor, cache, logger, commonRepository, languageRepository)
{ }
protected override bool SupportsPublishing => MemberType.SupportsPublishingConst;
@@ -57,7 +58,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
// the cache policy will always want everything
// even GetMany(ids) gets everything and filters afterwards
if (ids.Any()) throw new Exception("panic");
if (ids.Any()) throw new PanicException("There can be no ids specified");
return CommonRepository.GetAllTypes().OfType<IMemberType>();
}
@@ -55,6 +55,7 @@ namespace Umbraco.Core.PropertyEditors
/// <summary>
/// Gets the type of the dynamic configuration provider.
/// </summary>
//TODO: This is not used and should be implemented in a nicer way, see https://github.com/umbraco/Umbraco-CMS/issues/6017#issuecomment-516253562
public Type TagsConfigurationProviderType { get; }
}
}
@@ -886,6 +886,8 @@ namespace Umbraco.Core.Services.Implement
{
scope.WriteLock(Constants.Locks.ContentTree);
var allLangs = _languageRepository.GetMany().ToList();
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
@@ -894,13 +896,13 @@ namespace Umbraco.Core.Services.Implement
// if culture is '*', then publish them all (including variants)
//this will create the correct culture impact even if culture is * or null
var impact = CultureImpact.Create(culture, _languageRepository.IsDefault(culture), content);
var impact = CultureImpact.Create(culture, IsDefaultCulture(allLangs, culture), content);
// publish the culture(s)
// we don't care about the response here, this response will be rechecked below but we need to set the culture info values now.
content.PublishCulture(impact);
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId, raiseEvents);
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId, raiseEvents);
scope.Complete();
return result;
}
@@ -921,6 +923,8 @@ namespace Umbraco.Core.Services.Implement
{
scope.WriteLock(Constants.Locks.ContentTree);
var allLangs = _languageRepository.GetMany().ToList();
var evtMsgs = EventMessagesFactory.Get();
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
@@ -928,25 +932,23 @@ namespace Umbraco.Core.Services.Implement
var varies = content.ContentType.VariesByCulture();
if (cultures.Length == 0)
if (cultures.Length == 0 && !varies)
{
//no cultures specified and doesn't vary, so publish it, else nothing to publish
return !varies
? SaveAndPublish(content, userId: userId, raiseEvents: raiseEvents)
: new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content);
return SaveAndPublish(content, userId: userId, raiseEvents: raiseEvents);
}
if (cultures.Any(x => x == null || x == "*"))
throw new InvalidOperationException("Only valid cultures are allowed to be used in this method, wildcards or nulls are not allowed");
var impacts = cultures.Select(x => CultureImpact.Explicit(x, _languageRepository.IsDefault(x)));
var impacts = cultures.Select(x => CultureImpact.Explicit(x, IsDefaultCulture(allLangs, x)));
// publish the culture(s)
// we don't care about the response here, this response will be rechecked below but we need to set the culture info values now.
foreach (var impact in impacts)
content.PublishCulture(impact);
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId, raiseEvents);
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId, raiseEvents);
scope.Complete();
return result;
}
@@ -986,6 +988,8 @@ namespace Umbraco.Core.Services.Implement
{
scope.WriteLock(Constants.Locks.ContentTree);
var allLangs = _languageRepository.GetMany().ToList();
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
@@ -993,26 +997,39 @@ namespace Umbraco.Core.Services.Implement
// all cultures = unpublish whole
if (culture == "*" || (!content.ContentType.VariesByCulture() && culture == null))
{
// It's important to understand that when the document varies by culture but the "*" is used,
// we are just unpublishing the whole document but leaving all of the culture's as-is. This is expected
// because we don't want to actually unpublish every culture and then the document, we just want everything
// to be non-routable so that when it's re-published all variants were as they were.
content.PublishedState = PublishedState.Unpublishing;
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId);
scope.Complete();
return result;
}
else
{
// If the culture we want to unpublish was already unpublished, nothing to do.
// To check for that we need to lookup the persisted content item
var persisted = content.HasIdentity ? GetById(content.Id) : null;
// Unpublish the culture, this will change the document state to Publishing! ... which is expected because this will
// essentially be re-publishing the document with the requested culture removed.
// The call to CommitDocumentChangesInternal will perform all the checks like if this is a mandatory culture or the last culture being unpublished
// and will then unpublish the document accordingly.
// If the result of this is false it means there was no culture to unpublish (i.e. it was already unpublished or it did not exist)
var removed = content.UnpublishCulture(culture);
if (persisted != null && !persisted.IsCulturePublished(culture))
//save and publish any changes
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId);
scope.Complete();
// In one case the result will be PublishStatusType.FailedPublishNothingToPublish which means that no cultures
// were specified to be published which will be the case when removed is false. In that case
// we want to swap the result type to PublishResultType.SuccessUnpublishAlready (that was the expectation before).
if (result.Result == PublishResultType.FailedPublishNothingToPublish && !removed)
return new PublishResult(PublishResultType.SuccessUnpublishAlready, evtMsgs, content);
// unpublish the culture
content.UnpublishCulture(culture);
return result;
}
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId);
scope.Complete();
return result;
}
}
/// <summary>
@@ -1047,15 +1064,35 @@ namespace Umbraco.Core.Services.Implement
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId, raiseEvents);
var allLangs = _languageRepository.GetMany().ToList();
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId, raiseEvents);
scope.Complete();
return result;
}
}
/// <summary>
/// Handles a lot of business logic cases for how the document should be persisted
/// </summary>
/// <param name="scope"></param>
/// <param name="content"></param>
/// <param name="saveEventArgs"></param>
/// <param name="userId"></param>
/// <param name="raiseEvents"></param>
/// <param name="branchOne"></param>
/// <param name="branchRoot"></param>
/// <returns></returns>
/// <remarks>
/// <para>
/// Business logic cases such: as unpublishing a mandatory culture, or unpublishing the last culture, checking for pending scheduled publishing, etc... is dealt with in this method.
/// There is quite a lot of cases to take into account along with logic that needs to deal with scheduled saving/publishing, branch saving/publishing, etc...
/// </para>
/// </remarks>
private PublishResult CommitDocumentChangesInternal(IScope scope, IContent content,
ContentSavingEventArgs saveEventArgs,
int userId = Constants.Security.SuperUserId, bool raiseEvents = true, bool branchOne = false, bool branchRoot = false)
ContentSavingEventArgs saveEventArgs, IReadOnlyCollection<ILanguage> allLangs,
int userId = Constants.Security.SuperUserId,
bool raiseEvents = true, bool branchOne = false, bool branchRoot = false)
{
if (scope == null) throw new ArgumentNullException(nameof(scope));
if (content == null) throw new ArgumentNullException(nameof(content));
@@ -1070,8 +1107,8 @@ namespace Umbraco.Core.Services.Implement
if (content.PublishedState != PublishedState.Publishing && content.PublishedState != PublishedState.Unpublishing)
content.PublishedState = PublishedState.Publishing;
// state here is either Publishing or Unpublishing
// (even though, Publishing to unpublish a culture may end up unpublishing everything)
// State here is either Publishing or Unpublishing
// Publishing to unpublish a culture may end up unpublishing everything so these flags can be flipped later
var publishing = content.PublishedState == PublishedState.Publishing;
var unpublishing = content.PublishedState == PublishedState.Unpublishing;
@@ -1088,6 +1125,18 @@ namespace Umbraco.Core.Services.Implement
var changeType = isNew ? TreeChangeTypes.RefreshNode : TreeChangeTypes.RefreshBranch;
var previouslyPublished = content.HasIdentity && content.Published;
//inline method to persist the document with the documentRepository since this logic could be called a couple times below
void SaveDocument(IContent c)
{
// save, always
if (c.HasIdentity == false)
c.CreatorId = userId;
c.WriterId = userId;
// saving does NOT change the published version, unless PublishedState is Publishing or Unpublishing
_documentRepository.Save(c);
}
if (publishing)
{
//determine cultures publishing/unpublishing which will be based on previous calls to content.PublishCulture and ClearPublishInfo
@@ -1097,11 +1146,25 @@ namespace Umbraco.Core.Services.Implement
: null;
// ensure that the document can be published, and publish handling events, business rules, etc
publishResult = StrategyCanPublish(scope, content, /*checkPath:*/ (!branchOne || branchRoot), culturesPublishing, culturesUnpublishing, evtMsgs, saveEventArgs);
publishResult = StrategyCanPublish(scope, content, /*checkPath:*/ (!branchOne || branchRoot), culturesPublishing, culturesUnpublishing, evtMsgs, saveEventArgs, allLangs);
if (publishResult.Success)
{
// note: StrategyPublish flips the PublishedState to Publishing!
publishResult = StrategyPublish(content, culturesPublishing, culturesUnpublishing, evtMsgs);
//check if a culture has been unpublished and if there are no cultures left, and then unpublish document as a whole
if (publishResult.Result == PublishResultType.SuccessUnpublishCulture && content.PublishCultureInfos.Count == 0)
{
// This is a special case! We are unpublishing the last culture and to persist that we need to re-publish without any cultures
// so the state needs to remain Publishing to do that. However, we then also need to unpublish the document and to do that
// the state needs to be Unpublishing and it cannot be both. This state is used within the documentRepository to know how to
// persist certain things. So before proceeding below, we need to save the Publishing state to publish no cultures, then we can
// mark the document for Unpublishing.
SaveDocument(content);
//set the flag to unpublish and continue
unpublishing = content.Published; // if not published yet, nothing to do
}
}
else
{
@@ -1162,13 +1225,8 @@ namespace Umbraco.Core.Services.Implement
}
}
// save, always
if (content.HasIdentity == false)
content.CreatorId = userId;
content.WriterId = userId;
// saving does NOT change the published version, unless PublishedState is Publishing or Unpublishing
_documentRepository.Save(content);
//Persist the document
SaveDocument(content);
// raise the Saved event, always
if (raiseEvents)
@@ -1186,17 +1244,34 @@ namespace Umbraco.Core.Services.Implement
if (culturesUnpublishing != null)
{
//If we are here, it means we tried unpublishing a culture but it was mandatory so now everything is unpublished
var langs = string.Join(", ", _languageRepository.GetMany()
// This will mean that that we unpublished a mandatory culture or we unpublished the last culture.
var langs = string.Join(", ", allLangs
.Where(x => culturesUnpublishing.InvariantContains(x.IsoCode))
.Select(x => x.CultureName));
Audit(AuditType.UnpublishVariant, userId, content.Id, $"Unpublished languages: {langs}", langs);
//log that the whole content item has been unpublished due to mandatory culture unpublished
Audit(AuditType.Unpublish, userId, content.Id, "Unpublished (mandatory language unpublished)");
}
else
Audit(AuditType.Unpublish, userId, content.Id);
if (publishResult == null)
throw new PanicException("publishResult == null - should not happen");
switch(publishResult.Result)
{
case PublishResultType.FailedPublishMandatoryCultureMissing:
//occurs when a mandatory culture was unpublished (which means we tried publishing the document without a mandatory culture)
//log that the whole content item has been unpublished due to mandatory culture unpublished
Audit(AuditType.Unpublish, userId, content.Id, "Unpublished (mandatory language unpublished)");
return new PublishResult(PublishResultType.SuccessUnpublishMandatoryCulture, evtMsgs, content);
case PublishResultType.SuccessUnpublishCulture:
//occurs when the last culture is unpublished
Audit(AuditType.Unpublish, userId, content.Id, "Unpublished (last language unpublished)");
return new PublishResult(PublishResultType.SuccessUnpublishLastCulture, evtMsgs, content);
}
}
Audit(AuditType.Unpublish, userId, content.Id);
return new PublishResult(PublishResultType.SuccessUnpublish, evtMsgs, content);
}
@@ -1236,7 +1311,7 @@ namespace Umbraco.Core.Services.Implement
case PublishResultType.SuccessPublishCulture:
if (culturesPublishing != null)
{
var langs = string.Join(", ", _languageRepository.GetMany()
var langs = string.Join(", ", allLangs
.Where(x => culturesPublishing.InvariantContains(x.IsoCode))
.Select(x => x.CultureName));
Audit(AuditType.PublishVariant, userId, content.Id, $"Published languages: {langs}", langs);
@@ -1245,7 +1320,7 @@ namespace Umbraco.Core.Services.Implement
case PublishResultType.SuccessUnpublishCulture:
if (culturesUnpublishing != null)
{
var langs = string.Join(", ", _languageRepository.GetMany()
var langs = string.Join(", ", allLangs
.Where(x => culturesUnpublishing.InvariantContains(x.IsoCode))
.Select(x => x.CultureName));
Audit(AuditType.UnpublishVariant, userId, content.Id, $"Unpublished languages: {langs}", langs);
@@ -1259,14 +1334,14 @@ namespace Umbraco.Core.Services.Implement
// should not happen
if (branchOne && !branchRoot)
throw new Exception("panic");
throw new PanicException("branchOne && !branchRoot - should not happen");
//if publishing didn't happen or if it has failed, we still need to log which cultures were saved
if (!branchOne && (publishResult == null || !publishResult.Success))
{
if (culturesChanging != null)
{
var langs = string.Join(", ", _languageRepository.GetMany()
var langs = string.Join(", ", allLangs
.Where(x => culturesChanging.InvariantContains(x.IsoCode))
.Select(x => x.CultureName));
Audit(AuditType.SaveVariant, userId, content.Id, $"Saved languages: {langs}", langs);
@@ -1297,6 +1372,8 @@ namespace Umbraco.Core.Services.Implement
{
scope.WriteLock(Constants.Locks.ContentTree);
var allLangs = _languageRepository.GetMany().ToList();
foreach (var d in _documentRepository.GetContentForRelease(date))
{
PublishResult result;
@@ -1325,7 +1402,7 @@ namespace Umbraco.Core.Services.Implement
//publish the culture values and validate the property values, if validation fails, log the invalid properties so the develeper has an idea of what has failed
Property[] invalidProperties = null;
var impact = CultureImpact.Explicit(culture, _languageRepository.IsDefault(culture));
var impact = CultureImpact.Explicit(culture, IsDefaultCulture(allLangs, culture));
var tryPublish = d.PublishCulture(impact) && _propertyValidationService.Value.IsPropertyDataValid(d, out invalidProperties, impact);
if (invalidProperties != null && invalidProperties.Length > 0)
Logger.Warn<ContentService>("Scheduled publishing will fail for document {DocumentId} and culture {Culture} because of invalid properties {InvalidProperties}",
@@ -1340,7 +1417,7 @@ namespace Umbraco.Core.Services.Implement
else if (!publishing)
result = new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, d);
else
result = CommitDocumentChangesInternal(scope, d, saveEventArgs, d.WriterId);
result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs, d.WriterId);
if (result.Success == false)
@@ -1390,7 +1467,7 @@ namespace Umbraco.Core.Services.Implement
d.UnpublishCulture(c);
}
result = CommitDocumentChangesInternal(scope, d, saveEventArgs, d.WriterId);
result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs, d.WriterId);
if (result.Success == false)
Logger.Error<ContentService>(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
yield return result;
@@ -1416,7 +1493,7 @@ namespace Umbraco.Core.Services.Implement
}
// utility 'PublishCultures' func used by SaveAndPublishBranch
private bool SaveAndPublishBranch_PublishCultures(IContent content, HashSet<string> culturesToPublish)
private bool SaveAndPublishBranch_PublishCultures(IContent content, HashSet<string> culturesToPublish, IReadOnlyCollection<ILanguage> allLangs)
{
//TODO: This does not support being able to return invalid property details to bubble up to the UI
@@ -1426,7 +1503,7 @@ namespace Umbraco.Core.Services.Implement
{
return culturesToPublish.All(culture =>
{
var impact = CultureImpact.Create(culture, _languageRepository.IsDefault(culture), content);
var impact = CultureImpact.Create(culture, IsDefaultCulture(allLangs, culture), content);
return content.PublishCulture(impact) && _propertyValidationService.Value.IsPropertyDataValid(content, out _, impact);
});
}
@@ -1535,7 +1612,7 @@ namespace Umbraco.Core.Services.Implement
internal IEnumerable<PublishResult> SaveAndPublishBranch(IContent document, bool force,
Func<IContent, HashSet<string>> shouldPublish,
Func<IContent, HashSet<string>, bool> publishCultures,
Func<IContent, HashSet<string>, IReadOnlyCollection<ILanguage>, bool> publishCultures,
int userId = Constants.Security.SuperUserId)
{
if (shouldPublish == null) throw new ArgumentNullException(nameof(shouldPublish));
@@ -1549,6 +1626,8 @@ namespace Umbraco.Core.Services.Implement
{
scope.WriteLock(Constants.Locks.ContentTree);
var allLangs = _languageRepository.GetMany().ToList();
if (!document.HasIdentity)
throw new InvalidOperationException("Cannot not branch-publish a new document.");
@@ -1557,7 +1636,7 @@ namespace Umbraco.Core.Services.Implement
throw new InvalidOperationException("Cannot mix PublishCulture and SaveAndPublishBranch.");
// deal with the branch root - if it fails, abort
var result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, publishedDocuments, evtMsgs, userId);
var result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, publishedDocuments, evtMsgs, userId, allLangs);
if (result != null)
{
results.Add(result);
@@ -1588,7 +1667,7 @@ namespace Umbraco.Core.Services.Implement
}
// no need to check path here, parent has to be published here
result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, publishedDocuments, evtMsgs, userId);
result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, publishedDocuments, evtMsgs, userId, allLangs);
if (result != null)
{
results.Add(result);
@@ -1620,10 +1699,10 @@ namespace Umbraco.Core.Services.Implement
// publishValues: a function publishing values (using the appropriate PublishCulture calls)
private PublishResult SaveAndPublishBranchItem(IScope scope, IContent document,
Func<IContent, HashSet<string>> shouldPublish,
Func<IContent, HashSet<string>, bool> publishCultures,
Func<IContent, HashSet<string>, IReadOnlyCollection<ILanguage>, bool> publishCultures,
bool isRoot,
ICollection<IContent> publishedDocuments,
EventMessages evtMsgs, int userId)
EventMessages evtMsgs, int userId, IReadOnlyCollection<ILanguage> allLangs)
{
var culturesToPublish = shouldPublish(document);
if (culturesToPublish == null) // null = do not include
@@ -1636,13 +1715,13 @@ namespace Umbraco.Core.Services.Implement
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, document);
// publish & check if values are valid
if (!publishCultures(document, culturesToPublish))
if (!publishCultures(document, culturesToPublish, allLangs))
{
//TODO: Based on this callback behavior there is no way to know which properties may have been invalid if this failed, see other results of FailedPublishContentInvalid
return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, document);
}
var result = CommitDocumentChangesInternal(scope, document, saveEventArgs, userId, branchOne: true, branchRoot: isRoot);
var result = CommitDocumentChangesInternal(scope, document, saveEventArgs, allLangs, userId, branchOne: true, branchRoot: isRoot);
if (result.Success)
publishedDocuments.Add(document);
return result;
@@ -2343,6 +2422,9 @@ namespace Umbraco.Core.Services.Implement
_auditRepository.Save(new AuditItem(objectId, type, userId, ObjectTypes.GetName(UmbracoObjectTypes.Document), message, parameters));
}
private bool IsDefaultCulture(IReadOnlyCollection<ILanguage> langs, string culture) => langs.Any(x => x.IsDefault && x.IsoCode.InvariantEquals(culture));
private bool IsMandatoryCulture(IReadOnlyCollection<ILanguage> langs, string culture) => langs.Any(x => x.IsMandatory && x.IsoCode.InvariantEquals(culture));
#endregion
#region Event Handlers
@@ -2497,7 +2579,9 @@ namespace Umbraco.Core.Services.Implement
/// <param name="culturesPublishing"></param>
/// <param name="savingEventArgs"></param>
/// <returns></returns>
private PublishResult StrategyCanPublish(IScope scope, IContent content, bool checkPath, IReadOnlyList<string> culturesPublishing, IReadOnlyCollection<string> culturesUnpublishing, EventMessages evtMsgs, ContentSavingEventArgs savingEventArgs)
private PublishResult StrategyCanPublish(IScope scope, IContent content, bool checkPath, IReadOnlyList<string> culturesPublishing,
IReadOnlyCollection<string> culturesUnpublishing, EventMessages evtMsgs, ContentSavingEventArgs savingEventArgs,
IReadOnlyCollection<ILanguage> allLangs)
{
// raise Publishing event
if (scope.Events.DispatchCancelable(Publishing, this, savingEventArgs.ToContentPublishingEventArgs()))
@@ -2510,7 +2594,7 @@ namespace Umbraco.Core.Services.Implement
var impactsToPublish = culturesPublishing == null
? new[] {CultureImpact.Invariant} //if it's null it's invariant
: culturesPublishing.Select(x => CultureImpact.Explicit(x, _languageRepository.IsDefault(x))).ToArray();
: culturesPublishing.Select(x => CultureImpact.Explicit(x, allLangs.Any(lang => lang.IsoCode.InvariantEquals(x) && lang.IsMandatory))).ToArray();
// publish the culture(s)
if (!impactsToPublish.All(content.PublishCulture))
@@ -2531,11 +2615,17 @@ namespace Umbraco.Core.Services.Implement
if (culturesPublishing == null)
throw new InvalidOperationException("Internal error, variesByCulture but culturesPublishing is null.");
if (content.Published && culturesPublishing.Count == 0 && culturesUnpublishing.Count == 0) // no published cultures = cannot be published
if (content.Published && culturesPublishing.Count == 0 && culturesUnpublishing.Count == 0)
{
// no published cultures = cannot be published
// This will occur if for example, a culture that is already unpublished is sent to be unpublished again, or vice versa, in that case
// there will be nothing to publish/unpublish.
return new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content);
}
// missing mandatory culture = cannot be published
var mandatoryCultures = _languageRepository.GetMany().Where(x => x.IsMandatory).Select(x => x.IsoCode);
var mandatoryCultures = allLangs.Where(x => x.IsMandatory).Select(x => x.IsoCode);
var mandatoryMissing = mandatoryCultures.Any(x => !content.PublishedCultures.Contains(x, StringComparer.OrdinalIgnoreCase));
if (mandatoryMissing)
return new PublishResult(PublishResultType.FailedPublishMandatoryCultureMissing, evtMsgs, content);
@@ -2676,6 +2766,7 @@ namespace Umbraco.Core.Services.Implement
{
var attempt = new PublishResult(PublishResultType.SuccessUnpublish, evtMsgs, content);
//TODO: What is this check?? we just created this attempt and of course it is Success?!
if (attempt.Success == false)
return attempt;
@@ -49,6 +49,11 @@
/// </summary>
SuccessUnpublishMandatoryCulture = 6,
/// <summary>
/// The specified document culture was unpublished, and was the last published culture in the document, therefore the document itself was unpublished.
/// </summary>
SuccessUnpublishLastCulture = 8,
#endregion
#region Success - Mixed
@@ -113,9 +118,9 @@
FailedPublishContentInvalid = FailedPublish | 8,
/// <summary>
/// The document could not be published because it has no publishing flags or values.
/// The document could not be published because it has no publishing flags or values or if its a variant document, no cultures were specified to be published.
/// </summary>
FailedPublishNothingToPublish = FailedPublish | 9, // TODO: in ContentService.StrategyCanPublish - weird
FailedPublishNothingToPublish = FailedPublish | 9,
/// <summary>
/// The document could not be published because some mandatory cultures are missing.
+2
View File
@@ -204,6 +204,7 @@
<Compile Include="CompositionExtensions_Essentials.cs" />
<Compile Include="CompositionExtensions_FileSystems.cs" />
<Compile Include="CompositionExtensions_Uniques.cs" />
<Compile Include="Exceptions\PanicException.cs" />
<Compile Include="FactoryExtensions.cs" />
<Compile Include="Composing\RegisterFactory.cs" />
<Compile Include="Composing\Current.cs" />
@@ -241,6 +242,7 @@
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\DefaultPreValueMigrator.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\RenamingPreValueMigrator.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\RichTextPreValueMigrator.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\DropDownFlexiblePreValueMigrator.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\MergeDateAndDateTimePropertyEditor.cs" />
<Compile Include="Models\Entities\EntityExtensions.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\PreValueMigratorBase.cs" />
+1 -1
View File
@@ -45,7 +45,7 @@ namespace Umbraco.Examine
continue;
case string strVal:
{
if (strVal.IsNullOrWhiteSpace()) return;
if (strVal.IsNullOrWhiteSpace()) continue;
var key = $"{keyVal.Key}{cultureSuffix}";
if (values.TryGetValue(key, out var v))
values[key] = new List<object>(v) { val }.ToArray();
@@ -54,7 +54,7 @@ namespace Umbraco.Examine
{"updateDate", new object[] {c.UpdateDate}}, //Always add invariant updateDate
{"nodeName", (PublishedValuesOnly //Always add invariant nodeName
? c.PublishName?.Yield()
: c?.Name.Yield()) ?? Enumerable.Empty<string>()},
: c.Name?.Yield()) ?? Enumerable.Empty<string>()},
{"urlName", urlValue?.Yield() ?? Enumerable.Empty<string>()}, //Always add invariant urlName
{"path", c.Path?.Yield() ?? Enumerable.Empty<string>()},
{"nodeType", c.ContentType.Id.ToString().Yield() ?? Enumerable.Empty<string>()},
+50
View File
@@ -28,6 +28,31 @@ namespace Umbraco.Examine
/// </remarks>
internal static readonly Regex CultureIsoCodeFieldNameMatchExpression = new Regex("^([_\\w]+)_([a-z]{2}-[a-z0-9]{2,4})$", RegexOptions.Compiled);
private static volatile bool _isUnlocked = false;
private static readonly object IsUnlockedLocker = new object();
/// <summary>
/// Unlocks all Lucene based indexes registered with the <see cref="IExamineManager"/>
/// </summary>
/// <remarks>
/// Indexing rebuilding can occur on a normal boot if the indexes are empty or on a cold boot by the database server messenger. Before
/// either of these happens, we need to configure the indexes.
/// </remarks>
internal static void EnsureUnlocked(this IExamineManager examineManager, IMainDom mainDom, ILogger logger)
{
if (!mainDom.IsMainDom) return;
if (_isUnlocked) return;
lock (IsUnlockedLocker)
{
//double check
if (_isUnlocked) return;
_isUnlocked = true;
examineManager.UnlockLuceneIndexes(logger);
}
}
//TODO: We need a public method here to just match a field name against CultureIsoCodeFieldNameMatchExpression
/// <summary>
@@ -48,6 +73,31 @@ namespace Umbraco.Examine
}
}
/// <summary>
/// Returns all index fields that are culture specific (suffixed) or invariant
/// </summary>
/// <param name="index"></param>
/// <param name="culture"></param>
/// <returns></returns>
public static IEnumerable<string> GetCultureAndInvariantFields(this IUmbracoIndex index, string culture)
{
var allFields = index.GetFields();
// ReSharper disable once LoopCanBeConvertedToQuery
foreach (var field in allFields)
{
var match = CultureIsoCodeFieldNameMatchExpression.Match(field);
if (match.Success && match.Groups.Count == 3 && culture.InvariantEquals(match.Groups[2].Value))
{
yield return field; //matches this culture field
}
else if (!match.Success)
{
yield return field; //matches no culture field (invariant)
}
}
}
internal static bool TryParseLuceneQuery(string query)
{
// TODO: I'd assume there would be a more strict way to parse the query but not that i can find yet, for now we'll
+2 -1
View File
@@ -5,7 +5,8 @@ using System.Threading.Tasks;
using Examine;
namespace Umbraco.Examine
{
{
/// <summary>
/// Utility to rebuild all indexes ensuring minimal data queries
/// </summary>
@@ -0,0 +1,83 @@
using System.Collections.Generic;
using Examine.LuceneEngine.Providers;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Lucene.Net.Store;
using Umbraco.Core.IO;
using System.Linq;
namespace Umbraco.Examine
{
public class LuceneIndexDiagnostics : IIndexDiagnostics
{
public LuceneIndexDiagnostics(LuceneIndex index, ILogger logger)
{
Index = index;
Logger = logger;
}
public LuceneIndex Index { get; }
public ILogger Logger { get; }
public int DocumentCount
{
get
{
try
{
return Index.GetIndexDocumentCount();
}
catch (AlreadyClosedException)
{
Logger.Warn(typeof(UmbracoContentIndex), "Cannot get GetIndexDocumentCount, the writer is already closed");
return 0;
}
}
}
public int FieldCount
{
get
{
try
{
return Index.GetIndexFieldCount();
}
catch (AlreadyClosedException)
{
Logger.Warn(typeof(UmbracoContentIndex), "Cannot get GetIndexFieldCount, the writer is already closed");
return 0;
}
}
}
public Attempt<string> IsHealthy()
{
var isHealthy = Index.IsHealthy(out var indexError);
return isHealthy ? Attempt<string>.Succeed() : Attempt.Fail(indexError.Message);
}
public virtual IReadOnlyDictionary<string, object> Metadata
{
get
{
var luceneDir = Index.GetLuceneDirectory();
var d = new Dictionary<string, object>
{
[nameof(UmbracoExamineIndex.CommitCount)] = Index.CommitCount,
[nameof(UmbracoExamineIndex.DefaultAnalyzer)] = Index.DefaultAnalyzer.GetType().Name,
["LuceneDirectory"] = luceneDir.GetType().Name
};
if (luceneDir is FSDirectory fsDir)
{
d[nameof(UmbracoExamineIndex.LuceneIndexFolder)] = fsDir.Directory.ToString().ToLowerInvariant().TrimStart(IOHelper.MapPath(SystemDirectories.Root).ToLowerInvariant()).Replace("\\", "/").EnsureStartsWith('/');
}
return d;
}
}
}
}
@@ -72,6 +72,7 @@
<Compile Include="IPublishedContentValueSetBuilder.cs" />
<Compile Include="IUmbracoIndex.cs" />
<Compile Include="IValueSetBuilder.cs" />
<Compile Include="LuceneIndexDiagnostics.cs" />
<Compile Include="MediaIndexPopulator.cs" />
<Compile Include="MediaValueSetBuilder.cs" />
<Compile Include="MemberIndexPopulator.cs" />
@@ -7,73 +7,24 @@ using Umbraco.Core.Logging;
namespace Umbraco.Examine
{
public class UmbracoExamineIndexDiagnostics : IIndexDiagnostics
public class UmbracoExamineIndexDiagnostics : LuceneIndexDiagnostics
{
private readonly UmbracoExamineIndex _index;
private readonly ILogger _logger;
public UmbracoExamineIndexDiagnostics(UmbracoExamineIndex index, ILogger logger)
: base(index, logger)
{
_index = index;
_logger = logger;
}
public int DocumentCount
public override IReadOnlyDictionary<string, object> Metadata
{
get
{
try
{
return _index.GetIndexDocumentCount();
}
catch (AlreadyClosedException)
{
_logger.Warn(typeof(UmbracoContentIndex), "Cannot get GetIndexDocumentCount, the writer is already closed");
return 0;
}
}
}
var d = base.Metadata.ToDictionary(x => x.Key, x => x.Value);
public int FieldCount
{
get
{
try
{
return _index.GetIndexFieldCount();
}
catch (AlreadyClosedException)
{
_logger.Warn(typeof(UmbracoContentIndex), "Cannot get GetIndexFieldCount, the writer is already closed");
return 0;
}
}
}
public Attempt<string> IsHealthy()
{
var isHealthy = _index.IsHealthy(out var indexError);
return isHealthy ? Attempt<string>.Succeed() : Attempt.Fail(indexError.Message);
}
public virtual IReadOnlyDictionary<string, object> Metadata
{
get
{
var d = new Dictionary<string, object>
{
[nameof(UmbracoExamineIndex.CommitCount)] = _index.CommitCount,
[nameof(UmbracoExamineIndex.DefaultAnalyzer)] = _index.DefaultAnalyzer.GetType().Name,
["LuceneDirectory"] = _index.GetLuceneDirectory().GetType().Name,
[nameof(UmbracoExamineIndex.EnableDefaultEventHandler)] = _index.EnableDefaultEventHandler,
[nameof(UmbracoExamineIndex.LuceneIndexFolder)] =
_index.LuceneIndexFolder == null
? string.Empty
: _index.LuceneIndexFolder.ToString().ToLowerInvariant().TrimStart(IOHelper.MapPath(SystemDirectories.Root).ToLowerInvariant()).Replace("\\", "/").EnsureStartsWith('/'),
[nameof(UmbracoExamineIndex.PublishedValuesOnly)] = _index.PublishedValuesOnly,
//There's too much info here
//[nameof(UmbracoExamineIndexer.FieldDefinitionCollection)] = _index.FieldDefinitionCollection,
};
d[nameof(UmbracoExamineIndex.EnableDefaultEventHandler)] = _index.EnableDefaultEventHandler;
d[nameof(UmbracoExamineIndex.PublishedValuesOnly)] = _index.PublishedValuesOnly;
if (_index.ValueSetValidator is ValueSetValidator vsv)
{
@@ -29,10 +29,11 @@ namespace Umbraco.Tests.Persistence.Repositories
private DocumentRepository CreateRepository(IScopeAccessor scopeAccessor, out ContentTypeRepository contentTypeRepository)
{
var langRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger);
var templateRepository = new TemplateRepository(scopeAccessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepository = new TagRepository(scopeAccessor, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, AppCaches.Disabled);
contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository);
contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository, langRepository);
var languageRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger);
var repository = new DocumentRepository(scopeAccessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
return repository;
@@ -40,9 +41,10 @@ namespace Umbraco.Tests.Persistence.Repositories
private ContentTypeRepository CreateRepository(IScopeAccessor scopeAccessor)
{
var langRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger);
var templateRepository = new TemplateRepository(scopeAccessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, AppCaches.Disabled);
var contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository);
var contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository, langRepository);
return contentTypeRepository;
}
@@ -50,7 +52,8 @@ namespace Umbraco.Tests.Persistence.Repositories
{
var templateRepository = new TemplateRepository(scopeAccessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, AppCaches.Disabled);
var contentTypeRepository = new MediaTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository);
var langRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger);
var contentTypeRepository = new MediaTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository, langRepository);
return contentTypeRepository;
}
@@ -67,8 +67,8 @@ namespace Umbraco.Tests.Persistence.Repositories
templateRepository = new TemplateRepository(scopeAccessor, appCaches, Logger, TestObjects.GetFileSystemsMock());
var tagRepository = new TagRepository(scopeAccessor, appCaches, Logger);
var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, appCaches);
contentTypeRepository = new ContentTypeRepository(scopeAccessor, appCaches, Logger, commonRepository);
var languageRepository = new LanguageRepository(scopeAccessor, appCaches, Logger);
contentTypeRepository = new ContentTypeRepository(scopeAccessor, appCaches, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository(scopeAccessor, appCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
return repository;
}
@@ -23,8 +23,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var templateRepository = new TemplateRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepository = new TagRepository(accessor, Core.Cache.AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
contentTypeRepository = new ContentTypeRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, commonRepository);
languageRepository = new LanguageRepository(accessor, Core.Cache.AppCaches.Disabled, Logger);
contentTypeRepository = new ContentTypeRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, commonRepository, languageRepository);
documentRepository = new DocumentRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
var domainRepository = new DomainRepository(accessor, Core.Cache.AppCaches.Disabled, Logger);
return domainRepository;
@@ -38,7 +38,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var templateRepository = new TemplateRepository(scopeAccessor, appCaches, Logger, TestObjects.GetFileSystemsMock());
var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, appCaches);
mediaTypeRepository = new MediaTypeRepository(scopeAccessor, appCaches, Logger, commonRepository);
var languageRepository = new LanguageRepository(scopeAccessor, appCaches, Logger);
mediaTypeRepository = new MediaTypeRepository(scopeAccessor, appCaches, Logger, commonRepository, languageRepository);
var tagRepository = new TagRepository(scopeAccessor, appCaches, Logger);
var repository = new MediaRepository(scopeAccessor, appCaches, Logger, mediaTypeRepository, tagRepository, Mock.Of<ILanguageRepository>());
return repository;
@@ -23,7 +23,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var cacheHelper = AppCaches.Disabled;
var templateRepository = new TemplateRepository((IScopeAccessor)provider, cacheHelper, Logger, TestObjects.GetFileSystemsMock());
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, templateRepository, AppCaches);
return new MediaTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository);
var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches, Logger);
return new MediaTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
}
private EntityContainerRepository CreateContainerRepository(IScopeProvider provider)
@@ -31,7 +31,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var accessor = (IScopeAccessor) provider;
var templateRepository = Mock.Of<ITemplateRepository>();
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
memberTypeRepository = new MemberTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository);
var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger);
memberTypeRepository = new MemberTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository);
memberGroupRepository = new MemberGroupRepository(accessor, AppCaches.Disabled, Logger);
var tagRepo = new TagRepository(accessor, AppCaches.Disabled, Logger);
var repository = new MemberRepository(accessor, AppCaches.Disabled, Logger, memberTypeRepository, memberGroupRepository, tagRepo, Mock.Of<ILanguageRepository>());
@@ -24,7 +24,8 @@ namespace Umbraco.Tests.Persistence.Repositories
{
var templateRepository = Mock.Of<ITemplateRepository>();
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, templateRepository, AppCaches);
return new MemberTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of<ILogger>(), commonRepository);
var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches.Disabled, Mock.Of<ILogger>());
return new MemberTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of<ILogger>(), commonRepository, languageRepository);
}
[Test]
@@ -308,8 +308,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var templateRepository = new TemplateRepository(accessor, AppCaches, Logger, TestObjects.GetFileSystemsMock());
var tagRepository = new TagRepository(accessor, AppCaches, Logger);
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository);
var languageRepository = new LanguageRepository(accessor, AppCaches, Logger);
contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
return repository;
}
@@ -956,8 +956,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepository = new TagRepository(accessor, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches.Disabled);
contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository);
var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger);
contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
return repository;
}
@@ -968,7 +968,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepository = new TagRepository(accessor, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches.Disabled);
mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository);
var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger);
mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new MediaRepository(accessor, AppCaches.Disabled, Logger, mediaTypeRepository, tagRepository, Mock.Of<ILanguageRepository>());
return repository;
}
@@ -239,8 +239,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var tagRepository = new TagRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository(ScopeProvider, templateRepository, AppCaches);
var contentTypeRepository = new ContentTypeRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, commonRepository);
var languageRepository = new LanguageRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger);
var contentTypeRepository = new ContentTypeRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var contentRepo = new DocumentRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage2", "Textpage");
@@ -26,7 +26,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var accessor = (IScopeAccessor) provider;
var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches, Mock.Of<ILogger>(), commonRepository);
var languageRepository = new LanguageRepository(accessor, AppCaches, Logger);
mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches, Mock.Of<ILogger>(), commonRepository, languageRepository);
var tagRepository = new TagRepository(accessor, AppCaches, Mock.Of<ILogger>());
var repository = new MediaRepository(accessor, AppCaches, Mock.Of<ILogger>(), mediaTypeRepository, tagRepository, Mock.Of<ILanguageRepository>());
return repository;
@@ -44,8 +45,8 @@ namespace Umbraco.Tests.Persistence.Repositories
templateRepository = new TemplateRepository(accessor, AppCaches, Logger, TestObjects.GetFileSystemsMock());
var tagRepository = new TagRepository(accessor, AppCaches, Logger);
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository);
var languageRepository = new LanguageRepository(accessor, AppCaches, Logger);
contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
return repository;
}
@@ -81,6 +81,19 @@ namespace Umbraco.Tests.Routing
Assert.AreEqual(expected, resolvedUrl);
}
[Test]
public void Get_Media_Url_Returns_Absolute_Url_If_Stored_Url_Is_Absolute()
{
const string expected = "http://localhost/media/rfeiw584/test.jpg";
var umbracoContext = GetUmbracoContext("http://localhost", mediaUrlProviders: new[] { _mediaUrlProvider });
var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.UploadField, expected, null);
var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, UrlMode.Relative);
Assert.AreEqual(expected, resolvedUrl);
}
[Test]
public void Get_Media_Url_Returns_Empty_String_When_PropertyType_Is_Not_Supported()
{
@@ -166,8 +166,8 @@ namespace Umbraco.Tests.Services
var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, tRepository, AppCaches);
var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository);
var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository);
// Act
@@ -200,8 +200,8 @@ namespace Umbraco.Tests.Services
var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, tRepository, AppCaches);
var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository);
var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository);
// Act
@@ -232,8 +232,8 @@ namespace Umbraco.Tests.Services
var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor) provider, tRepository, AppCaches);
var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository);
var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches.Disabled, Logger);
var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository);
// Act
@@ -267,8 +267,8 @@ namespace Umbraco.Tests.Services
var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, tRepository, AppCaches);
var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository);
var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches.Disabled, Logger);
var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository);
// Act
+207 -28
View File
@@ -717,21 +717,8 @@ namespace Umbraco.Tests.Services
[Test]
public void Can_Unpublish_Content_Variation()
{
// Arrange
var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr, out var contentType);
var langUk = new Language("en-GB") { IsDefault = true };
var langFr = new Language("fr-FR");
ServiceContext.LocalizationService.Save(langFr);
ServiceContext.LocalizationService.Save(langUk);
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
IContent content = new Content("content", Constants.System.Root, contentType);
content.SetCultureName("content-fr", langFr.IsoCode);
content.SetCultureName("content-en", langUk.IsoCode);
content.PublishCulture(CultureImpact.Explicit(langFr.IsoCode, langFr.IsDefault));
content.PublishCulture(CultureImpact.Explicit(langUk.IsoCode, langUk.IsDefault));
Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
@@ -761,6 +748,185 @@ namespace Umbraco.Tests.Services
}
[Test]
public void Can_Publish_Culture_After_Last_Culture_Unpublished()
{
var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr, out var contentType);
var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode });
Assert.AreEqual(PublishedState.Published, content.PublishedState);
//re-get
content = ServiceContext.ContentService.GetById(content.Id);
var unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode); //first culture
Assert.IsTrue(unpublished.Success);
Assert.AreEqual(PublishResultType.SuccessUnpublishCulture, unpublished.Result);
Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
content = ServiceContext.ContentService.GetById(content.Id);
unpublished = ServiceContext.ContentService.Unpublish(content, langFr.IsoCode); //last culture
Assert.IsTrue(unpublished.Success);
Assert.AreEqual(PublishResultType.SuccessUnpublishLastCulture, unpublished.Result);
Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
content = ServiceContext.ContentService.GetById(content.Id);
published = ServiceContext.ContentService.SaveAndPublish(content, langUk.IsoCode);
Assert.AreEqual(PublishedState.Published, content.PublishedState);
Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
content = ServiceContext.ContentService.GetById(content.Id); //reget
Assert.AreEqual(PublishedState.Published, content.PublishedState);
Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
}
[Test]
public void Unpublish_All_Cultures_Has_Unpublished_State()
{
var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr, out var contentType);
var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode });
Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
Assert.IsTrue(published.Success);
Assert.AreEqual(PublishedState.Published, content.PublishedState);
//re-get
content = ServiceContext.ContentService.GetById(content.Id);
Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
Assert.AreEqual(PublishedState.Published, content.PublishedState);
var unpublished = ServiceContext.ContentService.Unpublish(content, langFr.IsoCode); //first culture
Assert.IsTrue(unpublished.Success);
Assert.AreEqual(PublishResultType.SuccessUnpublishCulture, unpublished.Result);
Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
Assert.AreEqual(PublishedState.Published, content.PublishedState); //still published
//re-get
content = ServiceContext.ContentService.GetById(content.Id);
Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode); //last culture
Assert.IsTrue(unpublished.Success);
Assert.AreEqual(PublishResultType.SuccessUnpublishLastCulture, unpublished.Result);
Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
Assert.AreEqual(PublishedState.Unpublished, content.PublishedState); //the last culture was unpublished so the document should also reflect this
//re-get
content = ServiceContext.ContentService.GetById(content.Id);
Assert.AreEqual(PublishedState.Unpublished, content.PublishedState); //just double checking
Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
}
[Test]
public void Unpublishing_Mandatory_Language_Unpublishes_Document()
{
var langUk = new Language("en-GB") { IsDefault = true, IsMandatory = true };
var langFr = new Language("fr-FR");
ServiceContext.LocalizationService.Save(langFr);
ServiceContext.LocalizationService.Save(langUk);
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
IContent content = new Content("content", Constants.System.Root, contentType);
content.SetCultureName("content-fr", langFr.IsoCode);
content.SetCultureName("content-en", langUk.IsoCode);
var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode });
Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
Assert.IsTrue(published.Success);
Assert.AreEqual(PublishedState.Published, content.PublishedState);
//re-get
content = ServiceContext.ContentService.GetById(content.Id);
var unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode); //unpublish mandatory lang
Assert.IsTrue(unpublished.Success);
Assert.AreEqual(PublishResultType.SuccessUnpublishMandatoryCulture, unpublished.Result);
Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode)); //remains published
Assert.AreEqual(PublishedState.Unpublished, content.PublishedState);
}
[Test]
public void Unpublishing_Already_Unpublished_Culture()
{
var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr, out var contentType);
var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode });
Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
Assert.IsTrue(published.Success);
Assert.AreEqual(PublishedState.Published, content.PublishedState);
//re-get
content = ServiceContext.ContentService.GetById(content.Id);
var unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode);
Assert.IsTrue(unpublished.Success);
Assert.AreEqual(PublishResultType.SuccessUnpublishCulture, unpublished.Result);
Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
content = ServiceContext.ContentService.GetById(content.Id);
//Change some data since Unpublish should always Save
content.SetCultureName("content-en-updated", langUk.IsoCode);
unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode); //unpublish again
Assert.IsTrue(unpublished.Success);
Assert.AreEqual(PublishResultType.SuccessUnpublishAlready, unpublished.Result);
Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
content = ServiceContext.ContentService.GetById(content.Id);
//ensure that even though the culture was already unpublished that the data was still persisted
Assert.AreEqual("content-en-updated", content.GetCultureName(langUk.IsoCode));
}
[Test]
public void Publishing_No_Cultures_Still_Saves()
{
var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr, out var contentType);
var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode });
Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
Assert.IsTrue(published.Success);
Assert.AreEqual(PublishedState.Published, content.PublishedState);
//re-get
content = ServiceContext.ContentService.GetById(content.Id);
//Change some data since SaveAndPublish should always Save
content.SetCultureName("content-en-updated", langUk.IsoCode);
var saved = ServiceContext.ContentService.SaveAndPublish(content, new string [] { }); //save without cultures
Assert.AreEqual(PublishResultType.FailedPublishNothingToPublish, saved.Result);
//re-get
content = ServiceContext.ContentService.GetById(content.Id);
//ensure that even though nothing was published that the data was still persisted
Assert.AreEqual("content-en-updated", content.GetCultureName(langUk.IsoCode));
}
[Test]
public void Pending_Invariant_Property_Changes_Affect_Default_Language_Edited_State()
{
@@ -811,17 +977,7 @@ namespace Umbraco.Tests.Services
[Test]
public void Can_Publish_Content_Variation_And_Detect_Changed_Cultures()
{
// Arrange
var langGB = new Language("en-GB") { IsDefault = true };
var langFr = new Language("fr-FR");
ServiceContext.LocalizationService.Save(langFr);
ServiceContext.LocalizationService.Save(langGB);
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
CreateEnglishAndFrenchDocumentType(out var langUk, out var langFr, out var contentType);
IContent content = new Content("content", Constants.System.Root, contentType);
content.SetCultureName("content-fr", langFr.IsoCode);
@@ -832,8 +988,8 @@ namespace Umbraco.Tests.Services
//re-get
content = ServiceContext.ContentService.GetById(content.Id);
content.SetCultureName("content-en", langGB.IsoCode);
published = ServiceContext.ContentService.SaveAndPublish(content, langGB.IsoCode);
content.SetCultureName("content-en", langUk.IsoCode);
published = ServiceContext.ContentService.SaveAndPublish(content, langUk.IsoCode);
//audit log will only show that english was published
lastLog = ServiceContext.AuditService.GetLogs(content.Id).Last();
Assert.AreEqual($"Published languages: English (United Kingdom)", lastLog.Comment);
@@ -3008,10 +3164,33 @@ namespace Umbraco.Tests.Services
var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepository = new TagRepository(accessor, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository);
var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger);
contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
return repository;
}
private void CreateEnglishAndFrenchDocumentType(out Language langUk, out Language langFr, out ContentType contentType)
{
langUk = new Language("en-GB") { IsDefault = true };
langFr = new Language("fr-FR");
ServiceContext.LocalizationService.Save(langFr);
ServiceContext.LocalizationService.Save(langUk);
contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
}
private IContent CreateEnglishAndFrenchDocument(out Language langUk, out Language langFr, out ContentType contentType)
{
CreateEnglishAndFrenchDocumentType(out langUk, out langFr, out contentType);
IContent content = new Content("content", Constants.System.Root, contentType);
content.SetCultureName("content-fr", langFr.IsoCode);
content.SetCultureName("content-en", langUk.IsoCode);
return content;
}
}
}
@@ -53,360 +53,7 @@ namespace Umbraco.Tests.Services
Assert.IsTrue(contentType.IsElement);
}
[Test]
public void Change_Content_Type_Variation_Clears_Redirects()
{
//create content type with a property type that varies by culture
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Nothing;
var contentCollection = new PropertyTypeCollection(true);
contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
{
Alias = "title",
Name = "Title",
Description = "",
Mandatory = false,
SortOrder = 1,
DataTypeId = -88,
Variations = ContentVariation.Nothing
});
contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
ServiceContext.ContentTypeService.Save(contentType);
var contentType2 = MockedContentTypes.CreateBasicContentType("test");
ServiceContext.ContentTypeService.Save(contentType2);
//create some content of this content type
IContent doc = MockedContent.CreateBasicContent(contentType);
doc.Name = "Hello1";
ServiceContext.ContentService.Save(doc);
IContent doc2 = MockedContent.CreateBasicContent(contentType2);
ServiceContext.ContentService.Save(doc2);
ServiceContext.RedirectUrlService.Register("hello/world", doc.Key);
ServiceContext.RedirectUrlService.Register("hello2/world2", doc2.Key);
Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count());
Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count());
//change variation
contentType.Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
Assert.AreEqual(0, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count());
Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count());
}
[Test]
public void Change_Content_Type_From_Invariant_Variant()
{
//create content type with a property type that varies by culture
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Nothing;
var contentCollection = new PropertyTypeCollection(true);
contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
{
Alias = "title",
Name = "Title",
Description = "",
Mandatory = false,
SortOrder = 1,
DataTypeId = -88,
Variations = ContentVariation.Nothing
});
contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
ServiceContext.ContentTypeService.Save(contentType);
//create some content of this content type
IContent doc = MockedContent.CreateBasicContent(contentType);
doc.Name = "Hello1";
doc.SetValue("title", "hello world");
ServiceContext.ContentService.Save(doc);
Assert.AreEqual("Hello1", doc.Name);
Assert.AreEqual("hello world", doc.GetValue("title"));
//change the content type to be variant, we will also update the name here to detect the copy changes
doc.Name = "Hello2";
ServiceContext.ContentService.Save(doc);
contentType.Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("Hello2", doc.GetCultureName("en-US"));
Assert.AreEqual("hello world", doc.GetValue("title")); //We are not checking against en-US here because properties will remain invariant
//change back property type to be invariant, we will also update the name here to detect the copy changes
doc.SetCultureName("Hello3", "en-US");
ServiceContext.ContentService.Save(doc);
contentType.Variations = ContentVariation.Nothing;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("Hello3", doc.Name);
Assert.AreEqual("hello world", doc.GetValue("title"));
}
[Test]
public void Change_Content_Type_From_Variant_Invariant()
{
//create content type with a property type that varies by culture
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Culture;
var contentCollection = new PropertyTypeCollection(true);
contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
{
Alias = "title",
Name = "Title",
Description = "",
Mandatory = false,
SortOrder = 1,
DataTypeId = -88,
Variations = ContentVariation.Culture
});
contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
ServiceContext.ContentTypeService.Save(contentType);
//create some content of this content type
IContent doc = MockedContent.CreateBasicContent(contentType);
doc.SetCultureName("Hello1", "en-US");
doc.SetValue("title", "hello world", "en-US");
ServiceContext.ContentService.Save(doc);
Assert.AreEqual("Hello1", doc.GetCultureName("en-US"));
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
//change the content type to be invariant, we will also update the name here to detect the copy changes
doc.SetCultureName("Hello2", "en-US");
ServiceContext.ContentService.Save(doc);
contentType.Variations = ContentVariation.Nothing;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("Hello2", doc.Name);
Assert.AreEqual("hello world", doc.GetValue("title"));
//change back property type to be variant, we will also update the name here to detect the copy changes
doc.Name = "Hello3";
ServiceContext.ContentService.Save(doc);
contentType.Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
//at this stage all property types were switched to invariant so even though the variant value
//exists it will not be returned because the property type is invariant,
//so this check proves that null will be returned
Assert.IsNull(doc.GetValue("title", "en-US"));
//we can now switch the property type to be variant and the value can be returned again
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("Hello3", doc.GetCultureName("en-US"));
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
}
[Test]
public void Change_Property_Type_From_Invariant_Variant()
{
//create content type with a property type that varies by culture
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Nothing;
var contentCollection = new PropertyTypeCollection(true);
contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
{
Alias = "title",
Name = "Title",
Description = "",
Mandatory = false,
SortOrder = 1,
DataTypeId = -88,
Variations = ContentVariation.Nothing
});
contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
ServiceContext.ContentTypeService.Save(contentType);
//create some content of this content type
IContent doc = MockedContent.CreateBasicContent(contentType);
doc.Name = "Home";
doc.SetValue("title", "hello world");
ServiceContext.ContentService.Save(doc);
Assert.AreEqual("hello world", doc.GetValue("title"));
//change the property type to be variant
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
//change back property type to be invariant
contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title"));
}
[Test]
public void Change_Property_Type_From_Variant_Invariant()
{
//create content type with a property type that varies by culture
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Culture;
var contentCollection = new PropertyTypeCollection(true);
contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
{
Alias = "title",
Name = "Title",
Description = "",
Mandatory = false,
SortOrder = 1,
DataTypeId = -88,
Variations = ContentVariation.Culture
});
contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
ServiceContext.ContentTypeService.Save(contentType);
//create some content of this content type
IContent doc = MockedContent.CreateBasicContent(contentType);
doc.SetCultureName("Home", "en-US");
doc.SetValue("title", "hello world", "en-US");
ServiceContext.ContentService.Save(doc);
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
//change the property type to be invariant
contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title"));
//change back property type to be variant
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
}
[Test]
public void Change_Property_Type_From_Variant_Invariant_On_A_Composition()
{
//create content type with a property type that varies by culture
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Culture;
var contentCollection = new PropertyTypeCollection(true);
contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
{
Alias = "title",
Name = "Title",
Description = "",
Mandatory = false,
SortOrder = 1,
DataTypeId = -88,
Variations = ContentVariation.Culture
});
contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
ServiceContext.ContentTypeService.Save(contentType);
//compose this from the other one
var contentType2 = MockedContentTypes.CreateBasicContentType("test");
contentType2.Variations = ContentVariation.Culture;
contentType2.AddContentType(contentType);
ServiceContext.ContentTypeService.Save(contentType2);
//create some content of this content type
IContent doc = MockedContent.CreateBasicContent(contentType);
doc.SetCultureName("Home", "en-US");
doc.SetValue("title", "hello world", "en-US");
ServiceContext.ContentService.Save(doc);
IContent doc2 = MockedContent.CreateBasicContent(contentType2);
doc2.SetCultureName("Home", "en-US");
doc2.SetValue("title", "hello world", "en-US");
ServiceContext.ContentService.Save(doc2);
//change the property type to be invariant
contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title"));
Assert.AreEqual("hello world", doc2.GetValue("title"));
//change back property type to be variant
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
Assert.AreEqual("hello world", doc2.GetValue("title", "en-US"));
}
[Test]
public void Change_Content_Type_From_Variant_Invariant_On_A_Composition()
{
//create content type with a property type that varies by culture
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Culture;
var contentCollection = new PropertyTypeCollection(true);
contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
{
Alias = "title",
Name = "Title",
Description = "",
Mandatory = false,
SortOrder = 1,
DataTypeId = -88,
Variations = ContentVariation.Culture
});
contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
ServiceContext.ContentTypeService.Save(contentType);
//compose this from the other one
var contentType2 = MockedContentTypes.CreateBasicContentType("test");
contentType2.Variations = ContentVariation.Culture;
contentType2.AddContentType(contentType);
ServiceContext.ContentTypeService.Save(contentType2);
//create some content of this content type
IContent doc = MockedContent.CreateBasicContent(contentType);
doc.SetCultureName("Home", "en-US");
doc.SetValue("title", "hello world", "en-US");
ServiceContext.ContentService.Save(doc);
IContent doc2 = MockedContent.CreateBasicContent(contentType2);
doc2.SetCultureName("Home", "en-US");
doc2.SetValue("title", "hello world", "en-US");
ServiceContext.ContentService.Save(doc2);
//change the content type to be invariant
contentType.Variations = ContentVariation.Nothing;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title"));
Assert.AreEqual("hello world", doc2.GetValue("title"));
//change back content type to be variant
contentType.Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
//this will be null because the doc type was changed back to variant but it's property types don't get changed back
Assert.IsNull(doc.GetValue("title", "en-US"));
Assert.IsNull(doc2.GetValue("title", "en-US"));
}
[Test]
public void Deleting_Content_Type_With_Hierarchy_Of_Content_Items_Moves_Orphaned_Content_To_Recycle_Bin()
@@ -17,6 +17,7 @@ using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
using Umbraco.Core.Sync;
using Umbraco.Tests.TestHelpers.Entities;
using Umbraco.Tests.Testing;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.PublishedCache.NuCache;
@@ -106,44 +107,346 @@ namespace Umbraco.Tests.Services
}
}
[Test]
public void Change_Content_Type_Variation_Clears_Redirects()
{
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Nothing;
var properties = CreatePropertyCollection(("title", ContentVariation.Nothing));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
var contentType2 = MockedContentTypes.CreateBasicContentType("test");
ServiceContext.ContentTypeService.Save(contentType2);
//create some content of this content type
IContent doc = MockedContent.CreateBasicContent(contentType);
doc.Name = "Hello1";
ServiceContext.ContentService.Save(doc);
IContent doc2 = MockedContent.CreateBasicContent(contentType2);
ServiceContext.ContentService.Save(doc2);
ServiceContext.RedirectUrlService.Register("hello/world", doc.Key);
ServiceContext.RedirectUrlService.Register("hello2/world2", doc2.Key);
Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count());
Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count());
//change variation
contentType.Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
Assert.AreEqual(0, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count());
Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count());
}
[Test]
public void Change_Content_Type_From_Invariant_Variant()
{
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Nothing;
var properties = CreatePropertyCollection(("title", ContentVariation.Nothing));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
//create some content of this content type
IContent doc = MockedContent.CreateBasicContent(contentType);
doc.Name = "Hello1";
doc.SetValue("title", "hello world");
ServiceContext.ContentService.Save(doc);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("Hello1", doc.Name);
Assert.AreEqual("hello world", doc.GetValue("title"));
Assert.IsTrue(doc.Edited);
Assert.IsFalse (doc.IsCultureEdited("en-US"));
//change the content type to be variant, we will also update the name here to detect the copy changes
doc.Name = "Hello2";
ServiceContext.ContentService.Save(doc);
contentType.Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("Hello2", doc.GetCultureName("en-US"));
Assert.AreEqual("hello world", doc.GetValue("title")); //We are not checking against en-US here because properties will remain invariant
Assert.IsTrue(doc.Edited);
Assert.IsTrue(doc.IsCultureEdited("en-US"));
//change back property type to be invariant, we will also update the name here to detect the copy changes
doc.SetCultureName("Hello3", "en-US");
ServiceContext.ContentService.Save(doc);
contentType.Variations = ContentVariation.Nothing;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("Hello3", doc.Name);
Assert.AreEqual("hello world", doc.GetValue("title"));
Assert.IsTrue(doc.Edited);
Assert.IsFalse(doc.IsCultureEdited("en-US"));
}
[Test]
public void Change_Content_Type_From_Variant_Invariant()
{
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Culture;
var properties = CreatePropertyCollection(("title", ContentVariation.Culture));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
//create some content of this content type
IContent doc = MockedContent.CreateBasicContent(contentType);
doc.SetCultureName("Hello1", "en-US");
doc.SetValue("title", "hello world", "en-US");
ServiceContext.ContentService.Save(doc);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("Hello1", doc.GetCultureName("en-US"));
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
Assert.IsTrue(doc.Edited);
Assert.IsTrue(doc.IsCultureEdited("en-US"));
//change the content type to be invariant, we will also update the name here to detect the copy changes
doc.SetCultureName("Hello2", "en-US");
ServiceContext.ContentService.Save(doc);
contentType.Variations = ContentVariation.Nothing;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("Hello2", doc.Name);
Assert.AreEqual("hello world", doc.GetValue("title"));
Assert.IsTrue(doc.Edited);
Assert.IsFalse(doc.IsCultureEdited("en-US"));
//change back property type to be variant, we will also update the name here to detect the copy changes
doc.Name = "Hello3";
ServiceContext.ContentService.Save(doc);
contentType.Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
//at this stage all property types were switched to invariant so even though the variant value
//exists it will not be returned because the property type is invariant,
//so this check proves that null will be returned
Assert.IsNull(doc.GetValue("title", "en-US"));
Assert.IsTrue(doc.Edited);
Assert.IsTrue(doc.IsCultureEdited("en-US")); // this is true because the name change is copied to the default language
//we can now switch the property type to be variant and the value can be returned again
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("Hello3", doc.GetCultureName("en-US"));
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
Assert.IsTrue(doc.Edited);
Assert.IsTrue(doc.IsCultureEdited("en-US"));
}
[Test]
public void Change_Property_Type_From_To_Variant_On_Invariant_Content_Type()
{
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Nothing;
var properties = CreatePropertyCollection(("title", ContentVariation.Nothing));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
//change the property type to be variant
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
//Cannot change a property type to be variant if the content type itself is not variant
Assert.Throws<InvalidOperationException>(() => ServiceContext.ContentTypeService.Save(contentType));
}
[Test]
public void Change_Property_Type_From_Invariant_Variant()
{
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Culture;
var properties = CreatePropertyCollection(("title", ContentVariation.Nothing));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
//create some content of this content type
IContent doc = MockedContent.CreateBasicContent(contentType);
doc.SetCultureName("Home", "en-US");
doc.SetValue("title", "hello world");
ServiceContext.ContentService.Save(doc);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title"));
Assert.IsTrue(doc.IsCultureEdited("en-US")); //invariant prop changes show up on default lang
Assert.IsTrue(doc.Edited);
//change the property type to be variant
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
Assert.IsTrue(doc.IsCultureEdited("en-US"));
Assert.IsTrue(doc.Edited);
//change back property type to be invariant
contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title"));
Assert.IsTrue(doc.IsCultureEdited("en-US")); //invariant prop changes show up on default lang
Assert.IsTrue(doc.Edited);
}
[Test]
public void Change_Property_Type_From_Variant_Invariant()
{
//create content type with a property type that varies by culture
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Culture;
var properties = CreatePropertyCollection(("title", ContentVariation.Culture));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
//create some content of this content type
IContent doc = MockedContent.CreateBasicContent(contentType);
doc.SetCultureName("Home", "en-US");
doc.SetValue("title", "hello world", "en-US");
ServiceContext.ContentService.Save(doc);
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
//change the property type to be invariant
contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title"));
//change back property type to be variant
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
}
[Test]
public void Change_Property_Type_From_Variant_Invariant_On_A_Composition()
{
//create content type with a property type that varies by culture
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Culture;
var properties = CreatePropertyCollection(("title", ContentVariation.Culture));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
//compose this from the other one
var contentType2 = MockedContentTypes.CreateBasicContentType("test");
contentType2.Variations = ContentVariation.Culture;
contentType2.AddContentType(contentType);
ServiceContext.ContentTypeService.Save(contentType2);
//create some content of this content type
IContent doc = MockedContent.CreateBasicContent(contentType);
doc.SetCultureName("Home", "en-US");
doc.SetValue("title", "hello world", "en-US");
ServiceContext.ContentService.Save(doc);
IContent doc2 = MockedContent.CreateBasicContent(contentType2);
doc2.SetCultureName("Home", "en-US");
doc2.SetValue("title", "hello world", "en-US");
ServiceContext.ContentService.Save(doc2);
//change the property type to be invariant
contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title"));
Assert.AreEqual("hello world", doc2.GetValue("title"));
//change back property type to be variant
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
Assert.AreEqual("hello world", doc2.GetValue("title", "en-US"));
}
[Test]
public void Change_Content_Type_From_Variant_Invariant_On_A_Composition()
{
//create content type with a property type that varies by culture
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Culture;
var properties = CreatePropertyCollection(("title", ContentVariation.Culture));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
//compose this from the other one
var contentType2 = MockedContentTypes.CreateBasicContentType("test");
contentType2.Variations = ContentVariation.Culture;
contentType2.AddContentType(contentType);
ServiceContext.ContentTypeService.Save(contentType2);
//create some content of this content type
IContent doc = MockedContent.CreateBasicContent(contentType);
doc.SetCultureName("Home", "en-US");
doc.SetValue("title", "hello world", "en-US");
ServiceContext.ContentService.Save(doc);
IContent doc2 = MockedContent.CreateBasicContent(contentType2);
doc2.SetCultureName("Home", "en-US");
doc2.SetValue("title", "hello world", "en-US");
ServiceContext.ContentService.Save(doc2);
//change the content type to be invariant
contentType.Variations = ContentVariation.Nothing;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title"));
Assert.AreEqual("hello world", doc2.GetValue("title"));
//change back content type to be variant
contentType.Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
//this will be null because the doc type was changed back to variant but it's property types don't get changed back
Assert.IsNull(doc.GetValue("title", "en-US"));
Assert.IsNull(doc2.GetValue("title", "en-US"));
}
[Test]
public void Change_Variations_SimpleContentType_VariantToInvariantAndBack()
{
// one simple content type, variant, with both variant and invariant properties
// can change it to invariant and back
var languageEn = new Language("en") { IsDefault = true };
ServiceContext.LocalizationService.Save(languageEn);
var languageFr = new Language("fr");
ServiceContext.LocalizationService.Save(languageFr);
CreateFrenchAndEnglishLangs();
var contentType = new ContentType(-1)
{
Alias = "contentType",
Name = "contentType",
Variations = ContentVariation.Culture
};
var contentType = CreateContentType(ContentVariation.Culture);
var properties = new PropertyTypeCollection(true)
{
new PropertyType("value1", ValueStorageType.Ntext)
{
Alias = "value1",
DataTypeId = -88,
Variations = ContentVariation.Culture
},
new PropertyType("value2", ValueStorageType.Ntext)
{
Alias = "value2",
DataTypeId = -88,
Variations = ContentVariation.Nothing
}
};
var properties = CreatePropertyCollection(
("value1", ContentVariation.Culture),
("value2", ContentVariation.Nothing));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
var document = (IContent) new Content("document", -1, contentType);
var document = (IContent)new Content("document", -1, contentType);
document.SetCultureName("doc1en", "en");
document.SetCultureName("doc1fr", "fr");
document.SetValue("value1", "v1en", "en");
@@ -226,28 +529,11 @@ namespace Umbraco.Tests.Services
var languageFr = new Language("fr");
ServiceContext.LocalizationService.Save(languageFr);
var contentType = new ContentType(-1)
{
Alias = "contentType",
Name = "contentType",
Variations = ContentVariation.Nothing
};
var contentType = CreateContentType(ContentVariation.Nothing);
var properties = new PropertyTypeCollection(true)
{
new PropertyType("value1", ValueStorageType.Ntext)
{
Alias = "value1",
DataTypeId = -88,
Variations = ContentVariation.Nothing
},
new PropertyType("value2", ValueStorageType.Ntext)
{
Alias = "value2",
DataTypeId = -88,
Variations = ContentVariation.Nothing
}
};
var properties = CreatePropertyCollection(
("value1", ContentVariation.Nothing),
("value2", ContentVariation.Nothing));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
@@ -326,33 +612,13 @@ namespace Umbraco.Tests.Services
// one simple content type, variant, with both variant and invariant properties
// can change an invariant property to variant and back
var languageEn = new Language("en") { IsDefault = true };
ServiceContext.LocalizationService.Save(languageEn);
var languageFr = new Language("fr");
ServiceContext.LocalizationService.Save(languageFr);
CreateFrenchAndEnglishLangs();
var contentType = new ContentType(-1)
{
Alias = "contentType",
Name = "contentType",
Variations = ContentVariation.Culture
};
var contentType = CreateContentType(ContentVariation.Culture);
var properties = new PropertyTypeCollection(true)
{
new PropertyType("value1", ValueStorageType.Ntext)
{
Alias = "value1",
DataTypeId = -88,
Variations = ContentVariation.Culture
},
new PropertyType("value2", ValueStorageType.Ntext)
{
Alias = "value2",
DataTypeId = -88,
Variations = ContentVariation.Nothing
}
};
var properties = CreatePropertyCollection(
("value1", ContentVariation.Culture),
("value2", ContentVariation.Nothing));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
@@ -429,6 +695,185 @@ namespace Umbraco.Tests.Services
"{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'en','seg':'','val':'v2'}]},'cultureData':");
}
[Test]
public void Change_Property_Variations_From_Variant_To_Invariant_And_Ensure_Edited_Values_Are_Renormalized()
{
// one simple content type, variant, with both variant and invariant properties
// can change an invariant property to variant and back
CreateFrenchAndEnglishLangs();
var contentType = CreateContentType(ContentVariation.Culture);
var properties = CreatePropertyCollection(("value1", ContentVariation.Culture));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
var document = (IContent)new Content("document", -1, contentType);
document.SetCultureName("doc1en", "en");
document.SetCultureName("doc1fr", "fr");
document.SetValue("value1", "v1en-init", "en");
document.SetValue("value1", "v1fr-init", "fr");
ServiceContext.ContentService.SaveAndPublish(document); //all values are published which means the document is not 'edited'
document = ServiceContext.ContentService.GetById(document.Id);
Assert.IsFalse(document.IsCultureEdited("en"));
Assert.IsFalse(document.IsCultureEdited("fr"));
Assert.IsFalse(document.Edited);
document.SetValue("value1", "v1en", "en"); //change the property culture value, so now this culture will be edited
document.SetValue("value1", "v1fr", "fr"); //change the property culture value, so now this culture will be edited
ServiceContext.ContentService.Save(document);
document = ServiceContext.ContentService.GetById(document.Id);
Assert.AreEqual("doc1en", document.Name);
Assert.AreEqual("doc1en", document.GetCultureName("en"));
Assert.AreEqual("doc1fr", document.GetCultureName("fr"));
Assert.AreEqual("v1en", document.GetValue("value1", "en"));
Assert.AreEqual("v1en-init", document.GetValue("value1", "en", published: true));
Assert.AreEqual("v1fr", document.GetValue("value1", "fr"));
Assert.AreEqual("v1fr-init", document.GetValue("value1", "fr", published: true));
Assert.IsTrue(document.IsCultureEdited("en")); //This will be true because the edited value isn't the same as the published value
Assert.IsTrue(document.IsCultureEdited("fr")); //This will be true because the edited value isn't the same as the published value
Assert.IsTrue(document.Edited);
// switch property type to Invariant
contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Nothing;
ServiceContext.ContentTypeService.Save(contentType); //This is going to have to re-normalize the "Edited" flag
document = ServiceContext.ContentService.GetById(document.Id);
Assert.IsTrue(document.IsCultureEdited("en")); //This will remain true because there is now a pending change for the invariant property data which is flagged under the default lang
Assert.IsFalse(document.IsCultureEdited("fr")); //This will be false because nothing has changed for this culture and the property no longer reflects variant changes
Assert.IsTrue(document.Edited);
//update the invariant value and publish
document.SetValue("value1", "v1inv");
ServiceContext.ContentService.SaveAndPublish(document);
document = ServiceContext.ContentService.GetById(document.Id);
Assert.AreEqual("doc1en", document.Name);
Assert.AreEqual("doc1en", document.GetCultureName("en"));
Assert.AreEqual("doc1fr", document.GetCultureName("fr"));
Assert.IsNull(document.GetValue("value1", "en")); //The values are there but the business logic returns null
Assert.IsNull(document.GetValue("value1", "fr")); //The values are there but the business logic returns null
Assert.IsNull(document.GetValue("value1", "en", published: true)); //The values are there but the business logic returns null
Assert.IsNull(document.GetValue("value1", "fr", published: true)); //The values are there but the business logic returns null
Assert.AreEqual("v1inv", document.GetValue("value1"));
Assert.AreEqual("v1inv", document.GetValue("value1", published: true));
Assert.IsFalse(document.IsCultureEdited("en")); //This returns false, everything is published
Assert.IsFalse(document.IsCultureEdited("fr")); //This will be false because nothing has changed for this culture and the property no longer reflects variant changes
Assert.IsFalse(document.Edited);
// switch property back to Culture
contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType);
document = ServiceContext.ContentService.GetById(document.Id);
Assert.AreEqual("v1inv", document.GetValue("value1", "en")); //The invariant property value gets copied over to the default language
Assert.AreEqual("v1inv", document.GetValue("value1", "en", published: true));
Assert.AreEqual("v1fr", document.GetValue("value1", "fr")); //values are still retained
Assert.AreEqual("v1fr-init", document.GetValue("value1", "fr", published: true)); //values are still retained
Assert.IsFalse(document.IsCultureEdited("en")); //The invariant published AND edited values are copied over to the default language
Assert.IsTrue(document.IsCultureEdited("fr")); //The previously existing french values are there and there is no published value
Assert.IsTrue(document.Edited); //Will be flagged edited again because the french culture had pending changes
// publish again
document.SetValue("value1", "v1en2", "en"); //update the value now that it's variant again
document.SetValue("value1", "v1fr2", "fr"); //update the value now that it's variant again
ServiceContext.ContentService.SaveAndPublish(document);
document = ServiceContext.ContentService.GetById(document.Id);
Assert.AreEqual("doc1en", document.Name);
Assert.AreEqual("doc1en", document.GetCultureName("en"));
Assert.AreEqual("doc1fr", document.GetCultureName("fr"));
Assert.AreEqual("v1en2", document.GetValue("value1", "en"));
Assert.AreEqual("v1fr2", document.GetValue("value1", "fr"));
Assert.IsNull(document.GetValue("value1")); //The value is there but the business logic returns null
Assert.IsFalse(document.IsCultureEdited("en")); //This returns false, the variant property value has been published
Assert.IsFalse(document.IsCultureEdited("fr")); //This returns false, the variant property value has been published
Assert.IsFalse(document.Edited);
}
[Test]
public void Change_Property_Variations_From_Invariant_To_Variant_And_Ensure_Edited_Values_Are_Renormalized()
{
// one simple content type, variant, with both variant and invariant properties
// can change an invariant property to variant and back
CreateFrenchAndEnglishLangs();
var contentType = CreateContentType(ContentVariation.Culture);
var properties = CreatePropertyCollection(("value1", ContentVariation.Nothing));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
var document = (IContent)new Content("document", -1, contentType);
document.SetCultureName("doc1en", "en");
document.SetCultureName("doc1fr", "fr");
document.SetValue("value1", "v1en-init");
ServiceContext.ContentService.SaveAndPublish(document); //all values are published which means the document is not 'edited'
document = ServiceContext.ContentService.GetById(document.Id);
Assert.IsFalse(document.IsCultureEdited("en"));
Assert.IsFalse(document.IsCultureEdited("fr"));
Assert.IsFalse(document.Edited);
document.SetValue("value1", "v1en"); //change the property value, so now the invariant (default) culture will be edited
ServiceContext.ContentService.Save(document);
document = ServiceContext.ContentService.GetById(document.Id);
Assert.AreEqual("doc1en", document.Name);
Assert.AreEqual("doc1en", document.GetCultureName("en"));
Assert.AreEqual("doc1fr", document.GetCultureName("fr"));
Assert.AreEqual("v1en", document.GetValue("value1"));
Assert.AreEqual("v1en-init", document.GetValue("value1", published: true));
Assert.IsTrue(document.IsCultureEdited("en")); //This is true because the invariant property reflects changes on the default lang
Assert.IsFalse(document.IsCultureEdited("fr"));
Assert.IsTrue(document.Edited);
// switch property type to Culture
contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Culture;
ServiceContext.ContentTypeService.Save(contentType); //This is going to have to re-normalize the "Edited" flag
document = ServiceContext.ContentService.GetById(document.Id);
Assert.IsTrue(document.IsCultureEdited("en")); //Remains true
Assert.IsFalse(document.IsCultureEdited("fr")); //False because no french property has ever been edited
Assert.IsTrue(document.Edited);
//update the culture value and publish
document.SetValue("value1", "v1en2", "en");
ServiceContext.ContentService.SaveAndPublish(document);
document = ServiceContext.ContentService.GetById(document.Id);
Assert.AreEqual("doc1en", document.Name);
Assert.AreEqual("doc1en", document.GetCultureName("en"));
Assert.AreEqual("doc1fr", document.GetCultureName("fr"));
Assert.IsNull(document.GetValue("value1")); //The values are there but the business logic returns null
Assert.IsNull(document.GetValue("value1", published: true)); //The values are there but the business logic returns null
Assert.AreEqual("v1en2", document.GetValue("value1", "en"));
Assert.AreEqual("v1en2", document.GetValue("value1", "en", published: true));
Assert.IsFalse(document.IsCultureEdited("en")); //This returns false, everything is published
Assert.IsFalse(document.IsCultureEdited("fr")); //False because no french property has ever been edited
Assert.IsFalse(document.Edited);
// switch property back to Invariant
contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Nothing;
ServiceContext.ContentTypeService.Save(contentType);
document = ServiceContext.ContentService.GetById(document.Id);
Assert.AreEqual("v1en2", document.GetValue("value1")); //The variant property value gets copied over to the invariant
Assert.AreEqual("v1en2", document.GetValue("value1", published: true));
Assert.IsNull(document.GetValue("value1", "fr")); //The values are there but the business logic returns null
Assert.IsNull(document.GetValue("value1", "fr", published: true)); //The values are there but the business logic returns null
Assert.IsFalse(document.IsCultureEdited("en")); //The variant published AND edited values are copied over to the invariant
Assert.IsFalse(document.IsCultureEdited("fr"));
Assert.IsFalse(document.Edited);
}
[Test]
public void Change_Variations_ComposedContentType_1()
{
@@ -437,59 +882,22 @@ namespace Umbraco.Tests.Services
// can change the composing content type to invariant and back
// can change the composed content type to invariant and back
var languageEn = new Language("en") { IsDefault = true };
ServiceContext.LocalizationService.Save(languageEn);
var languageFr = new Language("fr");
ServiceContext.LocalizationService.Save(languageFr);
CreateFrenchAndEnglishLangs();
var composing = new ContentType(-1)
{
Alias = "composing",
Name = "composing",
Variations = ContentVariation.Culture
};
var composing = CreateContentType(ContentVariation.Culture, "composing");
var properties1 = new PropertyTypeCollection(true)
{
new PropertyType("value11", ValueStorageType.Ntext)
{
Alias = "value11",
DataTypeId = -88,
Variations = ContentVariation.Culture
},
new PropertyType("value12", ValueStorageType.Ntext)
{
Alias = "value12",
DataTypeId = -88,
Variations = ContentVariation.Nothing
}
};
var properties1 = CreatePropertyCollection(
("value11", ContentVariation.Culture),
("value12", ContentVariation.Nothing));
composing.PropertyGroups.Add(new PropertyGroup(properties1) { Name = "Content" });
ServiceContext.ContentTypeService.Save(composing);
var composed = new ContentType(-1)
{
Alias = "composed",
Name = "composed",
Variations = ContentVariation.Culture
};
var composed = CreateContentType(ContentVariation.Culture, "composed");
var properties2 = new PropertyTypeCollection(true)
{
new PropertyType("value21", ValueStorageType.Ntext)
{
Alias = "value21",
DataTypeId = -88,
Variations = ContentVariation.Culture
},
new PropertyType("value22", ValueStorageType.Ntext)
{
Alias = "value22",
DataTypeId = -88,
Variations = ContentVariation.Nothing
}
};
var properties2 = CreatePropertyCollection(
("value21", ContentVariation.Culture),
("value22", ContentVariation.Nothing));
composed.PropertyGroups.Add(new PropertyGroup(properties2) { Name = "Content" });
composed.AddContentType(composing);
@@ -569,86 +977,32 @@ namespace Umbraco.Tests.Services
// can change the composing content type to invariant and back
// can change the variant composed content type to invariant and back
var languageEn = new Language("en") { IsDefault = true };
ServiceContext.LocalizationService.Save(languageEn);
var languageFr = new Language("fr");
ServiceContext.LocalizationService.Save(languageFr);
CreateFrenchAndEnglishLangs();
var composing = new ContentType(-1)
{
Alias = "composing",
Name = "composing",
Variations = ContentVariation.Culture
};
var composing = CreateContentType(ContentVariation.Culture, "composing");
var properties1 = new PropertyTypeCollection(true)
{
new PropertyType("value11", ValueStorageType.Ntext)
{
Alias = "value11",
DataTypeId = -88,
Variations = ContentVariation.Culture
},
new PropertyType("value12", ValueStorageType.Ntext)
{
Alias = "value12",
DataTypeId = -88,
Variations = ContentVariation.Nothing
}
};
var properties1 = CreatePropertyCollection(
("value11", ContentVariation.Culture),
("value12", ContentVariation.Nothing));
composing.PropertyGroups.Add(new PropertyGroup(properties1) { Name = "Content" });
ServiceContext.ContentTypeService.Save(composing);
var composed1 = new ContentType(-1)
{
Alias = "composed1",
Name = "composed1",
Variations = ContentVariation.Culture
};
var composed1 = CreateContentType(ContentVariation.Culture, "composed1");
var properties2 = new PropertyTypeCollection(true)
{
new PropertyType("value21", ValueStorageType.Ntext)
{
Alias = "value21",
DataTypeId = -88,
Variations = ContentVariation.Culture
},
new PropertyType("value22", ValueStorageType.Ntext)
{
Alias = "value22",
DataTypeId = -88,
Variations = ContentVariation.Nothing
}
};
var properties2 = CreatePropertyCollection(
("value21", ContentVariation.Culture),
("value22", ContentVariation.Nothing));
composed1.PropertyGroups.Add(new PropertyGroup(properties2) { Name = "Content" });
composed1.AddContentType(composing);
ServiceContext.ContentTypeService.Save(composed1);
var composed2 = new ContentType(-1)
{
Alias = "composed2",
Name = "composed2",
Variations = ContentVariation.Nothing
};
var composed2 = CreateContentType(ContentVariation.Nothing, "composed2");
var properties3 = new PropertyTypeCollection(true)
{
new PropertyType("value31", ValueStorageType.Ntext)
{
Alias = "value31",
DataTypeId = -88,
Variations = ContentVariation.Nothing
},
new PropertyType("value32", ValueStorageType.Ntext)
{
Alias = "value32",
DataTypeId = -88,
Variations = ContentVariation.Nothing
}
};
var properties3 = CreatePropertyCollection(
("value31", ContentVariation.Nothing),
("value32", ContentVariation.Nothing));
composed2.PropertyGroups.Add(new PropertyGroup(properties3) { Name = "Content" });
composed2.AddContentType(composing);
@@ -754,5 +1108,35 @@ namespace Umbraco.Tests.Services
AssertJsonStartsWith(document2.Id,
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':");
}
private void CreateFrenchAndEnglishLangs()
{
var languageEn = new Language("en") { IsDefault = true };
ServiceContext.LocalizationService.Save(languageEn);
var languageFr = new Language("fr");
ServiceContext.LocalizationService.Save(languageFr);
}
private IContentType CreateContentType(ContentVariation variance, string alias = "contentType") => new ContentType(-1)
{
Alias = alias,
Name = alias,
Variations = variance
};
private PropertyTypeCollection CreatePropertyCollection(params (string alias, ContentVariation variance)[] props)
{
var propertyCollection = new PropertyTypeCollection(true);
foreach (var (alias, variance) in props)
propertyCollection.Add(new PropertyType(alias, ValueStorageType.Ntext)
{
Alias = alias,
DataTypeId = -88,
Variations = variance
});
return propertyCollection;
}
}
}
@@ -25,19 +25,23 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting
public async Task<Tuple<HttpResponseMessage, string>> Execute(string controllerName, string actionName, HttpMethod method,
HttpContent content = null,
MediaTypeWithQualityHeaderValue mediaTypeHeader = null,
bool assertOkResponse = true)
bool assertOkResponse = true, object routeDefaults = null, string url = null)
{
if (mediaTypeHeader == null)
{
mediaTypeHeader = new MediaTypeWithQualityHeaderValue("application/json");
}
if (routeDefaults == null)
{
routeDefaults = new { controller = controllerName, action = actionName, id = RouteParameter.Optional };
}
var startup = new TestStartup(
configuration =>
{
configuration.Routes.MapHttpRoute("Default",
routeTemplate: "{controller}/{action}/{id}",
defaults: new { controller = controllerName, action = actionName, id = RouteParameter.Optional });
defaults: routeDefaults);
},
_controllerFactory);
@@ -45,7 +49,7 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting
{
var request = new HttpRequestMessage
{
RequestUri = new Uri("https://testserver/"),
RequestUri = new Uri("https://testserver/" + (url ?? "")),
Method = method
};
@@ -0,0 +1,22 @@
using Lucene.Net.Store;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Umbraco.Tests.TestHelpers
{
/// <summary>
/// Used for tests with Lucene so that each RAM directory is unique
/// </summary>
public class RandomIdRAMDirectory : RAMDirectory
{
private readonly string _lockId = Guid.NewGuid().ToString();
public override string GetLockId()
{
return _lockId;
}
}
}
@@ -2,6 +2,7 @@
using System.Linq;
using System.Reflection;
using NUnit.Framework;
using Umbraco.Core.Exceptions;
namespace Umbraco.Tests.Testing
{
@@ -29,7 +30,7 @@ namespace Umbraco.Tests.Testing
var methodName = test.MethodName;
var type = Type.GetType(typeName, true);
if (type == null)
throw new Exception("panic"); // makes no sense
throw new PanicException($"Could not resolve the type from type name {typeName}"); // makes no sense
var methodInfo = type.GetMethod(methodName); // what about overloads?
var options = GetTestOptions<TOptions>(methodInfo);
return options;
@@ -53,7 +54,7 @@ namespace Umbraco.Tests.Testing
{
if (other == null) throw new ArgumentNullException(nameof(other));
if (!(Merge((TestOptionAttributeBase) other) is TOptions merged))
throw new Exception("panic");
throw new PanicException("Could not merge test options");
return merged;
}
+3
View File
@@ -157,6 +157,7 @@
<Compile Include="Services\MemberGroupServiceTests.cs" />
<Compile Include="Services\MediaTypeServiceTests.cs" />
<Compile Include="Services\PropertyValidationServiceTests.cs" />
<Compile Include="TestHelpers\RandomIdRamDirectory.cs" />
<Compile Include="Testing\Objects\TestDataSource.cs" />
<Compile Include="Published\PublishedSnapshotTestObjects.cs" />
<Compile Include="Published\ModelTypeTests.cs" />
@@ -242,6 +243,7 @@
<Compile Include="Cache\FullDataSetCachePolicyTests.cs" />
<Compile Include="Cache\SingleItemsOnlyCachePolicyTests.cs" />
<Compile Include="Collections\DeepCloneableListTests.cs" />
<Compile Include="Web\Controllers\AuthenticationControllerTests.cs" />
<Compile Include="Web\Controllers\BackOfficeControllerUnitTests.cs" />
<Compile Include="CoreThings\DelegateExtensionsTests.cs" />
<Compile Include="Web\Controllers\ContentControllerTests.cs" />
@@ -268,6 +270,7 @@
<Compile Include="Strings\StylesheetHelperTests.cs" />
<Compile Include="Strings\StringValidationTests.cs" />
<Compile Include="Web\Mvc\ValidateUmbracoFormRouteStringAttributeTests.cs" />
<Compile Include="Web\PublishedContentQueryTests.cs" />
<Compile Include="Web\UmbracoHelperTests.cs" />
<Compile Include="Membership\MembershipProviderBaseTests.cs" />
<Compile Include="Membership\UmbracoServiceMembershipProviderTests.cs" />
@@ -0,0 +1,139 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Security.Cryptography;
using System.Threading;
using System.Web;
using System.Web.Hosting;
using System.Web.Http;
using Moq;
using Newtonsoft.Json;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Services;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.ControllerTesting;
using Umbraco.Tests.Testing;
using Umbraco.Web;
using Umbraco.Web.Editors;
using Umbraco.Web.Features;
using Umbraco.Web.Models.ContentEditing;
using IUser = Umbraco.Core.Models.Membership.IUser;
namespace Umbraco.Tests.Web.Controllers
{
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.None)]
public class AuthenticationControllerTests : TestWithDatabaseBase
{
protected override void ComposeApplication(bool withApplication)
{
base.ComposeApplication(withApplication);
//if (!withApplication) return;
// replace the true IUserService implementation with a mock
// so that each test can configure the service to their liking
Composition.RegisterUnique(f => Mock.Of<IUserService>());
// kill the true IEntityService too
Composition.RegisterUnique(f => Mock.Of<IEntityService>());
Composition.RegisterUnique<UmbracoFeatures>();
}
[Test]
public async System.Threading.Tasks.Task GetCurrentUser_Fips()
{
ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor, UmbracoHelper helper)
{
//setup some mocks
var userServiceMock = Mock.Get(Current.Services.UserService);
userServiceMock.Setup(service => service.GetUserById(It.IsAny<int>()))
.Returns(() => null);
if (Thread.GetDomain().GetData(".appPath") != null)
{
HttpContext.Current = new HttpContext(new SimpleWorkerRequest("", "", new StringWriter()));
}
else
{
var baseDir = IOHelper.MapPath("", false).TrimEnd(IOHelper.DirSepChar);
HttpContext.Current = new HttpContext(new SimpleWorkerRequest("/", baseDir, "", "", new StringWriter()));
}
IOHelper.ForceNotHosted = true;
var usersController = new AuthenticationController(
Factory.GetInstance<IGlobalSettings>(),
umbracoContextAccessor,
Factory.GetInstance<ISqlContext>(),
Factory.GetInstance<ServiceContext>(),
Factory.GetInstance<AppCaches>(),
Factory.GetInstance<IProfilingLogger>(),
Factory.GetInstance<IRuntimeState>(),
helper);
return usersController;
}
Mock.Get(Current.SqlContext)
.Setup(x => x.Query<IUser>())
.Returns(new Query<IUser>(Current.SqlContext));
var syntax = new SqlCeSyntaxProvider();
Mock.Get(Current.SqlContext)
.Setup(x => x.SqlSyntax)
.Returns(syntax);
var mappers = new MapperCollection(new[]
{
new UserMapper(new Lazy<ISqlContext>(() => Current.SqlContext), new ConcurrentDictionary<Type, ConcurrentDictionary<string, string>>())
});
Mock.Get(Current.SqlContext)
.Setup(x => x.Mappers)
.Returns(mappers);
// Testing what happens if the system were configured to only use FIPS-compliant algorithms
var typ = typeof(CryptoConfig);
var flds = typ.GetFields(BindingFlags.Static | BindingFlags.NonPublic);
var haveFld = flds.FirstOrDefault(f => f.Name == "s_haveFipsAlgorithmPolicy");
var isFld = flds.FirstOrDefault(f => f.Name == "s_fipsAlgorithmPolicy");
var originalFipsValue = CryptoConfig.AllowOnlyFipsAlgorithms;
try
{
if (!originalFipsValue)
{
haveFld.SetValue(null, true);
isFld.SetValue(null, true);
}
var runner = new TestRunner(CtrlFactory);
var response = await runner.Execute("Authentication", "GetCurrentUser", HttpMethod.Get);
var obj = JsonConvert.DeserializeObject<UserDetail>(response.Item2);
Assert.AreEqual(-1, obj.UserId);
}
finally
{
if (!originalFipsValue)
{
haveFld.SetValue(null, false);
isFld.SetValue(null, false);
}
}
}
}
}
@@ -4,6 +4,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Reflection;
using System.Security.Cryptography;
using System.Web.Http;
using Moq;
using Newtonsoft.Json;
@@ -155,7 +157,7 @@ namespace Umbraco.Tests.Web.Controllers
var runner = new TestRunner(CtrlFactory);
var response = await runner.Execute("Users", "GetPagedUsers", HttpMethod.Get);
var obj = JsonConvert.DeserializeObject<PagedResult<UserDisplay>>(response.Item2);
var obj = JsonConvert.DeserializeObject<PagedResult<UserBasic>>(response.Item2);
Assert.AreEqual(0, obj.TotalItems);
}
@@ -190,9 +192,100 @@ namespace Umbraco.Tests.Web.Controllers
var runner = new TestRunner(CtrlFactory);
var response = await runner.Execute("Users", "GetPagedUsers", HttpMethod.Get);
var obj = JsonConvert.DeserializeObject<PagedResult<UserDisplay>>(response.Item2);
var obj = JsonConvert.DeserializeObject<PagedResult<UserBasic>>(response.Item2);
Assert.AreEqual(10, obj.TotalItems);
Assert.AreEqual(10, obj.Items.Count());
}
[Test]
public async System.Threading.Tasks.Task GetPagedUsers_Fips()
{
await RunFipsTest("GetPagedUsers", mock =>
{
var users = MockedUser.CreateMulipleUsers(10);
long outVal = 10;
mock.Setup(service => service.GetAll(
It.IsAny<long>(), It.IsAny<int>(), out outVal, It.IsAny<string>(), It.IsAny<Direction>(),
It.IsAny<UserState[]>(), It.IsAny<string[]>(), It.IsAny<string[]>(), It.IsAny<IQuery<IUser>>()))
.Returns(() => users);
}, response =>
{
var obj = JsonConvert.DeserializeObject<PagedResult<UserBasic>>(response.Item2);
Assert.AreEqual(10, obj.TotalItems);
Assert.AreEqual(10, obj.Items.Count());
});
}
[Test]
public async System.Threading.Tasks.Task GetById_Fips()
{
const int mockUserId = 1234;
var user = MockedUser.CreateUser();
await RunFipsTest("GetById", mock =>
{
mock.Setup(service => service.GetUserById(1234))
.Returns((int i) => i == mockUserId ? user : null);
}, response =>
{
var obj = JsonConvert.DeserializeObject<UserDisplay>(response.Item2);
Assert.AreEqual(user.Username, obj.Username);
Assert.AreEqual(user.Email, obj.Email);
}, new { controller = "Users", action = "GetById" }, $"Users/GetById/{mockUserId}");
}
private async System.Threading.Tasks.Task RunFipsTest(string action, Action<Mock<IUserService>> userServiceSetup,
Action<Tuple<HttpResponseMessage, string>> verification,
object routeDefaults = null, string url = null)
{
ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor, UmbracoHelper helper)
{
//setup some mocks
var userServiceMock = Mock.Get(Current.Services.UserService);
userServiceSetup(userServiceMock);
var usersController = new UsersController(
Factory.GetInstance<IGlobalSettings>(),
umbracoContextAccessor,
Factory.GetInstance<ISqlContext>(),
Factory.GetInstance<ServiceContext>(),
Factory.GetInstance<AppCaches>(),
Factory.GetInstance<IProfilingLogger>(),
Factory.GetInstance<IRuntimeState>(),
helper);
return usersController;
}
// Testing what happens if the system were configured to only use FIPS-compliant algorithms
var typ = typeof(CryptoConfig);
var flds = typ.GetFields(BindingFlags.Static | BindingFlags.NonPublic);
var haveFld = flds.FirstOrDefault(f => f.Name == "s_haveFipsAlgorithmPolicy");
var isFld = flds.FirstOrDefault(f => f.Name == "s_fipsAlgorithmPolicy");
var originalFipsValue = CryptoConfig.AllowOnlyFipsAlgorithms;
try
{
if (!originalFipsValue)
{
haveFld.SetValue(null, true);
isFld.SetValue(null, true);
}
MockForGetPagedUsers();
var runner = new TestRunner(CtrlFactory);
var response = await runner.Execute("Users", action, HttpMethod.Get, routeDefaults: routeDefaults, url: url);
verification(response);
}
finally
{
if (!originalFipsValue)
{
haveFld.SetValue(null, false);
isFld.SetValue(null, false);
}
}
}
}
}
@@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Examine;
using Examine.LuceneEngine.Providers;
using Lucene.Net.Store;
using Moq;
using NUnit.Framework;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Examine;
using Umbraco.Tests.TestHelpers;
using Umbraco.Web;
using Umbraco.Web.PublishedCache;
namespace Umbraco.Tests.Web
{
[TestFixture]
public class PublishedContentQueryTests
{
private class TestIndex : LuceneIndex, IUmbracoIndex
{
private readonly string[] _fieldNames;
public TestIndex(string name, Directory luceneDirectory, string[] fieldNames)
: base(name, luceneDirectory, null, null, null, null)
{
_fieldNames = fieldNames;
}
public bool EnableDefaultEventHandler => throw new NotImplementedException();
public bool PublishedValuesOnly => throw new NotImplementedException();
public IEnumerable<string> GetFields() => _fieldNames;
}
private TestIndex CreateTestIndex(Directory luceneDirectory, string[] fieldNames)
{
var indexer = new TestIndex("TestIndex", luceneDirectory, fieldNames);
using (indexer.ProcessNonAsync())
{
//populate with some test data
indexer.IndexItem(new ValueSet("1", "content", new Dictionary<string, object>
{
[fieldNames[0]] = "Hello world, there are products here",
[UmbracoContentIndex.VariesByCultureFieldName] = "n"
}));
indexer.IndexItem(new ValueSet("2", "content", new Dictionary<string, object>
{
[fieldNames[1]] = "Hello world, there are products here",
[UmbracoContentIndex.VariesByCultureFieldName] = "y"
}));
indexer.IndexItem(new ValueSet("3", "content", new Dictionary<string, object>
{
[fieldNames[2]] = "Hello world, there are products here",
[UmbracoContentIndex.VariesByCultureFieldName] = "y"
}));
}
return indexer;
}
private PublishedContentQuery CreatePublishedContentQuery(IIndex indexer)
{
var examineManager = new Mock<IExamineManager>();
IIndex outarg = indexer;
examineManager.Setup(x => x.TryGetIndex("TestIndex", out outarg)).Returns(true);
var contentCache = new Mock<IPublishedContentCache>();
contentCache.Setup(x => x.GetById(It.IsAny<int>())).Returns((int intId) => Mock.Of<IPublishedContent>(x => x.Id == intId));
var snapshot = Mock.Of<IPublishedSnapshot>(x => x.Content == contentCache.Object);
var variationContext = new VariationContext();
var variationContextAccessor = Mock.Of<IVariationContextAccessor>(x => x.VariationContext == variationContext);
return new PublishedContentQuery(snapshot, variationContextAccessor, examineManager.Object);
}
[TestCase("fr-fr", ExpectedResult = "1, 3", TestName = "Search Culture: fr-fr. Must return both fr-fr and invariant results")]
[TestCase("en-us", ExpectedResult = "1, 2", TestName = "Search Culture: en-us. Must return both en-us and invariant results")]
[TestCase("*", ExpectedResult = "1, 2, 3", TestName = "Search Culture: *. Must return all cultures and all invariant results")]
[TestCase(null, ExpectedResult = "1", TestName = "Search Culture: null. Must return only invariant results")]
public string Search(string culture)
{
using (var luceneDir = new RandomIdRAMDirectory())
{
var fieldNames = new[] { "title", "title_en-us", "title_fr-fr" };
using (var indexer = CreateTestIndex(luceneDir, fieldNames))
{
var pcq = CreatePublishedContentQuery(indexer);
var results = pcq.Search("Products", culture, "TestIndex");
var ids = results.Select(x => x.Content.Id).ToArray();
return string.Join(", ", ids);
}
}
}
}
}
+7 -3
View File
@@ -1,5 +1,8 @@
using System;
using System.Text;
using Examine.LuceneEngine;
using Lucene.Net.Analysis;
using Lucene.Net.Analysis.Standard;
using Moq;
using NUnit.Framework;
using Umbraco.Core;
@@ -13,18 +16,19 @@ using Umbraco.Web;
namespace Umbraco.Tests.Web
{
[TestFixture]
public class UmbracoHelperTests
{
{
[TearDown]
public void TearDown()
{
Current.Reset();
}
// ------- Int32 conversion tests
[Test]
public static void Converting_Boxed_34_To_An_Int_Returns_34()
@@ -23,7 +23,7 @@ module.exports = function(files, out) {
// sort files in stream by path or any custom sort comparator
task = task.pipe(babel())
.pipe(sort())
.pipe(embedTemplates({ basePath: "./src/" }))
.pipe(embedTemplates({ basePath: "./src/", minimize:{ loose: true } }))
.pipe(concat(out))
.pipe(wrap('(function(){\n%= body %\n})();'))
.pipe(gulp.dest(config.root + config.targets.js));
@@ -47,6 +47,7 @@
function setSortingOptions() {
scope.sortableOptionsGroup = {
axis: 'y',
distance: 10,
tolerance: "pointer",
opacity: 0.7,
@@ -65,6 +66,7 @@
};
scope.sortableOptionsProperty = {
axis: 'y',
distance: 10,
tolerance: "pointer",
connectWith: ".umb-group-builder__properties",
@@ -94,6 +94,7 @@
font-size: 14px;
color: @black;
margin-left: 10px;
text-align: left;
}
small {
@@ -53,14 +53,14 @@
<form ng-if="login.linkedProviderKey == undefined" method="POST" name="externalLoginForm"
action="{{externalLinkLoginFormAction}}" id="oauthloginform" name="oauthloginform">
<input type="hidden" name="provider" value="{{login.authType}}" />
<button class="btn btn-block btn-social"
ng-class="login.properties.SocialStyle"
id="{{login.authType}}"
onclick="document.forms.oauthloginform.submit();">
<button class="btn btn-block btn-social"
ng-class="login.properties.SocialStyle"
id="{{login.authType}}"
onclick="document.forms.oauthloginform.submit();">
<i class="fa" ng-class="login.properties.SocialIcon"></i>
<localize key="defaultdialogs_linkYour">Link your</localize> {{login.caption}} <localize key="defaultdialogs_account">account</localize>
</button>
<i class="fa" ng-class="login.properties.SocialIcon"></i>
<localize key="defaultdialogs_linkYour">Link your</localize>&nbsp;{{login.caption}}&nbsp;<localize key="defaultdialogs_account">account</localize>
</button>
</form>
<button ng-if="login.linkedProviderKey != undefined"
@@ -71,7 +71,7 @@
name="provider"
value="{{login.authType}}">
<i class="fa" ng-class="login.properties.SocialIcon"></i>
<localize key="defaultdialogs_unLinkYour">Un-link your</localize> {{login.caption}} <localize key="defaultdialogs_account">account</localize>
<localize key="defaultdialogs_unLinkYour">Un-link your</localize>&nbsp;{{login.caption}}&nbsp;<localize key="defaultdialogs_account">account</localize>
</button>
</div>
@@ -138,7 +138,7 @@
id="{{login.authType}}" name="provider" value="{{login.authType}}"
title="Log in using your {{login.caption}} account">
<i class="fa" ng-class="login.properties.SocialIcon"></i>
<localize key="login_signInWith">Sign in with</localize> {{login.caption}}
<localize key="login_signInWith">Sign in with</localize>&nbsp;{{login.caption}}
</button>
</div>
@@ -32,7 +32,7 @@
</div>
<div class="text-center" ng-if="(availableItems | compareArrays:selectedItems:'alias').length === 0">
<small><localize key="general_all">Akk</localize> {{itemLabel}}s <localize key="grid_areAdded">are added</localize></small>
<small><localize key="general_all">All</localize> {{itemLabel}}s <localize key="grid_areAdded">are added</localize></small>
</div>
</div>
@@ -94,6 +94,11 @@ function contentCreateController($scope,
navigationService.hideDialog(showMenu);
};
$scope.editContentType = function() {
$location.path("/settings/documenttypes/edit/" + $scope.contentTypeId).search("view", "permissions");
close();
}
$scope.createBlank = createBlank;
$scope.createOrSelectBlueprintIfAny = createOrSelectBlueprintIfAny;
$scope.createFromBlueprint = createFromBlueprint;
@@ -10,7 +10,7 @@
<p class="abstract" ng-if="!hasSettingsAccess"><localize key="create_noDocumentTypesWithNoSettingsAccess"/></p>
<div ng-if="hasSettingsAccess">
<p class="abstract"><localize key="create_noDocumentTypes" /></p>
<button class="btn umb-outline" href="#settings/documentTypes/edit/{{contentTypeId}}?view=permissions" ng-click="close()">
<button class="btn umb-outline" ng-click="editContentType()">
<localize key="create_noDocumentTypesEditPermissions"/>
</button>
</div>
@@ -11,7 +11,7 @@
<ul class="umb-actions umb-actions-child">
<li class="umb-action" ng-repeat="documentType in vm.documentTypes | orderBy:'name':false">
<a href="" class="umb-action-link" ng-click="vm.createBlueprint(documentType)" prevent-default>
<button href="" class="umb-action-link umb-outline btn-reset" ng-click="vm.createBlueprint(documentType)" prevent-default>
<i class="large icon {{documentType.icon}}"></i>
<span class="menu-label">
{{documentType.name}}
@@ -19,7 +19,7 @@
{{documentType.description}}
</small>
</span>
</a>
</button>
</li>
</ul>
@@ -6,20 +6,20 @@
<ul class="umb-actions umb-actions-child">
<li data-element="action-data-type" class="umb-action">
<a href="" class="umb-action-link" ng-click="createDataType()" umb-auto-focus>
<button href="" class="umb-action-link umb-outline btn-reset" ng-click="createDataType()" umb-auto-focus>
<i class="large icon icon-autofill"></i>
<span class="menu-label">
<localize key="create_newDataType">New data type</localize>
</span>
</a>
</button>
</li>
<li data-element="action-folder" class="umb-action">
<a href="" class="umb-action-link" ng-click="showCreateFolder()">
<button href="" class="umb-action-link umb-outline btn-reset" ng-click="showCreateFolder()">
<i class="large icon icon-folder"></i>
<span class="menu-label">
<localize key="create_newFolder">New folder</localize>...
</span>
</a>
</button>
</li>
</ul>
</div>
@@ -7,36 +7,36 @@
<ul class="umb-actions umb-actions-child">
<li data-element="action-documentType" class="umb-action" ng-hide="model.disableTemplates">
<a href="" ng-click="createDocType()" class="umb-action-link" umb-auto-focus>
<button href="" ng-click="createDocType()" class="umb-action-link umb-outline btn-reset" umb-auto-focus>
<i class="large icon icon-item-arrangement"></i>
<span class="menu-label">
<localize key="content_documentType">Document type</localize>
</span>
</a>
</button>
</li>
<li data-element="action-documentTypeWithoutTemplate" class="umb-action">
<a href="" ng-click="createComponent()" class="umb-action-link">
<button href="" ng-click="createComponent()" class="umb-action-link umb-outline btn-reset">
<i class="large icon icon-item-arrangement"></i>
<span class="menu-label">
<localize ng-if="model.disableTemplates === false" key="create_documentTypeWithoutTemplate"></localize>
<localize ng-if="model.disableTemplates === true" key="content_documentType">Document type></localize>
</span>
</a>
</button>
</li>
<li data-element="action-documentTypeCollection" class="umb-action">
<a href="" ng-click="showCreateDocTypeCollection()" class="umb-action-link">
<button href="" ng-click="showCreateDocTypeCollection()" class="umb-action-link umb-outline btn-reset">
<i class="large icon icon-thumbnail-list"></i>
<span class="menu-label">
Document Type Collection...
<!-- <localize key="content_documentType_collection">Document Type Collection</localize>-->
</span>
</a>
</button>
</li>
<li data-element="action-folder" ng-if="model.allowCreateFolder" class="umb-action">
<a href="" ng-click="showCreateFolder()" class="umb-action-link">
<button href="" ng-click="showCreateFolder()" class="umb-action-link umb-outline btn-reset">
<i class="large icon icon-folder"></i>
<span class="menu-label"><localize key="general_folder"></localize>...</span>
</a>
</button>
</li>
</ul>
</div>
@@ -17,13 +17,13 @@
<ul class="umb-actions umb-actions-child">
<li data-element="action-{{docType.alias}}" class="umb-action" ng-repeat="docType in allowedTypes">
<a ng-href="" ng-click="createMediaItem(docType)" class="umb-action-link" prevent-default>
<button ng-click="createMediaItem(docType)" class="umb-action-link umb-outline btn-reset" prevent-default>
<i class="large icon {{docType.icon}}"></i>
<span class="menu-label">
{{docType.name}}
<small>{{docType.description}}</small>
</span>
</a>
</button>
</li>
<!--
<li class="add">
@@ -7,19 +7,19 @@
<ul class="umb-actions umb-actions-child">
<li class="umb-action">
<a href="" class="umb-action-link" ng-click="createMediaType()" umb-auto-focus>
<button href="" class="umb-action-link umb-outline btn-reset" ng-click="createMediaType()" umb-auto-focus>
<i class="large icon icon-item-arrangement"></i>
<span class="menu-label">
<localize key="general_new">New</localize>
<localize key="content_mediatype">Media type</localize>
</span>
</a>
</button>
</li>
<li class="umb-action">
<a href="" class="umb-action-link" ng-click="showCreateFolder()">
<button href="" class="umb-action-link umb-outline btn-reset" ng-click="showCreateFolder()">
<i class="large icon icon-folder"></i>
<span class="menu-label"><localize key="general_folder"></localize>...</span>
</a>
</button>
</li>
</ul>
</div>
@@ -6,13 +6,13 @@
<ul class="umb-actions umb-actions-child">
<li class="umb-action" ng-repeat="docType in allowedTypes">
<a class="umb-action-link" href="#member/member/edit/{{currentNode.id}}?doctype={{docType.alias}}&create=true" ng-click="hideActions()">
<button class="umb-action-link umb-outline btn-reset" href="#member/member/edit/{{currentNode.id}}?doctype={{docType.alias}}&create=true" ng-click="hideActions()">
<i class="large icon {{docType.icon}}"></i>
<span class="menu-label">
{{docType.name}}
<small>{{docType.description}}</small>
</span>
</a>
</button>
</li>
</ul>
@@ -5,13 +5,13 @@
<ul class="umb-actions umb-actions-child">
<li class="umb-action">
<a href="" class="umb-action-link" ng-click="showCreateFolder()">
<button href="" class="umb-action-link umb-outline btn-reset" ng-click="showCreateFolder()">
<i class="large icon icon-folder"></i>
<span class="menu-label"><localize key="general_folder"></localize></span>
</a>
</button>
</li>
<li class="umb-action">
<a href="" class="umb-action-link" ng-click="createMemberType()">
<button href="" class="umb-action-link umb-outline btn-reset" ng-click="createMemberType()">
<i class="large icon icon-item-arrangement"></i>
@@ -20,7 +20,7 @@
<localize key="content_memberType">Member type</localize>
</span>
</a>
</button>
</li>
</ul>
</div>
@@ -11,28 +11,28 @@
<ul class="umb-actions umb-actions-child">
<li class="umb-action">
<a href="" class="umb-action-link" ng-click="vm.createFile()" umb-auto-focus>
<button href="" class="umb-action-link umb-outline btn-reset" ng-click="vm.createFile()" umb-auto-focus>
<i class="large icon-article"></i>
<span class="menu-label"><localize key="create_newPartialViewMacro">New partial view macro</localize></span>
</a>
</button>
</li>
<li class="umb-action">
<a href="" class="umb-action-link" ng-click="vm.createFileWithoutMacro()">
<button href="" class="umb-action-link umb-outline btn-reset" ng-click="vm.createFileWithoutMacro()">
<i class="large icon icon-article"></i>
<span class="menu-label"><localize key="create_newPartialViewMacroNoMacro">New partial view macro (without macro)</localize></span>
</a>
</button>
</li>
<li class="umb-action">
<a href="" class="umb-action-link" ng-click="vm.showCreateFromSnippet()">
<button href="" class="umb-action-link umb-outline btn-reset" ng-click="vm.showCreateFromSnippet()">
<i class="large icon icon-article"></i>
<span class="menu-label"><localize key="create_newPartialViewMacroFromSnippet">>New partial view macro from snippet</localize>...</span>
</a>
</button>
</li>
<li class="umb-action">
<a href="" class="umb-action-link" ng-click="vm.showCreateFolder()">
<button href="" class="umb-action-link umb-outline btn-reset" ng-click="vm.showCreateFolder()">
<i class="large icon icon-folder"></i>
<span class="menu-label"><localize key="general_folder"></localize>...</span>
</a>
</button>
</li>
</ul>
</div>
@@ -44,10 +44,10 @@
<ul class="umb-actions umb-actions-child">
<li class="umb-action" ng-repeat="snippet in vm.snippets">
<a href="" class="umb-action-link" ng-click="vm.createFileFromSnippet(snippet)" style="padding-top: 6px; padding-bottom: 6px;">
<button href="" class="umb-action-link umb-outline btn-reset" ng-click="vm.createFileFromSnippet(snippet)" style="padding-top: 6px; padding-bottom: 6px;">
<i class="icon-article icon" style="font-size: 20px;"></i>
<span class="menu-label" style="margin-left: 0; padding-left: 5px;">{{ snippet.name }}</span>
</a>
</button>
</li>
</ul>
</div>
@@ -10,22 +10,22 @@
<div ng-if="!vm.showSnippets">
<ul class="umb-actions umb-actions-child">
<li class="umb-action">
<a href="" class="umb-action-link" ng-click="vm.createPartialView()" umb-auto-focus>
<button href="" class="umb-action-link umb-outline btn-reset" ng-click="vm.createPartialView()" umb-auto-focus>
<i class="large icon icon-article"></i>
<span class="menu-label"><localize key="create_newEmptyPartialView">New empty partial view</localize></span>
</a>
</button>
</li>
<li class="umb-action">
<a href="" class="umb-action-link" ng-click="vm.showCreateFromSnippet()">
<button href="" class="umb-action-link umb-outline btn-reset" ng-click="vm.showCreateFromSnippet()">
<i class="large icon icon-article"></i>
<span class="menu-label"><localize key="create_newPartialViewFromSnippet">New partial view from snippet</localize>...</span>
</a>
</button>
</li>
<li class="umb-action">
<a href="" class="umb-action-link" ng-click="vm.showCreateFolder()">
<button href="" class="umb-action-link umb-outline btn-reset" ng-click="vm.showCreateFolder()">
<i class="large icon icon-folder"></i>
<span class="menu-label"><localize key="general_folder"></localize>...</span>
</a>
</button>
</li>
</ul>
</div>
@@ -34,10 +34,10 @@
<div ng-if="vm.showSnippets">
<ul class="umb-actions umb-actions-child">
<li class="umb-action" ng-repeat="snippet in vm.snippets">
<a href="" class="umb-action-link" ng-click="vm.createPartialView(snippet)" style="padding-top: 6px; padding-bottom: 6px;">
<button href="" class="umb-action-link umb-outline btn-reset" ng-click="vm.createPartialView(snippet)" style="padding-top: 6px; padding-bottom: 6px;">
<i class="icon-article icon" style="font-size: 20px;"></i>
<span class="menu-label" style="margin-left: 0; padding-left: 5px;">{{ snippet.name }}</span>
</a>
</button>
</li>
</ul>
</div>
@@ -229,7 +229,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl
$scope.sortableOptions = {
disabled: !$scope.isMultiPicker,
disabled: !multiPicker,
items: "li:not(.add-wrapper)",
cancel: ".unsortable",
update: function (e, ui) {
@@ -7,16 +7,16 @@
<ul class="umb-actions umb-actions-child">
<li class="umb-action">
<a href="" class="umb-action-link" umb-auto-focus ng-click="vm.createFile()">
<button href="" class="umb-action-link umb-outline btn-reset" umb-auto-focus ng-click="vm.createFile()">
<i class="large icon icon-script"></i>
<span class="menu-label"><localize key="create_newJavascriptFile">New javascript file</localize></span>
</a>
</button>
</li>
<li class="umb-action">
<a href="" class="umb-action-link" ng-click="vm.showCreateFolder()">
<button href="" class="umb-action-link umb-outline btn-reset" ng-click="vm.showCreateFolder()">
<i class="large icon icon-folder"></i>
<span class="menu-label"><localize key="general_folder"></localize>...</span>
</a>
</button>
</li>
</ul>
@@ -7,22 +7,22 @@
<ul class="umb-actions umb-actions-child">
<li class="umb-action">
<a href="" class="umb-action-link" umb-auto-focus ng-click="vm.createFile()">
<button href="" class="umb-action-link umb-outline btn-reset" umb-auto-focus ng-click="vm.createFile()">
<i class="large icon icon-script"></i>
<span class="menu-label"><localize key="create_newStyleSheetFile">New style sheet file</localize></span>
</a>
</button>
</li>
<li class="umb-action">
<a href="" ng-click="vm.createRichtextStyle()" class="umb-action-link">
<button href="" ng-click="vm.createRichtextStyle()" class="umb-action-link umb-outline btn-reset">
<i class="large icon icon-script"></i>
<span class="menu-label"><localize key="create_newRteStyleSheetFile">New richtext style sheet file</localize></span>
</a>
</button>
</li>
<li class="umb-action">
<a href="" class="umb-action-link" ng-click="vm.showCreateFolder()">
<button href="" class="umb-action-link umb-outline btn-reset" ng-click="vm.showCreateFolder()">
<i class="large icon icon-folder"></i>
<span class="menu-label"><localize key="general_folder"></localize>...</span>
</a>
</button>
</li>
</ul>
@@ -90,6 +90,7 @@
ng-repeat="node in model.user.startContentIds"
icon="node.icon"
name="node.name"
allow-remove="true"
on-remove="model.removeSelectedItem($index, model.user.startContentIds)">
</umb-node-preview>
@@ -114,6 +115,7 @@
ng-repeat="node in model.user.startMediaIds"
icon="node.icon"
name="node.name"
allow-remove="true"
on-remove="model.removeSelectedItem($index, model.user.startMediaIds)">
</umb-node-preview>
+2 -2
View File
@@ -345,9 +345,9 @@
<WebProjectProperties>
<UseIIS>False</UseIIS>
<AutoAssignPort>True</AutoAssignPort>
<DevelopmentServerPort>8110</DevelopmentServerPort>
<DevelopmentServerPort>8120</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>http://localhost:8110</IISUrl>
<IISUrl>http://localhost:8120</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>
<UseCustomServer>False</UseCustomServer>
<CustomServerUrl>
+12 -4
View File
@@ -39,10 +39,14 @@ namespace Umbraco.Web
/// <param name="culture">Optional culture.</param>
/// <param name="indexName">Optional index name.</param>
/// <remarks>
/// <para>When the <paramref name="culture"/> is not specified, all cultures are searched.</para>
/// <para>
/// When the <paramref name="culture"/> is not specified or is *, all cultures are searched.
/// To search for only invariant documents and fields use null.
/// When searching on a specific culture, all culture specific fields are searched for the provided culture and all invariant fields for all documents.
/// </para>
/// <para>While enumerating results, the ambient culture is changed to be the searched culture.</para>
/// </remarks>
IEnumerable<PublishedSearchResult> Search(string term, string culture = null, string indexName = null);
IEnumerable<PublishedSearchResult> Search(string term, string culture = "*", string indexName = null);
/// <summary>
/// Searches content.
@@ -54,10 +58,14 @@ namespace Umbraco.Web
/// <param name="culture">Optional culture.</param>
/// <param name="indexName">Optional index name.</param>
/// <remarks>
/// <para>When the <paramref name="culture"/> is not specified, all cultures are searched.</para>
/// <para>
/// When the <paramref name="culture"/> is not specified or is *, all cultures are searched.
/// To search for only invariant documents and fields use null.
/// When searching on a specific culture, all culture specific fields are searched for the provided culture and all invariant fields for all documents.
/// </para>
/// <para>While enumerating results, the ambient culture is changed to be the searched culture.</para>
/// </remarks>
IEnumerable<PublishedSearchResult> Search(string term, int skip, int take, out long totalRecords, string culture = null, string indexName = null);
IEnumerable<PublishedSearchResult> Search(string term, int skip, int take, out long totalRecords, string culture = "*", string indexName = null);
/// <summary>
/// Executes the query and converts the results to PublishedSearchResult.
@@ -8,6 +8,7 @@ using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Core.Services;
using Umbraco.Core.Exceptions;
namespace Umbraco.Web.Models.Mapping
{
@@ -577,7 +578,7 @@ namespace Umbraco.Web.Models.Mapping
udiType = Constants.UdiEntityType.DocumentType;
break;
default:
throw new Exception("panic");
throw new PanicException($"Source is of type {source.GetType()} which isn't supported here");
}
return Udi.Create(udiType, source.Key);
@@ -301,7 +301,7 @@ namespace Umbraco.Web.Models.Mapping
target.Avatars = source.GetUserAvatarUrls(_appCaches.RuntimeCache);
target.Culture = source.GetUserCulture(_textService, _globalSettings).ToString();
target.Email = source.Email;
target.EmailHash = source.Email.ToLowerInvariant().Trim().ToMd5();
target.EmailHash = source.Email.ToLowerInvariant().Trim().GenerateHash();
target.Id = source.Id;
target.Key = source.Key;
target.LastLoginDate = source.LastLoginDate == default ? null : (DateTime?) source.LastLoginDate;
@@ -2,6 +2,7 @@
using System.Linq;
using Newtonsoft.Json.Linq;
using Umbraco.Core;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Editors;
@@ -56,7 +57,7 @@ namespace Umbraco.Web.PropertyEditors
}
if (!(editorValue.DataTypeConfiguration is MultipleTextStringConfiguration config))
throw new Exception("panic");
throw new PanicException($"editorValue.DataTypeConfiguration is {editorValue.DataTypeConfiguration.GetType()} but must be {typeof(MultipleTextStringConfiguration)}");
var max = config.Maximum;
//The legacy property editor saved this data as new line delimited! strange but we have to maintain that.
@@ -6,6 +6,7 @@ using System.Threading;
using System.Threading.Tasks;
using CSharpTest.Net.Collections;
using Umbraco.Core;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Scoping;
@@ -1044,7 +1045,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
if (_genObj == null)
_genObjs.Enqueue(_genObj = new GenObj(snapGen));
else if (_genObj.Gen != snapGen)
throw new Exception("panic");
throw new PanicException($"The generation {_genObj.Gen} does not equal the snapshot generation {snapGen}");
}
else
{
@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Scoping;
using Umbraco.Web.PublishedCache.NuCache.Snap;
@@ -371,7 +372,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
// if we have one already, ensure it's consistent
else if (_genObj.Gen != snapGen)
throw new Exception("panic");
throw new PanicException($"The generation {_genObj.Gen} does not equal the snapshot generation {snapGen}");
}
else
{
+28 -12
View File
@@ -20,14 +20,22 @@ namespace Umbraco.Web
{
private readonly IPublishedSnapshot _publishedSnapshot;
private readonly IVariationContextAccessor _variationContextAccessor;
private readonly IExamineManager _examineManager;
[Obsolete("Use the constructor with all parameters instead")]
public PublishedContentQuery(IPublishedSnapshot publishedSnapshot, IVariationContextAccessor variationContextAccessor)
: this (publishedSnapshot, variationContextAccessor, ExamineManager.Instance)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PublishedContentQuery"/> class.
/// </summary>
public PublishedContentQuery(IPublishedSnapshot publishedSnapshot, IVariationContextAccessor variationContextAccessor)
public PublishedContentQuery(IPublishedSnapshot publishedSnapshot, IVariationContextAccessor variationContextAccessor, IExamineManager examineManager)
{
_publishedSnapshot = publishedSnapshot ?? throw new ArgumentNullException(nameof(publishedSnapshot));
_variationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor));
_examineManager = examineManager ?? throw new ArgumentNullException(nameof(examineManager));
}
#region Content
@@ -175,19 +183,19 @@ namespace Umbraco.Web
#region Search
/// <inheritdoc />
public IEnumerable<PublishedSearchResult> Search(string term, string culture = null, string indexName = null)
public IEnumerable<PublishedSearchResult> Search(string term, string culture = "*", string indexName = null)
{
return Search(term, 0, 0, out _, culture, indexName);
}
/// <inheritdoc />
public IEnumerable<PublishedSearchResult> Search(string term, int skip, int take, out long totalRecords, string culture = null, string indexName = null)
public IEnumerable<PublishedSearchResult> Search(string term, int skip, int take, out long totalRecords, string culture = "*", string indexName = null)
{
indexName = string.IsNullOrEmpty(indexName)
? Constants.UmbracoIndexes.ExternalIndexName
: indexName;
if (!ExamineManager.Instance.TryGetIndex(indexName, out var index) || !(index is IUmbracoIndex umbIndex))
if (!_examineManager.TryGetIndex(indexName, out var index) || !(index is IUmbracoIndex umbIndex))
throw new InvalidOperationException($"No index found by name {indexName} or is not of type {typeof(IUmbracoIndex)}");
var searcher = umbIndex.GetSearcher();
@@ -195,20 +203,28 @@ namespace Umbraco.Web
// default to max 500 results
var count = skip == 0 && take == 0 ? 500 : skip + take;
//set this to the specific culture or to the culture in the request
culture = culture ?? _variationContextAccessor.VariationContext.Culture;
ISearchResults results;
if (culture.IsNullOrWhiteSpace())
if (culture == "*")
{
//search everything
results = searcher.Search(term, count);
}
else if (culture.IsNullOrWhiteSpace())
{
//only search invariant
var qry = searcher.CreateQuery().Field(UmbracoContentIndex.VariesByCultureFieldName, "n"); //must not vary by culture
qry = qry.And().ManagedQuery(term);
results = qry.Execute(count);
}
else
{
//search only the specified culture
//get all index fields suffixed with the culture name supplied
var cultureFields = umbIndex.GetCultureFields(culture);
var qry = searcher.CreateQuery().Field(UmbracoContentIndex.VariesByCultureFieldName, "y"); //must vary by culture
qry = qry.And().ManagedQuery(term, cultureFields.ToArray());
var cultureFields = umbIndex.GetCultureAndInvariantFields(culture).ToArray();
var qry = searcher.CreateQuery().ManagedQuery(term, cultureFields);
results = qry.Execute(count);
}
@@ -304,7 +320,7 @@ namespace Umbraco.Web
}
}
#endregion
@@ -45,6 +45,10 @@ namespace Umbraco.Web.Routing
if (string.IsNullOrEmpty(path))
return null;
// the stored path is absolute so we just return it as is
if(Uri.IsWellFormedUriString(path, UriKind.Absolute))
return new Uri(path);
Uri uri;
if (current == null)
@@ -0,0 +1,123 @@
using System;
using System.Threading;
using Umbraco.Core.Logging;
using Umbraco.Examine;
using System.Threading.Tasks;
using Umbraco.Core;
using Umbraco.Web.Scheduling;
namespace Umbraco.Web.Search
{
/// <summary>
/// Utility to rebuild all indexes on a background thread
/// </summary>
public sealed class BackgroundIndexRebuilder
{
private static readonly object RebuildLocker = new object();
private readonly IndexRebuilder _indexRebuilder;
private readonly IMainDom _mainDom;
private readonly IProfilingLogger _logger;
private static BackgroundTaskRunner<IBackgroundTask> _rebuildOnStartupRunner;
public BackgroundIndexRebuilder(IMainDom mainDom, IProfilingLogger logger, IndexRebuilder indexRebuilder)
{
_mainDom = mainDom;
_logger = logger;
_indexRebuilder = indexRebuilder;
}
/// <summary>
/// Called to rebuild empty indexes on startup
/// </summary>
/// <param name="indexRebuilder"></param>
/// <param name="logger"></param>
/// <param name="onlyEmptyIndexes"></param>
/// <param name="waitMilliseconds"></param>
public void RebuildIndexes(bool onlyEmptyIndexes, int waitMilliseconds = 0)
{
// TODO: need a way to disable rebuilding on startup
lock (RebuildLocker)
{
if (_rebuildOnStartupRunner != null && _rebuildOnStartupRunner.IsRunning)
{
_logger.Warn<BackgroundIndexRebuilder>("Call was made to RebuildIndexes but the task runner for rebuilding is already running");
return;
}
_logger.Info<BackgroundIndexRebuilder>("Starting initialize async background thread.");
//do the rebuild on a managed background thread
var task = new RebuildOnStartupTask(_mainDom, _indexRebuilder, _logger, onlyEmptyIndexes, waitMilliseconds);
_rebuildOnStartupRunner = new BackgroundTaskRunner<IBackgroundTask>(
"RebuildIndexesOnStartup",
_logger);
_rebuildOnStartupRunner.TryAdd(task);
}
}
/// <summary>
/// Background task used to rebuild empty indexes on startup
/// </summary>
private class RebuildOnStartupTask : IBackgroundTask
{
private readonly IMainDom _mainDom;
private readonly IndexRebuilder _indexRebuilder;
private readonly ILogger _logger;
private readonly bool _onlyEmptyIndexes;
private readonly int _waitMilliseconds;
public RebuildOnStartupTask(IMainDom mainDom,
IndexRebuilder indexRebuilder, ILogger logger, bool onlyEmptyIndexes, int waitMilliseconds = 0)
{
_mainDom = mainDom;
_indexRebuilder = indexRebuilder ?? throw new ArgumentNullException(nameof(indexRebuilder));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_onlyEmptyIndexes = onlyEmptyIndexes;
_waitMilliseconds = waitMilliseconds;
}
public bool IsAsync => false;
public void Dispose()
{
}
public void Run()
{
try
{
// rebuilds indexes
RebuildIndexes();
}
catch (Exception ex)
{
_logger.Error<RebuildOnStartupTask>(ex, "Failed to rebuild empty indexes.");
}
}
public Task RunAsync(CancellationToken token)
{
throw new NotImplementedException();
}
/// <summary>
/// Used to rebuild indexes on startup or cold boot
/// </summary>
private void RebuildIndexes()
{
//do not attempt to do this if this has been disabled since we are not the main dom.
//this can be called during a cold boot
if (!_mainDom.IsMainDom) return;
if (_waitMilliseconds > 0)
Thread.Sleep(_waitMilliseconds);
_indexRebuilder.ExamineManager.EnsureUnlocked(_mainDom, _logger);
_indexRebuilder.RebuildIndexes(_onlyEmptyIndexes);
}
}
}
}
+12 -132
View File
@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using Examine;
using Umbraco.Core;
using Umbraco.Core.Cache;
@@ -15,41 +14,35 @@ using Umbraco.Core.Sync;
using Umbraco.Web.Cache;
using Umbraco.Examine;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Web.Scheduling;
using System.Threading.Tasks;
using Examine.LuceneEngine.Directories;
using Umbraco.Core.Composing;
using System.ComponentModel;
namespace Umbraco.Web.Search
{
public sealed class ExamineComponent : IComponent
public sealed class ExamineComponent : Umbraco.Core.Composing.IComponent
{
private readonly IExamineManager _examineManager;
private readonly IContentValueSetBuilder _contentValueSetBuilder;
private readonly IPublishedContentValueSetBuilder _publishedContentValueSetBuilder;
private readonly IValueSetBuilder<IMedia> _mediaValueSetBuilder;
private readonly IValueSetBuilder<IMember> _memberValueSetBuilder;
private static bool _disableExamineIndexing = false;
private static volatile bool _isConfigured = false;
private static readonly object IsConfiguredLocker = new object();
private readonly IScopeProvider _scopeProvider;
private readonly ServiceContext _services;
private static BackgroundTaskRunner<IBackgroundTask> _rebuildOnStartupRunner;
private static readonly object RebuildLocker = new object();
private readonly ServiceContext _services;
private readonly IMainDom _mainDom;
private readonly IProfilingLogger _logger;
private readonly IUmbracoIndexesCreator _indexCreator;
private readonly IndexRebuilder _indexRebuilder;
// the default enlist priority is 100
// enlist with a lower priority to ensure that anything "default" runs after us
// but greater that SafeXmlReaderWriter priority which is 60
private const int EnlistPriority = 80;
public ExamineComponent(IMainDom mainDom,
IExamineManager examineManager, IProfilingLogger profilingLogger,
IScopeProvider scopeProvider, IUmbracoIndexesCreator indexCreator,
IndexRebuilder indexRebuilder, ServiceContext services,
ServiceContext services,
IContentValueSetBuilder contentValueSetBuilder,
IPublishedContentValueSetBuilder publishedContentValueSetBuilder,
IValueSetBuilder<IMedia> mediaValueSetBuilder,
@@ -66,7 +59,6 @@ namespace Umbraco.Web.Search
_mainDom = mainDom;
_logger = profilingLogger;
_indexCreator = indexCreator;
_indexRebuilder = indexRebuilder;
}
public void Initialize()
@@ -95,7 +87,6 @@ namespace Umbraco.Web.Search
//if we could not register the shutdown examine ourselves, it means we are not maindom! in this case all of examine should be disabled!
Suspendable.ExamineEvents.SuspendIndexers(_logger);
_disableExamineIndexing = true;
return; //exit, do not continue
}
@@ -116,71 +107,17 @@ namespace Umbraco.Web.Search
// bind to distributed cache events - this ensures that this logic occurs on ALL servers
// that are taking part in a load balanced environment.
ContentCacheRefresher.CacheUpdated += ContentCacheRefresherUpdated;
ContentTypeCacheRefresher.CacheUpdated += ContentTypeCacheRefresherUpdated; ;
ContentTypeCacheRefresher.CacheUpdated += ContentTypeCacheRefresherUpdated;
MediaCacheRefresher.CacheUpdated += MediaCacheRefresherUpdated;
MemberCacheRefresher.CacheUpdated += MemberCacheRefresherUpdated;
EnsureUnlocked(_logger, _examineManager);
// TODO: Instead of waiting 5000 ms, we could add an event handler on to fulfilling the first request, then start?
RebuildIndexes(_indexRebuilder, _logger, true, 5000);
}
public void Terminate()
{ }
/// <summary>
/// Called to rebuild empty indexes on startup
/// </summary>
/// <param name="indexRebuilder"></param>
/// <param name="logger"></param>
/// <param name="onlyEmptyIndexes"></param>
/// <param name="waitMilliseconds"></param>
public static void RebuildIndexes(IndexRebuilder indexRebuilder, ILogger logger, bool onlyEmptyIndexes, int waitMilliseconds = 0)
{
// TODO: need a way to disable rebuilding on startup
lock(RebuildLocker)
{
if (_rebuildOnStartupRunner != null && _rebuildOnStartupRunner.IsRunning)
{
logger.Warn<ExamineComponent>("Call was made to RebuildIndexes but the task runner for rebuilding is already running");
return;
}
logger.Info<ExamineComponent>("Starting initialize async background thread.");
//do the rebuild on a managed background thread
var task = new RebuildOnStartupTask(indexRebuilder, logger, onlyEmptyIndexes, waitMilliseconds);
_rebuildOnStartupRunner = new BackgroundTaskRunner<IBackgroundTask>(
"RebuildIndexesOnStartup",
logger);
_rebuildOnStartupRunner.TryAdd(task);
}
}
/// <summary>
/// Must be called to each index is unlocked before any indexing occurs
/// </summary>
/// <remarks>
/// Indexing rebuilding can occur on a normal boot if the indexes are empty or on a cold boot by the database server messenger. Before
/// either of these happens, we need to configure the indexes.
/// </remarks>
private static void EnsureUnlocked(ILogger logger, IExamineManager examineManager)
{
if (_disableExamineIndexing) return;
if (_isConfigured) return;
lock (IsConfiguredLocker)
{
//double check
if (_isConfigured) return;
_isConfigured = true;
examineManager.UnlockLuceneIndexes(logger);
}
}
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("This method should not be used and will be removed in future versions, rebuilding indexes can be done with the IndexRebuilder or the BackgroundIndexRebuilder")]
public static void RebuildIndexes(IndexRebuilder indexRebuilder, ILogger logger, bool onlyEmptyIndexes, int waitMilliseconds = 0) => Current.Factory.GetInstance<BackgroundIndexRebuilder>().RebuildIndexes(onlyEmptyIndexes, waitMilliseconds);
#region Cache refresher updated event handlers
@@ -737,7 +674,7 @@ namespace Umbraco.Web.Search
{
var strId = id.ToString(CultureInfo.InvariantCulture);
foreach (var index in examineComponent._examineManager.Indexes.OfType<IUmbracoIndex>()
.Where(x => (keepIfUnpublished && !x.PublishedValuesOnly) || !keepIfUnpublished)
.Where(x => x.PublishedValuesOnly || !keepIfUnpublished)
.Where(x => x.EnableDefaultEventHandler))
{
index.DeleteFromIndex(strId);
@@ -746,63 +683,6 @@ namespace Umbraco.Web.Search
}
#endregion
/// <summary>
/// Background task used to rebuild empty indexes on startup
/// </summary>
private class RebuildOnStartupTask : IBackgroundTask
{
private readonly IndexRebuilder _indexRebuilder;
private readonly ILogger _logger;
private readonly bool _onlyEmptyIndexes;
private readonly int _waitMilliseconds;
public RebuildOnStartupTask(IndexRebuilder indexRebuilder, ILogger logger, bool onlyEmptyIndexes, int waitMilliseconds = 0)
{
_indexRebuilder = indexRebuilder ?? throw new ArgumentNullException(nameof(indexRebuilder));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_onlyEmptyIndexes = onlyEmptyIndexes;
_waitMilliseconds = waitMilliseconds;
}
public bool IsAsync => false;
public void Dispose()
{
}
public void Run()
{
try
{
// rebuilds indexes
RebuildIndexes();
}
catch (Exception ex)
{
_logger.Error<ExamineComponent>(ex, "Failed to rebuild empty indexes.");
}
}
public Task RunAsync(CancellationToken token)
{
throw new NotImplementedException();
}
/// <summary>
/// Used to rebuild indexes on startup or cold boot
/// </summary>
private void RebuildIndexes()
{
//do not attempt to do this if this has been disabled since we are not the main dom.
//this can be called during a cold boot
if (_disableExamineIndexing) return;
if (_waitMilliseconds > 0)
Thread.Sleep(_waitMilliseconds);
EnsureUnlocked(_logger, _indexRebuilder.ExamineManager);
_indexRebuilder.RebuildIndexes(_onlyEmptyIndexes);
}
}
}
}
@@ -10,6 +10,7 @@ using Umbraco.Examine;
namespace Umbraco.Web.Search
{
/// <summary>
/// Configures and installs Examine.
/// </summary>
@@ -43,6 +44,7 @@ namespace Umbraco.Web.Search
false));
composition.RegisterUnique<IValueSetBuilder<IMedia>, MediaValueSetBuilder>();
composition.RegisterUnique<IValueSetBuilder<IMember>, MemberValueSetBuilder>();
composition.RegisterUnique<BackgroundIndexRebuilder>();
//We want to manage Examine's AppDomain shutdown sequence ourselves so first we'll disable Examine's default behavior
//and then we'll use MainDom to control Examine's shutdown - this MUST be done in Compose ie before ExamineManager
@@ -0,0 +1,42 @@
using Examine;
using Umbraco.Core.Logging;
using Umbraco.Examine;
using Umbraco.Core.Composing;
using Umbraco.Core;
namespace Umbraco.Web.Search
{
/// <summary>
/// Executes after all other examine components have executed
/// </summary>
public sealed class ExamineFinalComponent : IComponent
{
private readonly IProfilingLogger _logger;
private readonly IExamineManager _examineManager;
BackgroundIndexRebuilder _indexRebuilder;
private readonly IMainDom _mainDom;
public ExamineFinalComponent(IProfilingLogger logger, IExamineManager examineManager, BackgroundIndexRebuilder indexRebuilder, IMainDom mainDom)
{
_logger = logger;
_examineManager = examineManager;
_indexRebuilder = indexRebuilder;
_mainDom = mainDom;
}
public void Initialize()
{
if (!_mainDom.IsMainDom) return;
_examineManager.EnsureUnlocked(_mainDom, _logger);
// TODO: Instead of waiting 5000 ms, we could add an event handler on to fulfilling the first request, then start?
_indexRebuilder.RebuildIndexes(true, 5000);
}
public void Terminate()
{
}
}
}
@@ -0,0 +1,13 @@
using Umbraco.Core;
using Umbraco.Core.Composing;
namespace Umbraco.Web.Search
{
// examine's final composer composes after all user composers
// and *also* after ICoreComposer (in case IUserComposer is disabled)
[ComposeAfter(typeof(IUserComposer))]
[ComposeAfter(typeof(ICoreComposer))]
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
public class ExamineFinalComposer : ComponentComposer<ExamineFinalComponent>
{ }
}
@@ -0,0 +1,37 @@
using Umbraco.Core;
using Umbraco.Core.Composing;
namespace Umbraco.Web.Search
{
/// <summary>
/// An abstract class for custom index authors to inherit from
/// </summary>
public abstract class ExamineUserComponent : IComponent
{
private readonly IMainDom _mainDom;
public ExamineUserComponent(IMainDom mainDom)
{
_mainDom = mainDom;
}
/// <summary>
/// Initialize the component, eagerly exits if ExamineComponent.ExamineEnabled == false
/// </summary>
public void Initialize()
{
if (!_mainDom.IsMainDom) return;
InitializeComponent();
}
/// <summary>
/// Abstract method which executes to initialize this component if ExamineComponent.ExamineEnabled == true
/// </summary>
protected abstract void InitializeComponent();
public virtual void Terminate()
{
}
}
}
@@ -8,6 +8,7 @@ using Umbraco.Examine;
namespace Umbraco.Web.Search
{
/// <summary>
/// Used to return diagnostic data for any index
/// </summary>
+2
View File
@@ -50,6 +50,8 @@ namespace Umbraco.Web
}
}
//This is really needed at all since the only place this is used is in ExamineComponent and that already maintains a flag of whether it suspsended or not
// AHH... but Deploy probably uses this?
public static class ExamineEvents
{
private static bool _tried, _suspended;
+4
View File
@@ -233,6 +233,10 @@
<Compile Include="Routing\IPublishedRouter.cs" />
<Compile Include="Routing\MediaUrlProviderCollection.cs" />
<Compile Include="Routing\MediaUrlProviderCollectionBuilder.cs" />
<Compile Include="Search\BackgroundIndexRebuilder.cs" />
<Compile Include="Search\ExamineFinalComponent.cs" />
<Compile Include="Search\ExamineFinalComposer.cs" />
<Compile Include="Search\ExamineUserComponent.cs" />
<Compile Include="Services\DashboardService.cs" />
<Compile Include="Services\IDashboardService.cs" />
<Compile Include="Models\Link.cs" />
+8
View File
@@ -53,7 +53,15 @@ namespace Umbraco.Web
{
// make sure we have a variation context
if (_variationContextAccessor.VariationContext == null)
{
// TODO: By using _defaultCultureAccessor.DefaultCulture this means that the VariationContext will always return a variant culture, it will never
// return an empty string signifying that the culture is invariant. But does this matter? Are we actually expecting this to return an empty string
// for invariant routes? From what i can tell throughout the codebase is that whenever we are checking against the VariationContext.Culture we are
// also checking if the content type varies by culture or not. This is fine, however the code in the ctor of VariationContext is then misleading
// since it's assuming that the Culture can be empty (invariant) when in reality of a website this will never be empty since a real culture is always set here.
_variationContextAccessor.VariationContext = new VariationContext(_defaultCultureAccessor.DefaultCulture);
}
var webSecurity = new WebSecurity(httpContext, _userService, _globalSettings);