Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6190e2462a | |||
| 69fb17a154 | |||
| 90be3cb6c7 | |||
| c4b81a6273 | |||
| 4b9c58f88f | |||
| 4abb904389 | |||
| 291c30b5a1 | |||
| 60183fe899 | |||
| 61f27574b6 | |||
| e2c8b55b33 |
@@ -1,35 +0,0 @@
|
||||
# Contributing to Umbraco CMS
|
||||
|
||||
When you’re considering creating a pull request for Umbraco CMS, we will categorize them in two different sizes, small and large.
|
||||
|
||||
The process for both sizes is very similar, as [explained in the contribution document](CONTRIBUTING.md#how-do-i-begin).
|
||||
|
||||
## Small PRs
|
||||
Bug fixes and small improvements - can be recognized by seeing a small number of changes and possibly a small number of new files.
|
||||
|
||||
We’re usually able to handle small PRs pretty quickly. A community volunteer will do the initial review and flag it for Umbraco HQ as “community tested”. If everything looks good, it will be merged pretty quickly [as per the described process](REVIEW_PROCESS.md).
|
||||
|
||||
### Up for grabs
|
||||
|
||||
Umbraco HQ will regularly mark newly created issues on the issue tracker with the `Up for grabs` tag. This means that the proposed changes are wanted in Umbraco but the HQ does not have the time to make them at this time. We encourage anyone to pick them up and help out.
|
||||
|
||||
If you do start working on something, make sure leave a small comment on the issue saying something like: "I'm working on this". That way other people stumbling upon the issue know they don't need to pick it up, someone already has.
|
||||
|
||||
## Large PRs
|
||||
New features and large refactorings - can be recognized by seeing a large number of changes, plenty of new files, updates to package manager files (NuGet’s packages.config, NPM’s packages.json, etc.).
|
||||
|
||||
We would love to follow the same process for larger PRs but this is not always possible due to time limitations and priorities that need to be aligned. We don’t want to put up any barriers, but this document should set the correct expectations.
|
||||
|
||||
Please make sure to describe your idea in an issue, it helps to put in mockup screenshots or videos.
|
||||
|
||||
If the change makes sense for HQ to include in Umbraco CMS we will leave you some feedback on how we’d like to see it being implemented.
|
||||
|
||||
If a larger pull request is encouraged by Umbraco HQ, the process will be similar to what is described in the [small PRs process](#small-prs) above, we strive to feedback within 14 days. Finalizing and merging the PR might take longer though as it will likely need to be picked up by the development team to make sure everything is in order. We’ll keep you posted on the progress.
|
||||
|
||||
It is highly recommended that you speak to the HQ before making large, complex changes.
|
||||
|
||||
### Pull request or package?
|
||||
|
||||
If it doesn’t fit in CMS right now, we will likely encourage you to make it into a package instead. A package is a great way to check out popularity of a feature, learn how people use it, validate good usability and to fix bugs.
|
||||
|
||||
Eventually, a package could "graduate" to be included in the CMS.
|
||||
@@ -1,25 +0,0 @@
|
||||
# Review process
|
||||
|
||||
You're an awesome person and have sent us your contribution in the form of a pull request! It's now time to relax for a bit and wait for our response.
|
||||
|
||||
In order to set some expectations, here's what happens next.
|
||||
|
||||
## Review process
|
||||
|
||||
You will get an initial reply within 48 hours (workdays) to acknowledge that we’ve seen your PR and we’ll pick it up as soon as we can.
|
||||
|
||||
You will get feedback within at most 14 days after opening the PR. You’ll most likely get feedback sooner though. Then there are a few possible outcomes:
|
||||
|
||||
- Your proposed change is awesome! We merge it in and it will be included in the next minor release of Umbraco
|
||||
- If the change is a high priority bug fix, we will cherry-pick it into the next patch release as well so that we can release it as soon as possible
|
||||
- Your proposed change is awesome but needs a bit more work, we’ll give you feedback on the changes we’d like to see
|
||||
- Your proposed change is awesome but.. not something we’re looking to include at this point. We’ll close your PR and the related issue (we’ll be nice about it!)
|
||||
|
||||
## Are you still available?
|
||||
|
||||
We understand you have other things to do and can't just drop everything to help us out.
|
||||
So if we’re asking for your help to improve the PR we’ll wait for two weeks to give you a fair chance to make changes. We’ll ask for an update if we don’t hear back from you after that time.
|
||||
|
||||
If we don’t hear back from you for 4 weeks, we’ll close the PR so that it doesn’t just hang around forever. You’re very welcome to re-open it once you have some more time to spend on it.
|
||||
|
||||
There will be times that we really like your proposed changes and we’ll finish the final improvements we’d like to see ourselves. You still get the credits and your commits will live on in the git repository.
|
||||
@@ -1,11 +1,11 @@
|
||||
|
||||
888
|
||||
888
|
||||
888 888 88888b.d88b. 88888b. 888d888 8888b. .d8888b .d88b.
|
||||
888 888 888 "888 "88b 888 "88b 888P" "88b d88P" d88""88b
|
||||
888 888 888 888 888 888 888 888 .d888888 888 888 888
|
||||
Y88 88Y 888 888 888 888 d88P 888 888 888 Y88b. Y88..88P
|
||||
"Y888P" 888 888 888 88888P" 888 "Y888888 "Y8888P "Y88P"
|
||||
888
|
||||
888
|
||||
888 888 88888b.d88b. 88888b. 888d888 8888b. .d8888b .d88b.
|
||||
888 888 888 "888 "88b 888 "88b 888P" "88b d88P" d88""88b
|
||||
888 888 888 888 888 888 888 888 .d888888 888 888 888
|
||||
Y88b 888 888 888 888 888 d88P 888 888 888 Y88b. Y88..88P
|
||||
"Y88888 888 888 888 88888P" 888 "Y888888 "Y8888P "Y88P"
|
||||
|
||||
------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
|
||||
888
|
||||
888
|
||||
888 888 88888b.d88b. 88888b. 888d888 8888b. .d8888b .d88b.
|
||||
888 888 888 "888 "88b 888 "88b 888P" "88b d88P" d88""88b
|
||||
888 888 888 888 888 888 888 888 .d888888 888 888 888
|
||||
Y88 88Y 888 888 888 888 d88P 888 888 888 Y88b. Y88..88P
|
||||
"Y888P" 888 888 888 88888P" 888 "Y888888 "Y8888P "Y88P"
|
||||
_ _ __ __ ____ _____ _____ ____
|
||||
| | | | \/ | _ \| __ \ /\ / ____/ __ \
|
||||
| | | | \ / | |_) | |__) | / \ | | | | | |
|
||||
| | | | |\/| | _ <| _ / / /\ \| | | | | |
|
||||
| |__| | | | | |_) | | \ \ / ____ | |___| |__| |
|
||||
\____/|_| |_|____/|_| \_/_/ \_\_____\____/
|
||||
|
||||
------------------------------------------------------------------
|
||||
----------------------------------------------------
|
||||
|
||||
Don't forget to build!
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ namespace Umbraco.Core.Composing.CompositionExtensions
|
||||
// register the IFileSystem supporting the IMediaFileSystem
|
||||
// THIS IS THE ONLY THING THAT NEEDS TO CHANGE, IN ORDER TO REPLACE THE UNDERLYING FILESYSTEM
|
||||
// and, SupportingFileSystem.For<IMediaFileSystem>() returns the underlying filesystem
|
||||
composition.SetMediaFileSystem(() => new PhysicalFileSystem(SystemDirectories.Media));
|
||||
composition.SetMediaFileSystem(() => new PhysicalFileSystem("~/media"));
|
||||
|
||||
return composition;
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ namespace Umbraco.Core.Composing.CompositionExtensions
|
||||
var pluginLangFolders = appPlugins.Exists == false
|
||||
? Enumerable.Empty<LocalizedTextServiceSupplementaryFileSource>()
|
||||
: appPlugins.GetDirectories()
|
||||
.SelectMany(x => x.GetDirectories("Lang", SearchOption.AllDirectories))
|
||||
.SelectMany(x => x.GetDirectories("Lang"))
|
||||
.SelectMany(x => x.GetFiles("*.xml", SearchOption.TopDirectoryOnly))
|
||||
.Select(x => new LocalizedTextServiceSupplementaryFileSource(x, false));
|
||||
|
||||
|
||||
@@ -12,62 +12,31 @@
|
||||
/// 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>
|
||||
/// <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, it’s 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>
|
||||
// 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?
|
||||
Request,
|
||||
|
||||
/// <summary>
|
||||
/// One unique instance per scope.
|
||||
/// One unique instance per container scope.
|
||||
/// </summary>
|
||||
/// <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, it’s still just instance per matching lifetime scope."
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// LifestyleScoped in Castle Windsor
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <remarks>Corresponds to Scope in LightInject, Scoped in MS.DI
|
||||
/// or Castle Windsor, PerLifetimeScope in Autofac.</remarks>
|
||||
Scope,
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -96,11 +96,6 @@ namespace Umbraco.Core
|
||||
/// </summary>
|
||||
public const string MediaPicker = "Umbraco.MediaPicker";
|
||||
|
||||
/// <summary>
|
||||
/// Multiple Media Picker.
|
||||
/// </summary>
|
||||
public const string MultipleMediaPicker = "Umbraco.MultipleMediaPicker";
|
||||
|
||||
/// <summary>
|
||||
/// Member Picker.
|
||||
/// </summary>
|
||||
@@ -208,24 +203,6 @@ namespace Umbraco.Core
|
||||
/// <remarks>Must be a valid <see cref="ValueTypes"/> value.</remarks>
|
||||
public const string DataValueType = "umbracoDataValueType";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines Umbraco's built-in property editor groups.
|
||||
/// </summary>
|
||||
public static class Groups
|
||||
{
|
||||
public const string Common = "Common";
|
||||
|
||||
public const string Lists = "Lists";
|
||||
|
||||
public const string Media = "Media";
|
||||
|
||||
public const string People = "People";
|
||||
|
||||
public const string Pickers = "Pickers";
|
||||
|
||||
public const string RichContent = "Rich Content";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
@@ -16,7 +15,7 @@ namespace Umbraco.Core
|
||||
/// <returns></returns>
|
||||
public static string ToIsoString(this DateTime dt)
|
||||
{
|
||||
return dt.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
|
||||
return dt.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
}
|
||||
|
||||
public static DateTime TruncateTo(this DateTime dt, DateTruncate truncateTo)
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// An exception that is thrown if the a query as waited for the lock too long
|
||||
/// </summary>
|
||||
public class LockTimeoutException : Exception
|
||||
{
|
||||
public LockTimeoutException(Exception inner)
|
||||
: base(string.Empty, inner)
|
||||
{ }
|
||||
|
||||
public short Reason { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,12 +42,6 @@ namespace Umbraco.Core.Logging.Viewer
|
||||
|
||||
bool CheckCanOpenLogs(LogTimePeriod logTimePeriod);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current Serilog minimum log level
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
string GetLogLevel();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the collection of logs
|
||||
/// </summary>
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Formatting = Newtonsoft.Json.Formatting;
|
||||
|
||||
namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
@@ -92,16 +89,6 @@ namespace Umbraco.Core.Logging.Viewer
|
||||
return errorCounter.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the Serilog minimum-level value from the config file.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetLogLevel()
|
||||
{
|
||||
var logLevel = Enum.GetValues(typeof(LogEventLevel)).Cast<LogEventLevel>().Where(Log.Logger.IsEnabled)?.Min() ?? null;
|
||||
return logLevel?.ToString() ?? "";
|
||||
}
|
||||
|
||||
public LogLevelCounts GetLogLevelCounts(LogTimePeriod logTimePeriod)
|
||||
{
|
||||
var counter = new CountingFilter();
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Exceptions;
|
||||
|
||||
namespace Umbraco.Core.Mapping
|
||||
{
|
||||
@@ -260,7 +259,7 @@ namespace Umbraco.Core.Mapping
|
||||
if (typeof(TTarget).IsArray)
|
||||
{
|
||||
var elementType = typeof(TTarget).GetElementType();
|
||||
if (elementType == null) throw new PanicException("elementType == null which should never occur");
|
||||
if (elementType == null) throw new Exception("panic");
|
||||
var targetArray = Array.CreateInstance(elementType, targetList.Count);
|
||||
targetList.CopyTo(targetArray, 0);
|
||||
target = targetArray;
|
||||
@@ -383,7 +382,7 @@ namespace Umbraco.Core.Mapping
|
||||
{
|
||||
if (type.IsArray) return type.GetElementType();
|
||||
if (type.IsGenericType) return type.GenericTypeArguments[0];
|
||||
throw new PanicException($"Could not get enumerable or array type from {type}");
|
||||
throw new Exception("panic");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
+8
-36
@@ -1,7 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using NPoco;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Migrations.Expressions.Common;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
@@ -29,57 +27,31 @@ namespace Umbraco.Core.Migrations.Expressions.Delete.KeysAndIndexes
|
||||
{
|
||||
_context.BuildingExpression = false;
|
||||
|
||||
//get a list of all constraints - this will include all PK, FK and unique constraints
|
||||
var tableConstraints = _context.SqlContext.SqlSyntax.GetConstraintsPerTable(_context.Database).DistinctBy(x => x.Item2).ToList();
|
||||
|
||||
//get a list of defined indexes - this will include all indexes, unique indexes and unique constraint indexes
|
||||
var indexes = _context.SqlContext.SqlSyntax.GetDefinedIndexesDefinitions(_context.Database).DistinctBy(x => x.IndexName).ToList();
|
||||
|
||||
var uniqueConstraintNames = tableConstraints.Where(x => !x.Item2.InvariantStartsWith("PK_") && !x.Item2.InvariantStartsWith("FK_")).Select(x => x.Item2);
|
||||
var indexNames = indexes.Select(x => x.IndexName).ToList();
|
||||
|
||||
// drop keys
|
||||
if (DeleteLocal || DeleteForeign)
|
||||
{
|
||||
// table, constraint
|
||||
|
||||
var tableKeys = _context.SqlContext.SqlSyntax.GetConstraintsPerTable(_context.Database).DistinctBy(x => x.Item2).ToList();
|
||||
if (DeleteForeign)
|
||||
{
|
||||
//In some cases not all FK's are prefixed with "FK" :/ mostly with old upgraded databases so we need to check if it's either:
|
||||
// * starts with FK OR
|
||||
// * doesn't start with PK_ and doesn't exist in the list of indexes
|
||||
|
||||
foreach (var key in tableConstraints.Where(x => x.Item1 == TableName
|
||||
&& (x.Item2.InvariantStartsWith("FK_") || (!x.Item2.InvariantStartsWith("PK_") && !indexNames.InvariantContains(x.Item2)))))
|
||||
{
|
||||
foreach (var key in tableKeys.Where(x => x.Item1 == TableName && x.Item2.StartsWith("FK_")))
|
||||
Delete.ForeignKey(key.Item2).OnTable(key.Item1).Do();
|
||||
}
|
||||
|
||||
}
|
||||
if (DeleteLocal)
|
||||
{
|
||||
foreach (var key in tableConstraints.Where(x => x.Item1 == TableName && x.Item2.InvariantStartsWith("PK_")))
|
||||
foreach (var key in tableKeys.Where(x => x.Item1 == TableName && x.Item2.StartsWith("PK_")))
|
||||
Delete.PrimaryKey(key.Item2).FromTable(key.Item1).Do();
|
||||
|
||||
// note: we do *not* delete the DEFAULT constraints and if we wanted to we'd have to deal with that in interesting ways
|
||||
// since SQL server has a specific way to handle that, see SqlServerSyntaxProvider.GetDefaultConstraintsPerColumn
|
||||
// note: we do *not* delete the DEFAULT constraints
|
||||
}
|
||||
}
|
||||
|
||||
// drop indexes
|
||||
if (DeleteLocal)
|
||||
{
|
||||
{
|
||||
var indexes = _context.SqlContext.SqlSyntax.GetDefinedIndexesDefinitions(_context.Database).DistinctBy(x => x.IndexName).ToList();
|
||||
foreach (var index in indexes.Where(x => x.TableName == TableName))
|
||||
{
|
||||
//if this is a unique constraint we need to drop the constraint, else drop the index
|
||||
//to figure this out, the index must be tagged as unique and it must exist in the tableConstraints
|
||||
|
||||
if (index.IsUnique && uniqueConstraintNames.InvariantContains(index.IndexName))
|
||||
Delete.UniqueConstraint(index.IndexName).FromTable(index.TableName).Do();
|
||||
else
|
||||
Delete.Index(index.IndexName).OnTable(index.TableName).Do();
|
||||
}
|
||||
|
||||
Delete.Index(index.IndexName).OnTable(index.TableName).Do();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using Umbraco.Core.Migrations.Upgrade.Common;
|
||||
using Umbraco.Core.Migrations.Upgrade.V_8_0_0;
|
||||
using Umbraco.Core.Migrations.Upgrade.V_8_0_1;
|
||||
using Umbraco.Core.Migrations.Upgrade.V_8_1_0;
|
||||
using Umbraco.Core.Migrations.Upgrade.V_8_2_0;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Upgrade
|
||||
{
|
||||
@@ -182,6 +183,8 @@ namespace Umbraco.Core.Migrations.Upgrade
|
||||
To<RenameUserLoginDtoDateIndex>("{0372A42B-DECF-498D-B4D1-6379E907EB94}");
|
||||
To<FixContentNuCascade>("{5B1E0D93-F5A3-449B-84BA-65366B84E2D4}");
|
||||
|
||||
// to 8.2.0....
|
||||
To<AddLockTableColumns>("{8DC6A949-0FE4-4846-9C1D-61E49DDADFF9}");
|
||||
//FINAL
|
||||
}
|
||||
}
|
||||
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
|
||||
{
|
||||
class MarkdownEditorPreValueMigrator : DefaultPreValueMigrator //PreValueMigratorBase
|
||||
{
|
||||
public override bool CanMigrate(string editorAlias)
|
||||
=> editorAlias == Constants.PropertyEditors.Aliases.MarkdownEditor;
|
||||
|
||||
protected override object GetPreValueValue(PreValueDto preValue)
|
||||
{
|
||||
if (preValue.Alias == "preview")
|
||||
return preValue.Value == "1";
|
||||
|
||||
return base.GetPreValueValue(preValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,8 +20,7 @@ public class PreValueMigratorComposer : ICoreComposer
|
||||
.Append<DecimalPreValueMigrator>()
|
||||
.Append<ListViewPreValueMigrator>()
|
||||
.Append<DropDownFlexiblePreValueMigrator>()
|
||||
.Append<ValueListPreValueMigrator>()
|
||||
.Append<MarkdownEditorPreValueMigrator>();
|
||||
.Append<ValueListPreValueMigrator>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Exceptions;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
|
||||
{
|
||||
@@ -21,7 +20,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
|
||||
case "Umbraco.NoEdit":
|
||||
return Constants.PropertyEditors.Aliases.Label;
|
||||
default:
|
||||
throw new PanicException($"The alias {editorAlias} is not supported");
|
||||
throw new Exception("panic");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,17 +15,21 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
{
|
||||
//special trick to add the column without constraints and return the sql to add them later
|
||||
AddColumn<MacroDto>("macroType", out var sqls1);
|
||||
AddColumn<MacroDto>("macroSource", out var sqls2);
|
||||
|
||||
//populate the new columns with legacy data
|
||||
Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = '', macroType = {(int)MacroTypes.Unknown}").Do();
|
||||
Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroXSLT, macroType = {(int)MacroTypes.Unknown} WHERE macroXSLT != '' AND macroXSLT IS NOT NULL").Do();
|
||||
Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroScriptAssembly, macroType = {(int)MacroTypes.Unknown} WHERE macroScriptAssembly != '' AND macroScriptAssembly IS NOT NULL").Do();
|
||||
Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroScriptType, macroType = {(int)MacroTypes.Unknown} WHERE macroScriptType != '' AND macroScriptType IS NOT NULL").Do();
|
||||
Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroPython, macroType = {(int)MacroTypes.PartialView} WHERE macroPython != '' AND macroPython IS NOT NULL").Do();
|
||||
|
||||
//now we need to update the new column with some values because this column doesn't allow NULL values
|
||||
Update.Table(Constants.DatabaseSchema.Tables.Macro).Set(new { macroType = (int)MacroTypes.Unknown}).AllRows().Do();
|
||||
//now apply constraints (NOT NULL) to new table
|
||||
foreach (var sql in sqls1) Execute.Sql(sql).Do();
|
||||
|
||||
//special trick to add the column without constraints and return the sql to add them later
|
||||
AddColumn<MacroDto>("macroSource", out var sqls2);
|
||||
|
||||
//populate the new macroSource column with legacy data
|
||||
Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroXSLT, macroType = {(int)MacroTypes.Unknown} WHERE macroXSLT IS NOT NULL").Do();
|
||||
Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroScriptAssembly, macroType = {(int)MacroTypes.Unknown} WHERE macroScriptAssembly IS NOT NULL").Do();
|
||||
Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroScriptType, macroType = {(int)MacroTypes.Unknown} WHERE macroScriptType IS NOT NULL").Do();
|
||||
Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Macro} SET macroSource = macroPython, macroType = {(int)MacroTypes.PartialView} WHERE macroPython IS NOT NULL").Do();
|
||||
|
||||
//now apply constraints (NOT NULL) to new table
|
||||
foreach (var sql in sqls2) Execute.Sql(sql).Do();
|
||||
|
||||
//now remove these old columns
|
||||
|
||||
+2
-2
@@ -50,10 +50,10 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0
|
||||
var obj = JsonConvert.DeserializeObject<JObject>(value);
|
||||
var allControls = obj.SelectTokens("$.sections..rows..areas..controls");
|
||||
|
||||
foreach (var control in allControls.SelectMany(c => c).OfType<JObject>())
|
||||
foreach (var control in allControls.SelectMany(c => c))
|
||||
{
|
||||
var controlValue = control["value"];
|
||||
if (controlValue?.Type == JTokenType.String)
|
||||
if (controlValue.Type == JTokenType.String)
|
||||
{
|
||||
control["value"] = UpdateMediaUrls(mediaLinkPattern, controlValue.Value<string>());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Upgrade.V_8_2_0
|
||||
{
|
||||
public class AddLockTableColumns : MigrationBase
|
||||
{
|
||||
public AddLockTableColumns(IMigrationContext context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
public override void Migrate()
|
||||
{
|
||||
var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToList();
|
||||
|
||||
AddColumnIfNotExists<LockDto>(columns, "writeLockReasonId");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,13 +222,7 @@ namespace Umbraco.Core.Models
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <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 = "*")
|
||||
public static void UnpublishCulture(this IContent content, string culture = "*")
|
||||
{
|
||||
culture = culture.NullOrWhiteSpaceAsNull();
|
||||
|
||||
@@ -236,31 +230,16 @@ 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}\".");
|
||||
|
||||
|
||||
var keepProcessing = true;
|
||||
|
||||
if (culture == "*")
|
||||
{
|
||||
// all cultures
|
||||
if (culture == "*") // all cultures
|
||||
content.ClearPublishInfos();
|
||||
}
|
||||
else
|
||||
{
|
||||
// one single culture
|
||||
keepProcessing = content.ClearPublishInfo(culture);
|
||||
}
|
||||
|
||||
else // one single culture
|
||||
content.ClearPublishInfo(culture);
|
||||
|
||||
if (keepProcessing)
|
||||
{
|
||||
// property.PublishValues only publishes what is valid, variation-wise
|
||||
foreach (var property in content.Properties)
|
||||
property.UnpublishValues(culture);
|
||||
// property.PublishValues only publishes what is valid, variation-wise
|
||||
foreach (var property in content.Properties)
|
||||
property.UnpublishValues(culture);
|
||||
|
||||
content.PublishedState = PublishedState.Publishing;
|
||||
}
|
||||
|
||||
return keepProcessing;
|
||||
content.PublishedState = PublishedState.Publishing;
|
||||
}
|
||||
|
||||
public static void ClearPublishInfos(this IContent content)
|
||||
@@ -268,24 +247,15 @@ namespace Umbraco.Core.Models
|
||||
content.PublishCultureInfos = null;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
public static void ClearPublishInfo(this IContent content, string culture)
|
||||
{
|
||||
if (culture.IsNullOrWhiteSpace())
|
||||
throw new ArgumentNullOrEmptyException(nameof(culture));
|
||||
|
||||
var removed = content.PublishCultureInfos.Remove(culture);
|
||||
if (removed)
|
||||
{
|
||||
// set the culture to be dirty - it's been modified
|
||||
content.TouchCulture(culture);
|
||||
}
|
||||
return removed;
|
||||
content.PublishCultureInfos.Remove(culture);
|
||||
|
||||
// set the culture to be dirty - it's been modified
|
||||
content.TouchCulture(culture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -101,7 +101,7 @@ namespace Umbraco.Core.Models
|
||||
PropertyGroupCollection PropertyGroups { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets all local property types all local property groups or ungrouped.
|
||||
/// Gets all local property types belonging to a group, across all local property groups.
|
||||
/// </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 PanicException($"The type {type} has not generic type definition");
|
||||
throw new InvalidOperationException("panic");
|
||||
|
||||
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 PanicException($"The type {type} has not generic type definition");
|
||||
throw new InvalidOperationException("panic");
|
||||
|
||||
var args = type.GetGenericArguments().Select(x => MapToName(x, map, true)).ToArray();
|
||||
var defFullName = def.FullName.Substring(0, def.FullName.IndexOf('`'));
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace Umbraco.Core.Models
|
||||
/// </returns>
|
||||
internal static string[] GetUserAvatarUrls(this IUser user, IAppCache cache)
|
||||
{
|
||||
// If FIPS is required, never check the Gravatar service as it only supports MD5 hashing.
|
||||
// If FIPS is required, never check the Gravatar service as it only supports MD5 hashing.
|
||||
// Unfortunately, if the FIPS setting is enabled on Windows, using MD5 will throw an exception
|
||||
// and the website will not run.
|
||||
// Also, check if the user has explicitly removed all avatars including a Gravatar, this will be possible and the value will be "none"
|
||||
@@ -310,6 +310,7 @@ namespace Umbraco.Core.Models
|
||||
// assume groupSn and userSn each don't contain duplicates
|
||||
|
||||
var asn = groupSn.Concat(userSn).Distinct().ToArray();
|
||||
|
||||
var paths = asn.Length > 0
|
||||
? entityService.GetAllPaths(objectType, asn).ToDictionary(x => x.Id, x => x.Path)
|
||||
: new Dictionary<int, string>();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Web;
|
||||
using System.Xml.Linq;
|
||||
using System.Xml.XPath;
|
||||
@@ -574,11 +575,12 @@ namespace Umbraco.Core.Packaging
|
||||
contentType.Thumbnail = infoElement.Element("Thumbnail").Value;
|
||||
contentType.Description = infoElement.Element("Description").Value;
|
||||
|
||||
//NOTE AllowAtRoot, IsListView, IsElement and Variations are new properties in the package xml so we need to verify it exists before using it.
|
||||
//NOTE AllowAtRoot is a new property in the package xml so we need to verify it exists before using it.
|
||||
var allowAtRoot = infoElement.Element("AllowAtRoot");
|
||||
if (allowAtRoot != null)
|
||||
contentType.AllowedAsRoot = allowAtRoot.Value.InvariantEquals("true");
|
||||
|
||||
//NOTE IsListView is a new property in the package xml so we need to verify it exists before using it.
|
||||
var isListView = infoElement.Element("IsListView");
|
||||
if (isListView != null)
|
||||
contentType.IsContainer = isListView.Value.InvariantEquals("true");
|
||||
@@ -587,10 +589,6 @@ namespace Umbraco.Core.Packaging
|
||||
if (isElement != null)
|
||||
contentType.IsElement = isElement.Value.InvariantEquals("true");
|
||||
|
||||
var variationsElement = infoElement.Element("Variations");
|
||||
if (variationsElement != null)
|
||||
contentType.Variations = (ContentVariation)Enum.Parse(typeof(ContentVariation), variationsElement.Value);
|
||||
|
||||
//Name of the master corresponds to the parent and we need to ensure that the Parent Id is set
|
||||
var masterElement = infoElement.Element("Master");
|
||||
if (masterElement != null)
|
||||
@@ -616,7 +614,7 @@ namespace Umbraco.Core.Packaging
|
||||
var compositionContentType = importedContentTypes.ContainsKey(compositionAlias)
|
||||
? importedContentTypes[compositionAlias]
|
||||
: _contentTypeService.Get(compositionAlias);
|
||||
contentType.AddContentType(compositionContentType);
|
||||
var added = contentType.AddContentType(compositionContentType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -750,14 +748,9 @@ namespace Umbraco.Core.Packaging
|
||||
{
|
||||
Name = property.Element("Name").Value,
|
||||
Description = (string)property.Element("Description"),
|
||||
Mandatory = property.Element("Mandatory") != null
|
||||
? property.Element("Mandatory").Value.ToLowerInvariant().Equals("true")
|
||||
: false,
|
||||
Mandatory = property.Element("Mandatory") != null ? property.Element("Mandatory").Value.ToLowerInvariant().Equals("true") : false,
|
||||
ValidationRegExp = (string)property.Element("Validation"),
|
||||
SortOrder = sortOrder,
|
||||
Variations = property.Element("Variations") != null
|
||||
? (ContentVariation)Enum.Parse(typeof(ContentVariation), property.Element("Variations").Value)
|
||||
: ContentVariation.Nothing
|
||||
SortOrder = sortOrder
|
||||
};
|
||||
|
||||
var tab = (string)property.Element("Tab");
|
||||
|
||||
@@ -1,62 +1,76 @@
|
||||
// ReSharper disable once CheckNamespace
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
static partial class Constants
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines lock objects.
|
||||
/// Defines lock objects.
|
||||
/// </summary>
|
||||
public static class Locks
|
||||
{
|
||||
/// <summary>
|
||||
/// All servers.
|
||||
/// All servers.
|
||||
/// </summary>
|
||||
public const int Servers = -331;
|
||||
|
||||
/// <summary>
|
||||
/// All content and media types.
|
||||
/// All content and media types.
|
||||
/// </summary>
|
||||
public const int ContentTypes = -332;
|
||||
|
||||
/// <summary>
|
||||
/// The entire content tree, i.e. all content items.
|
||||
/// The entire content tree, i.e. all content items.
|
||||
/// </summary>
|
||||
public const int ContentTree = -333;
|
||||
|
||||
/// <summary>
|
||||
/// The entire media tree, i.e. all media items.
|
||||
/// The entire media tree, i.e. all media items.
|
||||
/// </summary>
|
||||
public const int MediaTree = -334;
|
||||
|
||||
/// <summary>
|
||||
/// The entire member tree, i.e. all members.
|
||||
/// The entire member tree, i.e. all members.
|
||||
/// </summary>
|
||||
public const int MemberTree = -335;
|
||||
|
||||
/// <summary>
|
||||
/// All media types.
|
||||
/// All media types.
|
||||
/// </summary>
|
||||
public const int MediaTypes = -336;
|
||||
|
||||
/// <summary>
|
||||
/// All member types.
|
||||
/// All member types.
|
||||
/// </summary>
|
||||
public const int MemberTypes = -337;
|
||||
|
||||
/// <summary>
|
||||
/// All domains.
|
||||
/// All domains.
|
||||
/// </summary>
|
||||
public const int Domains = -338;
|
||||
|
||||
/// <summary>
|
||||
/// All key-values.
|
||||
/// All key-values.
|
||||
/// </summary>
|
||||
public const int KeyValues = -339;
|
||||
|
||||
/// <summary>
|
||||
/// All languages.
|
||||
/// All languages.
|
||||
/// </summary>
|
||||
public const int Languages = -340;
|
||||
|
||||
public static class Reason
|
||||
{
|
||||
|
||||
public const short Default = 1;
|
||||
public const short MoveContentToRecycleBin = 10000;
|
||||
public const short RestoreContentFromRecycleBin = 10001;
|
||||
public const short EmptyRecycleBin = 10002;
|
||||
public const short MoveContent = 10003;
|
||||
public const short CopyContent = 10004;
|
||||
public const short SortContent = 10005;
|
||||
public const short DeleteContentOfTypes = 10006;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Umbraco.Core.Persistence
|
||||
{
|
||||
internal static class DatabaseNodeLockExtensions
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ValidateDatabase(IUmbracoDatabase database)
|
||||
{
|
||||
if (database == null)
|
||||
throw new ArgumentNullException("database");
|
||||
if (database.GetCurrentTransactionIsolationLevel() < IsolationLevel.RepeatableRead)
|
||||
throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required.");
|
||||
}
|
||||
|
||||
// updating a record within a repeatable-read transaction gets an exclusive lock on
|
||||
// that record which will be kept until the transaction is ended, effectively locking
|
||||
// out all other accesses to that record - thus obtaining an exclusive lock over the
|
||||
// protected resources.
|
||||
public static void AcquireLockNodeWriteLock(this IUmbracoDatabase database, int nodeId)
|
||||
{
|
||||
ValidateDatabase(database);
|
||||
|
||||
database.Execute("UPDATE umbracoLock SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id",
|
||||
new { @id = nodeId });
|
||||
}
|
||||
|
||||
// reading a record within a repeatable-read transaction gets a shared lock on
|
||||
// that record which will be kept until the transaction is ended, effectively preventing
|
||||
// other write accesses to that record - thus obtaining a shared lock over the protected
|
||||
// resources.
|
||||
public static void AcquireLockNodeReadLock(this IUmbracoDatabase database, int nodeId)
|
||||
{
|
||||
ValidateDatabase(database);
|
||||
|
||||
database.ExecuteScalar<int>("SELECT value FROM umbracoLock WHERE id=@id",
|
||||
new { @id = nodeId });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,5 +20,9 @@ namespace Umbraco.Core.Persistence.Dtos
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
[Length(64)]
|
||||
public string Name { get; set; }
|
||||
|
||||
[Column("writeLockReasonId")]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public short WriteLockReasonId { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,14 +104,10 @@ 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.
|
||||
/// The value of this will be used to populate the edited cultures in the umbracoDocumentCultureVariation table.
|
||||
/// </param>
|
||||
/// <param name="editedCultures">out parameter containing a collection of edited cultures when the contentVariation varies by culture</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;
|
||||
@@ -134,9 +130,6 @@ 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));
|
||||
@@ -145,36 +138,26 @@ 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 && !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 (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();
|
||||
editedCultures.Add(propertyValue.Culture); // report culture as edited
|
||||
}
|
||||
|
||||
editedCultures.Add(defaultCulture);
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -184,7 +167,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;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using NPoco;
|
||||
using System.Data;
|
||||
using NPoco;
|
||||
using Umbraco.Core.Persistence.Mappers;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using NPoco;
|
||||
using Umbraco.Core.Exceptions;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Interceptors
|
||||
{
|
||||
public class LockTimeoutInterceptor : IExceptionInterceptor
|
||||
{
|
||||
public void OnException(IDatabase database, Exception exception)
|
||||
{
|
||||
if (database is IUmbracoDatabase umbracoDatabase && umbracoDatabase.SqlContext.SqlSyntax.IsLockTimeoutException(exception))
|
||||
{
|
||||
throw new LockTimeoutException(exception);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -174,7 +174,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
totalRecords = page.TotalItems;
|
||||
|
||||
var items = page.Items.Select(
|
||||
dto => new AuditItem(dto.NodeId, Enum<AuditType>.ParseOrNull(dto.Header) ?? AuditType.Custom, dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Comment, dto.Parameters)).ToList();
|
||||
dto => new AuditItem(dto.Id, Enum<AuditType>.ParseOrNull(dto.Header) ?? AuditType.Custom, dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Comment, dto.Parameters)).ToList();
|
||||
|
||||
// map the DateStamp
|
||||
for (var i = 0; i < items.Count; i++)
|
||||
|
||||
@@ -512,16 +512,31 @@ 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 (Actually ... no we don't since i removed that code, but we don't need them anyways it seems)
|
||||
// - tag editors
|
||||
// and we need to build the proper property collections
|
||||
|
||||
return GetPropertyCollections(temps, allPropertyDataDtos);
|
||||
return GetPropertyCollections(temps, allPropertyDataDtos, tagEditors);
|
||||
}
|
||||
|
||||
private IDictionary<int, PropertyCollection> GetPropertyCollections<T>(List<TempContent<T>> temps, IEnumerable<PropertyDataDto> allPropertyDataDtos)
|
||||
private IDictionary<int, PropertyCollection> GetPropertyCollections<T>(List<TempContent<T>> temps, IEnumerable<PropertyDataDto> allPropertyDataDtos, Dictionary<string, TagConfiguration> tagConfigurations)
|
||||
where T : class, IContentBase
|
||||
{
|
||||
var result = new Dictionary<int, PropertyCollection>();
|
||||
|
||||
@@ -3,7 +3,6 @@ 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;
|
||||
@@ -91,7 +90,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 PanicException($"The node object type {contentTypeDto.NodeDto.NodeObjectType} is not supported");
|
||||
else throw new Exception("panic");
|
||||
contentTypes.Add(contentType.Id, contentType);
|
||||
|
||||
// map allowed content types
|
||||
|
||||
@@ -3,7 +3,6 @@ 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;
|
||||
@@ -19,8 +18,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
/// </summary>
|
||||
internal class ContentTypeRepository : ContentTypeRepositoryBase<IContentType>, IContentTypeRepository
|
||||
{
|
||||
public ContentTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository)
|
||||
: base(scopeAccessor, cache, logger, commonRepository, languageRepository)
|
||||
public ContentTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
|
||||
: base(scopeAccessor, cache, logger, commonRepository)
|
||||
{ }
|
||||
|
||||
protected override bool SupportsPublishing => ContentType.SupportsPublishingConst;
|
||||
@@ -57,7 +56,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 PanicException("There can be no ids specified");
|
||||
if (ids.Any()) throw new Exception("panic");
|
||||
return CommonRepository.GetAllTypes().OfType<IContentType>();
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ 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
|
||||
{
|
||||
@@ -27,15 +26,14 @@ 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, ILanguageRepository languageRepository)
|
||||
protected ContentTypeRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
|
||||
: 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)
|
||||
@@ -100,8 +98,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
protected void PersistNewBaseContentType(IContentTypeComposition entity)
|
||||
{
|
||||
ValidateVariations(entity);
|
||||
|
||||
var dto = ContentTypeFactory.BuildContentTypeDto(entity);
|
||||
|
||||
//Cannot add a duplicate content type
|
||||
@@ -167,11 +163,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
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -218,8 +214,6 @@ AND umbracoNode.nodeObjectType = @objectType",
|
||||
|
||||
protected void PersistUpdatedBaseContentType(IContentTypeComposition entity)
|
||||
{
|
||||
ValidateVariations(entity);
|
||||
|
||||
var dto = ContentTypeFactory.BuildContentTypeDto(entity);
|
||||
|
||||
// ensure the alias is not used already
|
||||
@@ -376,7 +370,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));
|
||||
@@ -394,7 +388,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)
|
||||
@@ -455,7 +449,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
|
||||
@@ -524,25 +518,6 @@ 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>();
|
||||
@@ -550,12 +525,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
|
||||
@@ -597,7 +572,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;
|
||||
@@ -661,7 +636,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;
|
||||
@@ -671,12 +646,10 @@ 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:
|
||||
@@ -990,205 +963,6 @@ 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, editedCultures));
|
||||
Database.BulkInsertRecords(GetDocumentVariationDtos(entity, publishing, 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, editedCultures));
|
||||
Database.BulkInsertRecords(GetDocumentVariationDtos(entity, publishing, editedCultures));
|
||||
}
|
||||
|
||||
// refresh content
|
||||
@@ -1297,28 +1297,25 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
};
|
||||
}
|
||||
|
||||
private IEnumerable<DocumentCultureVariationDto> GetDocumentVariationDtos(IContent content, HashSet<string> editedCultures)
|
||||
private IEnumerable<DocumentCultureVariationDto> GetDocumentVariationDtos(IContent content, bool publishing, HashSet<string> editedCultures)
|
||||
{
|
||||
var allCultures = content.AvailableCultures.Union(content.PublishedCultures); // union = distinct
|
||||
foreach (var culture in allCultures)
|
||||
{
|
||||
var dto = new DocumentCultureVariationDto
|
||||
yield return 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
|
||||
|
||||
@@ -404,7 +404,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
if (isMedia)
|
||||
{
|
||||
sql
|
||||
.LeftJoin<MediaVersionDto>().On<ContentVersionDto, MediaVersionDto>((left, right) => left.Id == right.Id);
|
||||
.InnerJoin<MediaVersionDto>().On<ContentVersionDto, MediaVersionDto>((left, right) => left.Id == right.Id);
|
||||
}
|
||||
|
||||
//Any LeftJoin statements need to come last
|
||||
|
||||
@@ -250,6 +250,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
lock (_codeIdMap)
|
||||
{
|
||||
if (_codeIdMap.TryGetValue(isoCode, out var id)) return id;
|
||||
if (isoCode.Contains('-') && _codeIdMap.TryGetValue(isoCode.Split('-').First(), out var invariantId)) return invariantId;
|
||||
}
|
||||
if (throwOnNotFound)
|
||||
throw new ArgumentException($"Code {isoCode} does not correspond to an existing language.", nameof(isoCode));
|
||||
|
||||
@@ -3,7 +3,6 @@ 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 +17,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
/// </summary>
|
||||
internal class MediaTypeRepository : ContentTypeRepositoryBase<IMediaType>, IMediaTypeRepository
|
||||
{
|
||||
public MediaTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository)
|
||||
: base(scopeAccessor, cache, logger, commonRepository, languageRepository)
|
||||
public MediaTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
|
||||
: base(scopeAccessor, cache, logger, commonRepository)
|
||||
{ }
|
||||
|
||||
protected override bool SupportsPublishing => MediaType.SupportsPublishingConst;
|
||||
@@ -51,7 +50,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 PanicException("There can be no ids specified");
|
||||
if (ids.Any()) throw new Exception("panic");
|
||||
return CommonRepository.GetAllTypes().OfType<IMediaType>();
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ 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;
|
||||
@@ -19,8 +18,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
/// </summary>
|
||||
internal class MemberTypeRepository : ContentTypeRepositoryBase<IMemberType>, IMemberTypeRepository
|
||||
{
|
||||
public MemberTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository)
|
||||
: base(scopeAccessor, cache, logger, commonRepository, languageRepository)
|
||||
public MemberTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
|
||||
: base(scopeAccessor, cache, logger, commonRepository)
|
||||
{ }
|
||||
|
||||
protected override bool SupportsPublishing => MemberType.SupportsPublishingConst;
|
||||
@@ -58,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 PanicException("There can be no ids specified");
|
||||
if (ids.Any()) throw new Exception("panic");
|
||||
return CommonRepository.GetAllTypes().OfType<IMemberType>();
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using NPoco;
|
||||
using Umbraco.Core.Persistence.DatabaseAnnotations;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using IsolationLevel = System.Data.IsolationLevel;
|
||||
|
||||
namespace Umbraco.Core.Persistence.SqlSyntax
|
||||
{
|
||||
@@ -76,6 +77,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax
|
||||
string ConvertIntegerToOrderableString { get; }
|
||||
string ConvertDateToOrderableString { get; }
|
||||
string ConvertDecimalToOrderableString { get; }
|
||||
bool IsReadUncommittedSupported { get; }
|
||||
IsolationLevel DefaultIsolationLevel { get; }
|
||||
|
||||
IEnumerable<string> GetTablesInSchema(IDatabase db);
|
||||
IEnumerable<ColumnInfo> GetColumnsInSchema(IDatabase db);
|
||||
@@ -121,5 +124,9 @@ namespace Umbraco.Core.Persistence.SqlSyntax
|
||||
/// unspecified.</para>
|
||||
/// </remarks>
|
||||
bool TryGetDefaultConstraint(IDatabase db, string tableName, string columnName, out string constraintName);
|
||||
|
||||
void ReadLock(IDatabase db, params int[] lockIds);
|
||||
void WriteLock(IDatabase db, int? writeLockReasonId, params int[] lockIds);
|
||||
bool IsLockTimeoutException(Exception exception);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SqlServerCe;
|
||||
using System.Linq;
|
||||
using System.Transactions;
|
||||
using NPoco;
|
||||
using Umbraco.Core.Persistence.DatabaseAnnotations;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
@@ -12,11 +14,13 @@ namespace Umbraco.Core.Persistence.SqlSyntax
|
||||
/// </summary>
|
||||
public class SqlCeSyntaxProvider : MicrosoftSqlSyntaxProviderBase<SqlCeSyntaxProvider>
|
||||
{
|
||||
|
||||
public override Sql<ISqlContext> SelectTop(Sql<ISqlContext> sql, int top)
|
||||
{
|
||||
return new Sql<ISqlContext>(sql.SqlContext, sql.SQL.Insert(sql.SQL.IndexOf(' '), " TOP " + top), sql.Arguments);
|
||||
}
|
||||
|
||||
|
||||
public override bool SupportsClustered()
|
||||
{
|
||||
return false;
|
||||
@@ -47,6 +51,9 @@ namespace Umbraco.Core.Persistence.SqlSyntax
|
||||
return indexType;
|
||||
}
|
||||
|
||||
public override bool IsReadUncommittedSupported => false;
|
||||
public override System.Data.IsolationLevel DefaultIsolationLevel => System.Data.IsolationLevel.RepeatableRead;
|
||||
|
||||
public override string GetConcat(params string[] args)
|
||||
{
|
||||
return "(" + string.Join("+", args) + ")";
|
||||
@@ -143,6 +150,45 @@ where table_name=@0 and column_name=@1", tableName, columnName).FirstOrDefault()
|
||||
return hasDefault;
|
||||
}
|
||||
|
||||
public override void WriteLock(IDatabase db, int? writeLockReasonId, params int[] lockIds)
|
||||
{
|
||||
// soon as we get Database, a transaction is started
|
||||
|
||||
if (db.Transaction.IsolationLevel < (System.Data.IsolationLevel) IsolationLevel.RepeatableRead)
|
||||
throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required.");
|
||||
|
||||
db.Execute(@"SET LOCK_TIMEOUT 1800;");
|
||||
// *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
|
||||
foreach (var lockId in lockIds)
|
||||
{
|
||||
|
||||
var i = db.Execute(@"UPDATE umbracoLock SET value = value*-1, writeLockReasonId = @writeLockReasonId WHERE id=@id", new { id = lockId, writeLockReasonId = writeLockReasonId });
|
||||
if (i == 0) // ensure we are actually locking!
|
||||
throw new Exception($"LockObject with id={lockId} does not exist.");
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsLockTimeoutException(Exception exception)
|
||||
{
|
||||
return exception is SqlCeLockTimeoutException;
|
||||
}
|
||||
|
||||
public override void ReadLock(IDatabase db, params int[] lockIds)
|
||||
{
|
||||
// soon as we get Database, a transaction is started
|
||||
|
||||
if (db.Transaction.IsolationLevel < (System.Data.IsolationLevel) IsolationLevel.RepeatableRead)
|
||||
throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required.");
|
||||
|
||||
// *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
|
||||
foreach (var lockId in lockIds)
|
||||
{
|
||||
var i = db.ExecuteScalar<int?>("SELECT value FROM umbracoLock WHERE id=@id", new { id = lockId });
|
||||
if (i == null) // ensure we are actually locking!
|
||||
throw new Exception($"LockObject with id={lockId} does not exist.");
|
||||
}
|
||||
}
|
||||
|
||||
public override bool DoesTableExist(IDatabase db, string tableName)
|
||||
{
|
||||
var result =
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Data.SqlClient;
|
||||
using System.Linq;
|
||||
using NPoco;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
|
||||
namespace Umbraco.Core.Persistence.SqlSyntax
|
||||
{
|
||||
@@ -173,6 +174,9 @@ namespace Umbraco.Core.Persistence.SqlSyntax
|
||||
return items.Select(x => new Tuple<string, string, string, string>(x.TableName, x.ColumnName, x.Name, x.Definition));
|
||||
}
|
||||
|
||||
public override bool IsReadUncommittedSupported => true;
|
||||
public override System.Data.IsolationLevel DefaultIsolationLevel => System.Data.IsolationLevel.ReadCommitted;
|
||||
|
||||
public override IEnumerable<string> GetTablesInSchema(IDatabase db)
|
||||
{
|
||||
var items = db.Fetch<dynamic>("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = (SELECT SCHEMA_NAME())");
|
||||
@@ -237,6 +241,44 @@ where tbl.[name]=@0 and col.[name]=@1;", tableName, columnName)
|
||||
return !constraintName.IsNullOrWhiteSpace();
|
||||
}
|
||||
|
||||
public override void WriteLock(IDatabase db, int? writeLockReasonId, params int[] lockIds)
|
||||
{
|
||||
// soon as we get Database, a transaction is started
|
||||
|
||||
if (db.Transaction.IsolationLevel < IsolationLevel.ReadCommitted)
|
||||
throw new InvalidOperationException("A transaction with minimum ReadCommitted isolation level is required.");
|
||||
|
||||
|
||||
// *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
|
||||
foreach (var lockId in lockIds)
|
||||
{
|
||||
db.Execute(@"SET LOCK_TIMEOUT 1800;");
|
||||
var i = db.Execute(@"UPDATE umbracoLock WITH (REPEATABLEREAD) SET value = value*-1, writeLockReasonId = @writeLockReasonId WHERE id=@id", new { id = lockId, writeLockReasonId = writeLockReasonId });
|
||||
if (i == 0) // ensure we are actually locking!
|
||||
throw new Exception($"LockObject with id={lockId} does not exist.");
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsLockTimeoutException(Exception exception)
|
||||
{
|
||||
return exception is SqlException sqlException && sqlException.Number == 1222;
|
||||
}
|
||||
|
||||
public override void ReadLock(IDatabase db, params int[] lockIds)
|
||||
{
|
||||
// soon as we get Database, a transaction is started
|
||||
|
||||
if (db.Transaction.IsolationLevel < IsolationLevel.ReadCommitted)
|
||||
throw new InvalidOperationException("A transaction with minimum ReadCommitted isolation level is required.");
|
||||
|
||||
// *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
|
||||
foreach (var lockId in lockIds)
|
||||
{
|
||||
var i = db.ExecuteScalar<int?>("SELECT value FROM umbracoLock WITH (REPEATABLEREAD) WHERE id=@id", new { id = lockId });
|
||||
if (i == null) // ensure we are actually locking!
|
||||
throw new ArgumentException($"LockObject with id={lockId} does not exist.", nameof(lockIds));
|
||||
}
|
||||
}
|
||||
public override bool DoesTableExist(IDatabase db, string tableName)
|
||||
{
|
||||
var result =
|
||||
|
||||
@@ -200,7 +200,10 @@ namespace Umbraco.Core.Persistence.SqlSyntax
|
||||
|
||||
return "NVARCHAR";
|
||||
}
|
||||
|
||||
|
||||
public abstract bool IsReadUncommittedSupported { get; }
|
||||
public abstract IsolationLevel DefaultIsolationLevel { get; }
|
||||
|
||||
public virtual IEnumerable<string> GetTablesInSchema(IDatabase db)
|
||||
{
|
||||
return new List<string>();
|
||||
@@ -224,6 +227,10 @@ namespace Umbraco.Core.Persistence.SqlSyntax
|
||||
public abstract IEnumerable<Tuple<string, string, string, bool>> GetDefinedIndexes(IDatabase db);
|
||||
|
||||
public abstract bool TryGetDefaultConstraint(IDatabase db, string tableName, string columnName, out string constraintName);
|
||||
public abstract void ReadLock(IDatabase db, params int[] lockIds);
|
||||
public abstract void WriteLock(IDatabase db, int? writeLockReasonId, params int[] lockIds);
|
||||
public abstract bool IsLockTimeoutException(Exception exception);
|
||||
|
||||
|
||||
public virtual bool DoesTableExist(IDatabase db, string tableName)
|
||||
{
|
||||
@@ -559,5 +566,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax
|
||||
public virtual string ConvertIntegerToOrderableString => "REPLACE(STR({0}, 8), SPACE(1), '0')";
|
||||
public virtual string ConvertDateToOrderableString => "CONVERT(nvarchar, {0}, 102)";
|
||||
public virtual string ConvertDecimalToOrderableString => "REPLACE(STR({0}, 20, 9), SPACE(1), '0')";
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,6 @@ namespace Umbraco.Core.Persistence
|
||||
/// </remarks>
|
||||
public class UmbracoDatabase : Database, IUmbracoDatabase
|
||||
{
|
||||
// Umbraco's default isolation level is RepeatableRead
|
||||
private const IsolationLevel DefaultIsolationLevel = IsolationLevel.RepeatableRead;
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private readonly RetryPolicy _connectionRetryPolicy;
|
||||
private readonly RetryPolicy _commandRetryPolicy;
|
||||
@@ -38,7 +35,7 @@ namespace Umbraco.Core.Persistence
|
||||
/// <para>Also used by DatabaseBuilder for creating databases and installing/upgrading.</para>
|
||||
/// </remarks>
|
||||
public UmbracoDatabase(string connectionString, ISqlContext sqlContext, DbProviderFactory provider, ILogger logger, RetryPolicy connectionRetryPolicy = null, RetryPolicy commandRetryPolicy = null)
|
||||
: base(connectionString, sqlContext.DatabaseType, provider, DefaultIsolationLevel)
|
||||
: base(connectionString, sqlContext.DatabaseType, provider, sqlContext.SqlSyntax.DefaultIsolationLevel)
|
||||
{
|
||||
SqlContext = sqlContext;
|
||||
|
||||
@@ -54,7 +51,7 @@ namespace Umbraco.Core.Persistence
|
||||
/// </summary>
|
||||
/// <remarks>Internal for unit tests only.</remarks>
|
||||
internal UmbracoDatabase(DbConnection connection, ISqlContext sqlContext, ILogger logger)
|
||||
: base(connection, sqlContext.DatabaseType, DefaultIsolationLevel)
|
||||
: base(connection, sqlContext.DatabaseType, sqlContext.SqlSyntax.DefaultIsolationLevel)
|
||||
{
|
||||
SqlContext = sqlContext;
|
||||
_logger = logger;
|
||||
|
||||
@@ -7,6 +7,7 @@ using NPoco.FluentMappings;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence.FaultHandling;
|
||||
using Umbraco.Core.Persistence.Interceptors;
|
||||
using Umbraco.Core.Persistence.Mappers;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
@@ -238,13 +239,15 @@ namespace Umbraco.Core.Persistence
|
||||
// so that everything NPoco is properly cached for the lifetime of the application
|
||||
_pocoMappers = new NPoco.MapperCollection { new PocoMapper() };
|
||||
var factory = new FluentPocoDataFactory(GetPocoDataFactoryResolver);
|
||||
|
||||
_pocoDataFactory = factory;
|
||||
var config = new FluentConfig(xmappers => factory);
|
||||
|
||||
// create the database factory
|
||||
_npocoDatabaseFactory = DatabaseFactory.Config(x => x
|
||||
.UsingDatabase(CreateDatabaseInstance) // creating UmbracoDatabase instances
|
||||
.WithFluentConfig(config)); // with proper configuration
|
||||
.WithFluentConfig(config) // with proper configuration
|
||||
.WithInterceptor(new LockTimeoutInterceptor()));
|
||||
|
||||
if (_npocoDatabaseFactory == null)
|
||||
throw new NullReferenceException("The call to UmbracoDatabaseFactory.Config yielded a null UmbracoDatabaseFactory instance.");
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace Umbraco.Core.PropertyEditors
|
||||
// defaults
|
||||
Type = type;
|
||||
Icon = Constants.Icons.PropertyEditor;
|
||||
Group = Constants.PropertyEditors.Groups.Common;
|
||||
Group = "common";
|
||||
|
||||
// assign properties based on the attribute, if it is found
|
||||
Attribute = GetType().GetCustomAttribute<DataEditorAttribute>(false);
|
||||
|
||||
@@ -121,7 +121,7 @@ namespace Umbraco.Core.PropertyEditors
|
||||
/// Gets or sets an optional group.
|
||||
/// </summary>
|
||||
/// <remarks>The group can be used for example to group the editors by category.</remarks>
|
||||
public string Group { get; set; } = Constants.PropertyEditors.Groups.Common;
|
||||
public string Group { get; set; } = "common";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the value editor is deprecated.
|
||||
|
||||
@@ -5,11 +5,7 @@ namespace Umbraco.Core.PropertyEditors
|
||||
/// <summary>
|
||||
/// Represents a property editor for label properties.
|
||||
/// </summary>
|
||||
[DataEditor(
|
||||
Constants.PropertyEditors.Aliases.Label,
|
||||
"Label",
|
||||
"readonlyvalue",
|
||||
Icon = "icon-readonly")]
|
||||
[DataEditor(Constants.PropertyEditors.Aliases.Label, "Label", "readonlyvalue", Icon = "icon-readonly")]
|
||||
public class LabelPropertyEditor : DataEditor
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -55,7 +55,6 @@ 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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Persistence;
|
||||
@@ -57,6 +59,29 @@ namespace Umbraco.Core.Scoping
|
||||
/// Write-locks some lock objects.
|
||||
/// </summary>
|
||||
/// <param name="lockIds">The lock object identifiers.</param>
|
||||
[Obsolete("Use the overload with reason")]
|
||||
[Browsable(false)]
|
||||
void WriteLock(params int[] lockIds);
|
||||
|
||||
/// <summary>
|
||||
/// Write-locks some lock objects.
|
||||
/// </summary>
|
||||
/// <param name="writeLockReasonId">The id of the reason to take the write lock. See Constants.Locks.Reason.*</param>
|
||||
/// <param name="lockIds">The lock object identifiers.</param>
|
||||
void WriteLock(short writeLockReasonId, params int[] lockIds);
|
||||
|
||||
/// <summary>
|
||||
/// Bypasses all db-locks and reads the WriteLockReasonId column on the specified locks.
|
||||
/// </summary>
|
||||
/// <param name="lockIds">The lock ids from the Constants.Locks.*</param>
|
||||
/// <returns>A dictionary with the lock is as key, and the last started work as value. Note value can be null if we don't know or use SqlCe.</returns>
|
||||
IDictionary<int, short> TrySpyLock(params int[] lockIds);
|
||||
|
||||
/// <summary>
|
||||
/// Bypasses all db-locks and reads the WriteLockReasonId column on the specified lock.
|
||||
/// </summary>
|
||||
/// <param name="lockId">The lock id from the Constants.Locks.*</param>
|
||||
/// <returns>The last started work. Note this can be null if we don't know or use SqlCe.</returns>
|
||||
short TrySpyLock(int lockId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
|
||||
namespace Umbraco.Core.Scoping
|
||||
{
|
||||
@@ -33,8 +37,6 @@ namespace Umbraco.Core.Scoping
|
||||
private ICompletable _fscope;
|
||||
private IEventDispatcher _eventDispatcher;
|
||||
|
||||
private const IsolationLevel DefaultIsolationLevel = IsolationLevel.RepeatableRead;
|
||||
|
||||
// initializes a new scope
|
||||
private Scope(ScopeProvider scopeProvider,
|
||||
ILogger logger, FileSystems fileSystems, Scope parent, ScopeContext scopeContext, bool detachable,
|
||||
@@ -205,7 +207,7 @@ namespace Umbraco.Core.Scoping
|
||||
{
|
||||
if (_isolationLevel != IsolationLevel.Unspecified) return _isolationLevel;
|
||||
if (ParentScope != null) return ParentScope.IsolationLevel;
|
||||
return DefaultIsolationLevel;
|
||||
return Database.SqlContext.SqlSyntax.DefaultIsolationLevel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,6 +237,7 @@ namespace Umbraco.Core.Scoping
|
||||
try
|
||||
{
|
||||
_database.BeginTransaction(IsolationLevel);
|
||||
_database.Execute("SET LOCK_TIMEOUT 1800;");
|
||||
return _database;
|
||||
}
|
||||
catch
|
||||
@@ -490,35 +493,43 @@ namespace Umbraco.Core.Scoping
|
||||
/// <inheritdoc />
|
||||
public void ReadLock(params int[] lockIds)
|
||||
{
|
||||
// soon as we get Database, a transaction is started
|
||||
Database.SqlContext.SqlSyntax.ReadLock(Database, lockIds);
|
||||
}
|
||||
|
||||
if (Database.Transaction.IsolationLevel < IsolationLevel.RepeatableRead)
|
||||
throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required.");
|
||||
/// <inheritdoc />
|
||||
[Obsolete("Use the overload with reason")]
|
||||
[Browsable(false)]
|
||||
public void WriteLock(params int[] lockIds)
|
||||
{
|
||||
WriteLock(Constants.Locks.Reason.Default, lockIds);
|
||||
}
|
||||
|
||||
// *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
|
||||
foreach (var lockId in lockIds)
|
||||
/// <inheritdoc />
|
||||
public void WriteLock(short writeLockReasonId, params int[] lockIds)
|
||||
{
|
||||
Database.SqlContext.SqlSyntax.WriteLock(Database, writeLockReasonId, lockIds);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDictionary<int, short> TrySpyLock(params int[] lockIds)
|
||||
{
|
||||
using (var db = _scopeProvider.DatabaseFactory.CreateDatabase())
|
||||
{
|
||||
var i = Database.ExecuteScalar<int?>("SELECT value FROM umbracoLock WHERE id=@id", new { id = lockId });
|
||||
if (i == null) // ensure we are actually locking!
|
||||
throw new Exception($"LockObject with id={lockId} does not exist.");
|
||||
if (!db.SqlContext.SqlSyntax.IsReadUncommittedSupported)
|
||||
{
|
||||
return lockIds.ToDictionary(x => x, x=> Constants.Locks.Reason.Default);
|
||||
};
|
||||
|
||||
return db.Fetch<LockDto>("SELECT * FROM umbracoLock WITH (NOLOCK) WHERE id IN (@lockIds)", new { lockIds })
|
||||
.ToDictionary(x => x.Id, x => x.WriteLockReasonId);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteLock(params int[] lockIds)
|
||||
public short TrySpyLock(int lockId)
|
||||
{
|
||||
// soon as we get Database, a transaction is started
|
||||
|
||||
if (Database.Transaction.IsolationLevel < IsolationLevel.RepeatableRead)
|
||||
throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required.");
|
||||
|
||||
// *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
|
||||
foreach (var lockId in lockIds)
|
||||
{
|
||||
var i = Database.Execute("UPDATE umbracoLock SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id", new { id = lockId });
|
||||
if (i == 0) // ensure we are actually locking!
|
||||
throw new Exception($"LockObject with id={lockId} does not exist.");
|
||||
}
|
||||
return TrySpyLock(new []{lockId})[lockId];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
private IQuery<IContent> _queryNotTrashed;
|
||||
//TODO: The non-lazy object should be injected
|
||||
private readonly Lazy<PropertyValidationService> _propertyValidationService = new Lazy<PropertyValidationService>(() => new PropertyValidationService());
|
||||
|
||||
|
||||
|
||||
#region Constructors
|
||||
|
||||
@@ -757,11 +757,11 @@ namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
var publishedState = content.PublishedState;
|
||||
if (publishedState != PublishedState.Published && publishedState != PublishedState.Unpublished)
|
||||
throw new InvalidOperationException($"Cannot save (un)publishing content with name: {content.Name} - and state: {content.PublishedState}, use the dedicated SavePublished method.");
|
||||
throw new InvalidOperationException("Cannot save (un)publishing content, use the dedicated SavePublished method.");
|
||||
|
||||
if (content.Name != null && content.Name.Length > 255)
|
||||
{
|
||||
throw new InvalidOperationException($"Content with the name {content.Name} cannot be more than 255 characters in length.");
|
||||
throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
|
||||
}
|
||||
|
||||
var evtMsgs = EventMessagesFactory.Get();
|
||||
@@ -886,8 +886,6 @@ 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);
|
||||
@@ -896,13 +894,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, IsDefaultCulture(allLangs, culture), content);
|
||||
var impact = CultureImpact.Create(culture, _languageRepository.IsDefault(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, allLangs, userId, raiseEvents);
|
||||
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId, raiseEvents);
|
||||
scope.Complete();
|
||||
return result;
|
||||
}
|
||||
@@ -923,8 +921,6 @@ 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)))
|
||||
@@ -932,23 +928,25 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
var varies = content.ContentType.VariesByCulture();
|
||||
|
||||
if (cultures.Length == 0 && !varies)
|
||||
if (cultures.Length == 0)
|
||||
{
|
||||
//no cultures specified and doesn't vary, so publish it, else nothing to publish
|
||||
return SaveAndPublish(content, userId: userId, raiseEvents: raiseEvents);
|
||||
return !varies
|
||||
? SaveAndPublish(content, userId: userId, raiseEvents: raiseEvents)
|
||||
: new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content);
|
||||
}
|
||||
|
||||
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, IsDefaultCulture(allLangs, x)));
|
||||
var impacts = cultures.Select(x => CultureImpact.Explicit(x, _languageRepository.IsDefault(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, allLangs, userId, raiseEvents);
|
||||
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId, raiseEvents);
|
||||
scope.Complete();
|
||||
return result;
|
||||
}
|
||||
@@ -988,8 +986,6 @@ 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);
|
||||
@@ -997,39 +993,26 @@ 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
|
||||
{
|
||||
// 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 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;
|
||||
|
||||
//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)
|
||||
if (persisted != null && !persisted.IsCulturePublished(culture))
|
||||
return new PublishResult(PublishResultType.SuccessUnpublishAlready, evtMsgs, content);
|
||||
|
||||
return result;
|
||||
// unpublish the culture
|
||||
content.UnpublishCulture(culture);
|
||||
}
|
||||
|
||||
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId);
|
||||
scope.Complete();
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1064,35 +1047,15 @@ namespace Umbraco.Core.Services.Implement
|
||||
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
|
||||
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
|
||||
|
||||
var allLangs = _languageRepository.GetMany().ToList();
|
||||
|
||||
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId, raiseEvents);
|
||||
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, 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, IReadOnlyCollection<ILanguage> allLangs,
|
||||
int userId = Constants.Security.SuperUserId,
|
||||
bool raiseEvents = true, bool branchOne = false, bool branchRoot = false)
|
||||
ContentSavingEventArgs saveEventArgs,
|
||||
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));
|
||||
@@ -1107,8 +1070,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
|
||||
// Publishing to unpublish a culture may end up unpublishing everything so these flags can be flipped later
|
||||
// state here is either Publishing or Unpublishing
|
||||
// (even though, Publishing to unpublish a culture may end up unpublishing everything)
|
||||
var publishing = content.PublishedState == PublishedState.Publishing;
|
||||
var unpublishing = content.PublishedState == PublishedState.Unpublishing;
|
||||
|
||||
@@ -1125,18 +1088,6 @@ 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
|
||||
@@ -1146,25 +1097,11 @@ 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, allLangs);
|
||||
publishResult = StrategyCanPublish(scope, content, /*checkPath:*/ (!branchOne || branchRoot), culturesPublishing, culturesUnpublishing, evtMsgs, saveEventArgs);
|
||||
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
|
||||
{
|
||||
@@ -1225,8 +1162,13 @@ namespace Umbraco.Core.Services.Implement
|
||||
}
|
||||
}
|
||||
|
||||
//Persist the document
|
||||
SaveDocument(content);
|
||||
// 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);
|
||||
|
||||
// raise the Saved event, always
|
||||
if (raiseEvents)
|
||||
@@ -1244,34 +1186,17 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
if (culturesUnpublishing != null)
|
||||
{
|
||||
// This will mean that that we unpublished a mandatory culture or we unpublished the last culture.
|
||||
|
||||
var langs = string.Join(", ", allLangs
|
||||
//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()
|
||||
.Where(x => culturesUnpublishing.InvariantContains(x.IsoCode))
|
||||
.Select(x => x.CultureName));
|
||||
Audit(AuditType.UnpublishVariant, userId, content.Id, $"Unpublished languages: {langs}", langs);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
//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);
|
||||
|
||||
Audit(AuditType.Unpublish, userId, content.Id);
|
||||
return new PublishResult(PublishResultType.SuccessUnpublish, evtMsgs, content);
|
||||
}
|
||||
|
||||
@@ -1311,7 +1236,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
case PublishResultType.SuccessPublishCulture:
|
||||
if (culturesPublishing != null)
|
||||
{
|
||||
var langs = string.Join(", ", allLangs
|
||||
var langs = string.Join(", ", _languageRepository.GetMany()
|
||||
.Where(x => culturesPublishing.InvariantContains(x.IsoCode))
|
||||
.Select(x => x.CultureName));
|
||||
Audit(AuditType.PublishVariant, userId, content.Id, $"Published languages: {langs}", langs);
|
||||
@@ -1320,7 +1245,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
case PublishResultType.SuccessUnpublishCulture:
|
||||
if (culturesUnpublishing != null)
|
||||
{
|
||||
var langs = string.Join(", ", allLangs
|
||||
var langs = string.Join(", ", _languageRepository.GetMany()
|
||||
.Where(x => culturesUnpublishing.InvariantContains(x.IsoCode))
|
||||
.Select(x => x.CultureName));
|
||||
Audit(AuditType.UnpublishVariant, userId, content.Id, $"Unpublished languages: {langs}", langs);
|
||||
@@ -1334,14 +1259,14 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
// should not happen
|
||||
if (branchOne && !branchRoot)
|
||||
throw new PanicException("branchOne && !branchRoot - should not happen");
|
||||
throw new Exception("panic");
|
||||
|
||||
//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(", ", allLangs
|
||||
var langs = string.Join(", ", _languageRepository.GetMany()
|
||||
.Where(x => culturesChanging.InvariantContains(x.IsoCode))
|
||||
.Select(x => x.CultureName));
|
||||
Audit(AuditType.SaveVariant, userId, content.Id, $"Saved languages: {langs}", langs);
|
||||
@@ -1372,8 +1297,6 @@ namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
|
||||
var allLangs = _languageRepository.GetMany().ToList();
|
||||
|
||||
foreach (var d in _documentRepository.GetContentForRelease(date))
|
||||
{
|
||||
PublishResult result;
|
||||
@@ -1402,7 +1325,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, IsDefaultCulture(allLangs, culture));
|
||||
var impact = CultureImpact.Explicit(culture, _languageRepository.IsDefault(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}",
|
||||
@@ -1417,7 +1340,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
else if (!publishing)
|
||||
result = new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, d);
|
||||
else
|
||||
result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs, d.WriterId);
|
||||
result = CommitDocumentChangesInternal(scope, d, saveEventArgs, d.WriterId);
|
||||
|
||||
|
||||
if (result.Success == false)
|
||||
@@ -1467,7 +1390,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
d.UnpublishCulture(c);
|
||||
}
|
||||
|
||||
result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs, d.WriterId);
|
||||
result = CommitDocumentChangesInternal(scope, d, saveEventArgs, 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;
|
||||
@@ -1493,7 +1416,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
}
|
||||
|
||||
// utility 'PublishCultures' func used by SaveAndPublishBranch
|
||||
private bool SaveAndPublishBranch_PublishCultures(IContent content, HashSet<string> culturesToPublish, IReadOnlyCollection<ILanguage> allLangs)
|
||||
private bool SaveAndPublishBranch_PublishCultures(IContent content, HashSet<string> culturesToPublish)
|
||||
{
|
||||
//TODO: This does not support being able to return invalid property details to bubble up to the UI
|
||||
|
||||
@@ -1503,7 +1426,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
return culturesToPublish.All(culture =>
|
||||
{
|
||||
var impact = CultureImpact.Create(culture, IsDefaultCulture(allLangs, culture), content);
|
||||
var impact = CultureImpact.Create(culture, _languageRepository.IsDefault(culture), content);
|
||||
return content.PublishCulture(impact) && _propertyValidationService.Value.IsPropertyDataValid(content, out _, impact);
|
||||
});
|
||||
}
|
||||
@@ -1612,7 +1535,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
internal IEnumerable<PublishResult> SaveAndPublishBranch(IContent document, bool force,
|
||||
Func<IContent, HashSet<string>> shouldPublish,
|
||||
Func<IContent, HashSet<string>, IReadOnlyCollection<ILanguage>, bool> publishCultures,
|
||||
Func<IContent, HashSet<string>, bool> publishCultures,
|
||||
int userId = Constants.Security.SuperUserId)
|
||||
{
|
||||
if (shouldPublish == null) throw new ArgumentNullException(nameof(shouldPublish));
|
||||
@@ -1626,8 +1549,6 @@ 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.");
|
||||
|
||||
@@ -1636,7 +1557,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, allLangs);
|
||||
var result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, publishedDocuments, evtMsgs, userId);
|
||||
if (result != null)
|
||||
{
|
||||
results.Add(result);
|
||||
@@ -1667,7 +1588,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, allLangs);
|
||||
result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, publishedDocuments, evtMsgs, userId);
|
||||
if (result != null)
|
||||
{
|
||||
results.Add(result);
|
||||
@@ -1699,10 +1620,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>, IReadOnlyCollection<ILanguage>, bool> publishCultures,
|
||||
Func<IContent, HashSet<string>, bool> publishCultures,
|
||||
bool isRoot,
|
||||
ICollection<IContent> publishedDocuments,
|
||||
EventMessages evtMsgs, int userId, IReadOnlyCollection<ILanguage> allLangs)
|
||||
EventMessages evtMsgs, int userId)
|
||||
{
|
||||
var culturesToPublish = shouldPublish(document);
|
||||
if (culturesToPublish == null) // null = do not include
|
||||
@@ -1715,13 +1636,13 @@ namespace Umbraco.Core.Services.Implement
|
||||
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, document);
|
||||
|
||||
// publish & check if values are valid
|
||||
if (!publishCultures(document, culturesToPublish, allLangs))
|
||||
if (!publishCultures(document, culturesToPublish))
|
||||
{
|
||||
//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, allLangs, userId, branchOne: true, branchRoot: isRoot);
|
||||
var result = CommitDocumentChangesInternal(scope, document, saveEventArgs, userId, branchOne: true, branchRoot: isRoot);
|
||||
if (result.Success)
|
||||
publishedDocuments.Add(document);
|
||||
return result;
|
||||
@@ -1745,7 +1666,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
return OperationResult.Cancel(evtMsgs);
|
||||
}
|
||||
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
scope.WriteLock(Constants.Locks.Reason.EmptyRecycleBin, Constants.Locks.ContentTree);
|
||||
|
||||
// if it's not trashed yet, and published, we should unpublish
|
||||
// but... Unpublishing event makes no sense (not going to cancel?) and no need to save
|
||||
@@ -1869,7 +1790,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
scope.WriteLock(Constants.Locks.Reason.MoveContentToRecycleBin, Constants.Locks.ContentTree);
|
||||
|
||||
var originalPath = content.Path;
|
||||
var moveEventInfo = new MoveEventInfo<IContent>(content, originalPath, Constants.System.RecycleBinContent);
|
||||
@@ -1928,7 +1849,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
scope.WriteLock(Constants.Locks.Reason.MoveContent, Constants.Locks.ContentTree);
|
||||
|
||||
var parent = parentId == Constants.System.Root ? null : GetById(parentId);
|
||||
if (parentId != Constants.System.Root && (parent == null || parent.Trashed))
|
||||
@@ -2048,7 +1969,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
scope.WriteLock(Constants.Locks.Reason.EmptyRecycleBin, Constants.Locks.ContentTree);
|
||||
|
||||
// v7 EmptyingRecycleBin and EmptiedRecycleBin events are greatly simplified since
|
||||
// each deleted items will have its own deleting/deleted events. so, files and such
|
||||
@@ -2131,7 +2052,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
var copies = new List<Tuple<IContent, IContent>>();
|
||||
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
scope.WriteLock(Constants.Locks.Reason.CopyContent, Constants.Locks.ContentTree);
|
||||
|
||||
// a copy is not published (but not really unpublishing either)
|
||||
// update the create author and last edit author
|
||||
@@ -2277,7 +2198,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
scope.WriteLock(Constants.Locks.Reason.SortContent, Constants.Locks.ContentTree);
|
||||
|
||||
var ret = Sort(scope, itemsA, userId, evtMsgs, raiseEvents);
|
||||
scope.Complete();
|
||||
@@ -2306,7 +2227,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
scope.WriteLock(Constants.Locks.Reason.SortContent, Constants.Locks.ContentTree);
|
||||
var itemsA = GetByIds(idsA).ToArray();
|
||||
|
||||
var ret = Sort(scope, itemsA, userId, evtMsgs, raiseEvents);
|
||||
@@ -2422,9 +2343,6 @@ 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
|
||||
@@ -2579,9 +2497,7 @@ 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,
|
||||
IReadOnlyCollection<ILanguage> allLangs)
|
||||
private PublishResult StrategyCanPublish(IScope scope, IContent content, bool checkPath, IReadOnlyList<string> culturesPublishing, IReadOnlyCollection<string> culturesUnpublishing, EventMessages evtMsgs, ContentSavingEventArgs savingEventArgs)
|
||||
{
|
||||
// raise Publishing event
|
||||
if (scope.Events.DispatchCancelable(Publishing, this, savingEventArgs.ToContentPublishingEventArgs()))
|
||||
@@ -2594,7 +2510,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, allLangs.Any(lang => lang.IsoCode.InvariantEquals(x) && lang.IsMandatory))).ToArray();
|
||||
: culturesPublishing.Select(x => CultureImpact.Explicit(x, _languageRepository.IsDefault(x))).ToArray();
|
||||
|
||||
// publish the culture(s)
|
||||
if (!impactsToPublish.All(content.PublishCulture))
|
||||
@@ -2615,17 +2531,11 @@ 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
|
||||
// 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.
|
||||
if (content.Published && culturesPublishing.Count == 0 && culturesUnpublishing.Count == 0) // no published cultures = cannot be published
|
||||
return new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content);
|
||||
}
|
||||
|
||||
|
||||
// missing mandatory culture = cannot be published
|
||||
var mandatoryCultures = allLangs.Where(x => x.IsMandatory).Select(x => x.IsoCode);
|
||||
var mandatoryCultures = _languageRepository.GetMany().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);
|
||||
@@ -2766,7 +2676,6 @@ 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;
|
||||
|
||||
@@ -2820,7 +2729,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
//
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
scope.WriteLock(Constants.Locks.Reason.DeleteContentOfTypes,Constants.Locks.ContentTree);
|
||||
|
||||
var query = Query<IContent>().WhereIn(x => x.ContentTypeId, contentTypeIdsA);
|
||||
var contents = _documentRepository.Get(query).ToArray();
|
||||
@@ -3119,6 +3028,6 @@ namespace Umbraco.Core.Services.Implement
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
@@ -257,20 +258,38 @@ namespace Umbraco.Core.Services.Implement
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<IEntitySlim> GetChildren(int parentId)
|
||||
{
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var query = Query<IUmbracoEntity>().Where(x => x.ParentId == parentId);
|
||||
return _entityRepository.GetByQuery(query);
|
||||
try
|
||||
{
|
||||
var query = Query<IUmbracoEntity>().Where(x => x.ParentId == parentId);
|
||||
return _entityRepository.GetByQuery(query);
|
||||
}
|
||||
catch (LockTimeoutException e)
|
||||
{
|
||||
e.Reason = scope.TrySpyLock(Constants.Locks.ContentTree);
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<IEntitySlim> GetChildren(int parentId, UmbracoObjectTypes objectType)
|
||||
{
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var query = Query<IUmbracoEntity>().Where(x => x.ParentId == parentId);
|
||||
return _entityRepository.GetByQuery(query, objectType.GetGuid());
|
||||
try
|
||||
{
|
||||
var query = Query<IUmbracoEntity>().Where(x => x.ParentId == parentId);
|
||||
return _entityRepository.GetByQuery(query, objectType.GetGuid());
|
||||
}
|
||||
catch (LockTimeoutException e)
|
||||
{
|
||||
e.Reason = scope.TrySpyLock(Constants.Locks.ContentTree);
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -437,8 +437,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
new XElement("Description", contentType.Description),
|
||||
new XElement("AllowAtRoot", contentType.AllowedAsRoot.ToString()),
|
||||
new XElement("IsListView", contentType.IsContainer.ToString()),
|
||||
new XElement("IsElement", contentType.IsElement.ToString()),
|
||||
new XElement("Variations", contentType.Variations.ToString()));
|
||||
new XElement("IsElement", contentType.IsElement.ToString()));
|
||||
|
||||
var masterContentType = contentType.ContentTypeComposition.FirstOrDefault(x => x.Id == contentType.ParentId);
|
||||
if(masterContentType != null)
|
||||
@@ -488,8 +487,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
new XElement("SortOrder", propertyType.SortOrder),
|
||||
new XElement("Mandatory", propertyType.Mandatory.ToString()),
|
||||
propertyType.ValidationRegExp != null ? new XElement("Validation", propertyType.ValidationRegExp) : null,
|
||||
propertyType.Description != null ? new XElement("Description", new XCData(propertyType.Description)) : null,
|
||||
new XElement("Variations", propertyType.Variations.ToString()));
|
||||
propertyType.Description != null ? new XElement("Description", new XCData(propertyType.Description)) : null);
|
||||
|
||||
genericProperties.Add(genericProperty);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,11 @@ namespace Umbraco.Core.Services
|
||||
return manager.Localize(fullKey, Thread.CurrentThread.CurrentUICulture);
|
||||
}
|
||||
|
||||
public static string LocalizeLockReason(this ILocalizedTextService manager, short writeLockReasonId)
|
||||
{
|
||||
return manager.Localize($"writeLockReasons/Code_{writeLockReasonId}", Thread.CurrentThread.CurrentUICulture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Localize using the current thread culture
|
||||
/// </summary>
|
||||
|
||||
@@ -49,11 +49,6 @@
|
||||
/// </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
|
||||
@@ -118,9 +113,9 @@
|
||||
FailedPublishContentInvalid = FailedPublish | 8,
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// The document could not be published because it has no publishing flags or values.
|
||||
/// </summary>
|
||||
FailedPublishNothingToPublish = FailedPublish | 9,
|
||||
FailedPublishNothingToPublish = FailedPublish | 9, // TODO: in ContentService.StrategyCanPublish - weird
|
||||
|
||||
/// <summary>
|
||||
/// The document could not be published because some mandatory cultures are missing.
|
||||
|
||||
@@ -97,7 +97,8 @@ namespace Umbraco.Core.Sync
|
||||
? ":" + request.ServerVariables["SERVER_PORT"]
|
||||
: "";
|
||||
|
||||
var ssl = globalSettings.UseHttps ? "s" : ""; // force, whatever the first request
|
||||
var useSsl = globalSettings.UseHttps || port == "443";
|
||||
var ssl = useSsl ? "s" : ""; // force, whatever the first request
|
||||
var url = "http" + ssl + "://" + request.ServerVariables["SERVER_NAME"] + port + IOHelper.ResolveUrl(SystemDirectories.Umbraco);
|
||||
|
||||
return url.TrimEnd('/');
|
||||
|
||||
@@ -204,7 +204,7 @@
|
||||
<Compile Include="CompositionExtensions_Essentials.cs" />
|
||||
<Compile Include="CompositionExtensions_FileSystems.cs" />
|
||||
<Compile Include="CompositionExtensions_Uniques.cs" />
|
||||
<Compile Include="Exceptions\PanicException.cs" />
|
||||
<Compile Include="Exceptions\LockTimeoutException.cs" />
|
||||
<Compile Include="FactoryExtensions.cs" />
|
||||
<Compile Include="Composing\RegisterFactory.cs" />
|
||||
<Compile Include="Composing\Current.cs" />
|
||||
@@ -235,7 +235,6 @@
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\IPreValueMigrator.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\ListViewPreValueMigrator.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\MediaPickerPreValueMigrator.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\MarkdownEditorPreValueMigrator.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\NestedContentPreValueMigrator.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\PreValueMigratorCollection.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\PreValueMigratorCollectionBuilder.cs" />
|
||||
@@ -245,6 +244,7 @@
|
||||
<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="Migrations\Upgrade\V_8_2_0\AddLockTableColumns.cs" />
|
||||
<Compile Include="Models\Entities\EntityExtensions.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\PreValueMigratorBase.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\PreValueDto.cs" />
|
||||
@@ -267,6 +267,7 @@
|
||||
<Compile Include="Models\PublishedContent\ILivePublishedModelFactory.cs" />
|
||||
<Compile Include="Models\PublishedContent\IPublishedContentType.cs" />
|
||||
<Compile Include="Models\PublishedContent\IPublishedPropertyType.cs" />
|
||||
<Compile Include="Persistence\Interceptors\LockTimeoutInterceptor.cs" />
|
||||
<Compile Include="PropertyEditors\IIgnoreUserStartNodesConfig.cs" />
|
||||
<Compile Include="PublishedContentExtensions.cs" />
|
||||
<Compile Include="Models\PublishedContent\UrlMode.cs" />
|
||||
@@ -981,7 +982,6 @@
|
||||
<Compile Include="Persistence\DatabaseModelDefinitions\ModificationType.cs" />
|
||||
<Compile Include="Persistence\DatabaseModelDefinitions\SystemMethods.cs" />
|
||||
<Compile Include="Persistence\DatabaseModelDefinitions\TableDefinition.cs" />
|
||||
<Compile Include="Persistence\DatabaseNodeLockExtensions.cs" />
|
||||
<Compile Include="Persistence\DbCommandExtensions.cs" />
|
||||
<Compile Include="Persistence\DbConnectionExtensions.cs" />
|
||||
<Compile Include="Persistence\EntityNotFoundException.cs" />
|
||||
|
||||
@@ -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>()},
|
||||
|
||||
@@ -12,7 +12,6 @@ using Lucene.Net.Store;
|
||||
using Umbraco.Core;
|
||||
using Version = Lucene.Net.Util.Version;
|
||||
using Umbraco.Core.Logging;
|
||||
using System.Threading;
|
||||
|
||||
namespace Umbraco.Examine
|
||||
{
|
||||
@@ -29,29 +28,6 @@ namespace Umbraco.Examine
|
||||
/// </remarks>
|
||||
internal static readonly Regex CultureIsoCodeFieldNameMatchExpression = new Regex("^([_\\w]+)_([a-z]{2}-[a-z0-9]{2,4})$", RegexOptions.Compiled);
|
||||
|
||||
private static bool _isConfigured = false;
|
||||
private static object _configuredInit = null;
|
||||
private static object _isConfiguredLocker = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Called on startup to configure each index.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Configures and unlocks all Lucene based indexes registered with the <see cref="IExamineManager"/>.
|
||||
/// </remarks>
|
||||
internal static void ConfigureIndexes(this IExamineManager examineManager, IMainDom mainDom, ILogger logger)
|
||||
{
|
||||
LazyInitializer.EnsureInitialized(
|
||||
ref _configuredInit,
|
||||
ref _isConfigured,
|
||||
ref _isConfiguredLocker,
|
||||
() =>
|
||||
{
|
||||
examineManager.ConfigureLuceneIndexes(logger, !mainDom.IsMainDom);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
//TODO: We need a public method here to just match a field name against CultureIsoCodeFieldNameMatchExpression
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -5,8 +5,7 @@ using System.Threading.Tasks;
|
||||
using Examine;
|
||||
|
||||
namespace Umbraco.Examine
|
||||
{
|
||||
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility to rebuild all indexes ensuring minimal data queries
|
||||
/// </summary>
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
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,7 +72,6 @@
|
||||
<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,24 +7,73 @@ using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Examine
|
||||
{
|
||||
public class UmbracoExamineIndexDiagnostics : LuceneIndexDiagnostics
|
||||
public class UmbracoExamineIndexDiagnostics : IIndexDiagnostics
|
||||
{
|
||||
private readonly UmbracoExamineIndex _index;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public UmbracoExamineIndexDiagnostics(UmbracoExamineIndex index, ILogger logger)
|
||||
: base(index, logger)
|
||||
{
|
||||
_index = index;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override IReadOnlyDictionary<string, object> Metadata
|
||||
public int DocumentCount
|
||||
{
|
||||
get
|
||||
{
|
||||
var d = base.Metadata.ToDictionary(x => x.Key, x => x.Value);
|
||||
try
|
||||
{
|
||||
return _index.GetIndexDocumentCount();
|
||||
}
|
||||
catch (AlreadyClosedException)
|
||||
{
|
||||
_logger.Warn(typeof(UmbracoContentIndex), "Cannot get GetIndexDocumentCount, the writer is already closed");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
d[nameof(UmbracoExamineIndex.EnableDefaultEventHandler)] = _index.EnableDefaultEventHandler;
|
||||
d[nameof(UmbracoExamineIndex.PublishedValuesOnly)] = _index.PublishedValuesOnly;
|
||||
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,
|
||||
};
|
||||
|
||||
if (_index.ValueSetValidator is ValueSetValidator vsv)
|
||||
{
|
||||
|
||||
@@ -268,7 +268,7 @@ AnotherContentFinder
|
||||
public void GetDataEditors()
|
||||
{
|
||||
var types = _typeLoader.GetDataEditors();
|
||||
Assert.AreEqual(39, types.Count());
|
||||
Assert.AreEqual(38, types.Count());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -56,6 +56,8 @@ namespace Umbraco.Tests.Models
|
||||
|
||||
[TestCase("1,-1", "1", "1")] // was an issue
|
||||
[TestCase("-1,1", "1", "1")] // was an issue
|
||||
[TestCase("-1", "", "-1")]
|
||||
[TestCase("", "-1", "-1")]
|
||||
|
||||
public void CombineStartNodes(string groupSn, string userSn, string expected)
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Threading;
|
||||
using NPoco;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
@@ -37,7 +38,7 @@ namespace Umbraco.Tests.Persistence
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
scope.Database.AcquireLockNodeReadLock(Constants.Locks.Servers);
|
||||
scope.WriteLock(Constants.Locks.Reason.Default, Constants.Locks.Servers);
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
@@ -62,7 +63,7 @@ namespace Umbraco.Tests.Persistence
|
||||
{
|
||||
try
|
||||
{
|
||||
scope.Database.AcquireLockNodeReadLock(Constants.Locks.Servers);
|
||||
scope.ReadLock(Constants.Locks.Servers);
|
||||
lock (locker)
|
||||
{
|
||||
acquired++;
|
||||
@@ -131,7 +132,7 @@ namespace Umbraco.Tests.Persistence
|
||||
if (entered == threadCount) m1.Set();
|
||||
}
|
||||
ms[ic].WaitOne();
|
||||
scope.Database.AcquireLockNodeWriteLock(Constants.Locks.Servers);
|
||||
scope.WriteLock(Constants.Locks.Reason.Default,Constants.Locks.Servers);
|
||||
lock (locker)
|
||||
{
|
||||
acquired++;
|
||||
@@ -201,7 +202,7 @@ namespace Umbraco.Tests.Persistence
|
||||
thread2.Join();
|
||||
|
||||
Assert.IsNotNull(e1);
|
||||
Assert.IsInstanceOf<SqlCeLockTimeoutException>(e1);
|
||||
Assert.IsInstanceOf<LockTimeoutException>(e1);
|
||||
|
||||
// the assertion below depends on timing conditions - on a fast enough environment,
|
||||
// thread1 dies (deadlock) and frees thread2, which succeeds - however on a slow
|
||||
@@ -210,7 +211,7 @@ namespace Umbraco.Tests.Persistence
|
||||
//
|
||||
//Assert.IsNull(e2);
|
||||
if (e2 != null)
|
||||
Assert.IsInstanceOf<SqlCeLockTimeoutException>(e2);
|
||||
Assert.IsInstanceOf<LockTimeoutException>(e2);
|
||||
}
|
||||
|
||||
private void DeadLockTestThread(int id1, int id2, EventWaitHandle myEv, WaitHandle otherEv, ref Exception exception)
|
||||
@@ -221,7 +222,7 @@ namespace Umbraco.Tests.Persistence
|
||||
{
|
||||
otherEv.WaitOne();
|
||||
Console.WriteLine($"[{id1}] WAIT {id1}");
|
||||
scope.Database.AcquireLockNodeWriteLock(id1);
|
||||
scope.WriteLock(Constants.Locks.Reason.Default,id1);
|
||||
Console.WriteLine($"[{id1}] GRANT {id1}");
|
||||
WriteLocks(scope.Database);
|
||||
myEv.Set();
|
||||
@@ -232,7 +233,7 @@ namespace Umbraco.Tests.Persistence
|
||||
Thread.Sleep(200); // cannot wait due to deadlock... just give it a bit of time
|
||||
|
||||
Console.WriteLine($"[{id1}] WAIT {id2}");
|
||||
scope.Database.AcquireLockNodeWriteLock(id2);
|
||||
scope.WriteLock(Constants.Locks.Reason.Default,id2);
|
||||
Console.WriteLine($"[{id1}] GRANT {id2}");
|
||||
WriteLocks(scope.Database);
|
||||
}
|
||||
@@ -284,7 +285,7 @@ namespace Umbraco.Tests.Persistence
|
||||
{
|
||||
otherEv.WaitOne();
|
||||
Console.WriteLine($"[{id}] WAIT {id}");
|
||||
scope.Database.AcquireLockNodeWriteLock(id);
|
||||
scope.WriteLock(Constants.Locks.Reason.Default, id);
|
||||
Console.WriteLine($"[{id}] GRANT {id}");
|
||||
WriteLocks(scope.Database);
|
||||
myEv.Set();
|
||||
|
||||
@@ -29,11 +29,10 @@ 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, langRepository);
|
||||
contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository);
|
||||
var languageRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger);
|
||||
var repository = new DocumentRepository(scopeAccessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
|
||||
return repository;
|
||||
@@ -41,10 +40,9 @@ 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, langRepository);
|
||||
var contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository);
|
||||
return contentTypeRepository;
|
||||
}
|
||||
|
||||
@@ -52,8 +50,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
{
|
||||
var templateRepository = new TemplateRepository(scopeAccessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
|
||||
var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, AppCaches.Disabled);
|
||||
var langRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger);
|
||||
var contentTypeRepository = new MediaTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository, langRepository);
|
||||
var contentTypeRepository = new MediaTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository);
|
||||
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;
|
||||
|
||||
@@ -79,6 +79,39 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Perform_Get_By_Invariant_Code_On_LanguageRepository()
|
||||
{
|
||||
var provider = TestObjects.GetScopeProvider(Logger);
|
||||
using (var scope = provider.CreateScope())
|
||||
{
|
||||
var repository = CreateRepository(provider);
|
||||
|
||||
var es = new CultureInfo("es");
|
||||
var esSpecific = new CultureInfo("es-ES");
|
||||
|
||||
var language = (ILanguage)new Language(es.Name)
|
||||
{
|
||||
CultureName = es.DisplayName,
|
||||
FallbackLanguageId = 1
|
||||
};
|
||||
repository.Save(language);
|
||||
|
||||
language = repository.GetByIsoCode(es.Name);
|
||||
var languageSpecific = repository.GetByIsoCode(esSpecific.Name);
|
||||
|
||||
// Assert
|
||||
Assert.That(language, Is.Not.Null);
|
||||
Assert.That(language.HasIdentity, Is.True);
|
||||
Assert.That(language.IsoCode, Is.EqualTo(es.Name));
|
||||
|
||||
Assert.That(languageSpecific, Is.Not.Null);
|
||||
Assert.That(languageSpecific.HasIdentity, Is.True);
|
||||
Assert.That(languageSpecific.Id, Is.EqualTo(language.Id));
|
||||
Assert.That(language.IsoCode, Is.EqualTo(language.IsoCode));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_When_Id_Doesnt_Exist_Returns_Null()
|
||||
{
|
||||
|
||||
@@ -38,8 +38,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
|
||||
var templateRepository = new TemplateRepository(scopeAccessor, appCaches, Logger, TestObjects.GetFileSystemsMock());
|
||||
var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, appCaches);
|
||||
var languageRepository = new LanguageRepository(scopeAccessor, appCaches, Logger);
|
||||
mediaTypeRepository = new MediaTypeRepository(scopeAccessor, appCaches, Logger, commonRepository, languageRepository);
|
||||
mediaTypeRepository = new MediaTypeRepository(scopeAccessor, appCaches, Logger, commonRepository);
|
||||
var tagRepository = new TagRepository(scopeAccessor, appCaches, Logger);
|
||||
var repository = new MediaRepository(scopeAccessor, appCaches, Logger, mediaTypeRepository, tagRepository, Mock.Of<ILanguageRepository>());
|
||||
return repository;
|
||||
|
||||
@@ -23,8 +23,7 @@ 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);
|
||||
var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches, Logger);
|
||||
return new MediaTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
|
||||
return new MediaTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository);
|
||||
}
|
||||
|
||||
private EntityContainerRepository CreateContainerRepository(IScopeProvider provider)
|
||||
|
||||
@@ -31,8 +31,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
var accessor = (IScopeAccessor) provider;
|
||||
var templateRepository = Mock.Of<ITemplateRepository>();
|
||||
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
|
||||
var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger);
|
||||
memberTypeRepository = new MemberTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository);
|
||||
memberTypeRepository = new MemberTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository);
|
||||
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,8 +24,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
{
|
||||
var templateRepository = Mock.Of<ITemplateRepository>();
|
||||
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, templateRepository, AppCaches);
|
||||
var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches.Disabled, Mock.Of<ILogger>());
|
||||
return new MemberTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of<ILogger>(), commonRepository, languageRepository);
|
||||
return new MemberTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of<ILogger>(), commonRepository);
|
||||
}
|
||||
|
||||
[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,8 +968,7 @@ 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);
|
||||
var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger);
|
||||
mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository);
|
||||
mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository);
|
||||
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,8 +26,7 @@ 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);
|
||||
var languageRepository = new LanguageRepository(accessor, AppCaches, Logger);
|
||||
mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches, Mock.Of<ILogger>(), commonRepository, languageRepository);
|
||||
mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches, Mock.Of<ILogger>(), commonRepository);
|
||||
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;
|
||||
@@ -45,8 +44,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,19 +81,6 @@ 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()
|
||||
{
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace Umbraco.Tests.Routing
|
||||
{
|
||||
base.SetUp();
|
||||
|
||||
WebInitialComponent.CreateRoutes(
|
||||
WebFinalComponent.CreateRoutes(
|
||||
new TestUmbracoContextAccessor(),
|
||||
TestObjects.GetGlobalSettings(),
|
||||
new SurfaceControllerTypeCollection(Enumerable.Empty<Type>()),
|
||||
|
||||
@@ -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 languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches.Disabled, Logger);
|
||||
var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
|
||||
var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository);
|
||||
var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
|
||||
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 languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches.Disabled, Logger);
|
||||
var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
|
||||
var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository);
|
||||
var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
|
||||
var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository);
|
||||
|
||||
// Act
|
||||
|
||||
@@ -717,8 +717,21 @@ namespace Umbraco.Tests.Services
|
||||
[Test]
|
||||
public void Can_Unpublish_Content_Variation()
|
||||
{
|
||||
var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr, out var contentType);
|
||||
// Arrange
|
||||
|
||||
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));
|
||||
@@ -748,185 +761,6 @@ 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()
|
||||
{
|
||||
@@ -977,7 +811,17 @@ namespace Umbraco.Tests.Services
|
||||
[Test]
|
||||
public void Can_Publish_Content_Variation_And_Detect_Changed_Cultures()
|
||||
{
|
||||
CreateEnglishAndFrenchDocumentType(out var langUk, out var langFr, out var contentType);
|
||||
// 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);
|
||||
|
||||
IContent content = new Content("content", Constants.System.Root, contentType);
|
||||
content.SetCultureName("content-fr", langFr.IsoCode);
|
||||
@@ -988,8 +832,8 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
//re-get
|
||||
content = ServiceContext.ContentService.GetById(content.Id);
|
||||
content.SetCultureName("content-en", langUk.IsoCode);
|
||||
published = ServiceContext.ContentService.SaveAndPublish(content, langUk.IsoCode);
|
||||
content.SetCultureName("content-en", langGB.IsoCode);
|
||||
published = ServiceContext.ContentService.SaveAndPublish(content, langGB.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);
|
||||
@@ -3164,33 +3008,10 @@ 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,7 +53,360 @@ 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,7 +17,6 @@ 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;
|
||||
@@ -107,346 +106,44 @@ 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
|
||||
|
||||
CreateFrenchAndEnglishLangs();
|
||||
var languageEn = new Language("en") { IsDefault = true };
|
||||
ServiceContext.LocalizationService.Save(languageEn);
|
||||
var languageFr = new Language("fr");
|
||||
ServiceContext.LocalizationService.Save(languageFr);
|
||||
|
||||
var contentType = CreateContentType(ContentVariation.Culture);
|
||||
var contentType = new ContentType(-1)
|
||||
{
|
||||
Alias = "contentType",
|
||||
Name = "contentType",
|
||||
Variations = ContentVariation.Culture
|
||||
};
|
||||
|
||||
var properties = CreatePropertyCollection(
|
||||
("value1", ContentVariation.Culture),
|
||||
("value2", ContentVariation.Nothing));
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
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");
|
||||
@@ -529,11 +226,28 @@ namespace Umbraco.Tests.Services
|
||||
var languageFr = new Language("fr");
|
||||
ServiceContext.LocalizationService.Save(languageFr);
|
||||
|
||||
var contentType = CreateContentType(ContentVariation.Nothing);
|
||||
var contentType = new ContentType(-1)
|
||||
{
|
||||
Alias = "contentType",
|
||||
Name = "contentType",
|
||||
Variations = ContentVariation.Nothing
|
||||
};
|
||||
|
||||
var properties = CreatePropertyCollection(
|
||||
("value1", ContentVariation.Nothing),
|
||||
("value2", 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
|
||||
}
|
||||
};
|
||||
|
||||
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
@@ -612,13 +326,33 @@ namespace Umbraco.Tests.Services
|
||||
// one simple content type, variant, with both variant and invariant properties
|
||||
// can change an invariant property to variant and back
|
||||
|
||||
CreateFrenchAndEnglishLangs();
|
||||
var languageEn = new Language("en") { IsDefault = true };
|
||||
ServiceContext.LocalizationService.Save(languageEn);
|
||||
var languageFr = new Language("fr");
|
||||
ServiceContext.LocalizationService.Save(languageFr);
|
||||
|
||||
var contentType = CreateContentType(ContentVariation.Culture);
|
||||
var contentType = new ContentType(-1)
|
||||
{
|
||||
Alias = "contentType",
|
||||
Name = "contentType",
|
||||
Variations = ContentVariation.Culture
|
||||
};
|
||||
|
||||
var properties = CreatePropertyCollection(
|
||||
("value1", ContentVariation.Culture),
|
||||
("value2", ContentVariation.Nothing));
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
@@ -695,185 +429,6 @@ 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()
|
||||
{
|
||||
@@ -882,22 +437,59 @@ namespace Umbraco.Tests.Services
|
||||
// can change the composing content type to invariant and back
|
||||
// can change the composed content type to invariant and back
|
||||
|
||||
CreateFrenchAndEnglishLangs();
|
||||
var languageEn = new Language("en") { IsDefault = true };
|
||||
ServiceContext.LocalizationService.Save(languageEn);
|
||||
var languageFr = new Language("fr");
|
||||
ServiceContext.LocalizationService.Save(languageFr);
|
||||
|
||||
var composing = CreateContentType(ContentVariation.Culture, "composing");
|
||||
var composing = new ContentType(-1)
|
||||
{
|
||||
Alias = "composing",
|
||||
Name = "composing",
|
||||
Variations = ContentVariation.Culture
|
||||
};
|
||||
|
||||
var properties1 = CreatePropertyCollection(
|
||||
("value11", ContentVariation.Culture),
|
||||
("value12", ContentVariation.Nothing));
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
composing.PropertyGroups.Add(new PropertyGroup(properties1) { Name = "Content" });
|
||||
ServiceContext.ContentTypeService.Save(composing);
|
||||
|
||||
var composed = CreateContentType(ContentVariation.Culture, "composed");
|
||||
var composed = new ContentType(-1)
|
||||
{
|
||||
Alias = "composed",
|
||||
Name = "composed",
|
||||
Variations = ContentVariation.Culture
|
||||
};
|
||||
|
||||
var properties2 = CreatePropertyCollection(
|
||||
("value21", ContentVariation.Culture),
|
||||
("value22", ContentVariation.Nothing));
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
composed.PropertyGroups.Add(new PropertyGroup(properties2) { Name = "Content" });
|
||||
composed.AddContentType(composing);
|
||||
@@ -977,32 +569,86 @@ 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
|
||||
|
||||
CreateFrenchAndEnglishLangs();
|
||||
var languageEn = new Language("en") { IsDefault = true };
|
||||
ServiceContext.LocalizationService.Save(languageEn);
|
||||
var languageFr = new Language("fr");
|
||||
ServiceContext.LocalizationService.Save(languageFr);
|
||||
|
||||
var composing = CreateContentType(ContentVariation.Culture, "composing");
|
||||
var composing = new ContentType(-1)
|
||||
{
|
||||
Alias = "composing",
|
||||
Name = "composing",
|
||||
Variations = ContentVariation.Culture
|
||||
};
|
||||
|
||||
var properties1 = CreatePropertyCollection(
|
||||
("value11", ContentVariation.Culture),
|
||||
("value12", ContentVariation.Nothing));
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
composing.PropertyGroups.Add(new PropertyGroup(properties1) { Name = "Content" });
|
||||
ServiceContext.ContentTypeService.Save(composing);
|
||||
|
||||
var composed1 = CreateContentType(ContentVariation.Culture, "composed1");
|
||||
var composed1 = new ContentType(-1)
|
||||
{
|
||||
Alias = "composed1",
|
||||
Name = "composed1",
|
||||
Variations = ContentVariation.Culture
|
||||
};
|
||||
|
||||
var properties2 = CreatePropertyCollection(
|
||||
("value21", ContentVariation.Culture),
|
||||
("value22", ContentVariation.Nothing));
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
composed1.PropertyGroups.Add(new PropertyGroup(properties2) { Name = "Content" });
|
||||
composed1.AddContentType(composing);
|
||||
ServiceContext.ContentTypeService.Save(composed1);
|
||||
|
||||
var composed2 = CreateContentType(ContentVariation.Nothing, "composed2");
|
||||
var composed2 = new ContentType(-1)
|
||||
{
|
||||
Alias = "composed2",
|
||||
Name = "composed2",
|
||||
Variations = ContentVariation.Nothing
|
||||
};
|
||||
|
||||
var properties3 = CreatePropertyCollection(
|
||||
("value31", ContentVariation.Nothing),
|
||||
("value32", ContentVariation.Nothing));
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
composed2.PropertyGroups.Add(new PropertyGroup(properties3) { Name = "Content" });
|
||||
composed2.AddContentType(composing);
|
||||
@@ -1108,35 +754,5 @@ 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,23 +25,19 @@ 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, object routeDefaults = null, string url = null)
|
||||
bool assertOkResponse = true)
|
||||
{
|
||||
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: routeDefaults);
|
||||
defaults: new { controller = controllerName, action = actionName, id = RouteParameter.Optional });
|
||||
},
|
||||
_controllerFactory);
|
||||
|
||||
@@ -49,7 +45,7 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting
|
||||
{
|
||||
var request = new HttpRequestMessage
|
||||
{
|
||||
RequestUri = new Uri("https://testserver/" + (url ?? "")),
|
||||
RequestUri = new Uri("https://testserver/"),
|
||||
Method = method
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Exceptions;
|
||||
|
||||
namespace Umbraco.Tests.Testing
|
||||
{
|
||||
@@ -30,7 +29,7 @@ namespace Umbraco.Tests.Testing
|
||||
var methodName = test.MethodName;
|
||||
var type = Type.GetType(typeName, true);
|
||||
if (type == null)
|
||||
throw new PanicException($"Could not resolve the type from type name {typeName}"); // makes no sense
|
||||
throw new Exception("panic"); // makes no sense
|
||||
var methodInfo = type.GetMethod(methodName); // what about overloads?
|
||||
var options = GetTestOptions<TOptions>(methodInfo);
|
||||
return options;
|
||||
@@ -54,7 +53,7 @@ namespace Umbraco.Tests.Testing
|
||||
{
|
||||
if (other == null) throw new ArgumentNullException(nameof(other));
|
||||
if (!(Merge((TestOptionAttributeBase) other) is TOptions merged))
|
||||
throw new PanicException("Could not merge test options");
|
||||
throw new Exception("panic");
|
||||
return merged;
|
||||
}
|
||||
|
||||
|
||||
@@ -243,7 +243,6 @@
|
||||
<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" />
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
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,8 +4,6 @@ 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;
|
||||
@@ -157,7 +155,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<UserBasic>>(response.Item2);
|
||||
var obj = JsonConvert.DeserializeObject<PagedResult<UserDisplay>>(response.Item2);
|
||||
Assert.AreEqual(0, obj.TotalItems);
|
||||
}
|
||||
|
||||
@@ -192,100 +190,9 @@ namespace Umbraco.Tests.Web.Controllers
|
||||
var runner = new TestRunner(CtrlFactory);
|
||||
var response = await runner.Execute("Users", "GetPagedUsers", HttpMethod.Get);
|
||||
|
||||
var obj = JsonConvert.DeserializeObject<PagedResult<UserBasic>>(response.Item2);
|
||||
var obj = JsonConvert.DeserializeObject<PagedResult<UserDisplay>>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,31 +16,13 @@ module.exports = {
|
||||
//processed in the js task
|
||||
js: {
|
||||
preview: { files: ["./src/preview/**/*.js"], out: "umbraco.preview.js" },
|
||||
installer: { files: ["./src/installer/**/*.js"], out: "umbraco.installer.js" },
|
||||
installer: { files: ["./src/installer/**/*.js"], out: "umbraco.installer.js" },
|
||||
controllers: { files: ["./src/{views,controllers}/**/*.controller.js"], out: "umbraco.controllers.js" },
|
||||
directives: { files: ["./src/common/directives/**/*.js"], out: "umbraco.directives.js" },
|
||||
filters: { files: ["./src/common/filters/**/*.js"], out: "umbraco.filters.js" },
|
||||
resources: { files: ["./src/common/resources/**/*.js"], out: "umbraco.resources.js" },
|
||||
services: { files: ["./src/common/services/**/*.js"], out: "umbraco.services.js" },
|
||||
security: { files: ["./src/common/interceptors/**/*.js"], out: "umbraco.interceptors.js" },
|
||||
|
||||
//the controllers for views
|
||||
controllers: {
|
||||
files: [
|
||||
"./src/views/**/*.controller.js",
|
||||
"./src/*.controller.js"
|
||||
], out: "umbraco.controllers.js"
|
||||
},
|
||||
|
||||
//directives/components
|
||||
// - any JS file found in common / directives or common/ components
|
||||
// - any JS file found inside views that has the suffix .directive.js or .component.js
|
||||
directives: {
|
||||
files: [
|
||||
"./src/common/directives/_module.js",
|
||||
"./src/{common/directives,common/components}/**/*.js",
|
||||
"./src/views/**/*.{directive,component}.js"
|
||||
],
|
||||
out: "umbraco.directives.js"
|
||||
},
|
||||
security: { files: ["./src/common/interceptors/**/*.js"], out: "umbraco.interceptors.js" }
|
||||
},
|
||||
|
||||
//selectors for copying all views into the build
|
||||
@@ -52,7 +34,7 @@ module.exports = {
|
||||
|
||||
//globs for file-watching
|
||||
globs:{
|
||||
views: ["./src/views/**/*.html", "./src/common/directives/**/*.html", "./src/common/components/**/*.html" ],
|
||||
views: "./src/views/**/*.html",
|
||||
less: "./src/less/**/*.less",
|
||||
js: "./src/*.js",
|
||||
lib: "./lib/**/*",
|
||||
|
||||
@@ -5,9 +5,6 @@ var gulp = require('gulp');
|
||||
var runSequence = require('run-sequence');
|
||||
|
||||
// Dev - build the files ready for development and start watchers
|
||||
gulp.task('dev', function (cb) {
|
||||
|
||||
global.isProd = false;
|
||||
|
||||
gulp.task('dev', function(cb) {
|
||||
runSequence(["dependencies", "js", "less", "views"], "watch", cb);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
var config = require('../config');
|
||||
var gulp = require('gulp');
|
||||
var runSequence = require('run-sequence');
|
||||
|
||||
// Dev - build the files ready for development and start watchers
|
||||
gulp.task('fastdev', function(cb) {
|
||||
|
||||
global.isProd = false;
|
||||
|
||||
runSequence(["dependencies", "js", "less", "views"], "watch", cb);
|
||||
});
|
||||
@@ -9,28 +9,26 @@ var concat = require('gulp-concat');
|
||||
var wrap = require("gulp-wrap-js");
|
||||
var embedTemplates = require('gulp-angular-embed-templates');
|
||||
|
||||
module.exports = function (files, out) {
|
||||
|
||||
module.exports = function(files, out) {
|
||||
|
||||
var task = gulp.src(files);
|
||||
|
||||
// check for js errors
|
||||
task = task.pipe(eslint());
|
||||
// outputs the lint results to the console
|
||||
task = task.pipe(eslint.format());
|
||||
|
||||
|
||||
if (global.isProd === true) {
|
||||
// check for js errors
|
||||
task = task.pipe(eslint());
|
||||
// outputs the lint results to the console
|
||||
task = task.pipe(eslint.format());
|
||||
}
|
||||
|
||||
// sort files in stream by path or any custom sort comparator
|
||||
task = task.pipe(babel())
|
||||
.pipe(sort());
|
||||
|
||||
if (global.isProd === true) {
|
||||
//in production, embed the templates
|
||||
task = task.pipe(embedTemplates({ basePath: "./src/", minimize: { loose: true } }))
|
||||
}
|
||||
task = task.pipe(concat(out))
|
||||
.pipe(sort())
|
||||
.pipe(embedTemplates({ basePath: "./src/" }))
|
||||
.pipe(concat(out))
|
||||
.pipe(wrap('(function(){\n%= body %\n})();'))
|
||||
.pipe(gulp.dest(config.root + config.targets.js));
|
||||
|
||||
|
||||
|
||||
|
||||
return task;
|
||||
|
||||
|
||||
};
|
||||
|
||||
@@ -17,12 +17,17 @@ module.exports = function(files, out) {
|
||||
];
|
||||
|
||||
var task = gulp.src(files)
|
||||
.pipe(less())
|
||||
.pipe(cleanCss())
|
||||
.pipe(postcss(processors))
|
||||
.pipe(less());
|
||||
|
||||
|
||||
if (global.isProd === true) {
|
||||
task = task.pipe(cleanCss());
|
||||
}
|
||||
|
||||
task = task.pipe(postcss(processors))
|
||||
.pipe(rename(out))
|
||||
.pipe(gulp.dest(config.root + config.targets.css));
|
||||
|
||||
return task;
|
||||
|
||||
};
|
||||
};
|
||||
@@ -1239,11 +1239,6 @@
|
||||
// Perform the button's action.
|
||||
function doClick(button) {
|
||||
|
||||
// don't do anything if the editor input or button bar isn't the currently active element
|
||||
if (document.activeElement !== panels.input && !panels.buttonBar.contains(document.activeElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
inputBox.focus();
|
||||
|
||||
if (button.textOp) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user