Compare commits
270 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 209c50c585 | |||
| d726f5ffe9 | |||
| cecc1e4b52 | |||
| 157c0a2479 | |||
| 3d3a836c52 | |||
| 24efa54481 | |||
| 1bf85ab1db | |||
| 1d94c4b9bb | |||
| 13e704e36c | |||
| 413b266335 | |||
| 7cb0886b35 | |||
| d0e7344e2d | |||
| 8fdc14f360 | |||
| 915b24e4e4 | |||
| a24e5bd10c | |||
| 3457018fb4 | |||
| bfc87922cb | |||
| 09def9f352 | |||
| 934e2066a0 | |||
| b254b4fcda | |||
| 4dad427a64 | |||
| 9e1424fb24 | |||
| ad978c60df | |||
| dfe96dccde | |||
| 981519eac0 | |||
| 16ab7b70cf | |||
| b2093e6dfa | |||
| 3f8dffee3f | |||
| 204a9c9d53 | |||
| 531dee1e75 | |||
| 297e054381 | |||
| e9eecb2031 | |||
| 4f289a222f | |||
| e92631c623 | |||
| 93bb75511b | |||
| 5d9e1087f4 | |||
| 0308c8096e | |||
| 02469f110f | |||
| 851a2a3de8 | |||
| 74ccc34124 | |||
| f5a445101d | |||
| 2cdeb38521 | |||
| b3834c296b | |||
| 198b74f4de | |||
| 3c7580ff7b | |||
| c0ad292ebb | |||
| 4ef55aa7d0 | |||
| 3dc0bbeff3 | |||
| b205f19f40 | |||
| 727c32843c | |||
| 78a63b20c7 | |||
| c3e99205de | |||
| 66ba6f51da | |||
| 8625c8da43 | |||
| d86db3fc11 | |||
| ac4128d89e | |||
| 11f2855442 | |||
| d09be604d9 | |||
| d8355c368f | |||
| 703092c12c | |||
| 9b212121e3 | |||
| 88145d70fc | |||
| 320cc48132 | |||
| ac6e399847 | |||
| d2649e9354 | |||
| 0aaf284f7f | |||
| a21c9420b1 | |||
| b396cc340c | |||
| 4b9ce6bb10 | |||
| 55d0ca2557 | |||
| d57ebf021c | |||
| a2899b21b6 | |||
| 1089b4b9bb | |||
| 2428af3c40 | |||
| ffd5d61f9f | |||
| 5b1f2ef95d | |||
| 08c3b6bff5 | |||
| ccc2a0e811 | |||
| 445e6d41b7 | |||
| 82e11d7e0a | |||
| 2cc5256c00 | |||
| 6f83d3c865 | |||
| 3325627fd2 | |||
| bec54a0467 | |||
| 4fc265275b | |||
| a95360036d | |||
| 6873c12f04 | |||
| 0daa80efa4 | |||
| 1f2c4e38ac | |||
| b9b4721295 | |||
| 7a7adfb61f | |||
| defcb727f9 | |||
| e4f19f7192 | |||
| 0e565ddbf8 | |||
| 438133ae1f | |||
| 7ef7b23e93 | |||
| 6e1314ae30 | |||
| 75ff3b26c1 | |||
| 505b541040 | |||
| fbc12b607d | |||
| cb7440bbbf | |||
| cd355910b3 | |||
| 5452ebaf6d | |||
| cec4376d19 | |||
| 53c52abde5 | |||
| 0d63b13bf0 | |||
| 65b2d08997 | |||
| 2b7154622e | |||
| 0baf5eab0a | |||
| 16ac2c5589 | |||
| 4743fc0e1d | |||
| f8409be506 | |||
| f1f9e1742e | |||
| 455395154a | |||
| 3087eff147 | |||
| 8e2eccb392 | |||
| a0af474c75 | |||
| 7e8364eae4 | |||
| e7a9ee8142 | |||
| 806d835a90 | |||
| 617fd9ab17 | |||
| fe6df58e66 | |||
| 6bd647f8c6 | |||
| 8d0df6cec3 | |||
| 5997e0ce8c | |||
| 8bdd694a12 | |||
| ee2068de56 | |||
| dfed93a4ab | |||
| 4e17463eb8 | |||
| df391c86f3 | |||
| 67f0a65811 | |||
| 9d77114d50 | |||
| d2be30a228 | |||
| 9ed1101f27 | |||
| f60b91110f | |||
| db6714d3b8 | |||
| 47c8de4ca1 | |||
| 802e6d7979 | |||
| 249126a2b5 | |||
| ea591c87a2 | |||
| f3fc83fae6 | |||
| d005af2b11 | |||
| 3b60815e53 | |||
| b0b05cf785 | |||
| d5d213cd44 | |||
| 2d56879d92 | |||
| 13d07c7ba3 | |||
| ece7178ef5 | |||
| ab63d2664c | |||
| f829ac4e6c | |||
| 28dd9aa081 | |||
| eb8556c792 | |||
| 84e4c78bb9 | |||
| 6123c0d475 | |||
| 12ad9dd36b | |||
| ba040674cb | |||
| 3fa77dbf3d | |||
| 9fa435dbb2 | |||
| 02c1e88da2 | |||
| 87e60ef26b | |||
| 87845ee907 | |||
| 405538544f | |||
| afca2e70bd | |||
| 31d67161ab | |||
| 66e3869b06 | |||
| 600be987a7 | |||
| fdcfc48e06 | |||
| eb46b16800 | |||
| 392445b53a | |||
| 7c93b8f157 | |||
| 2f84e76952 | |||
| 12a30d3ffd | |||
| 7e1f90fb3b | |||
| 9865596dcc | |||
| 7393d9c372 | |||
| c87a22f347 | |||
| dc44a7a3a7 | |||
| 79c4d7797c | |||
| feb5567621 | |||
| 9cab789425 | |||
| ab3202b560 | |||
| a0cd542ba3 | |||
| e10ce234db | |||
| 7f02bc7d1d | |||
| 44b6738b0d | |||
| fbb19cba0b | |||
| 84554101f2 | |||
| 8a3b2e038c | |||
| ff595df7ca | |||
| 01a895bbf7 | |||
| 296d7964ae | |||
| 88349c6b66 | |||
| ed5effd190 | |||
| c6671f7f8e | |||
| 44c966bd44 | |||
| 046ed8ec85 | |||
| aabe6defb2 | |||
| 3245c82ea6 | |||
| beacc5bba8 | |||
| 57e58c6cf7 | |||
| 34e45abcc7 | |||
| 7ffe0398f9 | |||
| ce5b3531e0 | |||
| b3497011e8 | |||
| a398881fa1 | |||
| a9a5d00916 | |||
| 2cf3a7831c | |||
| 62a71f8052 | |||
| 0138091a6c | |||
| 36eccb472c | |||
| 6c0fb2f849 | |||
| 289d50d170 | |||
| e495faf83c | |||
| 0517f29134 | |||
| 467cf8d762 | |||
| 2cdb6d86ad | |||
| d474f60465 | |||
| caf4d1fe34 | |||
| e4f346620e | |||
| be4536cf16 | |||
| 89429bd65a | |||
| 6d3faf91d8 | |||
| 345e6091d2 | |||
| 86a97c30d1 | |||
| 14915e9da7 | |||
| 366058581a | |||
| 9f6a7dec2e | |||
| be30bb0ad0 | |||
| 600a29514f | |||
| 9efec9244b | |||
| ded1a22e45 | |||
| 2e83ac9282 | |||
| 9538981730 | |||
| 791222c3ce | |||
| 1095815878 | |||
| a8bdab8f5c | |||
| ff952a6df1 | |||
| 87e7cec02e | |||
| 5c8cd60275 | |||
| e2483dc874 | |||
| 6482d849e3 | |||
| 34f137c8cf | |||
| 16d8f8846e | |||
| 108416638c | |||
| 20d28ea215 | |||
| 27bc309c55 | |||
| b5b4ee79b4 | |||
| 45227357fd | |||
| a2ba0f6a4c | |||
| 415916c527 | |||
| 40a55cb9df | |||
| 5a9ca8d09f | |||
| 1bfcc9053e | |||
| 3930ae244c | |||
| 365ad2981d | |||
| ca56655d07 | |||
| 8721423f40 | |||
| 49b3351c72 | |||
| 4612c03657 | |||
| 41093e5b8b | |||
| 0ab84fc443 | |||
| efd6bd82bd | |||
| 17527c99eb | |||
| 361557dc39 | |||
| 4fa523db43 | |||
| 186460ac08 | |||
| 342b2087e9 | |||
| dc371c1979 | |||
| 2ec8281163 | |||
| d31e71a838 |
@@ -0,0 +1,35 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,25 @@
|
||||
# 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
|
||||
Y88b 888 888 888 888 888 d88P 888 888 888 Y88b. Y88..88P
|
||||
"Y88888 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
|
||||
Y88 88Y 888 888 888 888 d88P 888 888 888 Y88b. Y88..88P
|
||||
"Y888P" 888 888 888 88888P" 888 "Y888888 "Y8888P "Y88P"
|
||||
|
||||
------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
|
||||
_ _ __ __ ____ _____ _____ ____
|
||||
| | | | \/ | _ \| __ \ /\ / ____/ __ \
|
||||
| | | | \ / | |_) | |__) | / \ | | | | | |
|
||||
| | | | |\/| | _ <| _ / / /\ \| | | | | |
|
||||
| |__| | | | | |_) | | \ \ / ____ | |___| |__| |
|
||||
\____/|_| |_|____/|_| \_/_/ \_\_____\____/
|
||||
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("~/media"));
|
||||
composition.SetMediaFileSystem(() => new PhysicalFileSystem(SystemDirectories.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"))
|
||||
.SelectMany(x => x.GetDirectories("Lang", SearchOption.AllDirectories))
|
||||
.SelectMany(x => x.GetFiles("*.xml", SearchOption.TopDirectoryOnly))
|
||||
.Select(x => new LocalizedTextServiceSupplementaryFileSource(x, false));
|
||||
|
||||
|
||||
@@ -12,31 +12,62 @@
|
||||
/// or MS.DI, PerDependency in Autofac.</remarks>
|
||||
Transient,
|
||||
|
||||
// TODO: We need to fix this up, currently LightInject is the only one that behaves differently from all other containers.
|
||||
// ... the simple fix would be to map this to PerScopeLifetime in LI but need to wait on a response here https://github.com/seesharper/LightInject/issues/494#issuecomment-518942625
|
||||
//
|
||||
// we use it for controllers, httpContextBase and other request scoped objects: MembershpHelper, TagQuery, UmbracoTreeSearcher and ISearchableTree
|
||||
// - so that they are automatically disposed at the end of the scope (ie request)
|
||||
// - not sure they should not be simply 'scoped'?
|
||||
|
||||
/// <summary>
|
||||
/// One unique instance per request.
|
||||
/// </summary>
|
||||
// TODO: review lifetimes for LightInject vs other containers
|
||||
// currently, corresponds to 'Request' in LightInject which is 'Transient + disposed by Scope'
|
||||
// but NOT (in LightInject) a per-web-request lifetime, more a TransientScoped
|
||||
//
|
||||
// we use it for controllers, httpContextBase and umbracoContext
|
||||
// - so that they are automatically disposed at the end of the scope (ie request)
|
||||
// - not sure they should not be simply 'scoped'?
|
||||
//
|
||||
// Castle has an extra PerWebRequest something, and others use scope
|
||||
// what about Request before first request ie during application startup?
|
||||
// see http://blog.ploeh.dk/2009/11/17/UsingCastleWindsor'sPerWebRequestlifestylewithASP.NETMVConIIS7/
|
||||
// Castle ends up requiring a special scope manager too
|
||||
// see https://groups.google.com/forum/#!topic/castle-project-users/1E2W9LVIYR4
|
||||
//
|
||||
// but maybe also - why are we requiring scoped services at startup?
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Any instance created with this lifetime will be disposed at the end of a request.
|
||||
/// </para>
|
||||
/// Corresponds to
|
||||
/// <para>
|
||||
/// PerRequestLifeTime in LightInject - means transient but disposed at the end of the current web request.
|
||||
/// see: https://github.com/seesharper/LightInject/issues/494#issuecomment-518493262
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Scoped in MS.DI - means one per web request.
|
||||
/// see https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2#service-lifetimes</para>
|
||||
/// <para>
|
||||
/// InstancePerRequest in Autofac - means one per web request.
|
||||
/// see https://autofaccn.readthedocs.io/en/latest/lifetime/instance-scope.html#instance-per-request
|
||||
/// But "Behind the scenes, though, 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>
|
||||
Request,
|
||||
|
||||
/// <summary>
|
||||
/// One unique instance per container scope.
|
||||
/// One unique instance per scope.
|
||||
/// </summary>
|
||||
/// <remarks>Corresponds to Scope in LightInject, Scoped in MS.DI
|
||||
/// or Castle Windsor, PerLifetimeScope in Autofac.</remarks>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Any instance created with this lifetime will be disposed at the end of the current scope.
|
||||
/// </para>
|
||||
/// Corresponds to
|
||||
/// <para>PerScopeLifetime in LightInject (when in a request, means one per web request)</para>
|
||||
/// <para>
|
||||
/// Scoped in MS.DI (when in a request, means one per web request)
|
||||
/// see https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2#service-lifetimes</para>
|
||||
/// <para>
|
||||
/// InstancePerLifetimeScope in Autofac (when in a request, means one per web request)
|
||||
/// see https://autofaccn.readthedocs.io/en/latest/lifetime/instance-scope.html#instance-per-lifetime-scope
|
||||
/// Also note that Autofac's InstancePerRequest is the same as this, see https://autofaccn.readthedocs.io/en/latest/lifetime/instance-scope.html#instance-per-request
|
||||
/// it says "Behind the scenes, though, it’s still just instance per matching lifetime scope."
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// LifestyleScoped in Castle Windsor
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
Scope,
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -96,6 +96,11 @@ 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>
|
||||
@@ -203,6 +208,24 @@ 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,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
@@ -15,7 +16,7 @@ namespace Umbraco.Core
|
||||
/// <returns></returns>
|
||||
public static string ToIsoString(this DateTime dt)
|
||||
{
|
||||
return dt.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
return dt.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
public static DateTime TruncateTo(this DateTime dt, DateTruncate truncateTo)
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Core.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal exception that in theory should never ben thrown, it is only thrown in circumstances that should never happen
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
internal class PanicException : Exception
|
||||
{
|
||||
public PanicException()
|
||||
{
|
||||
}
|
||||
|
||||
public PanicException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public PanicException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected PanicException(SerializationInfo info, StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,12 @@ 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,11 +1,14 @@
|
||||
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
|
||||
{
|
||||
@@ -89,6 +92,16 @@ 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,6 +3,7 @@ using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Exceptions;
|
||||
|
||||
namespace Umbraco.Core.Mapping
|
||||
{
|
||||
@@ -259,7 +260,7 @@ namespace Umbraco.Core.Mapping
|
||||
if (typeof(TTarget).IsArray)
|
||||
{
|
||||
var elementType = typeof(TTarget).GetElementType();
|
||||
if (elementType == null) throw new Exception("panic");
|
||||
if (elementType == null) throw new PanicException("elementType == null which should never occur");
|
||||
var targetArray = Array.CreateInstance(elementType, targetList.Count);
|
||||
targetList.CopyTo(targetArray, 0);
|
||||
target = targetArray;
|
||||
@@ -382,7 +383,7 @@ namespace Umbraco.Core.Mapping
|
||||
{
|
||||
if (type.IsArray) return type.GetElementType();
|
||||
if (type.IsGenericType) return type.GenericTypeArguments[0];
|
||||
throw new Exception("panic");
|
||||
throw new PanicException($"Could not get enumerable or array type from {type}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
+36
-8
@@ -1,5 +1,7 @@
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NPoco;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Migrations.Expressions.Common;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
@@ -27,31 +29,57 @@ 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)
|
||||
{
|
||||
foreach (var key in tableKeys.Where(x => x.Item1 == TableName && x.Item2.StartsWith("FK_")))
|
||||
//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)))))
|
||||
{
|
||||
Delete.ForeignKey(key.Item2).OnTable(key.Item1).Do();
|
||||
}
|
||||
|
||||
}
|
||||
if (DeleteLocal)
|
||||
{
|
||||
foreach (var key in tableKeys.Where(x => x.Item1 == TableName && x.Item2.StartsWith("PK_")))
|
||||
foreach (var key in tableConstraints.Where(x => x.Item1 == TableName && x.Item2.InvariantStartsWith("PK_")))
|
||||
Delete.PrimaryKey(key.Item2).FromTable(key.Item1).Do();
|
||||
|
||||
// note: we do *not* delete the DEFAULT constraints
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// 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))
|
||||
Delete.Index(index.IndexName).OnTable(index.TableName).Do();
|
||||
{
|
||||
//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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
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,7 +20,8 @@ public class PreValueMigratorComposer : ICoreComposer
|
||||
.Append<DecimalPreValueMigrator>()
|
||||
.Append<ListViewPreValueMigrator>()
|
||||
.Append<DropDownFlexiblePreValueMigrator>()
|
||||
.Append<ValueListPreValueMigrator>();
|
||||
.Append<ValueListPreValueMigrator>()
|
||||
.Append<MarkdownEditorPreValueMigrator>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Exceptions;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
|
||||
{
|
||||
@@ -20,7 +21,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
|
||||
case "Umbraco.NoEdit":
|
||||
return Constants.PropertyEditors.Aliases.Label;
|
||||
default:
|
||||
throw new Exception("panic");
|
||||
throw new PanicException($"The alias {editorAlias} is not supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,21 +15,17 @@ 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);
|
||||
//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();
|
||||
//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 apply constraints (NOT NULL) to new table
|
||||
foreach (var sql in sqls1) Execute.Sql(sql).Do();
|
||||
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))
|
||||
foreach (var control in allControls.SelectMany(c => c).OfType<JObject>())
|
||||
{
|
||||
var controlValue = control["value"];
|
||||
if (controlValue.Type == JTokenType.String)
|
||||
if (controlValue?.Type == JTokenType.String)
|
||||
{
|
||||
control["value"] = UpdateMediaUrls(mediaLinkPattern, controlValue.Value<string>());
|
||||
}
|
||||
|
||||
@@ -222,7 +222,13 @@ namespace Umbraco.Core.Models
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void UnpublishCulture(this IContent content, string culture = "*")
|
||||
/// <summary>
|
||||
/// Returns false if the culture is already unpublished
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public static bool UnpublishCulture(this IContent content, string culture = "*")
|
||||
{
|
||||
culture = culture.NullOrWhiteSpaceAsNull();
|
||||
|
||||
@@ -230,16 +236,31 @@ namespace Umbraco.Core.Models
|
||||
if (!content.ContentType.SupportsPropertyVariation(culture, "*", true))
|
||||
throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{content.ContentType.Alias}\" with variation \"{content.ContentType.Variations}\".");
|
||||
|
||||
if (culture == "*") // all cultures
|
||||
|
||||
var keepProcessing = true;
|
||||
|
||||
if (culture == "*")
|
||||
{
|
||||
// all cultures
|
||||
content.ClearPublishInfos();
|
||||
else // one single culture
|
||||
content.ClearPublishInfo(culture);
|
||||
}
|
||||
else
|
||||
{
|
||||
// one single culture
|
||||
keepProcessing = content.ClearPublishInfo(culture);
|
||||
}
|
||||
|
||||
|
||||
// property.PublishValues only publishes what is valid, variation-wise
|
||||
foreach (var property in content.Properties)
|
||||
property.UnpublishValues(culture);
|
||||
if (keepProcessing)
|
||||
{
|
||||
// property.PublishValues only publishes what is valid, variation-wise
|
||||
foreach (var property in content.Properties)
|
||||
property.UnpublishValues(culture);
|
||||
|
||||
content.PublishedState = PublishedState.Publishing;
|
||||
content.PublishedState = PublishedState.Publishing;
|
||||
}
|
||||
|
||||
return keepProcessing;
|
||||
}
|
||||
|
||||
public static void ClearPublishInfos(this IContent content)
|
||||
@@ -247,15 +268,24 @@ namespace Umbraco.Core.Models
|
||||
content.PublishCultureInfos = null;
|
||||
}
|
||||
|
||||
public static void ClearPublishInfo(this IContent content, string culture)
|
||||
/// <summary>
|
||||
/// Returns false if the culture is already unpublished
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public static bool ClearPublishInfo(this IContent content, string culture)
|
||||
{
|
||||
if (culture.IsNullOrWhiteSpace())
|
||||
throw new ArgumentNullOrEmptyException(nameof(culture));
|
||||
|
||||
content.PublishCultureInfos.Remove(culture);
|
||||
|
||||
// set the culture to be dirty - it's been modified
|
||||
content.TouchCulture(culture);
|
||||
var removed = content.PublishCultureInfos.Remove(culture);
|
||||
if (removed)
|
||||
{
|
||||
// set the culture to be dirty - it's been modified
|
||||
content.TouchCulture(culture);
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -101,7 +101,7 @@ namespace Umbraco.Core.Models
|
||||
PropertyGroupCollection PropertyGroups { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets all local property types belonging to a group, across all local property groups.
|
||||
/// Gets all local property types all local property groups or ungrouped.
|
||||
/// </summary>
|
||||
IEnumerable<PropertyType> PropertyTypes { get; }
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
return type;
|
||||
var def = type.GetGenericTypeDefinition();
|
||||
if (def == null)
|
||||
throw new InvalidOperationException("panic");
|
||||
throw new PanicException($"The type {type} has not generic type definition");
|
||||
|
||||
var args = type.GetGenericArguments().Select(x => Map(x, modelTypes, true)).ToArray();
|
||||
return def.MakeGenericType(args);
|
||||
@@ -114,7 +114,7 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
return type.FullName;
|
||||
var def = type.GetGenericTypeDefinition();
|
||||
if (def == null)
|
||||
throw new InvalidOperationException("panic");
|
||||
throw new PanicException($"The type {type} has not generic type definition");
|
||||
|
||||
var args = type.GetGenericArguments().Select(x => MapToName(x, map, true)).ToArray();
|
||||
var defFullName = def.FullName.Substring(0, def.FullName.IndexOf('`'));
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
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;
|
||||
@@ -575,12 +574,11 @@ namespace Umbraco.Core.Packaging
|
||||
contentType.Thumbnail = infoElement.Element("Thumbnail").Value;
|
||||
contentType.Description = infoElement.Element("Description").Value;
|
||||
|
||||
//NOTE AllowAtRoot is a new property in the package xml so we need to verify it exists before using it.
|
||||
//NOTE AllowAtRoot, IsListView, IsElement and Variations are new properties 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");
|
||||
@@ -589,6 +587,10 @@ 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)
|
||||
@@ -614,7 +616,7 @@ namespace Umbraco.Core.Packaging
|
||||
var compositionContentType = importedContentTypes.ContainsKey(compositionAlias)
|
||||
? importedContentTypes[compositionAlias]
|
||||
: _contentTypeService.Get(compositionAlias);
|
||||
var added = contentType.AddContentType(compositionContentType);
|
||||
contentType.AddContentType(compositionContentType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -748,9 +750,14 @@ 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
|
||||
SortOrder = sortOrder,
|
||||
Variations = property.Element("Variations") != null
|
||||
? (ContentVariation)Enum.Parse(typeof(ContentVariation), property.Element("Variations").Value)
|
||||
: ContentVariation.Nothing
|
||||
};
|
||||
|
||||
var tab = (string)property.Element("Tab");
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
public static IEnumerable<Property> BuildEntities(PropertyType[] propertyTypes, IReadOnlyCollection<PropertyDataDto> dtos, int publishedVersionId, ILanguageRepository languageRepository)
|
||||
{
|
||||
var properties = new List<Property>();
|
||||
var xdtos = dtos.GroupBy(x => x.PropertyTypeId).ToDictionary(x => x.Key, x => (IEnumerable<PropertyDataDto>) x);
|
||||
var xdtos = dtos.GroupBy(x => x.PropertyTypeId).ToDictionary(x => x.Key, x => (IEnumerable<PropertyDataDto>)x);
|
||||
|
||||
foreach (var propertyType in propertyTypes)
|
||||
{
|
||||
@@ -104,10 +104,14 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
/// <param name="properties">The properties to map</param>
|
||||
/// <param name="languageRepository"></param>
|
||||
/// <param name="edited">out parameter indicating that one or more properties have been edited</param>
|
||||
/// <param name="editedCultures">out parameter containing a collection of edited cultures when the contentVariation varies by culture</param>
|
||||
/// <param name="editedCultures">
|
||||
/// Out parameter containing a collection of edited cultures when the contentVariation varies by culture.
|
||||
/// The value of this will be used to populate the edited cultures in the umbracoDocumentCultureVariation table.
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<PropertyDataDto> BuildDtos(ContentVariation contentVariation, int currentVersionId, int publishedVersionId, IEnumerable<Property> properties,
|
||||
ILanguageRepository languageRepository, out bool edited, out HashSet<string> editedCultures)
|
||||
ILanguageRepository languageRepository, out bool edited,
|
||||
out HashSet<string> editedCultures)
|
||||
{
|
||||
var propertyDataDtos = new List<PropertyDataDto>();
|
||||
edited = false;
|
||||
@@ -130,6 +134,9 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
// publishing = deal with edit and published values
|
||||
foreach (var propertyValue in property.Values)
|
||||
{
|
||||
var isInvariantValue = propertyValue.Culture == null;
|
||||
var isCultureValue = propertyValue.Culture != null && propertyValue.Segment == null;
|
||||
|
||||
// deal with published value
|
||||
if (propertyValue.PublishedValue != null && publishedVersionId > 0)
|
||||
propertyDataDtos.Add(BuildDto(publishedVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.PublishedValue));
|
||||
@@ -138,26 +145,36 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
if (propertyValue.EditedValue != null)
|
||||
propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue));
|
||||
|
||||
// property.Values will contain ALL of it's values, both variant and invariant which will be populated if the
|
||||
// administrator has previously changed the property type to be variant vs invariant.
|
||||
// We need to check for this scenario here because otherwise the editedCultures and edited flags
|
||||
// will end up incorrectly set in the umbracoDocumentCultureVariation table so here we need to
|
||||
// only process edited cultures based on the current value type and how the property varies.
|
||||
// The above logic will still persist the currently saved property value for each culture in case the admin
|
||||
// decides to swap the property's variance again, in which case the edited flag will be recalculated.
|
||||
|
||||
if (property.PropertyType.VariesByCulture() && isInvariantValue || !property.PropertyType.VariesByCulture() && isCultureValue)
|
||||
continue;
|
||||
|
||||
// use explicit equals here, else object comparison fails at comparing eg strings
|
||||
var sameValues = propertyValue.PublishedValue == null ? propertyValue.EditedValue == null : propertyValue.PublishedValue.Equals(propertyValue.EditedValue);
|
||||
|
||||
edited |= !sameValues;
|
||||
|
||||
if (entityVariesByCulture // cultures can be edited, ie CultureNeutral is supported
|
||||
&& propertyValue.Culture != null && propertyValue.Segment == null // and value is CultureNeutral
|
||||
&& !sameValues) // and edited and published are different
|
||||
if (entityVariesByCulture && !sameValues)
|
||||
{
|
||||
editedCultures.Add(propertyValue.Culture); // report culture as edited
|
||||
}
|
||||
if (isCultureValue)
|
||||
{
|
||||
editedCultures.Add(propertyValue.Culture); // report culture as edited
|
||||
}
|
||||
else if (isInvariantValue)
|
||||
{
|
||||
// flag culture as edited if it contains an edited invariant property
|
||||
if (defaultCulture == null)
|
||||
defaultCulture = languageRepository.GetDefaultIsoCode();
|
||||
|
||||
// flag culture as edited if it contains an edited invariant property
|
||||
if (propertyValue.Culture == null //invariant property
|
||||
&& !sameValues // and edited and published are different
|
||||
&& entityVariesByCulture) //only when the entity is variant
|
||||
{
|
||||
if (defaultCulture == null)
|
||||
defaultCulture = languageRepository.GetDefaultIsoCode();
|
||||
|
||||
editedCultures.Add(defaultCulture);
|
||||
editedCultures.Add(defaultCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,7 +184,7 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
{
|
||||
// not publishing = only deal with edit values
|
||||
if (propertyValue.EditedValue != null)
|
||||
propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue));
|
||||
propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue));
|
||||
}
|
||||
edited = true;
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
totalRecords = page.TotalItems;
|
||||
|
||||
var items = page.Items.Select(
|
||||
dto => new AuditItem(dto.Id, Enum<AuditType>.ParseOrNull(dto.Header) ?? AuditType.Custom, dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Comment, dto.Parameters)).ToList();
|
||||
dto => new AuditItem(dto.NodeId, 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,31 +512,16 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
foreach (var a in allPropertyDataDtos)
|
||||
a.PropertyTypeDto = indexedPropertyTypeDtos[a.PropertyTypeId];
|
||||
|
||||
// prefetch configuration for tag properties
|
||||
var tagEditors = new Dictionary<string, TagConfiguration>();
|
||||
foreach (var propertyTypeDto in indexedPropertyTypeDtos.Values)
|
||||
{
|
||||
var editorAlias = propertyTypeDto.DataTypeDto.EditorAlias;
|
||||
var editorAttribute = PropertyEditors[editorAlias].GetTagAttribute();
|
||||
if (editorAttribute == null) continue;
|
||||
var tagConfigurationSource = propertyTypeDto.DataTypeDto.Configuration;
|
||||
var tagConfiguration = string.IsNullOrWhiteSpace(tagConfigurationSource)
|
||||
? new TagConfiguration()
|
||||
: JsonConvert.DeserializeObject<TagConfiguration>(tagConfigurationSource);
|
||||
if (tagConfiguration.Delimiter == default) tagConfiguration.Delimiter = editorAttribute.Delimiter;
|
||||
tagEditors[editorAlias] = tagConfiguration;
|
||||
}
|
||||
|
||||
// now we have
|
||||
// - the definitions
|
||||
// - all property data dtos
|
||||
// - tag editors
|
||||
// - tag editors (Actually ... no we don't since i removed that code, but we don't need them anyways it seems)
|
||||
// and we need to build the proper property collections
|
||||
|
||||
return GetPropertyCollections(temps, allPropertyDataDtos, tagEditors);
|
||||
return GetPropertyCollections(temps, allPropertyDataDtos);
|
||||
}
|
||||
|
||||
private IDictionary<int, PropertyCollection> GetPropertyCollections<T>(List<TempContent<T>> temps, IEnumerable<PropertyDataDto> allPropertyDataDtos, Dictionary<string, TagConfiguration> tagConfigurations)
|
||||
private IDictionary<int, PropertyCollection> GetPropertyCollections<T>(List<TempContent<T>> temps, IEnumerable<PropertyDataDto> allPropertyDataDtos)
|
||||
where T : class, IContentBase
|
||||
{
|
||||
var result = new Dictionary<int, PropertyCollection>();
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NPoco;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
using Umbraco.Core.Persistence.Factories;
|
||||
@@ -90,7 +91,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
contentType = ContentTypeFactory.BuildContentTypeEntity(contentTypeDto);
|
||||
else if (contentTypeDto.NodeDto.NodeObjectType == Constants.ObjectTypes.MemberType)
|
||||
contentType = ContentTypeFactory.BuildMemberTypeEntity(contentTypeDto);
|
||||
else throw new Exception("panic");
|
||||
else throw new PanicException($"The node object type {contentTypeDto.NodeDto.NodeObjectType} is not supported");
|
||||
contentTypes.Add(contentType.Id, contentType);
|
||||
|
||||
// map allowed content types
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NPoco;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
@@ -18,8 +19,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
/// </summary>
|
||||
internal class ContentTypeRepository : ContentTypeRepositoryBase<IContentType>, IContentTypeRepository
|
||||
{
|
||||
public ContentTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
|
||||
: base(scopeAccessor, cache, logger, commonRepository)
|
||||
public ContentTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository)
|
||||
: base(scopeAccessor, cache, logger, commonRepository, languageRepository)
|
||||
{ }
|
||||
|
||||
protected override bool SupportsPublishing => ContentType.SupportsPublishingConst;
|
||||
@@ -56,7 +57,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
// the cache policy will always want everything
|
||||
// even GetMany(ids) gets everything and filters afterwards
|
||||
if (ids.Any()) throw new Exception("panic");
|
||||
if (ids.Any()) throw new PanicException("There can be no ids specified");
|
||||
return CommonRepository.GetAllTypes().OfType<IContentType>();
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ using Umbraco.Core.Persistence.Factories;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Services;
|
||||
using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
@@ -26,14 +27,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
internal abstract class ContentTypeRepositoryBase<TEntity> : NPocoRepositoryBase<int, TEntity>, IReadRepository<Guid, TEntity>
|
||||
where TEntity : class, IContentTypeComposition
|
||||
{
|
||||
protected ContentTypeRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
|
||||
protected ContentTypeRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository)
|
||||
: base(scopeAccessor, cache, logger)
|
||||
{
|
||||
CommonRepository = commonRepository;
|
||||
LanguageRepository = languageRepository;
|
||||
}
|
||||
|
||||
protected IContentTypeCommonRepository CommonRepository { get; }
|
||||
|
||||
protected ILanguageRepository LanguageRepository { get; }
|
||||
protected abstract bool SupportsPublishing { get; }
|
||||
|
||||
public IEnumerable<MoveEventInfo<TEntity>> Move(TEntity moving, EntityContainer container)
|
||||
@@ -98,6 +100,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
protected void PersistNewBaseContentType(IContentTypeComposition entity)
|
||||
{
|
||||
ValidateVariations(entity);
|
||||
|
||||
var dto = ContentTypeFactory.BuildContentTypeDto(entity);
|
||||
|
||||
//Cannot add a duplicate content type
|
||||
@@ -163,11 +167,11 @@ AND umbracoNode.nodeObjectType = @objectType",
|
||||
foreach (var allowedContentType in entity.AllowedContentTypes)
|
||||
{
|
||||
Database.Insert(new ContentTypeAllowedContentTypeDto
|
||||
{
|
||||
Id = entity.Id,
|
||||
AllowedId = allowedContentType.Id.Value,
|
||||
SortOrder = allowedContentType.SortOrder
|
||||
});
|
||||
{
|
||||
Id = entity.Id,
|
||||
AllowedId = allowedContentType.Id.Value,
|
||||
SortOrder = allowedContentType.SortOrder
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -214,6 +218,8 @@ AND umbracoNode.nodeObjectType = @objectType",
|
||||
|
||||
protected void PersistUpdatedBaseContentType(IContentTypeComposition entity)
|
||||
{
|
||||
ValidateVariations(entity);
|
||||
|
||||
var dto = ContentTypeFactory.BuildContentTypeDto(entity);
|
||||
|
||||
// ensure the alias is not used already
|
||||
@@ -370,7 +376,7 @@ AND umbracoNode.id <> @id",
|
||||
foreach (var propertyGroup in entity.PropertyGroups)
|
||||
{
|
||||
// insert or update group
|
||||
var groupDto = PropertyGroupFactory.BuildGroupDto(propertyGroup,entity.Id);
|
||||
var groupDto = PropertyGroupFactory.BuildGroupDto(propertyGroup, entity.Id);
|
||||
var groupId = propertyGroup.HasIdentity
|
||||
? Database.Update(groupDto)
|
||||
: Convert.ToInt32(Database.Insert(groupDto));
|
||||
@@ -388,7 +394,7 @@ AND umbracoNode.id <> @id",
|
||||
|
||||
//check if the content type variation has been changed
|
||||
var contentTypeVariationDirty = entity.IsPropertyDirty("Variations");
|
||||
var oldContentTypeVariation = (ContentVariation) dtoPk.Variations;
|
||||
var oldContentTypeVariation = (ContentVariation)dtoPk.Variations;
|
||||
var newContentTypeVariation = entity.Variations;
|
||||
var contentTypeVariationChanging = contentTypeVariationDirty && oldContentTypeVariation != newContentTypeVariation;
|
||||
if (contentTypeVariationChanging)
|
||||
@@ -449,7 +455,7 @@ AND umbracoNode.id <> @id",
|
||||
// via composition, with their original variations (ie not filtered by this
|
||||
// content type variations - we need this true value to make decisions.
|
||||
|
||||
foreach (var propertyType in ((ContentTypeCompositionBase) entity).RawComposedPropertyTypes)
|
||||
foreach (var propertyType in ((ContentTypeCompositionBase)entity).RawComposedPropertyTypes)
|
||||
{
|
||||
if (propertyType.VariesBySegment() || newContentTypeVariation.VariesBySegment())
|
||||
throw new NotSupportedException(); // TODO: support this
|
||||
@@ -518,6 +524,25 @@ AND umbracoNode.id <> @id",
|
||||
CommonRepository.ClearCache(); // always
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that no property types are flagged for a variance that is not supported by the content type itself
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
private void ValidateVariations(IContentTypeComposition entity)
|
||||
{
|
||||
//if the entity does not vary at all, then the property cannot have a variance value greater than it
|
||||
if (entity.Variations == ContentVariation.Nothing)
|
||||
{
|
||||
foreach (var prop in entity.PropertyTypes)
|
||||
{
|
||||
if (prop.IsPropertyDirty(nameof(prop.Variations)) && prop.Variations > entity.Variations)
|
||||
throw new InvalidOperationException($"The property {prop.Alias} cannot have variations of {prop.Variations} with the content type variations of {entity.Variations}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private IEnumerable<IContentTypeComposition> GetImpactedContentTypes(IContentTypeComposition contentType, IEnumerable<IContentTypeComposition> all)
|
||||
{
|
||||
var impact = new List<IContentTypeComposition>();
|
||||
@@ -525,12 +550,12 @@ AND umbracoNode.id <> @id",
|
||||
|
||||
var tree = new Dictionary<int, List<IContentTypeComposition>>();
|
||||
foreach (var x in all)
|
||||
foreach (var y in x.ContentTypeComposition)
|
||||
{
|
||||
if (!tree.TryGetValue(y.Id, out var list))
|
||||
list = tree[y.Id] = new List<IContentTypeComposition>();
|
||||
list.Add(x);
|
||||
}
|
||||
foreach (var y in x.ContentTypeComposition)
|
||||
{
|
||||
if (!tree.TryGetValue(y.Id, out var list))
|
||||
list = tree[y.Id] = new List<IContentTypeComposition>();
|
||||
list.Add(x);
|
||||
}
|
||||
|
||||
var nset = new List<IContentTypeComposition>();
|
||||
do
|
||||
@@ -572,7 +597,7 @@ AND umbracoNode.id <> @id",
|
||||
// new property type, ignore
|
||||
if (!oldVariations.TryGetValue(propertyType.Id, out var oldVariationB))
|
||||
continue;
|
||||
var oldVariation = (ContentVariation) oldVariationB; // NPoco cannot fetch directly
|
||||
var oldVariation = (ContentVariation)oldVariationB; // NPoco cannot fetch directly
|
||||
|
||||
// only those property types that *actually* changed
|
||||
var newVariation = propertyType.Variations;
|
||||
@@ -636,7 +661,7 @@ AND umbracoNode.id <> @id",
|
||||
var impactedL = impacted.Select(x => x.Id).ToList();
|
||||
|
||||
//Group by the "To" variation so we can bulk update in the correct batches
|
||||
foreach(var grouping in propertyTypeChanges.GroupBy(x => x.Value.ToVariation))
|
||||
foreach (var grouping in propertyTypeChanges.GroupBy(x => x.Value.ToVariation))
|
||||
{
|
||||
var propertyTypeIds = grouping.Select(x => x.Key).ToList();
|
||||
var toVariation = grouping.Key;
|
||||
@@ -646,10 +671,12 @@ AND umbracoNode.id <> @id",
|
||||
case ContentVariation.Culture:
|
||||
CopyPropertyData(null, defaultLanguageId, propertyTypeIds, impactedL);
|
||||
CopyTagData(null, defaultLanguageId, propertyTypeIds, impactedL);
|
||||
RenormalizeDocumentEditedFlags(propertyTypeIds, impactedL);
|
||||
break;
|
||||
case ContentVariation.Nothing:
|
||||
CopyPropertyData(defaultLanguageId, null, propertyTypeIds, impactedL);
|
||||
CopyTagData(defaultLanguageId, null, propertyTypeIds, impactedL);
|
||||
RenormalizeDocumentEditedFlags(propertyTypeIds, impactedL);
|
||||
break;
|
||||
case ContentVariation.CultureAndSegment:
|
||||
case ContentVariation.Segment:
|
||||
@@ -963,6 +990,205 @@ AND umbracoNode.id <> @id",
|
||||
|
||||
Database.Execute(sqlDelete);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Re-normalizes the edited value in the umbracoDocumentCultureVariation and umbracoDocument table when variations are changed
|
||||
/// </summary>
|
||||
/// <param name="propertyTypeIds"></param>
|
||||
/// <param name="contentTypeIds"></param>
|
||||
/// <remarks>
|
||||
/// If this is not done, then in some cases the "edited" value for a particular culture for a document will remain true when it should be false
|
||||
/// if the property was changed to invariant. In order to do this we need to recalculate this value based on the values stored for each
|
||||
/// property, culture and current/published version.
|
||||
/// </remarks>
|
||||
private void RenormalizeDocumentEditedFlags(IReadOnlyCollection<int> propertyTypeIds, IReadOnlyCollection<int> contentTypeIds = null)
|
||||
{
|
||||
var defaultLang = LanguageRepository.GetDefaultId();
|
||||
|
||||
//This will build up a query to get the property values of both the current and the published version so that we can check
|
||||
//based on the current variance of each item to see if it's 'edited' value should be true/false.
|
||||
|
||||
var whereInArgsCount = propertyTypeIds.Count + (contentTypeIds?.Count ?? 0);
|
||||
if (whereInArgsCount > 2000)
|
||||
throw new NotSupportedException("Too many property/content types.");
|
||||
|
||||
var propertySql = Sql()
|
||||
.Select<PropertyDataDto>()
|
||||
.AndSelect<ContentVersionDto>(x => x.NodeId, x => x.Current)
|
||||
.AndSelect<DocumentVersionDto>(x => x.Published)
|
||||
.AndSelect<PropertyTypeDto>(x => x.Variations)
|
||||
.From<PropertyDataDto>()
|
||||
.InnerJoin<ContentVersionDto>().On<ContentVersionDto, PropertyDataDto>((left, right) => left.Id == right.VersionId)
|
||||
.InnerJoin<PropertyTypeDto>().On<PropertyTypeDto, PropertyDataDto>((left, right) => left.Id == right.PropertyTypeId);
|
||||
|
||||
if (contentTypeIds != null)
|
||||
{
|
||||
propertySql.InnerJoin<ContentDto>().On<ContentDto, ContentVersionDto>((c, cversion) => c.NodeId == cversion.NodeId);
|
||||
}
|
||||
|
||||
propertySql.LeftJoin<DocumentVersionDto>().On<DocumentVersionDto, ContentVersionDto>((docversion, cversion) => cversion.Id == docversion.Id)
|
||||
.Where<DocumentVersionDto, ContentVersionDto>((docversion, cversion) => cversion.Current || docversion.Published)
|
||||
.WhereIn<PropertyDataDto>(x => x.PropertyTypeId, propertyTypeIds);
|
||||
|
||||
if (contentTypeIds != null)
|
||||
{
|
||||
propertySql.WhereIn<ContentDto>(x => x.ContentTypeId, contentTypeIds);
|
||||
}
|
||||
|
||||
propertySql
|
||||
.OrderBy<ContentVersionDto>(x => x.NodeId)
|
||||
.OrderBy<PropertyDataDto>(x => x.PropertyTypeId, x => x.LanguageId, x => x.VersionId);
|
||||
|
||||
//keep track of this node/lang to mark or unmark a culture as edited
|
||||
var editedLanguageVersions = new Dictionary<(int nodeId, int? langId), bool>();
|
||||
//keep track of which node to mark or unmark as edited
|
||||
var editedDocument = new Dictionary<int, bool>();
|
||||
var nodeId = -1;
|
||||
var propertyTypeId = -1;
|
||||
|
||||
PropertyValueVersionDto pubRow = null;
|
||||
|
||||
//This is a reader (Query), we are not fetching this all into memory so we cannot make any changes during this iteration, we are just collecting data.
|
||||
//Published data will always come before Current data based on the version id sort.
|
||||
//There will only be one published row (max) and one current row per property.
|
||||
foreach (var row in Database.Query<PropertyValueVersionDto>(propertySql))
|
||||
{
|
||||
//make sure to reset on each node/property change
|
||||
if (nodeId != row.NodeId || propertyTypeId != row.PropertyTypeId)
|
||||
{
|
||||
nodeId = row.NodeId;
|
||||
propertyTypeId = row.PropertyTypeId;
|
||||
pubRow = null;
|
||||
}
|
||||
|
||||
if (row.Published)
|
||||
pubRow = row;
|
||||
|
||||
if (row.Current)
|
||||
{
|
||||
var propVariations = (ContentVariation)row.Variations;
|
||||
|
||||
//if this prop doesn't vary but the row has a lang assigned or vice versa, flag this as not edited
|
||||
if (!propVariations.VariesByCulture() && row.LanguageId.HasValue
|
||||
|| propVariations.VariesByCulture() && !row.LanguageId.HasValue)
|
||||
{
|
||||
//Flag this as not edited for this node/lang if the key doesn't exist
|
||||
if (!editedLanguageVersions.TryGetValue((row.NodeId, row.LanguageId), out _))
|
||||
editedLanguageVersions.Add((row.NodeId, row.LanguageId), false);
|
||||
|
||||
//mark as false if the item doesn't exist, else coerce to true
|
||||
editedDocument[row.NodeId] = editedDocument.TryGetValue(row.NodeId, out var edited) ? (edited |= false) : false;
|
||||
}
|
||||
else if (pubRow == null)
|
||||
{
|
||||
//this would mean that that this property is 'edited' since there is no published version
|
||||
editedLanguageVersions[(row.NodeId, row.LanguageId)] = true;
|
||||
editedDocument[row.NodeId] = true;
|
||||
}
|
||||
//compare the property values, if they differ from versions then flag the current version as edited
|
||||
else if (IsPropertyValueChanged(pubRow, row))
|
||||
{
|
||||
//Here we would check if the property is invariant, in which case the edited language should be indicated by the default lang
|
||||
editedLanguageVersions[(row.NodeId, !propVariations.VariesByCulture() ? defaultLang : row.LanguageId)] = true;
|
||||
editedDocument[row.NodeId] = true;
|
||||
}
|
||||
|
||||
//reset
|
||||
pubRow = null;
|
||||
}
|
||||
}
|
||||
|
||||
//lookup all matching rows in umbracoDocumentCultureVariation
|
||||
var docCultureVariationsToUpdate = editedLanguageVersions.InGroupsOf(2000)
|
||||
.SelectMany(_ => Database.Fetch<DocumentCultureVariationDto>(
|
||||
Sql().Select<DocumentCultureVariationDto>().From<DocumentCultureVariationDto>()
|
||||
.WhereIn<DocumentCultureVariationDto>(x => x.LanguageId, editedLanguageVersions.Keys.Select(x => x.langId).ToList())
|
||||
.WhereIn<DocumentCultureVariationDto>(x => x.NodeId, editedLanguageVersions.Keys.Select(x => x.nodeId))))
|
||||
//convert to dictionary with the same key type
|
||||
.ToDictionary(x => (x.NodeId, (int?)x.LanguageId), x => x);
|
||||
|
||||
var toUpdate = new List<DocumentCultureVariationDto>();
|
||||
foreach (var ev in editedLanguageVersions)
|
||||
{
|
||||
if (docCultureVariationsToUpdate.TryGetValue(ev.Key, out var docVariations))
|
||||
{
|
||||
//check if it needs updating
|
||||
if (docVariations.Edited != ev.Value)
|
||||
{
|
||||
docVariations.Edited = ev.Value;
|
||||
toUpdate.Add(docVariations);
|
||||
}
|
||||
}
|
||||
else if (ev.Key.langId.HasValue)
|
||||
{
|
||||
//This should never happen! If a property culture is flagged as edited then the culture must exist at the document level
|
||||
throw new PanicException($"The existing DocumentCultureVariationDto was not found for node {ev.Key.nodeId} and language {ev.Key.langId}");
|
||||
}
|
||||
}
|
||||
|
||||
//Now bulk update the table DocumentCultureVariationDto, once for edited = true, another for edited = false
|
||||
foreach (var editValue in toUpdate.GroupBy(x => x.Edited))
|
||||
{
|
||||
Database.Execute(Sql().Update<DocumentCultureVariationDto>(u => u.Set(x => x.Edited, editValue.Key))
|
||||
.WhereIn<DocumentCultureVariationDto>(x => x.Id, editValue.Select(x => x.Id)));
|
||||
}
|
||||
|
||||
//Now bulk update the umbracoDocument table
|
||||
foreach (var editValue in editedDocument.GroupBy(x => x.Value))
|
||||
{
|
||||
Database.Execute(Sql().Update<DocumentDto>(u => u.Set(x => x.Edited, editValue.Key))
|
||||
.WhereIn<DocumentDto>(x => x.NodeId, editValue.Select(x => x.Key)));
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsPropertyValueChanged(PropertyValueVersionDto pubRow, PropertyValueVersionDto row)
|
||||
{
|
||||
return !pubRow.TextValue.IsNullOrWhiteSpace() && pubRow.TextValue != row.TextValue
|
||||
|| !pubRow.VarcharValue.IsNullOrWhiteSpace() && pubRow.VarcharValue != row.VarcharValue
|
||||
|| pubRow.DateValue.HasValue && pubRow.DateValue != row.DateValue
|
||||
|| pubRow.DecimalValue.HasValue && pubRow.DecimalValue != row.DecimalValue
|
||||
|| pubRow.IntValue.HasValue && pubRow.IntValue != row.IntValue;
|
||||
}
|
||||
|
||||
private class NameCompareDto
|
||||
{
|
||||
public int NodeId { get; set; }
|
||||
public int CurrentVersion { get; set; }
|
||||
public int LanguageId { get; set; }
|
||||
public string CurrentName { get; set; }
|
||||
public string PublishedName { get; set; }
|
||||
public int? PublishedVersion { get; set; }
|
||||
public int Id { get; set; } // the Id of the DocumentCultureVariationDto
|
||||
public bool Edited { get; set; }
|
||||
}
|
||||
|
||||
private class PropertyValueVersionDto
|
||||
{
|
||||
public int VersionId { get; set; }
|
||||
public int PropertyTypeId { get; set; }
|
||||
public int? LanguageId { get; set; }
|
||||
public string Segment { get; set; }
|
||||
public int? IntValue { get; set; }
|
||||
|
||||
private decimal? _decimalValue;
|
||||
[Column("decimalValue")]
|
||||
public decimal? DecimalValue
|
||||
{
|
||||
get => _decimalValue;
|
||||
set => _decimalValue = value?.Normalize();
|
||||
}
|
||||
|
||||
public DateTime? DateValue { get; set; }
|
||||
public string VarcharValue { get; set; }
|
||||
public string TextValue { get; set; }
|
||||
|
||||
public int NodeId { get; set; }
|
||||
public bool Current { get; set; }
|
||||
public bool Published { get; set; }
|
||||
|
||||
public byte Variations { get; set; }
|
||||
}
|
||||
|
||||
private void DeletePropertyType(int contentTypeId, int propertyTypeId)
|
||||
|
||||
@@ -386,7 +386,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
Database.BulkInsertRecords(GetContentVariationDtos(entity, publishing));
|
||||
|
||||
// insert document variations
|
||||
Database.BulkInsertRecords(GetDocumentVariationDtos(entity, publishing, editedCultures));
|
||||
Database.BulkInsertRecords(GetDocumentVariationDtos(entity, editedCultures));
|
||||
}
|
||||
|
||||
// refresh content
|
||||
@@ -511,7 +511,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
entity.VersionId = documentVersionDto.Id = contentVersionDto.Id; // get the new id
|
||||
|
||||
documentVersionDto.Published = false; // non-published version
|
||||
Database.Insert(documentVersionDto);
|
||||
Database.Insert(documentVersionDto);
|
||||
}
|
||||
|
||||
// replace the property data (rather than updating)
|
||||
@@ -571,7 +571,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
Database.BulkInsertRecords(GetContentVariationDtos(entity, publishing));
|
||||
|
||||
// insert document variations
|
||||
Database.BulkInsertRecords(GetDocumentVariationDtos(entity, publishing, editedCultures));
|
||||
Database.BulkInsertRecords(GetDocumentVariationDtos(entity, editedCultures));
|
||||
}
|
||||
|
||||
// refresh content
|
||||
@@ -1297,25 +1297,28 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
};
|
||||
}
|
||||
|
||||
private IEnumerable<DocumentCultureVariationDto> GetDocumentVariationDtos(IContent content, bool publishing, HashSet<string> editedCultures)
|
||||
private IEnumerable<DocumentCultureVariationDto> GetDocumentVariationDtos(IContent content, HashSet<string> editedCultures)
|
||||
{
|
||||
var allCultures = content.AvailableCultures.Union(content.PublishedCultures); // union = distinct
|
||||
foreach (var culture in allCultures)
|
||||
yield return new DocumentCultureVariationDto
|
||||
{
|
||||
var dto = new DocumentCultureVariationDto
|
||||
{
|
||||
NodeId = content.Id,
|
||||
LanguageId = LanguageRepository.GetIdByIsoCode(culture) ?? throw new InvalidOperationException("Not a valid culture."),
|
||||
Culture = culture,
|
||||
|
||||
Name = content.GetCultureName(culture) ?? content.GetPublishName(culture),
|
||||
|
||||
// note: can't use IsCultureEdited at that point - hasn't been updated yet - see PersistUpdatedItem
|
||||
|
||||
Available = content.IsCultureAvailable(culture),
|
||||
Published = content.IsCulturePublished(culture),
|
||||
// note: can't use IsCultureEdited at that point - hasn't been updated yet - see PersistUpdatedItem
|
||||
Edited = content.IsCultureAvailable(culture) &&
|
||||
(!content.IsCulturePublished(culture) || (editedCultures != null && editedCultures.Contains(culture)))
|
||||
};
|
||||
|
||||
yield return dto;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class ContentVariation
|
||||
|
||||
@@ -404,7 +404,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
if (isMedia)
|
||||
{
|
||||
sql
|
||||
.InnerJoin<MediaVersionDto>().On<ContentVersionDto, MediaVersionDto>((left, right) => left.Id == right.Id);
|
||||
.LeftJoin<MediaVersionDto>().On<ContentVersionDto, MediaVersionDto>((left, right) => left.Id == right.Id);
|
||||
}
|
||||
|
||||
//Any LeftJoin statements need to come last
|
||||
|
||||
@@ -250,7 +250,6 @@ 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,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NPoco;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
@@ -17,8 +18,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
/// </summary>
|
||||
internal class MediaTypeRepository : ContentTypeRepositoryBase<IMediaType>, IMediaTypeRepository
|
||||
{
|
||||
public MediaTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
|
||||
: base(scopeAccessor, cache, logger, commonRepository)
|
||||
public MediaTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository)
|
||||
: base(scopeAccessor, cache, logger, commonRepository, languageRepository)
|
||||
{ }
|
||||
|
||||
protected override bool SupportsPublishing => MediaType.SupportsPublishingConst;
|
||||
@@ -50,7 +51,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
// the cache policy will always want everything
|
||||
// even GetMany(ids) gets everything and filters afterwards
|
||||
if (ids.Any()) throw new Exception("panic");
|
||||
if (ids.Any()) throw new PanicException("There can be no ids specified");
|
||||
return CommonRepository.GetAllTypes().OfType<IMediaType>();
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NPoco;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
@@ -18,8 +19,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
/// </summary>
|
||||
internal class MemberTypeRepository : ContentTypeRepositoryBase<IMemberType>, IMemberTypeRepository
|
||||
{
|
||||
public MemberTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
|
||||
: base(scopeAccessor, cache, logger, commonRepository)
|
||||
public MemberTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository)
|
||||
: base(scopeAccessor, cache, logger, commonRepository, languageRepository)
|
||||
{ }
|
||||
|
||||
protected override bool SupportsPublishing => MemberType.SupportsPublishingConst;
|
||||
@@ -57,7 +58,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
// the cache policy will always want everything
|
||||
// even GetMany(ids) gets everything and filters afterwards
|
||||
if (ids.Any()) throw new Exception("panic");
|
||||
if (ids.Any()) throw new PanicException("There can be no ids specified");
|
||||
return CommonRepository.GetAllTypes().OfType<IMemberType>();
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace Umbraco.Core.PropertyEditors
|
||||
// defaults
|
||||
Type = type;
|
||||
Icon = Constants.Icons.PropertyEditor;
|
||||
Group = "common";
|
||||
Group = Constants.PropertyEditors.Groups.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; } = "common";
|
||||
public string Group { get; set; } = Constants.PropertyEditors.Groups.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the value editor is deprecated.
|
||||
|
||||
@@ -5,7 +5,11 @@ 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,6 +55,7 @@ namespace Umbraco.Core.PropertyEditors
|
||||
/// <summary>
|
||||
/// Gets the type of the dynamic configuration provider.
|
||||
/// </summary>
|
||||
//TODO: This is not used and should be implemented in a nicer way, see https://github.com/umbraco/Umbraco-CMS/issues/6017#issuecomment-516253562
|
||||
public Type TagsConfigurationProviderType { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, use the dedicated SavePublished method.");
|
||||
throw new InvalidOperationException($"Cannot save (un)publishing content with name: {content.Name} - and state: {content.PublishedState}, use the dedicated SavePublished method.");
|
||||
|
||||
if (content.Name != null && content.Name.Length > 255)
|
||||
{
|
||||
throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
|
||||
throw new InvalidOperationException($"Content with the name {content.Name} cannot be more than 255 characters in length.");
|
||||
}
|
||||
|
||||
var evtMsgs = EventMessagesFactory.Get();
|
||||
@@ -886,6 +886,8 @@ namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
|
||||
var allLangs = _languageRepository.GetMany().ToList();
|
||||
|
||||
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
|
||||
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
|
||||
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
|
||||
@@ -894,13 +896,13 @@ namespace Umbraco.Core.Services.Implement
|
||||
// if culture is '*', then publish them all (including variants)
|
||||
|
||||
//this will create the correct culture impact even if culture is * or null
|
||||
var impact = CultureImpact.Create(culture, _languageRepository.IsDefault(culture), content);
|
||||
var impact = CultureImpact.Create(culture, IsDefaultCulture(allLangs, culture), content);
|
||||
|
||||
// publish the culture(s)
|
||||
// we don't care about the response here, this response will be rechecked below but we need to set the culture info values now.
|
||||
content.PublishCulture(impact);
|
||||
|
||||
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId, raiseEvents);
|
||||
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId, raiseEvents);
|
||||
scope.Complete();
|
||||
return result;
|
||||
}
|
||||
@@ -921,6 +923,8 @@ namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
|
||||
var allLangs = _languageRepository.GetMany().ToList();
|
||||
|
||||
var evtMsgs = EventMessagesFactory.Get();
|
||||
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
|
||||
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
|
||||
@@ -928,25 +932,23 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
var varies = content.ContentType.VariesByCulture();
|
||||
|
||||
if (cultures.Length == 0)
|
||||
if (cultures.Length == 0 && !varies)
|
||||
{
|
||||
//no cultures specified and doesn't vary, so publish it, else nothing to publish
|
||||
return !varies
|
||||
? SaveAndPublish(content, userId: userId, raiseEvents: raiseEvents)
|
||||
: new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content);
|
||||
return SaveAndPublish(content, userId: userId, raiseEvents: raiseEvents);
|
||||
}
|
||||
|
||||
if (cultures.Any(x => x == null || x == "*"))
|
||||
throw new InvalidOperationException("Only valid cultures are allowed to be used in this method, wildcards or nulls are not allowed");
|
||||
|
||||
var impacts = cultures.Select(x => CultureImpact.Explicit(x, _languageRepository.IsDefault(x)));
|
||||
var impacts = cultures.Select(x => CultureImpact.Explicit(x, IsDefaultCulture(allLangs, x)));
|
||||
|
||||
// publish the culture(s)
|
||||
// we don't care about the response here, this response will be rechecked below but we need to set the culture info values now.
|
||||
foreach (var impact in impacts)
|
||||
content.PublishCulture(impact);
|
||||
|
||||
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId, raiseEvents);
|
||||
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId, raiseEvents);
|
||||
scope.Complete();
|
||||
return result;
|
||||
}
|
||||
@@ -986,6 +988,8 @@ namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
|
||||
var allLangs = _languageRepository.GetMany().ToList();
|
||||
|
||||
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
|
||||
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
|
||||
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
|
||||
@@ -993,26 +997,39 @@ namespace Umbraco.Core.Services.Implement
|
||||
// all cultures = unpublish whole
|
||||
if (culture == "*" || (!content.ContentType.VariesByCulture() && culture == null))
|
||||
{
|
||||
// It's important to understand that when the document varies by culture but the "*" is used,
|
||||
// we are just unpublishing the whole document but leaving all of the culture's as-is. This is expected
|
||||
// because we don't want to actually unpublish every culture and then the document, we just want everything
|
||||
// to be non-routable so that when it's re-published all variants were as they were.
|
||||
|
||||
content.PublishedState = PublishedState.Unpublishing;
|
||||
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId);
|
||||
scope.Complete();
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the culture we want to unpublish was already unpublished, nothing to do.
|
||||
// To check for that we need to lookup the persisted content item
|
||||
var persisted = content.HasIdentity ? GetById(content.Id) : null;
|
||||
// Unpublish the culture, this will change the document state to Publishing! ... which is expected because this will
|
||||
// essentially be re-publishing the document with the requested culture removed.
|
||||
// The call to CommitDocumentChangesInternal will perform all the checks like if this is a mandatory culture or the last culture being unpublished
|
||||
// and will then unpublish the document accordingly.
|
||||
// If the result of this is false it means there was no culture to unpublish (i.e. it was already unpublished or it did not exist)
|
||||
var removed = content.UnpublishCulture(culture);
|
||||
|
||||
if (persisted != null && !persisted.IsCulturePublished(culture))
|
||||
//save and publish any changes
|
||||
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId);
|
||||
|
||||
scope.Complete();
|
||||
|
||||
// In one case the result will be PublishStatusType.FailedPublishNothingToPublish which means that no cultures
|
||||
// were specified to be published which will be the case when removed is false. In that case
|
||||
// we want to swap the result type to PublishResultType.SuccessUnpublishAlready (that was the expectation before).
|
||||
if (result.Result == PublishResultType.FailedPublishNothingToPublish && !removed)
|
||||
return new PublishResult(PublishResultType.SuccessUnpublishAlready, evtMsgs, content);
|
||||
|
||||
// unpublish the culture
|
||||
content.UnpublishCulture(culture);
|
||||
return result;
|
||||
}
|
||||
|
||||
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId);
|
||||
scope.Complete();
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1047,15 +1064,35 @@ namespace Umbraco.Core.Services.Implement
|
||||
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
|
||||
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
|
||||
|
||||
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId, raiseEvents);
|
||||
var allLangs = _languageRepository.GetMany().ToList();
|
||||
|
||||
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId, raiseEvents);
|
||||
scope.Complete();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles a lot of business logic cases for how the document should be persisted
|
||||
/// </summary>
|
||||
/// <param name="scope"></param>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="saveEventArgs"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="raiseEvents"></param>
|
||||
/// <param name="branchOne"></param>
|
||||
/// <param name="branchRoot"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Business logic cases such: as unpublishing a mandatory culture, or unpublishing the last culture, checking for pending scheduled publishing, etc... is dealt with in this method.
|
||||
/// There is quite a lot of cases to take into account along with logic that needs to deal with scheduled saving/publishing, branch saving/publishing, etc...
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
private PublishResult CommitDocumentChangesInternal(IScope scope, IContent content,
|
||||
ContentSavingEventArgs saveEventArgs,
|
||||
int userId = Constants.Security.SuperUserId, bool raiseEvents = true, bool branchOne = false, bool branchRoot = false)
|
||||
ContentSavingEventArgs saveEventArgs, IReadOnlyCollection<ILanguage> allLangs,
|
||||
int userId = Constants.Security.SuperUserId,
|
||||
bool raiseEvents = true, bool branchOne = false, bool branchRoot = false)
|
||||
{
|
||||
if (scope == null) throw new ArgumentNullException(nameof(scope));
|
||||
if (content == null) throw new ArgumentNullException(nameof(content));
|
||||
@@ -1070,8 +1107,8 @@ namespace Umbraco.Core.Services.Implement
|
||||
if (content.PublishedState != PublishedState.Publishing && content.PublishedState != PublishedState.Unpublishing)
|
||||
content.PublishedState = PublishedState.Publishing;
|
||||
|
||||
// state here is either Publishing or Unpublishing
|
||||
// (even though, Publishing to unpublish a culture may end up unpublishing everything)
|
||||
// State here is either Publishing or Unpublishing
|
||||
// Publishing to unpublish a culture may end up unpublishing everything so these flags can be flipped later
|
||||
var publishing = content.PublishedState == PublishedState.Publishing;
|
||||
var unpublishing = content.PublishedState == PublishedState.Unpublishing;
|
||||
|
||||
@@ -1088,6 +1125,18 @@ namespace Umbraco.Core.Services.Implement
|
||||
var changeType = isNew ? TreeChangeTypes.RefreshNode : TreeChangeTypes.RefreshBranch;
|
||||
var previouslyPublished = content.HasIdentity && content.Published;
|
||||
|
||||
//inline method to persist the document with the documentRepository since this logic could be called a couple times below
|
||||
void SaveDocument(IContent c)
|
||||
{
|
||||
// save, always
|
||||
if (c.HasIdentity == false)
|
||||
c.CreatorId = userId;
|
||||
c.WriterId = userId;
|
||||
|
||||
// saving does NOT change the published version, unless PublishedState is Publishing or Unpublishing
|
||||
_documentRepository.Save(c);
|
||||
}
|
||||
|
||||
if (publishing)
|
||||
{
|
||||
//determine cultures publishing/unpublishing which will be based on previous calls to content.PublishCulture and ClearPublishInfo
|
||||
@@ -1097,11 +1146,25 @@ namespace Umbraco.Core.Services.Implement
|
||||
: null;
|
||||
|
||||
// ensure that the document can be published, and publish handling events, business rules, etc
|
||||
publishResult = StrategyCanPublish(scope, content, /*checkPath:*/ (!branchOne || branchRoot), culturesPublishing, culturesUnpublishing, evtMsgs, saveEventArgs);
|
||||
publishResult = StrategyCanPublish(scope, content, /*checkPath:*/ (!branchOne || branchRoot), culturesPublishing, culturesUnpublishing, evtMsgs, saveEventArgs, allLangs);
|
||||
if (publishResult.Success)
|
||||
{
|
||||
// note: StrategyPublish flips the PublishedState to Publishing!
|
||||
publishResult = StrategyPublish(content, culturesPublishing, culturesUnpublishing, evtMsgs);
|
||||
|
||||
//check if a culture has been unpublished and if there are no cultures left, and then unpublish document as a whole
|
||||
if (publishResult.Result == PublishResultType.SuccessUnpublishCulture && content.PublishCultureInfos.Count == 0)
|
||||
{
|
||||
// This is a special case! We are unpublishing the last culture and to persist that we need to re-publish without any cultures
|
||||
// so the state needs to remain Publishing to do that. However, we then also need to unpublish the document and to do that
|
||||
// the state needs to be Unpublishing and it cannot be both. This state is used within the documentRepository to know how to
|
||||
// persist certain things. So before proceeding below, we need to save the Publishing state to publish no cultures, then we can
|
||||
// mark the document for Unpublishing.
|
||||
SaveDocument(content);
|
||||
|
||||
//set the flag to unpublish and continue
|
||||
unpublishing = content.Published; // if not published yet, nothing to do
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1162,13 +1225,8 @@ namespace Umbraco.Core.Services.Implement
|
||||
}
|
||||
}
|
||||
|
||||
// save, always
|
||||
if (content.HasIdentity == false)
|
||||
content.CreatorId = userId;
|
||||
content.WriterId = userId;
|
||||
|
||||
// saving does NOT change the published version, unless PublishedState is Publishing or Unpublishing
|
||||
_documentRepository.Save(content);
|
||||
//Persist the document
|
||||
SaveDocument(content);
|
||||
|
||||
// raise the Saved event, always
|
||||
if (raiseEvents)
|
||||
@@ -1186,17 +1244,34 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
if (culturesUnpublishing != null)
|
||||
{
|
||||
//If we are here, it means we tried unpublishing a culture but it was mandatory so now everything is unpublished
|
||||
var langs = string.Join(", ", _languageRepository.GetMany()
|
||||
// This will mean that that we unpublished a mandatory culture or we unpublished the last culture.
|
||||
|
||||
var langs = string.Join(", ", allLangs
|
||||
.Where(x => culturesUnpublishing.InvariantContains(x.IsoCode))
|
||||
.Select(x => x.CultureName));
|
||||
Audit(AuditType.UnpublishVariant, userId, content.Id, $"Unpublished languages: {langs}", langs);
|
||||
//log that the whole content item has been unpublished due to mandatory culture unpublished
|
||||
Audit(AuditType.Unpublish, userId, content.Id, "Unpublished (mandatory language unpublished)");
|
||||
}
|
||||
else
|
||||
Audit(AuditType.Unpublish, userId, content.Id);
|
||||
|
||||
if (publishResult == null)
|
||||
throw new PanicException("publishResult == null - should not happen");
|
||||
|
||||
switch(publishResult.Result)
|
||||
{
|
||||
case PublishResultType.FailedPublishMandatoryCultureMissing:
|
||||
//occurs when a mandatory culture was unpublished (which means we tried publishing the document without a mandatory culture)
|
||||
|
||||
//log that the whole content item has been unpublished due to mandatory culture unpublished
|
||||
Audit(AuditType.Unpublish, userId, content.Id, "Unpublished (mandatory language unpublished)");
|
||||
return new PublishResult(PublishResultType.SuccessUnpublishMandatoryCulture, evtMsgs, content);
|
||||
case PublishResultType.SuccessUnpublishCulture:
|
||||
//occurs when the last culture is unpublished
|
||||
|
||||
Audit(AuditType.Unpublish, userId, content.Id, "Unpublished (last language unpublished)");
|
||||
return new PublishResult(PublishResultType.SuccessUnpublishLastCulture, evtMsgs, content);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Audit(AuditType.Unpublish, userId, content.Id);
|
||||
return new PublishResult(PublishResultType.SuccessUnpublish, evtMsgs, content);
|
||||
}
|
||||
|
||||
@@ -1236,7 +1311,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
case PublishResultType.SuccessPublishCulture:
|
||||
if (culturesPublishing != null)
|
||||
{
|
||||
var langs = string.Join(", ", _languageRepository.GetMany()
|
||||
var langs = string.Join(", ", allLangs
|
||||
.Where(x => culturesPublishing.InvariantContains(x.IsoCode))
|
||||
.Select(x => x.CultureName));
|
||||
Audit(AuditType.PublishVariant, userId, content.Id, $"Published languages: {langs}", langs);
|
||||
@@ -1245,7 +1320,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
case PublishResultType.SuccessUnpublishCulture:
|
||||
if (culturesUnpublishing != null)
|
||||
{
|
||||
var langs = string.Join(", ", _languageRepository.GetMany()
|
||||
var langs = string.Join(", ", allLangs
|
||||
.Where(x => culturesUnpublishing.InvariantContains(x.IsoCode))
|
||||
.Select(x => x.CultureName));
|
||||
Audit(AuditType.UnpublishVariant, userId, content.Id, $"Unpublished languages: {langs}", langs);
|
||||
@@ -1259,14 +1334,14 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
// should not happen
|
||||
if (branchOne && !branchRoot)
|
||||
throw new Exception("panic");
|
||||
throw new PanicException("branchOne && !branchRoot - should not happen");
|
||||
|
||||
//if publishing didn't happen or if it has failed, we still need to log which cultures were saved
|
||||
if (!branchOne && (publishResult == null || !publishResult.Success))
|
||||
{
|
||||
if (culturesChanging != null)
|
||||
{
|
||||
var langs = string.Join(", ", _languageRepository.GetMany()
|
||||
var langs = string.Join(", ", allLangs
|
||||
.Where(x => culturesChanging.InvariantContains(x.IsoCode))
|
||||
.Select(x => x.CultureName));
|
||||
Audit(AuditType.SaveVariant, userId, content.Id, $"Saved languages: {langs}", langs);
|
||||
@@ -1297,6 +1372,8 @@ namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
|
||||
var allLangs = _languageRepository.GetMany().ToList();
|
||||
|
||||
foreach (var d in _documentRepository.GetContentForRelease(date))
|
||||
{
|
||||
PublishResult result;
|
||||
@@ -1325,7 +1402,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
//publish the culture values and validate the property values, if validation fails, log the invalid properties so the develeper has an idea of what has failed
|
||||
Property[] invalidProperties = null;
|
||||
var impact = CultureImpact.Explicit(culture, _languageRepository.IsDefault(culture));
|
||||
var impact = CultureImpact.Explicit(culture, IsDefaultCulture(allLangs, culture));
|
||||
var tryPublish = d.PublishCulture(impact) && _propertyValidationService.Value.IsPropertyDataValid(d, out invalidProperties, impact);
|
||||
if (invalidProperties != null && invalidProperties.Length > 0)
|
||||
Logger.Warn<ContentService>("Scheduled publishing will fail for document {DocumentId} and culture {Culture} because of invalid properties {InvalidProperties}",
|
||||
@@ -1340,7 +1417,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
else if (!publishing)
|
||||
result = new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, d);
|
||||
else
|
||||
result = CommitDocumentChangesInternal(scope, d, saveEventArgs, d.WriterId);
|
||||
result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs, d.WriterId);
|
||||
|
||||
|
||||
if (result.Success == false)
|
||||
@@ -1390,7 +1467,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
d.UnpublishCulture(c);
|
||||
}
|
||||
|
||||
result = CommitDocumentChangesInternal(scope, d, saveEventArgs, d.WriterId);
|
||||
result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs, d.WriterId);
|
||||
if (result.Success == false)
|
||||
Logger.Error<ContentService>(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
|
||||
yield return result;
|
||||
@@ -1416,7 +1493,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
}
|
||||
|
||||
// utility 'PublishCultures' func used by SaveAndPublishBranch
|
||||
private bool SaveAndPublishBranch_PublishCultures(IContent content, HashSet<string> culturesToPublish)
|
||||
private bool SaveAndPublishBranch_PublishCultures(IContent content, HashSet<string> culturesToPublish, IReadOnlyCollection<ILanguage> allLangs)
|
||||
{
|
||||
//TODO: This does not support being able to return invalid property details to bubble up to the UI
|
||||
|
||||
@@ -1426,7 +1503,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
return culturesToPublish.All(culture =>
|
||||
{
|
||||
var impact = CultureImpact.Create(culture, _languageRepository.IsDefault(culture), content);
|
||||
var impact = CultureImpact.Create(culture, IsDefaultCulture(allLangs, culture), content);
|
||||
return content.PublishCulture(impact) && _propertyValidationService.Value.IsPropertyDataValid(content, out _, impact);
|
||||
});
|
||||
}
|
||||
@@ -1535,7 +1612,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
internal IEnumerable<PublishResult> SaveAndPublishBranch(IContent document, bool force,
|
||||
Func<IContent, HashSet<string>> shouldPublish,
|
||||
Func<IContent, HashSet<string>, bool> publishCultures,
|
||||
Func<IContent, HashSet<string>, IReadOnlyCollection<ILanguage>, bool> publishCultures,
|
||||
int userId = Constants.Security.SuperUserId)
|
||||
{
|
||||
if (shouldPublish == null) throw new ArgumentNullException(nameof(shouldPublish));
|
||||
@@ -1549,6 +1626,8 @@ namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
|
||||
var allLangs = _languageRepository.GetMany().ToList();
|
||||
|
||||
if (!document.HasIdentity)
|
||||
throw new InvalidOperationException("Cannot not branch-publish a new document.");
|
||||
|
||||
@@ -1557,7 +1636,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
throw new InvalidOperationException("Cannot mix PublishCulture and SaveAndPublishBranch.");
|
||||
|
||||
// deal with the branch root - if it fails, abort
|
||||
var result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, publishedDocuments, evtMsgs, userId);
|
||||
var result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, publishedDocuments, evtMsgs, userId, allLangs);
|
||||
if (result != null)
|
||||
{
|
||||
results.Add(result);
|
||||
@@ -1588,7 +1667,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
}
|
||||
|
||||
// no need to check path here, parent has to be published here
|
||||
result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, publishedDocuments, evtMsgs, userId);
|
||||
result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, publishedDocuments, evtMsgs, userId, allLangs);
|
||||
if (result != null)
|
||||
{
|
||||
results.Add(result);
|
||||
@@ -1620,10 +1699,10 @@ namespace Umbraco.Core.Services.Implement
|
||||
// publishValues: a function publishing values (using the appropriate PublishCulture calls)
|
||||
private PublishResult SaveAndPublishBranchItem(IScope scope, IContent document,
|
||||
Func<IContent, HashSet<string>> shouldPublish,
|
||||
Func<IContent, HashSet<string>, bool> publishCultures,
|
||||
Func<IContent, HashSet<string>, IReadOnlyCollection<ILanguage>, bool> publishCultures,
|
||||
bool isRoot,
|
||||
ICollection<IContent> publishedDocuments,
|
||||
EventMessages evtMsgs, int userId)
|
||||
EventMessages evtMsgs, int userId, IReadOnlyCollection<ILanguage> allLangs)
|
||||
{
|
||||
var culturesToPublish = shouldPublish(document);
|
||||
if (culturesToPublish == null) // null = do not include
|
||||
@@ -1636,13 +1715,13 @@ namespace Umbraco.Core.Services.Implement
|
||||
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, document);
|
||||
|
||||
// publish & check if values are valid
|
||||
if (!publishCultures(document, culturesToPublish))
|
||||
if (!publishCultures(document, culturesToPublish, allLangs))
|
||||
{
|
||||
//TODO: Based on this callback behavior there is no way to know which properties may have been invalid if this failed, see other results of FailedPublishContentInvalid
|
||||
return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, document);
|
||||
}
|
||||
|
||||
var result = CommitDocumentChangesInternal(scope, document, saveEventArgs, userId, branchOne: true, branchRoot: isRoot);
|
||||
var result = CommitDocumentChangesInternal(scope, document, saveEventArgs, allLangs, userId, branchOne: true, branchRoot: isRoot);
|
||||
if (result.Success)
|
||||
publishedDocuments.Add(document);
|
||||
return result;
|
||||
@@ -2343,6 +2422,9 @@ namespace Umbraco.Core.Services.Implement
|
||||
_auditRepository.Save(new AuditItem(objectId, type, userId, ObjectTypes.GetName(UmbracoObjectTypes.Document), message, parameters));
|
||||
}
|
||||
|
||||
private bool IsDefaultCulture(IReadOnlyCollection<ILanguage> langs, string culture) => langs.Any(x => x.IsDefault && x.IsoCode.InvariantEquals(culture));
|
||||
private bool IsMandatoryCulture(IReadOnlyCollection<ILanguage> langs, string culture) => langs.Any(x => x.IsMandatory && x.IsoCode.InvariantEquals(culture));
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handlers
|
||||
@@ -2497,7 +2579,9 @@ namespace Umbraco.Core.Services.Implement
|
||||
/// <param name="culturesPublishing"></param>
|
||||
/// <param name="savingEventArgs"></param>
|
||||
/// <returns></returns>
|
||||
private PublishResult StrategyCanPublish(IScope scope, IContent content, bool checkPath, IReadOnlyList<string> culturesPublishing, IReadOnlyCollection<string> culturesUnpublishing, EventMessages evtMsgs, ContentSavingEventArgs savingEventArgs)
|
||||
private PublishResult StrategyCanPublish(IScope scope, IContent content, bool checkPath, IReadOnlyList<string> culturesPublishing,
|
||||
IReadOnlyCollection<string> culturesUnpublishing, EventMessages evtMsgs, ContentSavingEventArgs savingEventArgs,
|
||||
IReadOnlyCollection<ILanguage> allLangs)
|
||||
{
|
||||
// raise Publishing event
|
||||
if (scope.Events.DispatchCancelable(Publishing, this, savingEventArgs.ToContentPublishingEventArgs()))
|
||||
@@ -2510,7 +2594,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
var impactsToPublish = culturesPublishing == null
|
||||
? new[] {CultureImpact.Invariant} //if it's null it's invariant
|
||||
: culturesPublishing.Select(x => CultureImpact.Explicit(x, _languageRepository.IsDefault(x))).ToArray();
|
||||
: culturesPublishing.Select(x => CultureImpact.Explicit(x, allLangs.Any(lang => lang.IsoCode.InvariantEquals(x) && lang.IsMandatory))).ToArray();
|
||||
|
||||
// publish the culture(s)
|
||||
if (!impactsToPublish.All(content.PublishCulture))
|
||||
@@ -2531,11 +2615,17 @@ namespace Umbraco.Core.Services.Implement
|
||||
if (culturesPublishing == null)
|
||||
throw new InvalidOperationException("Internal error, variesByCulture but culturesPublishing is null.");
|
||||
|
||||
if (content.Published && culturesPublishing.Count == 0 && culturesUnpublishing.Count == 0) // no published cultures = cannot be published
|
||||
if (content.Published && culturesPublishing.Count == 0 && culturesUnpublishing.Count == 0)
|
||||
{
|
||||
// no published cultures = cannot be published
|
||||
// This will occur if for example, a culture that is already unpublished is sent to be unpublished again, or vice versa, in that case
|
||||
// there will be nothing to publish/unpublish.
|
||||
return new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content);
|
||||
}
|
||||
|
||||
|
||||
// missing mandatory culture = cannot be published
|
||||
var mandatoryCultures = _languageRepository.GetMany().Where(x => x.IsMandatory).Select(x => x.IsoCode);
|
||||
var mandatoryCultures = allLangs.Where(x => x.IsMandatory).Select(x => x.IsoCode);
|
||||
var mandatoryMissing = mandatoryCultures.Any(x => !content.PublishedCultures.Contains(x, StringComparer.OrdinalIgnoreCase));
|
||||
if (mandatoryMissing)
|
||||
return new PublishResult(PublishResultType.FailedPublishMandatoryCultureMissing, evtMsgs, content);
|
||||
@@ -2676,6 +2766,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
var attempt = new PublishResult(PublishResultType.SuccessUnpublish, evtMsgs, content);
|
||||
|
||||
//TODO: What is this check?? we just created this attempt and of course it is Success?!
|
||||
if (attempt.Success == false)
|
||||
return attempt;
|
||||
|
||||
|
||||
@@ -437,7 +437,8 @@ 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("IsElement", contentType.IsElement.ToString()),
|
||||
new XElement("Variations", contentType.Variations.ToString()));
|
||||
|
||||
var masterContentType = contentType.ContentTypeComposition.FirstOrDefault(x => x.Id == contentType.ParentId);
|
||||
if(masterContentType != null)
|
||||
@@ -487,7 +488,8 @@ 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);
|
||||
propertyType.Description != null ? new XElement("Description", new XCData(propertyType.Description)) : null,
|
||||
new XElement("Variations", propertyType.Variations.ToString()));
|
||||
|
||||
genericProperties.Add(genericProperty);
|
||||
}
|
||||
|
||||
@@ -49,6 +49,11 @@
|
||||
/// </summary>
|
||||
SuccessUnpublishMandatoryCulture = 6,
|
||||
|
||||
/// <summary>
|
||||
/// The specified document culture was unpublished, and was the last published culture in the document, therefore the document itself was unpublished.
|
||||
/// </summary>
|
||||
SuccessUnpublishLastCulture = 8,
|
||||
|
||||
#endregion
|
||||
|
||||
#region Success - Mixed
|
||||
@@ -113,9 +118,9 @@
|
||||
FailedPublishContentInvalid = FailedPublish | 8,
|
||||
|
||||
/// <summary>
|
||||
/// The document could not be published because it has no publishing flags or values.
|
||||
/// The document could not be published because it has no publishing flags or values or if its a variant document, no cultures were specified to be published.
|
||||
/// </summary>
|
||||
FailedPublishNothingToPublish = FailedPublish | 9, // TODO: in ContentService.StrategyCanPublish - weird
|
||||
FailedPublishNothingToPublish = FailedPublish | 9,
|
||||
|
||||
/// <summary>
|
||||
/// The document could not be published because some mandatory cultures are missing.
|
||||
|
||||
@@ -97,8 +97,7 @@ namespace Umbraco.Core.Sync
|
||||
? ":" + request.ServerVariables["SERVER_PORT"]
|
||||
: "";
|
||||
|
||||
var useSsl = globalSettings.UseHttps || port == "443";
|
||||
var ssl = useSsl ? "s" : ""; // force, whatever the first request
|
||||
var ssl = globalSettings.UseHttps ? "s" : ""; // force, whatever the first request
|
||||
var url = "http" + ssl + "://" + request.ServerVariables["SERVER_NAME"] + port + IOHelper.ResolveUrl(SystemDirectories.Umbraco);
|
||||
|
||||
return url.TrimEnd('/');
|
||||
|
||||
@@ -204,6 +204,7 @@
|
||||
<Compile Include="CompositionExtensions_Essentials.cs" />
|
||||
<Compile Include="CompositionExtensions_FileSystems.cs" />
|
||||
<Compile Include="CompositionExtensions_Uniques.cs" />
|
||||
<Compile Include="Exceptions\PanicException.cs" />
|
||||
<Compile Include="FactoryExtensions.cs" />
|
||||
<Compile Include="Composing\RegisterFactory.cs" />
|
||||
<Compile Include="Composing\Current.cs" />
|
||||
@@ -234,6 +235,7 @@
|
||||
<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" />
|
||||
|
||||
@@ -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,6 +12,7 @@ using Lucene.Net.Store;
|
||||
using Umbraco.Core;
|
||||
using Version = Lucene.Net.Util.Version;
|
||||
using Umbraco.Core.Logging;
|
||||
using System.Threading;
|
||||
|
||||
namespace Umbraco.Examine
|
||||
{
|
||||
@@ -28,6 +29,29 @@ 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,7 +5,8 @@ using System.Threading.Tasks;
|
||||
using Examine;
|
||||
|
||||
namespace Umbraco.Examine
|
||||
{
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Utility to rebuild all indexes ensuring minimal data queries
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
using System.Collections.Generic;
|
||||
using Examine.LuceneEngine.Providers;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Lucene.Net.Store;
|
||||
using Umbraco.Core.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.Examine
|
||||
{
|
||||
public class LuceneIndexDiagnostics : IIndexDiagnostics
|
||||
{
|
||||
public LuceneIndexDiagnostics(LuceneIndex index, ILogger logger)
|
||||
{
|
||||
Index = index;
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
public LuceneIndex Index { get; }
|
||||
public ILogger Logger { get; }
|
||||
|
||||
public int DocumentCount
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
return Index.GetIndexDocumentCount();
|
||||
}
|
||||
catch (AlreadyClosedException)
|
||||
{
|
||||
Logger.Warn(typeof(UmbracoContentIndex), "Cannot get GetIndexDocumentCount, the writer is already closed");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int FieldCount
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
return Index.GetIndexFieldCount();
|
||||
}
|
||||
catch (AlreadyClosedException)
|
||||
{
|
||||
Logger.Warn(typeof(UmbracoContentIndex), "Cannot get GetIndexFieldCount, the writer is already closed");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Attempt<string> IsHealthy()
|
||||
{
|
||||
var isHealthy = Index.IsHealthy(out var indexError);
|
||||
return isHealthy ? Attempt<string>.Succeed() : Attempt.Fail(indexError.Message);
|
||||
}
|
||||
|
||||
public virtual IReadOnlyDictionary<string, object> Metadata
|
||||
{
|
||||
get
|
||||
{
|
||||
var luceneDir = Index.GetLuceneDirectory();
|
||||
var d = new Dictionary<string, object>
|
||||
{
|
||||
[nameof(UmbracoExamineIndex.CommitCount)] = Index.CommitCount,
|
||||
[nameof(UmbracoExamineIndex.DefaultAnalyzer)] = Index.DefaultAnalyzer.GetType().Name,
|
||||
["LuceneDirectory"] = luceneDir.GetType().Name
|
||||
};
|
||||
|
||||
if (luceneDir is FSDirectory fsDir)
|
||||
{
|
||||
d[nameof(UmbracoExamineIndex.LuceneIndexFolder)] = fsDir.Directory.ToString().ToLowerInvariant().TrimStart(IOHelper.MapPath(SystemDirectories.Root).ToLowerInvariant()).Replace("\\", "/").EnsureStartsWith('/');
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -72,6 +72,7 @@
|
||||
<Compile Include="IPublishedContentValueSetBuilder.cs" />
|
||||
<Compile Include="IUmbracoIndex.cs" />
|
||||
<Compile Include="IValueSetBuilder.cs" />
|
||||
<Compile Include="LuceneIndexDiagnostics.cs" />
|
||||
<Compile Include="MediaIndexPopulator.cs" />
|
||||
<Compile Include="MediaValueSetBuilder.cs" />
|
||||
<Compile Include="MemberIndexPopulator.cs" />
|
||||
|
||||
@@ -7,73 +7,24 @@ using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Examine
|
||||
{
|
||||
public class UmbracoExamineIndexDiagnostics : IIndexDiagnostics
|
||||
public class UmbracoExamineIndexDiagnostics : LuceneIndexDiagnostics
|
||||
{
|
||||
private readonly UmbracoExamineIndex _index;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public UmbracoExamineIndexDiagnostics(UmbracoExamineIndex index, ILogger logger)
|
||||
: base(index, logger)
|
||||
{
|
||||
_index = index;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public int DocumentCount
|
||||
public override IReadOnlyDictionary<string, object> Metadata
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
return _index.GetIndexDocumentCount();
|
||||
}
|
||||
catch (AlreadyClosedException)
|
||||
{
|
||||
_logger.Warn(typeof(UmbracoContentIndex), "Cannot get GetIndexDocumentCount, the writer is already closed");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
var d = base.Metadata.ToDictionary(x => x.Key, x => x.Value);
|
||||
|
||||
public int FieldCount
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
return _index.GetIndexFieldCount();
|
||||
}
|
||||
catch (AlreadyClosedException)
|
||||
{
|
||||
_logger.Warn(typeof(UmbracoContentIndex), "Cannot get GetIndexFieldCount, the writer is already closed");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Attempt<string> IsHealthy()
|
||||
{
|
||||
var isHealthy = _index.IsHealthy(out var indexError);
|
||||
return isHealthy ? Attempt<string>.Succeed() : Attempt.Fail(indexError.Message);
|
||||
}
|
||||
|
||||
public virtual IReadOnlyDictionary<string, object> Metadata
|
||||
{
|
||||
get
|
||||
{
|
||||
var d = new Dictionary<string, object>
|
||||
{
|
||||
[nameof(UmbracoExamineIndex.CommitCount)] = _index.CommitCount,
|
||||
[nameof(UmbracoExamineIndex.DefaultAnalyzer)] = _index.DefaultAnalyzer.GetType().Name,
|
||||
["LuceneDirectory"] = _index.GetLuceneDirectory().GetType().Name,
|
||||
[nameof(UmbracoExamineIndex.EnableDefaultEventHandler)] = _index.EnableDefaultEventHandler,
|
||||
[nameof(UmbracoExamineIndex.LuceneIndexFolder)] =
|
||||
_index.LuceneIndexFolder == null
|
||||
? string.Empty
|
||||
: _index.LuceneIndexFolder.ToString().ToLowerInvariant().TrimStart(IOHelper.MapPath(SystemDirectories.Root).ToLowerInvariant()).Replace("\\", "/").EnsureStartsWith('/'),
|
||||
[nameof(UmbracoExamineIndex.PublishedValuesOnly)] = _index.PublishedValuesOnly,
|
||||
//There's too much info here
|
||||
//[nameof(UmbracoExamineIndexer.FieldDefinitionCollection)] = _index.FieldDefinitionCollection,
|
||||
};
|
||||
d[nameof(UmbracoExamineIndex.EnableDefaultEventHandler)] = _index.EnableDefaultEventHandler;
|
||||
d[nameof(UmbracoExamineIndex.PublishedValuesOnly)] = _index.PublishedValuesOnly;
|
||||
|
||||
if (_index.ValueSetValidator is ValueSetValidator vsv)
|
||||
{
|
||||
|
||||
@@ -268,7 +268,7 @@ AnotherContentFinder
|
||||
public void GetDataEditors()
|
||||
{
|
||||
var types = _typeLoader.GetDataEditors();
|
||||
Assert.AreEqual(38, types.Count());
|
||||
Assert.AreEqual(39, types.Count());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -29,10 +29,11 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
|
||||
private DocumentRepository CreateRepository(IScopeAccessor scopeAccessor, out ContentTypeRepository contentTypeRepository)
|
||||
{
|
||||
var langRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger);
|
||||
var templateRepository = new TemplateRepository(scopeAccessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
|
||||
var tagRepository = new TagRepository(scopeAccessor, AppCaches.Disabled, Logger);
|
||||
var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, AppCaches.Disabled);
|
||||
contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository);
|
||||
contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository, langRepository);
|
||||
var languageRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger);
|
||||
var repository = new DocumentRepository(scopeAccessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
|
||||
return repository;
|
||||
@@ -40,9 +41,10 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
|
||||
private ContentTypeRepository CreateRepository(IScopeAccessor scopeAccessor)
|
||||
{
|
||||
var langRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger);
|
||||
var templateRepository = new TemplateRepository(scopeAccessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
|
||||
var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, AppCaches.Disabled);
|
||||
var contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository);
|
||||
var contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository, langRepository);
|
||||
return contentTypeRepository;
|
||||
}
|
||||
|
||||
@@ -50,7 +52,8 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
{
|
||||
var templateRepository = new TemplateRepository(scopeAccessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
|
||||
var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, AppCaches.Disabled);
|
||||
var contentTypeRepository = new MediaTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository);
|
||||
var langRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger);
|
||||
var contentTypeRepository = new MediaTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository, langRepository);
|
||||
return contentTypeRepository;
|
||||
}
|
||||
|
||||
|
||||
@@ -67,8 +67,8 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
templateRepository = new TemplateRepository(scopeAccessor, appCaches, Logger, TestObjects.GetFileSystemsMock());
|
||||
var tagRepository = new TagRepository(scopeAccessor, appCaches, Logger);
|
||||
var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, appCaches);
|
||||
contentTypeRepository = new ContentTypeRepository(scopeAccessor, appCaches, Logger, commonRepository);
|
||||
var languageRepository = new LanguageRepository(scopeAccessor, appCaches, Logger);
|
||||
contentTypeRepository = new ContentTypeRepository(scopeAccessor, appCaches, Logger, commonRepository, languageRepository);
|
||||
var repository = new DocumentRepository(scopeAccessor, appCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
|
||||
return repository;
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
var templateRepository = new TemplateRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
|
||||
var tagRepository = new TagRepository(accessor, Core.Cache.AppCaches.Disabled, Logger);
|
||||
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
|
||||
contentTypeRepository = new ContentTypeRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, commonRepository);
|
||||
languageRepository = new LanguageRepository(accessor, Core.Cache.AppCaches.Disabled, Logger);
|
||||
contentTypeRepository = new ContentTypeRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, commonRepository, languageRepository);
|
||||
documentRepository = new DocumentRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
|
||||
var domainRepository = new DomainRepository(accessor, Core.Cache.AppCaches.Disabled, Logger);
|
||||
return domainRepository;
|
||||
|
||||
@@ -79,39 +79,6 @@ 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,7 +38,8 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
|
||||
var templateRepository = new TemplateRepository(scopeAccessor, appCaches, Logger, TestObjects.GetFileSystemsMock());
|
||||
var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, appCaches);
|
||||
mediaTypeRepository = new MediaTypeRepository(scopeAccessor, appCaches, Logger, commonRepository);
|
||||
var languageRepository = new LanguageRepository(scopeAccessor, appCaches, Logger);
|
||||
mediaTypeRepository = new MediaTypeRepository(scopeAccessor, appCaches, Logger, commonRepository, languageRepository);
|
||||
var tagRepository = new TagRepository(scopeAccessor, appCaches, Logger);
|
||||
var repository = new MediaRepository(scopeAccessor, appCaches, Logger, mediaTypeRepository, tagRepository, Mock.Of<ILanguageRepository>());
|
||||
return repository;
|
||||
|
||||
@@ -23,7 +23,8 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
var cacheHelper = AppCaches.Disabled;
|
||||
var templateRepository = new TemplateRepository((IScopeAccessor)provider, cacheHelper, Logger, TestObjects.GetFileSystemsMock());
|
||||
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, templateRepository, AppCaches);
|
||||
return new MediaTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository);
|
||||
var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches, Logger);
|
||||
return new MediaTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
|
||||
}
|
||||
|
||||
private EntityContainerRepository CreateContainerRepository(IScopeProvider provider)
|
||||
|
||||
@@ -31,7 +31,8 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
var accessor = (IScopeAccessor) provider;
|
||||
var templateRepository = Mock.Of<ITemplateRepository>();
|
||||
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
|
||||
memberTypeRepository = new MemberTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository);
|
||||
var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger);
|
||||
memberTypeRepository = new MemberTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository);
|
||||
memberGroupRepository = new MemberGroupRepository(accessor, AppCaches.Disabled, Logger);
|
||||
var tagRepo = new TagRepository(accessor, AppCaches.Disabled, Logger);
|
||||
var repository = new MemberRepository(accessor, AppCaches.Disabled, Logger, memberTypeRepository, memberGroupRepository, tagRepo, Mock.Of<ILanguageRepository>());
|
||||
|
||||
@@ -24,7 +24,8 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
{
|
||||
var templateRepository = Mock.Of<ITemplateRepository>();
|
||||
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, templateRepository, AppCaches);
|
||||
return new MemberTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of<ILogger>(), commonRepository);
|
||||
var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches.Disabled, Mock.Of<ILogger>());
|
||||
return new MemberTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of<ILogger>(), commonRepository, languageRepository);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -308,8 +308,8 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
var templateRepository = new TemplateRepository(accessor, AppCaches, Logger, TestObjects.GetFileSystemsMock());
|
||||
var tagRepository = new TagRepository(accessor, AppCaches, Logger);
|
||||
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
|
||||
contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository);
|
||||
var languageRepository = new LanguageRepository(accessor, AppCaches, Logger);
|
||||
contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository, languageRepository);
|
||||
var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
|
||||
return repository;
|
||||
}
|
||||
|
||||
@@ -956,8 +956,8 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
|
||||
var tagRepository = new TagRepository(accessor, AppCaches.Disabled, Logger);
|
||||
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches.Disabled);
|
||||
contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository);
|
||||
var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger);
|
||||
contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository);
|
||||
var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
|
||||
return repository;
|
||||
}
|
||||
@@ -968,7 +968,8 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
|
||||
var tagRepository = new TagRepository(accessor, AppCaches.Disabled, Logger);
|
||||
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches.Disabled);
|
||||
mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository);
|
||||
var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger);
|
||||
mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository);
|
||||
var repository = new MediaRepository(accessor, AppCaches.Disabled, Logger, mediaTypeRepository, tagRepository, Mock.Of<ILanguageRepository>());
|
||||
return repository;
|
||||
}
|
||||
|
||||
@@ -239,8 +239,8 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
|
||||
var tagRepository = new TagRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger);
|
||||
var commonRepository = new ContentTypeCommonRepository(ScopeProvider, templateRepository, AppCaches);
|
||||
var contentTypeRepository = new ContentTypeRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, commonRepository);
|
||||
var languageRepository = new LanguageRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger);
|
||||
var contentTypeRepository = new ContentTypeRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
|
||||
var contentRepo = new DocumentRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
|
||||
|
||||
var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage2", "Textpage");
|
||||
|
||||
@@ -26,7 +26,8 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
var accessor = (IScopeAccessor) provider;
|
||||
var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
|
||||
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
|
||||
mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches, Mock.Of<ILogger>(), commonRepository);
|
||||
var languageRepository = new LanguageRepository(accessor, AppCaches, Logger);
|
||||
mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches, Mock.Of<ILogger>(), commonRepository, languageRepository);
|
||||
var tagRepository = new TagRepository(accessor, AppCaches, Mock.Of<ILogger>());
|
||||
var repository = new MediaRepository(accessor, AppCaches, Mock.Of<ILogger>(), mediaTypeRepository, tagRepository, Mock.Of<ILanguageRepository>());
|
||||
return repository;
|
||||
@@ -44,8 +45,8 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
templateRepository = new TemplateRepository(accessor, AppCaches, Logger, TestObjects.GetFileSystemsMock());
|
||||
var tagRepository = new TagRepository(accessor, AppCaches, Logger);
|
||||
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
|
||||
contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository);
|
||||
var languageRepository = new LanguageRepository(accessor, AppCaches, Logger);
|
||||
contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository, languageRepository);
|
||||
var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
|
||||
return repository;
|
||||
}
|
||||
|
||||
@@ -81,6 +81,19 @@ namespace Umbraco.Tests.Routing
|
||||
Assert.AreEqual(expected, resolvedUrl);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Media_Url_Returns_Absolute_Url_If_Stored_Url_Is_Absolute()
|
||||
{
|
||||
const string expected = "http://localhost/media/rfeiw584/test.jpg";
|
||||
|
||||
var umbracoContext = GetUmbracoContext("http://localhost", mediaUrlProviders: new[] { _mediaUrlProvider });
|
||||
var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.UploadField, expected, null);
|
||||
|
||||
var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, UrlMode.Relative);
|
||||
|
||||
Assert.AreEqual(expected, resolvedUrl);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Media_Url_Returns_Empty_String_When_PropertyType_Is_Not_Supported()
|
||||
{
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace Umbraco.Tests.Routing
|
||||
{
|
||||
base.SetUp();
|
||||
|
||||
WebFinalComponent.CreateRoutes(
|
||||
WebInitialComponent.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 ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository);
|
||||
var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
|
||||
var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches.Disabled, Logger);
|
||||
var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
|
||||
var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository);
|
||||
|
||||
// Act
|
||||
@@ -267,8 +267,8 @@ namespace Umbraco.Tests.Services
|
||||
var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
|
||||
var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
|
||||
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, tRepository, AppCaches);
|
||||
var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository);
|
||||
var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
|
||||
var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches.Disabled, Logger);
|
||||
var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
|
||||
var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository);
|
||||
|
||||
// Act
|
||||
|
||||
@@ -717,21 +717,8 @@ namespace Umbraco.Tests.Services
|
||||
[Test]
|
||||
public void Can_Unpublish_Content_Variation()
|
||||
{
|
||||
// Arrange
|
||||
var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr, out var contentType);
|
||||
|
||||
var langUk = new Language("en-GB") { IsDefault = true };
|
||||
var langFr = new Language("fr-FR");
|
||||
|
||||
ServiceContext.LocalizationService.Save(langFr);
|
||||
ServiceContext.LocalizationService.Save(langUk);
|
||||
|
||||
var contentType = MockedContentTypes.CreateBasicContentType();
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
IContent content = new Content("content", Constants.System.Root, contentType);
|
||||
content.SetCultureName("content-fr", langFr.IsoCode);
|
||||
content.SetCultureName("content-en", langUk.IsoCode);
|
||||
content.PublishCulture(CultureImpact.Explicit(langFr.IsoCode, langFr.IsDefault));
|
||||
content.PublishCulture(CultureImpact.Explicit(langUk.IsoCode, langUk.IsDefault));
|
||||
Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
|
||||
@@ -761,6 +748,185 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Publish_Culture_After_Last_Culture_Unpublished()
|
||||
{
|
||||
var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr, out var contentType);
|
||||
|
||||
var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode });
|
||||
Assert.AreEqual(PublishedState.Published, content.PublishedState);
|
||||
|
||||
//re-get
|
||||
content = ServiceContext.ContentService.GetById(content.Id);
|
||||
|
||||
var unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode); //first culture
|
||||
Assert.IsTrue(unpublished.Success);
|
||||
Assert.AreEqual(PublishResultType.SuccessUnpublishCulture, unpublished.Result);
|
||||
Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
|
||||
Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
|
||||
|
||||
content = ServiceContext.ContentService.GetById(content.Id);
|
||||
|
||||
unpublished = ServiceContext.ContentService.Unpublish(content, langFr.IsoCode); //last culture
|
||||
Assert.IsTrue(unpublished.Success);
|
||||
Assert.AreEqual(PublishResultType.SuccessUnpublishLastCulture, unpublished.Result);
|
||||
Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
|
||||
Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
|
||||
|
||||
content = ServiceContext.ContentService.GetById(content.Id);
|
||||
|
||||
published = ServiceContext.ContentService.SaveAndPublish(content, langUk.IsoCode);
|
||||
Assert.AreEqual(PublishedState.Published, content.PublishedState);
|
||||
Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
|
||||
Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
|
||||
|
||||
content = ServiceContext.ContentService.GetById(content.Id); //reget
|
||||
Assert.AreEqual(PublishedState.Published, content.PublishedState);
|
||||
Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
|
||||
Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
[Test]
|
||||
public void Unpublish_All_Cultures_Has_Unpublished_State()
|
||||
{
|
||||
var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr, out var contentType);
|
||||
|
||||
var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode });
|
||||
Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
|
||||
Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
|
||||
Assert.IsTrue(published.Success);
|
||||
Assert.AreEqual(PublishedState.Published, content.PublishedState);
|
||||
|
||||
//re-get
|
||||
content = ServiceContext.ContentService.GetById(content.Id);
|
||||
Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
|
||||
Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
|
||||
Assert.AreEqual(PublishedState.Published, content.PublishedState);
|
||||
|
||||
var unpublished = ServiceContext.ContentService.Unpublish(content, langFr.IsoCode); //first culture
|
||||
Assert.IsTrue(unpublished.Success);
|
||||
Assert.AreEqual(PublishResultType.SuccessUnpublishCulture, unpublished.Result);
|
||||
Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
|
||||
Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
|
||||
Assert.AreEqual(PublishedState.Published, content.PublishedState); //still published
|
||||
|
||||
//re-get
|
||||
content = ServiceContext.ContentService.GetById(content.Id);
|
||||
Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
|
||||
Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
|
||||
|
||||
unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode); //last culture
|
||||
Assert.IsTrue(unpublished.Success);
|
||||
Assert.AreEqual(PublishResultType.SuccessUnpublishLastCulture, unpublished.Result);
|
||||
Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
|
||||
Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
|
||||
Assert.AreEqual(PublishedState.Unpublished, content.PublishedState); //the last culture was unpublished so the document should also reflect this
|
||||
|
||||
//re-get
|
||||
content = ServiceContext.ContentService.GetById(content.Id);
|
||||
Assert.AreEqual(PublishedState.Unpublished, content.PublishedState); //just double checking
|
||||
Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
|
||||
Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Unpublishing_Mandatory_Language_Unpublishes_Document()
|
||||
{
|
||||
var langUk = new Language("en-GB") { IsDefault = true, IsMandatory = true };
|
||||
var langFr = new Language("fr-FR");
|
||||
|
||||
ServiceContext.LocalizationService.Save(langFr);
|
||||
ServiceContext.LocalizationService.Save(langUk);
|
||||
|
||||
var contentType = MockedContentTypes.CreateBasicContentType();
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
IContent content = new Content("content", Constants.System.Root, contentType);
|
||||
content.SetCultureName("content-fr", langFr.IsoCode);
|
||||
content.SetCultureName("content-en", langUk.IsoCode);
|
||||
|
||||
var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode });
|
||||
Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
|
||||
Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
|
||||
Assert.IsTrue(published.Success);
|
||||
Assert.AreEqual(PublishedState.Published, content.PublishedState);
|
||||
|
||||
//re-get
|
||||
content = ServiceContext.ContentService.GetById(content.Id);
|
||||
|
||||
var unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode); //unpublish mandatory lang
|
||||
Assert.IsTrue(unpublished.Success);
|
||||
Assert.AreEqual(PublishResultType.SuccessUnpublishMandatoryCulture, unpublished.Result);
|
||||
Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
|
||||
Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode)); //remains published
|
||||
Assert.AreEqual(PublishedState.Unpublished, content.PublishedState);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Unpublishing_Already_Unpublished_Culture()
|
||||
{
|
||||
var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr, out var contentType);
|
||||
|
||||
var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode });
|
||||
Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
|
||||
Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
|
||||
Assert.IsTrue(published.Success);
|
||||
Assert.AreEqual(PublishedState.Published, content.PublishedState);
|
||||
|
||||
//re-get
|
||||
content = ServiceContext.ContentService.GetById(content.Id);
|
||||
|
||||
var unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode);
|
||||
Assert.IsTrue(unpublished.Success);
|
||||
Assert.AreEqual(PublishResultType.SuccessUnpublishCulture, unpublished.Result);
|
||||
Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
|
||||
|
||||
content = ServiceContext.ContentService.GetById(content.Id);
|
||||
|
||||
//Change some data since Unpublish should always Save
|
||||
content.SetCultureName("content-en-updated", langUk.IsoCode);
|
||||
|
||||
unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode); //unpublish again
|
||||
Assert.IsTrue(unpublished.Success);
|
||||
Assert.AreEqual(PublishResultType.SuccessUnpublishAlready, unpublished.Result);
|
||||
Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
|
||||
|
||||
content = ServiceContext.ContentService.GetById(content.Id);
|
||||
//ensure that even though the culture was already unpublished that the data was still persisted
|
||||
Assert.AreEqual("content-en-updated", content.GetCultureName(langUk.IsoCode));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Publishing_No_Cultures_Still_Saves()
|
||||
{
|
||||
var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr, out var contentType);
|
||||
|
||||
var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode });
|
||||
Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
|
||||
Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
|
||||
Assert.IsTrue(published.Success);
|
||||
Assert.AreEqual(PublishedState.Published, content.PublishedState);
|
||||
|
||||
//re-get
|
||||
content = ServiceContext.ContentService.GetById(content.Id);
|
||||
|
||||
//Change some data since SaveAndPublish should always Save
|
||||
content.SetCultureName("content-en-updated", langUk.IsoCode);
|
||||
|
||||
var saved = ServiceContext.ContentService.SaveAndPublish(content, new string [] { }); //save without cultures
|
||||
Assert.AreEqual(PublishResultType.FailedPublishNothingToPublish, saved.Result);
|
||||
|
||||
//re-get
|
||||
content = ServiceContext.ContentService.GetById(content.Id);
|
||||
//ensure that even though nothing was published that the data was still persisted
|
||||
Assert.AreEqual("content-en-updated", content.GetCultureName(langUk.IsoCode));
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void Pending_Invariant_Property_Changes_Affect_Default_Language_Edited_State()
|
||||
{
|
||||
@@ -811,17 +977,7 @@ namespace Umbraco.Tests.Services
|
||||
[Test]
|
||||
public void Can_Publish_Content_Variation_And_Detect_Changed_Cultures()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
var langGB = new Language("en-GB") { IsDefault = true };
|
||||
var langFr = new Language("fr-FR");
|
||||
|
||||
ServiceContext.LocalizationService.Save(langFr);
|
||||
ServiceContext.LocalizationService.Save(langGB);
|
||||
|
||||
var contentType = MockedContentTypes.CreateBasicContentType();
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
CreateEnglishAndFrenchDocumentType(out var langUk, out var langFr, out var contentType);
|
||||
|
||||
IContent content = new Content("content", Constants.System.Root, contentType);
|
||||
content.SetCultureName("content-fr", langFr.IsoCode);
|
||||
@@ -832,8 +988,8 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
//re-get
|
||||
content = ServiceContext.ContentService.GetById(content.Id);
|
||||
content.SetCultureName("content-en", langGB.IsoCode);
|
||||
published = ServiceContext.ContentService.SaveAndPublish(content, langGB.IsoCode);
|
||||
content.SetCultureName("content-en", langUk.IsoCode);
|
||||
published = ServiceContext.ContentService.SaveAndPublish(content, langUk.IsoCode);
|
||||
//audit log will only show that english was published
|
||||
lastLog = ServiceContext.AuditService.GetLogs(content.Id).Last();
|
||||
Assert.AreEqual($"Published languages: English (United Kingdom)", lastLog.Comment);
|
||||
@@ -3008,10 +3164,33 @@ namespace Umbraco.Tests.Services
|
||||
var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
|
||||
var tagRepository = new TagRepository(accessor, AppCaches.Disabled, Logger);
|
||||
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
|
||||
contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository);
|
||||
var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger);
|
||||
contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository);
|
||||
var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
|
||||
return repository;
|
||||
}
|
||||
|
||||
private void CreateEnglishAndFrenchDocumentType(out Language langUk, out Language langFr, out ContentType contentType)
|
||||
{
|
||||
langUk = new Language("en-GB") { IsDefault = true };
|
||||
langFr = new Language("fr-FR");
|
||||
ServiceContext.LocalizationService.Save(langFr);
|
||||
ServiceContext.LocalizationService.Save(langUk);
|
||||
|
||||
contentType = MockedContentTypes.CreateBasicContentType();
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
}
|
||||
|
||||
private IContent CreateEnglishAndFrenchDocument(out Language langUk, out Language langFr, out ContentType contentType)
|
||||
{
|
||||
CreateEnglishAndFrenchDocumentType(out langUk, out langFr, out contentType);
|
||||
|
||||
IContent content = new Content("content", Constants.System.Root, contentType);
|
||||
content.SetCultureName("content-fr", langFr.IsoCode);
|
||||
content.SetCultureName("content-en", langUk.IsoCode);
|
||||
|
||||
return content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,360 +53,7 @@ namespace Umbraco.Tests.Services
|
||||
Assert.IsTrue(contentType.IsElement);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Change_Content_Type_Variation_Clears_Redirects()
|
||||
{
|
||||
//create content type with a property type that varies by culture
|
||||
var contentType = MockedContentTypes.CreateBasicContentType();
|
||||
contentType.Variations = ContentVariation.Nothing;
|
||||
var contentCollection = new PropertyTypeCollection(true);
|
||||
contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
|
||||
{
|
||||
Alias = "title",
|
||||
Name = "Title",
|
||||
Description = "",
|
||||
Mandatory = false,
|
||||
SortOrder = 1,
|
||||
DataTypeId = -88,
|
||||
Variations = ContentVariation.Nothing
|
||||
});
|
||||
contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
var contentType2 = MockedContentTypes.CreateBasicContentType("test");
|
||||
ServiceContext.ContentTypeService.Save(contentType2);
|
||||
|
||||
//create some content of this content type
|
||||
IContent doc = MockedContent.CreateBasicContent(contentType);
|
||||
doc.Name = "Hello1";
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
|
||||
IContent doc2 = MockedContent.CreateBasicContent(contentType2);
|
||||
ServiceContext.ContentService.Save(doc2);
|
||||
|
||||
ServiceContext.RedirectUrlService.Register("hello/world", doc.Key);
|
||||
ServiceContext.RedirectUrlService.Register("hello2/world2", doc2.Key);
|
||||
|
||||
Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count());
|
||||
Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count());
|
||||
|
||||
//change variation
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
Assert.AreEqual(0, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count());
|
||||
Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count());
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Change_Content_Type_From_Invariant_Variant()
|
||||
{
|
||||
//create content type with a property type that varies by culture
|
||||
var contentType = MockedContentTypes.CreateBasicContentType();
|
||||
contentType.Variations = ContentVariation.Nothing;
|
||||
var contentCollection = new PropertyTypeCollection(true);
|
||||
contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
|
||||
{
|
||||
Alias = "title",
|
||||
Name = "Title",
|
||||
Description = "",
|
||||
Mandatory = false,
|
||||
SortOrder = 1,
|
||||
DataTypeId = -88,
|
||||
Variations = ContentVariation.Nothing
|
||||
});
|
||||
contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
//create some content of this content type
|
||||
IContent doc = MockedContent.CreateBasicContent(contentType);
|
||||
doc.Name = "Hello1";
|
||||
doc.SetValue("title", "hello world");
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
|
||||
Assert.AreEqual("Hello1", doc.Name);
|
||||
Assert.AreEqual("hello world", doc.GetValue("title"));
|
||||
|
||||
//change the content type to be variant, we will also update the name here to detect the copy changes
|
||||
doc.Name = "Hello2";
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
|
||||
Assert.AreEqual("Hello2", doc.GetCultureName("en-US"));
|
||||
Assert.AreEqual("hello world", doc.GetValue("title")); //We are not checking against en-US here because properties will remain invariant
|
||||
|
||||
//change back property type to be invariant, we will also update the name here to detect the copy changes
|
||||
doc.SetCultureName("Hello3", "en-US");
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
contentType.Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
|
||||
Assert.AreEqual("Hello3", doc.Name);
|
||||
Assert.AreEqual("hello world", doc.GetValue("title"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Change_Content_Type_From_Variant_Invariant()
|
||||
{
|
||||
//create content type with a property type that varies by culture
|
||||
var contentType = MockedContentTypes.CreateBasicContentType();
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
var contentCollection = new PropertyTypeCollection(true);
|
||||
contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
|
||||
{
|
||||
Alias = "title",
|
||||
Name = "Title",
|
||||
Description = "",
|
||||
Mandatory = false,
|
||||
SortOrder = 1,
|
||||
DataTypeId = -88,
|
||||
Variations = ContentVariation.Culture
|
||||
});
|
||||
contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
//create some content of this content type
|
||||
IContent doc = MockedContent.CreateBasicContent(contentType);
|
||||
doc.SetCultureName("Hello1", "en-US");
|
||||
doc.SetValue("title", "hello world", "en-US");
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
|
||||
Assert.AreEqual("Hello1", doc.GetCultureName("en-US"));
|
||||
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
|
||||
|
||||
//change the content type to be invariant, we will also update the name here to detect the copy changes
|
||||
doc.SetCultureName("Hello2", "en-US");
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
contentType.Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
|
||||
Assert.AreEqual("Hello2", doc.Name);
|
||||
Assert.AreEqual("hello world", doc.GetValue("title"));
|
||||
|
||||
//change back property type to be variant, we will also update the name here to detect the copy changes
|
||||
doc.Name = "Hello3";
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
|
||||
//at this stage all property types were switched to invariant so even though the variant value
|
||||
//exists it will not be returned because the property type is invariant,
|
||||
//so this check proves that null will be returned
|
||||
Assert.IsNull(doc.GetValue("title", "en-US"));
|
||||
|
||||
//we can now switch the property type to be variant and the value can be returned again
|
||||
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
|
||||
Assert.AreEqual("Hello3", doc.GetCultureName("en-US"));
|
||||
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Change_Property_Type_From_Invariant_Variant()
|
||||
{
|
||||
//create content type with a property type that varies by culture
|
||||
var contentType = MockedContentTypes.CreateBasicContentType();
|
||||
contentType.Variations = ContentVariation.Nothing;
|
||||
var contentCollection = new PropertyTypeCollection(true);
|
||||
contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
|
||||
{
|
||||
Alias = "title",
|
||||
Name = "Title",
|
||||
Description = "",
|
||||
Mandatory = false,
|
||||
SortOrder = 1,
|
||||
DataTypeId = -88,
|
||||
Variations = ContentVariation.Nothing
|
||||
});
|
||||
contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
//create some content of this content type
|
||||
IContent doc = MockedContent.CreateBasicContent(contentType);
|
||||
doc.Name = "Home";
|
||||
doc.SetValue("title", "hello world");
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
|
||||
Assert.AreEqual("hello world", doc.GetValue("title"));
|
||||
|
||||
//change the property type to be variant
|
||||
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
|
||||
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
|
||||
|
||||
//change back property type to be invariant
|
||||
contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
|
||||
Assert.AreEqual("hello world", doc.GetValue("title"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Change_Property_Type_From_Variant_Invariant()
|
||||
{
|
||||
//create content type with a property type that varies by culture
|
||||
var contentType = MockedContentTypes.CreateBasicContentType();
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
var contentCollection = new PropertyTypeCollection(true);
|
||||
contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
|
||||
{
|
||||
Alias = "title",
|
||||
Name = "Title",
|
||||
Description = "",
|
||||
Mandatory = false,
|
||||
SortOrder = 1,
|
||||
DataTypeId = -88,
|
||||
Variations = ContentVariation.Culture
|
||||
});
|
||||
contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
//create some content of this content type
|
||||
IContent doc = MockedContent.CreateBasicContent(contentType);
|
||||
doc.SetCultureName("Home", "en-US");
|
||||
doc.SetValue("title", "hello world", "en-US");
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
|
||||
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
|
||||
|
||||
//change the property type to be invariant
|
||||
contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
|
||||
Assert.AreEqual("hello world", doc.GetValue("title"));
|
||||
|
||||
//change back property type to be variant
|
||||
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
|
||||
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Change_Property_Type_From_Variant_Invariant_On_A_Composition()
|
||||
{
|
||||
//create content type with a property type that varies by culture
|
||||
var contentType = MockedContentTypes.CreateBasicContentType();
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
var contentCollection = new PropertyTypeCollection(true);
|
||||
contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
|
||||
{
|
||||
Alias = "title",
|
||||
Name = "Title",
|
||||
Description = "",
|
||||
Mandatory = false,
|
||||
SortOrder = 1,
|
||||
DataTypeId = -88,
|
||||
Variations = ContentVariation.Culture
|
||||
});
|
||||
contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
//compose this from the other one
|
||||
var contentType2 = MockedContentTypes.CreateBasicContentType("test");
|
||||
contentType2.Variations = ContentVariation.Culture;
|
||||
contentType2.AddContentType(contentType);
|
||||
ServiceContext.ContentTypeService.Save(contentType2);
|
||||
|
||||
//create some content of this content type
|
||||
IContent doc = MockedContent.CreateBasicContent(contentType);
|
||||
doc.SetCultureName("Home", "en-US");
|
||||
doc.SetValue("title", "hello world", "en-US");
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
|
||||
IContent doc2 = MockedContent.CreateBasicContent(contentType2);
|
||||
doc2.SetCultureName("Home", "en-US");
|
||||
doc2.SetValue("title", "hello world", "en-US");
|
||||
ServiceContext.ContentService.Save(doc2);
|
||||
|
||||
//change the property type to be invariant
|
||||
contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
|
||||
|
||||
Assert.AreEqual("hello world", doc.GetValue("title"));
|
||||
Assert.AreEqual("hello world", doc2.GetValue("title"));
|
||||
|
||||
//change back property type to be variant
|
||||
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
|
||||
|
||||
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
|
||||
Assert.AreEqual("hello world", doc2.GetValue("title", "en-US"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Change_Content_Type_From_Variant_Invariant_On_A_Composition()
|
||||
{
|
||||
//create content type with a property type that varies by culture
|
||||
var contentType = MockedContentTypes.CreateBasicContentType();
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
var contentCollection = new PropertyTypeCollection(true);
|
||||
contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
|
||||
{
|
||||
Alias = "title",
|
||||
Name = "Title",
|
||||
Description = "",
|
||||
Mandatory = false,
|
||||
SortOrder = 1,
|
||||
DataTypeId = -88,
|
||||
Variations = ContentVariation.Culture
|
||||
});
|
||||
contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
//compose this from the other one
|
||||
var contentType2 = MockedContentTypes.CreateBasicContentType("test");
|
||||
contentType2.Variations = ContentVariation.Culture;
|
||||
contentType2.AddContentType(contentType);
|
||||
ServiceContext.ContentTypeService.Save(contentType2);
|
||||
|
||||
//create some content of this content type
|
||||
IContent doc = MockedContent.CreateBasicContent(contentType);
|
||||
doc.SetCultureName("Home", "en-US");
|
||||
doc.SetValue("title", "hello world", "en-US");
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
|
||||
IContent doc2 = MockedContent.CreateBasicContent(contentType2);
|
||||
doc2.SetCultureName("Home", "en-US");
|
||||
doc2.SetValue("title", "hello world", "en-US");
|
||||
ServiceContext.ContentService.Save(doc2);
|
||||
|
||||
//change the content type to be invariant
|
||||
contentType.Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
|
||||
|
||||
Assert.AreEqual("hello world", doc.GetValue("title"));
|
||||
Assert.AreEqual("hello world", doc2.GetValue("title"));
|
||||
|
||||
//change back content type to be variant
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
|
||||
|
||||
//this will be null because the doc type was changed back to variant but it's property types don't get changed back
|
||||
Assert.IsNull(doc.GetValue("title", "en-US"));
|
||||
Assert.IsNull(doc2.GetValue("title", "en-US"));
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void Deleting_Content_Type_With_Hierarchy_Of_Content_Items_Moves_Orphaned_Content_To_Recycle_Bin()
|
||||
|
||||
@@ -17,6 +17,7 @@ using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Core.Sync;
|
||||
using Umbraco.Tests.TestHelpers.Entities;
|
||||
using Umbraco.Tests.Testing;
|
||||
using Umbraco.Web.PublishedCache;
|
||||
using Umbraco.Web.PublishedCache.NuCache;
|
||||
@@ -106,44 +107,346 @@ namespace Umbraco.Tests.Services
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Change_Content_Type_Variation_Clears_Redirects()
|
||||
{
|
||||
var contentType = MockedContentTypes.CreateBasicContentType();
|
||||
contentType.Variations = ContentVariation.Nothing;
|
||||
var properties = CreatePropertyCollection(("title", ContentVariation.Nothing));
|
||||
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
var contentType2 = MockedContentTypes.CreateBasicContentType("test");
|
||||
ServiceContext.ContentTypeService.Save(contentType2);
|
||||
|
||||
//create some content of this content type
|
||||
IContent doc = MockedContent.CreateBasicContent(contentType);
|
||||
doc.Name = "Hello1";
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
|
||||
IContent doc2 = MockedContent.CreateBasicContent(contentType2);
|
||||
ServiceContext.ContentService.Save(doc2);
|
||||
|
||||
ServiceContext.RedirectUrlService.Register("hello/world", doc.Key);
|
||||
ServiceContext.RedirectUrlService.Register("hello2/world2", doc2.Key);
|
||||
|
||||
Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count());
|
||||
Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count());
|
||||
|
||||
//change variation
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
Assert.AreEqual(0, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count());
|
||||
Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count());
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Change_Content_Type_From_Invariant_Variant()
|
||||
{
|
||||
var contentType = MockedContentTypes.CreateBasicContentType();
|
||||
contentType.Variations = ContentVariation.Nothing;
|
||||
var properties = CreatePropertyCollection(("title", ContentVariation.Nothing));
|
||||
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
//create some content of this content type
|
||||
IContent doc = MockedContent.CreateBasicContent(contentType);
|
||||
doc.Name = "Hello1";
|
||||
doc.SetValue("title", "hello world");
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
|
||||
Assert.AreEqual("Hello1", doc.Name);
|
||||
Assert.AreEqual("hello world", doc.GetValue("title"));
|
||||
Assert.IsTrue(doc.Edited);
|
||||
Assert.IsFalse (doc.IsCultureEdited("en-US"));
|
||||
|
||||
//change the content type to be variant, we will also update the name here to detect the copy changes
|
||||
doc.Name = "Hello2";
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
|
||||
Assert.AreEqual("Hello2", doc.GetCultureName("en-US"));
|
||||
Assert.AreEqual("hello world", doc.GetValue("title")); //We are not checking against en-US here because properties will remain invariant
|
||||
Assert.IsTrue(doc.Edited);
|
||||
Assert.IsTrue(doc.IsCultureEdited("en-US"));
|
||||
|
||||
//change back property type to be invariant, we will also update the name here to detect the copy changes
|
||||
doc.SetCultureName("Hello3", "en-US");
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
contentType.Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
|
||||
Assert.AreEqual("Hello3", doc.Name);
|
||||
Assert.AreEqual("hello world", doc.GetValue("title"));
|
||||
Assert.IsTrue(doc.Edited);
|
||||
Assert.IsFalse(doc.IsCultureEdited("en-US"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Change_Content_Type_From_Variant_Invariant()
|
||||
{
|
||||
var contentType = MockedContentTypes.CreateBasicContentType();
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
var properties = CreatePropertyCollection(("title", ContentVariation.Culture));
|
||||
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
//create some content of this content type
|
||||
IContent doc = MockedContent.CreateBasicContent(contentType);
|
||||
doc.SetCultureName("Hello1", "en-US");
|
||||
doc.SetValue("title", "hello world", "en-US");
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
Assert.AreEqual("Hello1", doc.GetCultureName("en-US"));
|
||||
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
|
||||
Assert.IsTrue(doc.Edited);
|
||||
Assert.IsTrue(doc.IsCultureEdited("en-US"));
|
||||
|
||||
//change the content type to be invariant, we will also update the name here to detect the copy changes
|
||||
doc.SetCultureName("Hello2", "en-US");
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
contentType.Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
|
||||
Assert.AreEqual("Hello2", doc.Name);
|
||||
Assert.AreEqual("hello world", doc.GetValue("title"));
|
||||
Assert.IsTrue(doc.Edited);
|
||||
Assert.IsFalse(doc.IsCultureEdited("en-US"));
|
||||
|
||||
//change back property type to be variant, we will also update the name here to detect the copy changes
|
||||
doc.Name = "Hello3";
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
|
||||
//at this stage all property types were switched to invariant so even though the variant value
|
||||
//exists it will not be returned because the property type is invariant,
|
||||
//so this check proves that null will be returned
|
||||
Assert.IsNull(doc.GetValue("title", "en-US"));
|
||||
Assert.IsTrue(doc.Edited);
|
||||
Assert.IsTrue(doc.IsCultureEdited("en-US")); // this is true because the name change is copied to the default language
|
||||
|
||||
//we can now switch the property type to be variant and the value can be returned again
|
||||
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
|
||||
Assert.AreEqual("Hello3", doc.GetCultureName("en-US"));
|
||||
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
|
||||
Assert.IsTrue(doc.Edited);
|
||||
Assert.IsTrue(doc.IsCultureEdited("en-US"));
|
||||
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void Change_Property_Type_From_To_Variant_On_Invariant_Content_Type()
|
||||
{
|
||||
var contentType = MockedContentTypes.CreateBasicContentType();
|
||||
contentType.Variations = ContentVariation.Nothing;
|
||||
var properties = CreatePropertyCollection(("title", ContentVariation.Nothing));
|
||||
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
//change the property type to be variant
|
||||
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
|
||||
|
||||
//Cannot change a property type to be variant if the content type itself is not variant
|
||||
Assert.Throws<InvalidOperationException>(() => ServiceContext.ContentTypeService.Save(contentType));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Change_Property_Type_From_Invariant_Variant()
|
||||
{
|
||||
var contentType = MockedContentTypes.CreateBasicContentType();
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
var properties = CreatePropertyCollection(("title", ContentVariation.Nothing));
|
||||
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
//create some content of this content type
|
||||
IContent doc = MockedContent.CreateBasicContent(contentType);
|
||||
doc.SetCultureName("Home", "en-US");
|
||||
doc.SetValue("title", "hello world");
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
Assert.AreEqual("hello world", doc.GetValue("title"));
|
||||
Assert.IsTrue(doc.IsCultureEdited("en-US")); //invariant prop changes show up on default lang
|
||||
Assert.IsTrue(doc.Edited);
|
||||
|
||||
//change the property type to be variant
|
||||
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
|
||||
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
|
||||
Assert.IsTrue(doc.IsCultureEdited("en-US"));
|
||||
Assert.IsTrue(doc.Edited);
|
||||
|
||||
//change back property type to be invariant
|
||||
contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
|
||||
Assert.AreEqual("hello world", doc.GetValue("title"));
|
||||
Assert.IsTrue(doc.IsCultureEdited("en-US")); //invariant prop changes show up on default lang
|
||||
Assert.IsTrue(doc.Edited);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Change_Property_Type_From_Variant_Invariant()
|
||||
{
|
||||
//create content type with a property type that varies by culture
|
||||
var contentType = MockedContentTypes.CreateBasicContentType();
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
var properties = CreatePropertyCollection(("title", ContentVariation.Culture));
|
||||
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
//create some content of this content type
|
||||
IContent doc = MockedContent.CreateBasicContent(contentType);
|
||||
doc.SetCultureName("Home", "en-US");
|
||||
doc.SetValue("title", "hello world", "en-US");
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
|
||||
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
|
||||
|
||||
//change the property type to be invariant
|
||||
contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
|
||||
Assert.AreEqual("hello world", doc.GetValue("title"));
|
||||
|
||||
//change back property type to be variant
|
||||
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
|
||||
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Change_Property_Type_From_Variant_Invariant_On_A_Composition()
|
||||
{
|
||||
//create content type with a property type that varies by culture
|
||||
var contentType = MockedContentTypes.CreateBasicContentType();
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
var properties = CreatePropertyCollection(("title", ContentVariation.Culture));
|
||||
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
//compose this from the other one
|
||||
var contentType2 = MockedContentTypes.CreateBasicContentType("test");
|
||||
contentType2.Variations = ContentVariation.Culture;
|
||||
contentType2.AddContentType(contentType);
|
||||
ServiceContext.ContentTypeService.Save(contentType2);
|
||||
|
||||
//create some content of this content type
|
||||
IContent doc = MockedContent.CreateBasicContent(contentType);
|
||||
doc.SetCultureName("Home", "en-US");
|
||||
doc.SetValue("title", "hello world", "en-US");
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
|
||||
IContent doc2 = MockedContent.CreateBasicContent(contentType2);
|
||||
doc2.SetCultureName("Home", "en-US");
|
||||
doc2.SetValue("title", "hello world", "en-US");
|
||||
ServiceContext.ContentService.Save(doc2);
|
||||
|
||||
//change the property type to be invariant
|
||||
contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
|
||||
|
||||
Assert.AreEqual("hello world", doc.GetValue("title"));
|
||||
Assert.AreEqual("hello world", doc2.GetValue("title"));
|
||||
|
||||
//change back property type to be variant
|
||||
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
|
||||
|
||||
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
|
||||
Assert.AreEqual("hello world", doc2.GetValue("title", "en-US"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Change_Content_Type_From_Variant_Invariant_On_A_Composition()
|
||||
{
|
||||
//create content type with a property type that varies by culture
|
||||
var contentType = MockedContentTypes.CreateBasicContentType();
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
var properties = CreatePropertyCollection(("title", ContentVariation.Culture));
|
||||
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
//compose this from the other one
|
||||
var contentType2 = MockedContentTypes.CreateBasicContentType("test");
|
||||
contentType2.Variations = ContentVariation.Culture;
|
||||
contentType2.AddContentType(contentType);
|
||||
ServiceContext.ContentTypeService.Save(contentType2);
|
||||
|
||||
//create some content of this content type
|
||||
IContent doc = MockedContent.CreateBasicContent(contentType);
|
||||
doc.SetCultureName("Home", "en-US");
|
||||
doc.SetValue("title", "hello world", "en-US");
|
||||
ServiceContext.ContentService.Save(doc);
|
||||
|
||||
IContent doc2 = MockedContent.CreateBasicContent(contentType2);
|
||||
doc2.SetCultureName("Home", "en-US");
|
||||
doc2.SetValue("title", "hello world", "en-US");
|
||||
ServiceContext.ContentService.Save(doc2);
|
||||
|
||||
//change the content type to be invariant
|
||||
contentType.Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
|
||||
|
||||
Assert.AreEqual("hello world", doc.GetValue("title"));
|
||||
Assert.AreEqual("hello world", doc2.GetValue("title"));
|
||||
|
||||
//change back content type to be variant
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
|
||||
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
|
||||
|
||||
//this will be null because the doc type was changed back to variant but it's property types don't get changed back
|
||||
Assert.IsNull(doc.GetValue("title", "en-US"));
|
||||
Assert.IsNull(doc2.GetValue("title", "en-US"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Change_Variations_SimpleContentType_VariantToInvariantAndBack()
|
||||
{
|
||||
// one simple content type, variant, with both variant and invariant properties
|
||||
// can change it to invariant and back
|
||||
|
||||
var languageEn = new Language("en") { IsDefault = true };
|
||||
ServiceContext.LocalizationService.Save(languageEn);
|
||||
var languageFr = new Language("fr");
|
||||
ServiceContext.LocalizationService.Save(languageFr);
|
||||
CreateFrenchAndEnglishLangs();
|
||||
|
||||
var contentType = new ContentType(-1)
|
||||
{
|
||||
Alias = "contentType",
|
||||
Name = "contentType",
|
||||
Variations = ContentVariation.Culture
|
||||
};
|
||||
var contentType = CreateContentType(ContentVariation.Culture);
|
||||
|
||||
var properties = new PropertyTypeCollection(true)
|
||||
{
|
||||
new PropertyType("value1", ValueStorageType.Ntext)
|
||||
{
|
||||
Alias = "value1",
|
||||
DataTypeId = -88,
|
||||
Variations = ContentVariation.Culture
|
||||
},
|
||||
new PropertyType("value2", ValueStorageType.Ntext)
|
||||
{
|
||||
Alias = "value2",
|
||||
DataTypeId = -88,
|
||||
Variations = ContentVariation.Nothing
|
||||
}
|
||||
};
|
||||
var properties = CreatePropertyCollection(
|
||||
("value1", ContentVariation.Culture),
|
||||
("value2", ContentVariation.Nothing));
|
||||
|
||||
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
var document = (IContent) new Content("document", -1, contentType);
|
||||
var document = (IContent)new Content("document", -1, contentType);
|
||||
document.SetCultureName("doc1en", "en");
|
||||
document.SetCultureName("doc1fr", "fr");
|
||||
document.SetValue("value1", "v1en", "en");
|
||||
@@ -226,28 +529,11 @@ namespace Umbraco.Tests.Services
|
||||
var languageFr = new Language("fr");
|
||||
ServiceContext.LocalizationService.Save(languageFr);
|
||||
|
||||
var contentType = new ContentType(-1)
|
||||
{
|
||||
Alias = "contentType",
|
||||
Name = "contentType",
|
||||
Variations = ContentVariation.Nothing
|
||||
};
|
||||
var contentType = CreateContentType(ContentVariation.Nothing);
|
||||
|
||||
var properties = new PropertyTypeCollection(true)
|
||||
{
|
||||
new PropertyType("value1", ValueStorageType.Ntext)
|
||||
{
|
||||
Alias = "value1",
|
||||
DataTypeId = -88,
|
||||
Variations = ContentVariation.Nothing
|
||||
},
|
||||
new PropertyType("value2", ValueStorageType.Ntext)
|
||||
{
|
||||
Alias = "value2",
|
||||
DataTypeId = -88,
|
||||
Variations = ContentVariation.Nothing
|
||||
}
|
||||
};
|
||||
var properties = CreatePropertyCollection(
|
||||
("value1", ContentVariation.Nothing),
|
||||
("value2", ContentVariation.Nothing));
|
||||
|
||||
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
@@ -326,33 +612,13 @@ namespace Umbraco.Tests.Services
|
||||
// one simple content type, variant, with both variant and invariant properties
|
||||
// can change an invariant property to variant and back
|
||||
|
||||
var languageEn = new Language("en") { IsDefault = true };
|
||||
ServiceContext.LocalizationService.Save(languageEn);
|
||||
var languageFr = new Language("fr");
|
||||
ServiceContext.LocalizationService.Save(languageFr);
|
||||
CreateFrenchAndEnglishLangs();
|
||||
|
||||
var contentType = new ContentType(-1)
|
||||
{
|
||||
Alias = "contentType",
|
||||
Name = "contentType",
|
||||
Variations = ContentVariation.Culture
|
||||
};
|
||||
var contentType = CreateContentType(ContentVariation.Culture);
|
||||
|
||||
var properties = new PropertyTypeCollection(true)
|
||||
{
|
||||
new PropertyType("value1", ValueStorageType.Ntext)
|
||||
{
|
||||
Alias = "value1",
|
||||
DataTypeId = -88,
|
||||
Variations = ContentVariation.Culture
|
||||
},
|
||||
new PropertyType("value2", ValueStorageType.Ntext)
|
||||
{
|
||||
Alias = "value2",
|
||||
DataTypeId = -88,
|
||||
Variations = ContentVariation.Nothing
|
||||
}
|
||||
};
|
||||
var properties = CreatePropertyCollection(
|
||||
("value1", ContentVariation.Culture),
|
||||
("value2", ContentVariation.Nothing));
|
||||
|
||||
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
@@ -429,6 +695,185 @@ namespace Umbraco.Tests.Services
|
||||
"{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'en','seg':'','val':'v2'}]},'cultureData':");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Change_Property_Variations_From_Variant_To_Invariant_And_Ensure_Edited_Values_Are_Renormalized()
|
||||
{
|
||||
// one simple content type, variant, with both variant and invariant properties
|
||||
// can change an invariant property to variant and back
|
||||
|
||||
CreateFrenchAndEnglishLangs();
|
||||
|
||||
var contentType = CreateContentType(ContentVariation.Culture);
|
||||
|
||||
var properties = CreatePropertyCollection(("value1", ContentVariation.Culture));
|
||||
|
||||
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
var document = (IContent)new Content("document", -1, contentType);
|
||||
document.SetCultureName("doc1en", "en");
|
||||
document.SetCultureName("doc1fr", "fr");
|
||||
document.SetValue("value1", "v1en-init", "en");
|
||||
document.SetValue("value1", "v1fr-init", "fr");
|
||||
ServiceContext.ContentService.SaveAndPublish(document); //all values are published which means the document is not 'edited'
|
||||
|
||||
document = ServiceContext.ContentService.GetById(document.Id);
|
||||
Assert.IsFalse(document.IsCultureEdited("en"));
|
||||
Assert.IsFalse(document.IsCultureEdited("fr"));
|
||||
Assert.IsFalse(document.Edited);
|
||||
|
||||
document.SetValue("value1", "v1en", "en"); //change the property culture value, so now this culture will be edited
|
||||
document.SetValue("value1", "v1fr", "fr"); //change the property culture value, so now this culture will be edited
|
||||
ServiceContext.ContentService.Save(document);
|
||||
|
||||
document = ServiceContext.ContentService.GetById(document.Id);
|
||||
Assert.AreEqual("doc1en", document.Name);
|
||||
Assert.AreEqual("doc1en", document.GetCultureName("en"));
|
||||
Assert.AreEqual("doc1fr", document.GetCultureName("fr"));
|
||||
Assert.AreEqual("v1en", document.GetValue("value1", "en"));
|
||||
Assert.AreEqual("v1en-init", document.GetValue("value1", "en", published: true));
|
||||
Assert.AreEqual("v1fr", document.GetValue("value1", "fr"));
|
||||
Assert.AreEqual("v1fr-init", document.GetValue("value1", "fr", published: true));
|
||||
Assert.IsTrue(document.IsCultureEdited("en")); //This will be true because the edited value isn't the same as the published value
|
||||
Assert.IsTrue(document.IsCultureEdited("fr")); //This will be true because the edited value isn't the same as the published value
|
||||
Assert.IsTrue(document.Edited);
|
||||
|
||||
// switch property type to Invariant
|
||||
contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(contentType); //This is going to have to re-normalize the "Edited" flag
|
||||
|
||||
document = ServiceContext.ContentService.GetById(document.Id);
|
||||
Assert.IsTrue(document.IsCultureEdited("en")); //This will remain true because there is now a pending change for the invariant property data which is flagged under the default lang
|
||||
Assert.IsFalse(document.IsCultureEdited("fr")); //This will be false because nothing has changed for this culture and the property no longer reflects variant changes
|
||||
Assert.IsTrue(document.Edited);
|
||||
|
||||
//update the invariant value and publish
|
||||
document.SetValue("value1", "v1inv");
|
||||
ServiceContext.ContentService.SaveAndPublish(document);
|
||||
|
||||
document = ServiceContext.ContentService.GetById(document.Id);
|
||||
Assert.AreEqual("doc1en", document.Name);
|
||||
Assert.AreEqual("doc1en", document.GetCultureName("en"));
|
||||
Assert.AreEqual("doc1fr", document.GetCultureName("fr"));
|
||||
Assert.IsNull(document.GetValue("value1", "en")); //The values are there but the business logic returns null
|
||||
Assert.IsNull(document.GetValue("value1", "fr")); //The values are there but the business logic returns null
|
||||
Assert.IsNull(document.GetValue("value1", "en", published: true)); //The values are there but the business logic returns null
|
||||
Assert.IsNull(document.GetValue("value1", "fr", published: true)); //The values are there but the business logic returns null
|
||||
Assert.AreEqual("v1inv", document.GetValue("value1"));
|
||||
Assert.AreEqual("v1inv", document.GetValue("value1", published: true));
|
||||
Assert.IsFalse(document.IsCultureEdited("en")); //This returns false, everything is published
|
||||
Assert.IsFalse(document.IsCultureEdited("fr")); //This will be false because nothing has changed for this culture and the property no longer reflects variant changes
|
||||
Assert.IsFalse(document.Edited);
|
||||
|
||||
// switch property back to Culture
|
||||
contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
document = ServiceContext.ContentService.GetById(document.Id);
|
||||
Assert.AreEqual("v1inv", document.GetValue("value1", "en")); //The invariant property value gets copied over to the default language
|
||||
Assert.AreEqual("v1inv", document.GetValue("value1", "en", published: true));
|
||||
Assert.AreEqual("v1fr", document.GetValue("value1", "fr")); //values are still retained
|
||||
Assert.AreEqual("v1fr-init", document.GetValue("value1", "fr", published: true)); //values are still retained
|
||||
Assert.IsFalse(document.IsCultureEdited("en")); //The invariant published AND edited values are copied over to the default language
|
||||
Assert.IsTrue(document.IsCultureEdited("fr")); //The previously existing french values are there and there is no published value
|
||||
Assert.IsTrue(document.Edited); //Will be flagged edited again because the french culture had pending changes
|
||||
|
||||
// publish again
|
||||
document.SetValue("value1", "v1en2", "en"); //update the value now that it's variant again
|
||||
document.SetValue("value1", "v1fr2", "fr"); //update the value now that it's variant again
|
||||
ServiceContext.ContentService.SaveAndPublish(document);
|
||||
|
||||
document = ServiceContext.ContentService.GetById(document.Id);
|
||||
Assert.AreEqual("doc1en", document.Name);
|
||||
Assert.AreEqual("doc1en", document.GetCultureName("en"));
|
||||
Assert.AreEqual("doc1fr", document.GetCultureName("fr"));
|
||||
Assert.AreEqual("v1en2", document.GetValue("value1", "en"));
|
||||
Assert.AreEqual("v1fr2", document.GetValue("value1", "fr"));
|
||||
Assert.IsNull(document.GetValue("value1")); //The value is there but the business logic returns null
|
||||
Assert.IsFalse(document.IsCultureEdited("en")); //This returns false, the variant property value has been published
|
||||
Assert.IsFalse(document.IsCultureEdited("fr")); //This returns false, the variant property value has been published
|
||||
Assert.IsFalse(document.Edited);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Change_Property_Variations_From_Invariant_To_Variant_And_Ensure_Edited_Values_Are_Renormalized()
|
||||
{
|
||||
// one simple content type, variant, with both variant and invariant properties
|
||||
// can change an invariant property to variant and back
|
||||
|
||||
CreateFrenchAndEnglishLangs();
|
||||
|
||||
var contentType = CreateContentType(ContentVariation.Culture);
|
||||
|
||||
var properties = CreatePropertyCollection(("value1", ContentVariation.Nothing));
|
||||
|
||||
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
var document = (IContent)new Content("document", -1, contentType);
|
||||
document.SetCultureName("doc1en", "en");
|
||||
document.SetCultureName("doc1fr", "fr");
|
||||
document.SetValue("value1", "v1en-init");
|
||||
ServiceContext.ContentService.SaveAndPublish(document); //all values are published which means the document is not 'edited'
|
||||
|
||||
document = ServiceContext.ContentService.GetById(document.Id);
|
||||
Assert.IsFalse(document.IsCultureEdited("en"));
|
||||
Assert.IsFalse(document.IsCultureEdited("fr"));
|
||||
Assert.IsFalse(document.Edited);
|
||||
|
||||
document.SetValue("value1", "v1en"); //change the property value, so now the invariant (default) culture will be edited
|
||||
ServiceContext.ContentService.Save(document);
|
||||
|
||||
document = ServiceContext.ContentService.GetById(document.Id);
|
||||
Assert.AreEqual("doc1en", document.Name);
|
||||
Assert.AreEqual("doc1en", document.GetCultureName("en"));
|
||||
Assert.AreEqual("doc1fr", document.GetCultureName("fr"));
|
||||
Assert.AreEqual("v1en", document.GetValue("value1"));
|
||||
Assert.AreEqual("v1en-init", document.GetValue("value1", published: true));
|
||||
Assert.IsTrue(document.IsCultureEdited("en")); //This is true because the invariant property reflects changes on the default lang
|
||||
Assert.IsFalse(document.IsCultureEdited("fr"));
|
||||
Assert.IsTrue(document.Edited);
|
||||
|
||||
// switch property type to Culture
|
||||
contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType); //This is going to have to re-normalize the "Edited" flag
|
||||
|
||||
document = ServiceContext.ContentService.GetById(document.Id);
|
||||
Assert.IsTrue(document.IsCultureEdited("en")); //Remains true
|
||||
Assert.IsFalse(document.IsCultureEdited("fr")); //False because no french property has ever been edited
|
||||
Assert.IsTrue(document.Edited);
|
||||
|
||||
//update the culture value and publish
|
||||
document.SetValue("value1", "v1en2", "en");
|
||||
ServiceContext.ContentService.SaveAndPublish(document);
|
||||
|
||||
document = ServiceContext.ContentService.GetById(document.Id);
|
||||
Assert.AreEqual("doc1en", document.Name);
|
||||
Assert.AreEqual("doc1en", document.GetCultureName("en"));
|
||||
Assert.AreEqual("doc1fr", document.GetCultureName("fr"));
|
||||
Assert.IsNull(document.GetValue("value1")); //The values are there but the business logic returns null
|
||||
Assert.IsNull(document.GetValue("value1", published: true)); //The values are there but the business logic returns null
|
||||
Assert.AreEqual("v1en2", document.GetValue("value1", "en"));
|
||||
Assert.AreEqual("v1en2", document.GetValue("value1", "en", published: true));
|
||||
Assert.IsFalse(document.IsCultureEdited("en")); //This returns false, everything is published
|
||||
Assert.IsFalse(document.IsCultureEdited("fr")); //False because no french property has ever been edited
|
||||
Assert.IsFalse(document.Edited);
|
||||
|
||||
// switch property back to Invariant
|
||||
contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
document = ServiceContext.ContentService.GetById(document.Id);
|
||||
Assert.AreEqual("v1en2", document.GetValue("value1")); //The variant property value gets copied over to the invariant
|
||||
Assert.AreEqual("v1en2", document.GetValue("value1", published: true));
|
||||
Assert.IsNull(document.GetValue("value1", "fr")); //The values are there but the business logic returns null
|
||||
Assert.IsNull(document.GetValue("value1", "fr", published: true)); //The values are there but the business logic returns null
|
||||
Assert.IsFalse(document.IsCultureEdited("en")); //The variant published AND edited values are copied over to the invariant
|
||||
Assert.IsFalse(document.IsCultureEdited("fr"));
|
||||
Assert.IsFalse(document.Edited);
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Change_Variations_ComposedContentType_1()
|
||||
{
|
||||
@@ -437,59 +882,22 @@ namespace Umbraco.Tests.Services
|
||||
// can change the composing content type to invariant and back
|
||||
// can change the composed content type to invariant and back
|
||||
|
||||
var languageEn = new Language("en") { IsDefault = true };
|
||||
ServiceContext.LocalizationService.Save(languageEn);
|
||||
var languageFr = new Language("fr");
|
||||
ServiceContext.LocalizationService.Save(languageFr);
|
||||
CreateFrenchAndEnglishLangs();
|
||||
|
||||
var composing = new ContentType(-1)
|
||||
{
|
||||
Alias = "composing",
|
||||
Name = "composing",
|
||||
Variations = ContentVariation.Culture
|
||||
};
|
||||
var composing = CreateContentType(ContentVariation.Culture, "composing");
|
||||
|
||||
var properties1 = new PropertyTypeCollection(true)
|
||||
{
|
||||
new PropertyType("value11", ValueStorageType.Ntext)
|
||||
{
|
||||
Alias = "value11",
|
||||
DataTypeId = -88,
|
||||
Variations = ContentVariation.Culture
|
||||
},
|
||||
new PropertyType("value12", ValueStorageType.Ntext)
|
||||
{
|
||||
Alias = "value12",
|
||||
DataTypeId = -88,
|
||||
Variations = ContentVariation.Nothing
|
||||
}
|
||||
};
|
||||
var properties1 = CreatePropertyCollection(
|
||||
("value11", ContentVariation.Culture),
|
||||
("value12", ContentVariation.Nothing));
|
||||
|
||||
composing.PropertyGroups.Add(new PropertyGroup(properties1) { Name = "Content" });
|
||||
ServiceContext.ContentTypeService.Save(composing);
|
||||
|
||||
var composed = new ContentType(-1)
|
||||
{
|
||||
Alias = "composed",
|
||||
Name = "composed",
|
||||
Variations = ContentVariation.Culture
|
||||
};
|
||||
var composed = CreateContentType(ContentVariation.Culture, "composed");
|
||||
|
||||
var properties2 = new PropertyTypeCollection(true)
|
||||
{
|
||||
new PropertyType("value21", ValueStorageType.Ntext)
|
||||
{
|
||||
Alias = "value21",
|
||||
DataTypeId = -88,
|
||||
Variations = ContentVariation.Culture
|
||||
},
|
||||
new PropertyType("value22", ValueStorageType.Ntext)
|
||||
{
|
||||
Alias = "value22",
|
||||
DataTypeId = -88,
|
||||
Variations = ContentVariation.Nothing
|
||||
}
|
||||
};
|
||||
var properties2 = CreatePropertyCollection(
|
||||
("value21", ContentVariation.Culture),
|
||||
("value22", ContentVariation.Nothing));
|
||||
|
||||
composed.PropertyGroups.Add(new PropertyGroup(properties2) { Name = "Content" });
|
||||
composed.AddContentType(composing);
|
||||
@@ -569,86 +977,32 @@ namespace Umbraco.Tests.Services
|
||||
// can change the composing content type to invariant and back
|
||||
// can change the variant composed content type to invariant and back
|
||||
|
||||
var languageEn = new Language("en") { IsDefault = true };
|
||||
ServiceContext.LocalizationService.Save(languageEn);
|
||||
var languageFr = new Language("fr");
|
||||
ServiceContext.LocalizationService.Save(languageFr);
|
||||
CreateFrenchAndEnglishLangs();
|
||||
|
||||
var composing = new ContentType(-1)
|
||||
{
|
||||
Alias = "composing",
|
||||
Name = "composing",
|
||||
Variations = ContentVariation.Culture
|
||||
};
|
||||
var composing = CreateContentType(ContentVariation.Culture, "composing");
|
||||
|
||||
var properties1 = new PropertyTypeCollection(true)
|
||||
{
|
||||
new PropertyType("value11", ValueStorageType.Ntext)
|
||||
{
|
||||
Alias = "value11",
|
||||
DataTypeId = -88,
|
||||
Variations = ContentVariation.Culture
|
||||
},
|
||||
new PropertyType("value12", ValueStorageType.Ntext)
|
||||
{
|
||||
Alias = "value12",
|
||||
DataTypeId = -88,
|
||||
Variations = ContentVariation.Nothing
|
||||
}
|
||||
};
|
||||
var properties1 = CreatePropertyCollection(
|
||||
("value11", ContentVariation.Culture),
|
||||
("value12", ContentVariation.Nothing));
|
||||
|
||||
composing.PropertyGroups.Add(new PropertyGroup(properties1) { Name = "Content" });
|
||||
ServiceContext.ContentTypeService.Save(composing);
|
||||
|
||||
var composed1 = new ContentType(-1)
|
||||
{
|
||||
Alias = "composed1",
|
||||
Name = "composed1",
|
||||
Variations = ContentVariation.Culture
|
||||
};
|
||||
var composed1 = CreateContentType(ContentVariation.Culture, "composed1");
|
||||
|
||||
var properties2 = new PropertyTypeCollection(true)
|
||||
{
|
||||
new PropertyType("value21", ValueStorageType.Ntext)
|
||||
{
|
||||
Alias = "value21",
|
||||
DataTypeId = -88,
|
||||
Variations = ContentVariation.Culture
|
||||
},
|
||||
new PropertyType("value22", ValueStorageType.Ntext)
|
||||
{
|
||||
Alias = "value22",
|
||||
DataTypeId = -88,
|
||||
Variations = ContentVariation.Nothing
|
||||
}
|
||||
};
|
||||
var properties2 = CreatePropertyCollection(
|
||||
("value21", ContentVariation.Culture),
|
||||
("value22", ContentVariation.Nothing));
|
||||
|
||||
composed1.PropertyGroups.Add(new PropertyGroup(properties2) { Name = "Content" });
|
||||
composed1.AddContentType(composing);
|
||||
ServiceContext.ContentTypeService.Save(composed1);
|
||||
|
||||
var composed2 = new ContentType(-1)
|
||||
{
|
||||
Alias = "composed2",
|
||||
Name = "composed2",
|
||||
Variations = ContentVariation.Nothing
|
||||
};
|
||||
var composed2 = CreateContentType(ContentVariation.Nothing, "composed2");
|
||||
|
||||
var properties3 = new PropertyTypeCollection(true)
|
||||
{
|
||||
new PropertyType("value31", ValueStorageType.Ntext)
|
||||
{
|
||||
Alias = "value31",
|
||||
DataTypeId = -88,
|
||||
Variations = ContentVariation.Nothing
|
||||
},
|
||||
new PropertyType("value32", ValueStorageType.Ntext)
|
||||
{
|
||||
Alias = "value32",
|
||||
DataTypeId = -88,
|
||||
Variations = ContentVariation.Nothing
|
||||
}
|
||||
};
|
||||
var properties3 = CreatePropertyCollection(
|
||||
("value31", ContentVariation.Nothing),
|
||||
("value32", ContentVariation.Nothing));
|
||||
|
||||
composed2.PropertyGroups.Add(new PropertyGroup(properties3) { Name = "Content" });
|
||||
composed2.AddContentType(composing);
|
||||
@@ -754,5 +1108,35 @@ namespace Umbraco.Tests.Services
|
||||
AssertJsonStartsWith(document2.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':");
|
||||
}
|
||||
|
||||
private void CreateFrenchAndEnglishLangs()
|
||||
{
|
||||
var languageEn = new Language("en") { IsDefault = true };
|
||||
ServiceContext.LocalizationService.Save(languageEn);
|
||||
var languageFr = new Language("fr");
|
||||
ServiceContext.LocalizationService.Save(languageFr);
|
||||
}
|
||||
|
||||
private IContentType CreateContentType(ContentVariation variance, string alias = "contentType") => new ContentType(-1)
|
||||
{
|
||||
Alias = alias,
|
||||
Name = alias,
|
||||
Variations = variance
|
||||
};
|
||||
|
||||
private PropertyTypeCollection CreatePropertyCollection(params (string alias, ContentVariation variance)[] props)
|
||||
{
|
||||
var propertyCollection = new PropertyTypeCollection(true);
|
||||
|
||||
foreach (var (alias, variance) in props)
|
||||
propertyCollection.Add(new PropertyType(alias, ValueStorageType.Ntext)
|
||||
{
|
||||
Alias = alias,
|
||||
DataTypeId = -88,
|
||||
Variations = variance
|
||||
});
|
||||
|
||||
return propertyCollection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,19 +25,23 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting
|
||||
public async Task<Tuple<HttpResponseMessage, string>> Execute(string controllerName, string actionName, HttpMethod method,
|
||||
HttpContent content = null,
|
||||
MediaTypeWithQualityHeaderValue mediaTypeHeader = null,
|
||||
bool assertOkResponse = true)
|
||||
bool assertOkResponse = true, object routeDefaults = null, string url = null)
|
||||
{
|
||||
if (mediaTypeHeader == null)
|
||||
{
|
||||
mediaTypeHeader = new MediaTypeWithQualityHeaderValue("application/json");
|
||||
}
|
||||
if (routeDefaults == null)
|
||||
{
|
||||
routeDefaults = new { controller = controllerName, action = actionName, id = RouteParameter.Optional };
|
||||
}
|
||||
|
||||
var startup = new TestStartup(
|
||||
configuration =>
|
||||
{
|
||||
configuration.Routes.MapHttpRoute("Default",
|
||||
routeTemplate: "{controller}/{action}/{id}",
|
||||
defaults: new { controller = controllerName, action = actionName, id = RouteParameter.Optional });
|
||||
defaults: routeDefaults);
|
||||
},
|
||||
_controllerFactory);
|
||||
|
||||
@@ -45,7 +49,7 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting
|
||||
{
|
||||
var request = new HttpRequestMessage
|
||||
{
|
||||
RequestUri = new Uri("https://testserver/"),
|
||||
RequestUri = new Uri("https://testserver/" + (url ?? "")),
|
||||
Method = method
|
||||
};
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Exceptions;
|
||||
|
||||
namespace Umbraco.Tests.Testing
|
||||
{
|
||||
@@ -29,7 +30,7 @@ namespace Umbraco.Tests.Testing
|
||||
var methodName = test.MethodName;
|
||||
var type = Type.GetType(typeName, true);
|
||||
if (type == null)
|
||||
throw new Exception("panic"); // makes no sense
|
||||
throw new PanicException($"Could not resolve the type from type name {typeName}"); // makes no sense
|
||||
var methodInfo = type.GetMethod(methodName); // what about overloads?
|
||||
var options = GetTestOptions<TOptions>(methodInfo);
|
||||
return options;
|
||||
@@ -53,7 +54,7 @@ namespace Umbraco.Tests.Testing
|
||||
{
|
||||
if (other == null) throw new ArgumentNullException(nameof(other));
|
||||
if (!(Merge((TestOptionAttributeBase) other) is TOptions merged))
|
||||
throw new Exception("panic");
|
||||
throw new PanicException("Could not merge test options");
|
||||
return merged;
|
||||
}
|
||||
|
||||
|
||||
@@ -243,6 +243,7 @@
|
||||
<Compile Include="Cache\FullDataSetCachePolicyTests.cs" />
|
||||
<Compile Include="Cache\SingleItemsOnlyCachePolicyTests.cs" />
|
||||
<Compile Include="Collections\DeepCloneableListTests.cs" />
|
||||
<Compile Include="Web\Controllers\AuthenticationControllerTests.cs" />
|
||||
<Compile Include="Web\Controllers\BackOfficeControllerUnitTests.cs" />
|
||||
<Compile Include="CoreThings\DelegateExtensionsTests.cs" />
|
||||
<Compile Include="Web\Controllers\ContentControllerTests.cs" />
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
using System.Web.Hosting;
|
||||
using System.Web.Http;
|
||||
using Moq;
|
||||
using Newtonsoft.Json;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.Mappers;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.TestHelpers.ControllerTesting;
|
||||
using Umbraco.Tests.Testing;
|
||||
using Umbraco.Web;
|
||||
using Umbraco.Web.Editors;
|
||||
using Umbraco.Web.Features;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using IUser = Umbraco.Core.Models.Membership.IUser;
|
||||
|
||||
namespace Umbraco.Tests.Web.Controllers
|
||||
{
|
||||
[TestFixture]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.None)]
|
||||
public class AuthenticationControllerTests : TestWithDatabaseBase
|
||||
{
|
||||
protected override void ComposeApplication(bool withApplication)
|
||||
{
|
||||
base.ComposeApplication(withApplication);
|
||||
//if (!withApplication) return;
|
||||
|
||||
// replace the true IUserService implementation with a mock
|
||||
// so that each test can configure the service to their liking
|
||||
Composition.RegisterUnique(f => Mock.Of<IUserService>());
|
||||
|
||||
// kill the true IEntityService too
|
||||
Composition.RegisterUnique(f => Mock.Of<IEntityService>());
|
||||
|
||||
Composition.RegisterUnique<UmbracoFeatures>();
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public async System.Threading.Tasks.Task GetCurrentUser_Fips()
|
||||
{
|
||||
ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor, UmbracoHelper helper)
|
||||
{
|
||||
//setup some mocks
|
||||
var userServiceMock = Mock.Get(Current.Services.UserService);
|
||||
userServiceMock.Setup(service => service.GetUserById(It.IsAny<int>()))
|
||||
.Returns(() => null);
|
||||
|
||||
if (Thread.GetDomain().GetData(".appPath") != null)
|
||||
{
|
||||
HttpContext.Current = new HttpContext(new SimpleWorkerRequest("", "", new StringWriter()));
|
||||
}
|
||||
else
|
||||
{
|
||||
var baseDir = IOHelper.MapPath("", false).TrimEnd(IOHelper.DirSepChar);
|
||||
HttpContext.Current = new HttpContext(new SimpleWorkerRequest("/", baseDir, "", "", new StringWriter()));
|
||||
}
|
||||
IOHelper.ForceNotHosted = true;
|
||||
var usersController = new AuthenticationController(
|
||||
Factory.GetInstance<IGlobalSettings>(),
|
||||
umbracoContextAccessor,
|
||||
Factory.GetInstance<ISqlContext>(),
|
||||
Factory.GetInstance<ServiceContext>(),
|
||||
Factory.GetInstance<AppCaches>(),
|
||||
Factory.GetInstance<IProfilingLogger>(),
|
||||
Factory.GetInstance<IRuntimeState>(),
|
||||
helper);
|
||||
return usersController;
|
||||
}
|
||||
|
||||
Mock.Get(Current.SqlContext)
|
||||
.Setup(x => x.Query<IUser>())
|
||||
.Returns(new Query<IUser>(Current.SqlContext));
|
||||
|
||||
var syntax = new SqlCeSyntaxProvider();
|
||||
|
||||
Mock.Get(Current.SqlContext)
|
||||
.Setup(x => x.SqlSyntax)
|
||||
.Returns(syntax);
|
||||
|
||||
var mappers = new MapperCollection(new[]
|
||||
{
|
||||
new UserMapper(new Lazy<ISqlContext>(() => Current.SqlContext), new ConcurrentDictionary<Type, ConcurrentDictionary<string, string>>())
|
||||
});
|
||||
|
||||
Mock.Get(Current.SqlContext)
|
||||
.Setup(x => x.Mappers)
|
||||
.Returns(mappers);
|
||||
|
||||
// Testing what happens if the system were configured to only use FIPS-compliant algorithms
|
||||
var typ = typeof(CryptoConfig);
|
||||
var flds = typ.GetFields(BindingFlags.Static | BindingFlags.NonPublic);
|
||||
var haveFld = flds.FirstOrDefault(f => f.Name == "s_haveFipsAlgorithmPolicy");
|
||||
var isFld = flds.FirstOrDefault(f => f.Name == "s_fipsAlgorithmPolicy");
|
||||
var originalFipsValue = CryptoConfig.AllowOnlyFipsAlgorithms;
|
||||
|
||||
try
|
||||
{
|
||||
if (!originalFipsValue)
|
||||
{
|
||||
haveFld.SetValue(null, true);
|
||||
isFld.SetValue(null, true);
|
||||
}
|
||||
|
||||
var runner = new TestRunner(CtrlFactory);
|
||||
var response = await runner.Execute("Authentication", "GetCurrentUser", HttpMethod.Get);
|
||||
|
||||
var obj = JsonConvert.DeserializeObject<UserDetail>(response.Item2);
|
||||
Assert.AreEqual(-1, obj.UserId);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!originalFipsValue)
|
||||
{
|
||||
haveFld.SetValue(null, false);
|
||||
isFld.SetValue(null, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Formatting;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
using System.Web.Http;
|
||||
using Moq;
|
||||
using Newtonsoft.Json;
|
||||
@@ -155,7 +157,7 @@ namespace Umbraco.Tests.Web.Controllers
|
||||
var runner = new TestRunner(CtrlFactory);
|
||||
var response = await runner.Execute("Users", "GetPagedUsers", HttpMethod.Get);
|
||||
|
||||
var obj = JsonConvert.DeserializeObject<PagedResult<UserDisplay>>(response.Item2);
|
||||
var obj = JsonConvert.DeserializeObject<PagedResult<UserBasic>>(response.Item2);
|
||||
Assert.AreEqual(0, obj.TotalItems);
|
||||
}
|
||||
|
||||
@@ -190,9 +192,100 @@ namespace Umbraco.Tests.Web.Controllers
|
||||
var runner = new TestRunner(CtrlFactory);
|
||||
var response = await runner.Execute("Users", "GetPagedUsers", HttpMethod.Get);
|
||||
|
||||
var obj = JsonConvert.DeserializeObject<PagedResult<UserDisplay>>(response.Item2);
|
||||
var obj = JsonConvert.DeserializeObject<PagedResult<UserBasic>>(response.Item2);
|
||||
Assert.AreEqual(10, obj.TotalItems);
|
||||
Assert.AreEqual(10, obj.Items.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async System.Threading.Tasks.Task GetPagedUsers_Fips()
|
||||
{
|
||||
await RunFipsTest("GetPagedUsers", mock =>
|
||||
{
|
||||
var users = MockedUser.CreateMulipleUsers(10);
|
||||
long outVal = 10;
|
||||
mock.Setup(service => service.GetAll(
|
||||
It.IsAny<long>(), It.IsAny<int>(), out outVal, It.IsAny<string>(), It.IsAny<Direction>(),
|
||||
It.IsAny<UserState[]>(), It.IsAny<string[]>(), It.IsAny<string[]>(), It.IsAny<IQuery<IUser>>()))
|
||||
.Returns(() => users);
|
||||
}, response =>
|
||||
{
|
||||
var obj = JsonConvert.DeserializeObject<PagedResult<UserBasic>>(response.Item2);
|
||||
Assert.AreEqual(10, obj.TotalItems);
|
||||
Assert.AreEqual(10, obj.Items.Count());
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async System.Threading.Tasks.Task GetById_Fips()
|
||||
{
|
||||
const int mockUserId = 1234;
|
||||
var user = MockedUser.CreateUser();
|
||||
|
||||
await RunFipsTest("GetById", mock =>
|
||||
{
|
||||
mock.Setup(service => service.GetUserById(1234))
|
||||
.Returns((int i) => i == mockUserId ? user : null);
|
||||
}, response =>
|
||||
{
|
||||
var obj = JsonConvert.DeserializeObject<UserDisplay>(response.Item2);
|
||||
Assert.AreEqual(user.Username, obj.Username);
|
||||
Assert.AreEqual(user.Email, obj.Email);
|
||||
}, new { controller = "Users", action = "GetById" }, $"Users/GetById/{mockUserId}");
|
||||
}
|
||||
|
||||
|
||||
private async System.Threading.Tasks.Task RunFipsTest(string action, Action<Mock<IUserService>> userServiceSetup,
|
||||
Action<Tuple<HttpResponseMessage, string>> verification,
|
||||
object routeDefaults = null, string url = null)
|
||||
{
|
||||
ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor, UmbracoHelper helper)
|
||||
{
|
||||
//setup some mocks
|
||||
var userServiceMock = Mock.Get(Current.Services.UserService);
|
||||
userServiceSetup(userServiceMock);
|
||||
|
||||
var usersController = new UsersController(
|
||||
Factory.GetInstance<IGlobalSettings>(),
|
||||
umbracoContextAccessor,
|
||||
Factory.GetInstance<ISqlContext>(),
|
||||
Factory.GetInstance<ServiceContext>(),
|
||||
Factory.GetInstance<AppCaches>(),
|
||||
Factory.GetInstance<IProfilingLogger>(),
|
||||
Factory.GetInstance<IRuntimeState>(),
|
||||
helper);
|
||||
return usersController;
|
||||
}
|
||||
|
||||
// Testing what happens if the system were configured to only use FIPS-compliant algorithms
|
||||
var typ = typeof(CryptoConfig);
|
||||
var flds = typ.GetFields(BindingFlags.Static | BindingFlags.NonPublic);
|
||||
var haveFld = flds.FirstOrDefault(f => f.Name == "s_haveFipsAlgorithmPolicy");
|
||||
var isFld = flds.FirstOrDefault(f => f.Name == "s_fipsAlgorithmPolicy");
|
||||
var originalFipsValue = CryptoConfig.AllowOnlyFipsAlgorithms;
|
||||
|
||||
try
|
||||
{
|
||||
if (!originalFipsValue)
|
||||
{
|
||||
haveFld.SetValue(null, true);
|
||||
isFld.SetValue(null, true);
|
||||
}
|
||||
|
||||
MockForGetPagedUsers();
|
||||
|
||||
var runner = new TestRunner(CtrlFactory);
|
||||
var response = await runner.Execute("Users", action, HttpMethod.Get, routeDefaults: routeDefaults, url: url);
|
||||
verification(response);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!originalFipsValue)
|
||||
{
|
||||
haveFld.SetValue(null, false);
|
||||
isFld.SetValue(null, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,13 +16,31 @@ 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" },
|
||||
controllers: { files: ["./src/{views,controllers}/**/*.controller.js"], out: "umbraco.controllers.js" },
|
||||
directives: { files: ["./src/common/directives/**/*.js"], out: "umbraco.directives.js" },
|
||||
installer: { files: ["./src/installer/**/*.js"], out: "umbraco.installer.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" }
|
||||
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"
|
||||
},
|
||||
},
|
||||
|
||||
//selectors for copying all views into the build
|
||||
@@ -34,7 +52,7 @@ module.exports = {
|
||||
|
||||
//globs for file-watching
|
||||
globs:{
|
||||
views: "./src/views/**/*.html",
|
||||
views: ["./src/views/**/*.html", "./src/common/directives/**/*.html", "./src/common/components/**/*.html" ],
|
||||
less: "./src/less/**/*.less",
|
||||
js: "./src/*.js",
|
||||
lib: "./lib/**/*",
|
||||
|
||||
@@ -5,6 +5,9 @@ var gulp = require('gulp');
|
||||
var runSequence = require('run-sequence');
|
||||
|
||||
// Dev - build the files ready for development and start watchers
|
||||
gulp.task('dev', function(cb) {
|
||||
gulp.task('dev', function (cb) {
|
||||
|
||||
global.isProd = false;
|
||||
|
||||
runSequence(["dependencies", "js", "less", "views"], "watch", cb);
|
||||
});
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
'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,26 +9,28 @@ 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);
|
||||
|
||||
if (global.isProd === true) {
|
||||
// check for js errors
|
||||
task = task.pipe(eslint());
|
||||
// outputs the lint results to the console
|
||||
task = task.pipe(eslint.format());
|
||||
}
|
||||
|
||||
|
||||
// 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())
|
||||
.pipe(embedTemplates({ basePath: "./src/" }))
|
||||
.pipe(concat(out))
|
||||
.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(wrap('(function(){\n%= body %\n})();'))
|
||||
.pipe(gulp.dest(config.root + config.targets.js));
|
||||
|
||||
|
||||
|
||||
|
||||
return task;
|
||||
|
||||
|
||||
};
|
||||
|
||||
@@ -17,17 +17,12 @@ module.exports = function(files, out) {
|
||||
];
|
||||
|
||||
var task = gulp.src(files)
|
||||
.pipe(less());
|
||||
|
||||
|
||||
if (global.isProd === true) {
|
||||
task = task.pipe(cleanCss());
|
||||
}
|
||||
|
||||
task = task.pipe(postcss(processors))
|
||||
.pipe(less())
|
||||
.pipe(cleanCss())
|
||||
.pipe(postcss(processors))
|
||||
.pipe(rename(out))
|
||||
.pipe(gulp.dest(config.root + config.targets.css));
|
||||
|
||||
return task;
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1239,6 +1239,11 @@
|
||||
// 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) {
|
||||
|
||||
+278
-86
File diff suppressed because it is too large
Load Diff
@@ -21,6 +21,12 @@
|
||||
$scope.page.menu.currentSection = appState.getSectionState("currentSection");
|
||||
$scope.page.listViewPath = null;
|
||||
$scope.page.isNew = $scope.isNew ? true : false;
|
||||
|
||||
if (infiniteMode) {
|
||||
$scope.page.allowInfinitePublishAndClose = $scope.infiniteModel.allowPublishAndClose;
|
||||
$scope.page.allowInfiniteSaveAndClose = $scope.infiniteModel.allowSaveAndClose;
|
||||
}
|
||||
|
||||
$scope.page.buttonGroupState = "init";
|
||||
$scope.page.hideActionsMenu = infiniteMode ? true : false;
|
||||
$scope.page.hideChangeVariant = false;
|
||||
@@ -369,12 +375,17 @@
|
||||
saveMethod: args.saveMethod,
|
||||
scope: $scope,
|
||||
content: $scope.content,
|
||||
create: $scope.page.isNew,
|
||||
action: args.action,
|
||||
showNotifications: args.showNotifications,
|
||||
softRedirect: true
|
||||
}).then(function (data) {
|
||||
//success
|
||||
init();
|
||||
|
||||
//needs to be manually set for infinite editing mode
|
||||
$scope.page.isNew = false;
|
||||
|
||||
syncTreeNode($scope.content, data.path);
|
||||
|
||||
eventsService.emit("content.saved", { content: $scope.content, action: args.action });
|
||||
|
||||
+10
-2
@@ -1,7 +1,7 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function ContentNodeInfoDirective($timeout, logResource, eventsService, userService, localizationService, dateHelper, editorService, redirectUrlsResource, overlayService) {
|
||||
function ContentNodeInfoDirective($timeout, logResource, eventsService, userService, localizationService, dateHelper, editorService, redirectUrlsResource, overlayService, entityResource) {
|
||||
|
||||
function link(scope) {
|
||||
|
||||
@@ -16,8 +16,12 @@
|
||||
scope.disableTemplates = Umbraco.Sys.ServerVariables.features.disabledFeatures.disableTemplates;
|
||||
scope.allowChangeDocumentType = false;
|
||||
scope.allowChangeTemplate = false;
|
||||
scope.allTemplates = [];
|
||||
|
||||
function onInit() {
|
||||
entityResource.getAll("Template").then(function (templates) {
|
||||
scope.allTemplates = templates;
|
||||
});
|
||||
|
||||
// set currentVariant
|
||||
scope.currentVariant = _.find(scope.node.variants, (v) => v.active);
|
||||
@@ -158,8 +162,12 @@
|
||||
}
|
||||
|
||||
scope.openTemplate = function () {
|
||||
var template = _.findWhere(scope.allTemplates, {alias: scope.node.template})
|
||||
if (!template) {
|
||||
return;
|
||||
}
|
||||
var templateEditor = {
|
||||
id: scope.node.templateId,
|
||||
id: template.id,
|
||||
submit: function (model) {
|
||||
editorService.close();
|
||||
},
|
||||
|
||||
+19
-2
@@ -1,7 +1,7 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function EditorContentHeader(serverValidationManager) {
|
||||
function EditorContentHeader(serverValidationManager, localizationService, editorState) {
|
||||
|
||||
function link(scope, el, attr, ctrl) {
|
||||
|
||||
@@ -13,7 +13,24 @@
|
||||
if (!scope.serverValidationAliasField) {
|
||||
scope.serverValidationAliasField = "Alias";
|
||||
}
|
||||
|
||||
|
||||
scope.isNew = scope.content.state == "NotCreated";
|
||||
|
||||
localizationService.localizeMany([
|
||||
scope.isNew ? "placeholders_a11yCreateItem" : "placeholders_a11yEdit",
|
||||
"placeholders_a11yName"]
|
||||
).then(function (data) {
|
||||
scope.a11yMessage = data[0];
|
||||
scope.a11yName = data[1];
|
||||
if (!scope.isNew) {
|
||||
scope.a11yMessage += " " + scope.content.name;
|
||||
|
||||
} else {
|
||||
var name = editorState.current.contentTypeName;
|
||||
scope.a11yMessage += " " + name;
|
||||
scope.a11yName = name + " " + scope.a11yName;
|
||||
}
|
||||
});
|
||||
scope.vm = {};
|
||||
scope.vm.dropdownOpen = false;
|
||||
scope.vm.currentVariant = "";
|
||||
|
||||
+1
-3
@@ -16,9 +16,7 @@
|
||||
editor.moveRight = true;
|
||||
editor.level = 0;
|
||||
editor.styleIndex = 0;
|
||||
|
||||
editor.infinityMode = true;
|
||||
|
||||
|
||||
// push the new editor to the dom
|
||||
scope.editors.push(editor);
|
||||
|
||||
|
||||
+14
-6
@@ -11,6 +11,11 @@ function hexBgColor() {
|
||||
restrict: "A",
|
||||
link: function (scope, element, attr, formCtrl) {
|
||||
|
||||
function setBackgroundColor(color) {
|
||||
// note: can't use element.css(), it doesn't support hexa background colors
|
||||
angular.element(element)[0].style.backgroundColor = "#" + color;
|
||||
}
|
||||
|
||||
// Only add inline hex background color if defined and not "true".
|
||||
if (attr.hexBgInline === undefined || (attr.hexBgInline !== undefined && attr.hexBgInline === "true")) {
|
||||
|
||||
@@ -26,18 +31,21 @@ function hexBgColor() {
|
||||
// Get the orig color before changing it.
|
||||
origColor = element.css("border-color");
|
||||
}
|
||||
// Validate it - test with and without the leading hash.
|
||||
if (/^([0-9a-f]{3}|[0-9a-f]{6})$/i.test(newVal)) {
|
||||
element.css("background-color", "#" + newVal);
|
||||
// Is it a regular hex value - (#)AABBCC ?
|
||||
var match = newVal.match(/^#?([0-9a-f]{3}|[0-9a-f]{6})$/i);
|
||||
if (match && match.length) {
|
||||
setBackgroundColor(match[1]);
|
||||
return;
|
||||
}
|
||||
if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(newVal)) {
|
||||
element.css("background-color", newVal);
|
||||
// Is it a hexa value - (#)AABBCCDD ?
|
||||
match = newVal.match(/^#?([0-9a-f]{4}|[0-9a-f]{8})$/i);
|
||||
if (match && match.length) {
|
||||
setBackgroundColor(match[1]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
element.css("background-color", origColor);
|
||||
setBackgroundColor(origColor);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ angular.module("umbraco.directives")
|
||||
|
||||
// TODO: A lot of the code below should be shared between the grid rte and the normal rte
|
||||
|
||||
scope.isLoading = true;
|
||||
|
||||
var promises = [];
|
||||
|
||||
//To id the html textarea we need to use the datetime ticks because we can have multiple rte's per a single property alias
|
||||
@@ -91,6 +93,10 @@ angular.module("umbraco.directives")
|
||||
//custom initialization for this editor within the grid
|
||||
editor.on('init', function (e) {
|
||||
|
||||
// Used this init event - as opposed to property init_instance_callback
|
||||
// to turn off the loader
|
||||
scope.isLoading = false;
|
||||
|
||||
//force overflow to hidden to prevent no needed scroll
|
||||
editor.getBody().style.overflow = "hidden";
|
||||
|
||||
|
||||
+21
-1
@@ -1,7 +1,7 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function MediaNodeInfoDirective($timeout, $location, eventsService, userService, dateHelper, editorService) {
|
||||
function MediaNodeInfoDirective($timeout, $location, eventsService, userService, dateHelper, editorService, mediaHelper) {
|
||||
|
||||
function link(scope, element, attrs, ctrl) {
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
|
||||
// make sure dates are formatted to the user's locale
|
||||
formatDatesToLocal();
|
||||
|
||||
// set media file extension initially
|
||||
setMediaExtension();
|
||||
}
|
||||
|
||||
function formatDatesToLocal() {
|
||||
@@ -49,6 +52,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
function setMediaExtension() {
|
||||
scope.node.extension = mediaHelper.getFileExtension(scope.nodeUrl);
|
||||
}
|
||||
|
||||
scope.openMediaType = function (mediaType) {
|
||||
var editor = {
|
||||
id: mediaType.id,
|
||||
@@ -62,6 +69,16 @@
|
||||
editorService.mediaTypeEditor(editor);
|
||||
};
|
||||
|
||||
scope.openSVG = function () {
|
||||
var popup = window.open('', '_blank');
|
||||
var html = '<!DOCTYPE html><body><img src="' + scope.nodeUrl + '"/>' +
|
||||
'<script>history.pushState(null, null,"' + $location.$$absUrl + '");</script></body>';
|
||||
|
||||
popup.document.open();
|
||||
popup.document.write(html);
|
||||
popup.document.close();
|
||||
}
|
||||
|
||||
// watch for content updates - reload content when node is saved, published etc.
|
||||
scope.$watch('node.updateDate', function(newValue, oldValue){
|
||||
if(!newValue) { return; }
|
||||
@@ -72,6 +89,9 @@
|
||||
|
||||
// Update the create and update dates
|
||||
formatDatesToLocal();
|
||||
|
||||
//Update the media file format
|
||||
setMediaExtension();
|
||||
});
|
||||
|
||||
//ensure to unregister from all events!
|
||||
|
||||
+52
-42
@@ -208,8 +208,9 @@ Opens an overlay to show a custom YSOD. </br>
|
||||
var numberOfOverlays = 0;
|
||||
var isRegistered = false;
|
||||
|
||||
var modelCopy = {};
|
||||
var unsubscribe = [];
|
||||
|
||||
var modelCopy = {};
|
||||
var unsubscribe = [];
|
||||
|
||||
function activate() {
|
||||
|
||||
@@ -221,8 +222,23 @@ Opens an overlay to show a custom YSOD. </br>
|
||||
|
||||
$timeout(function() {
|
||||
|
||||
if (scope.position === "target") {
|
||||
if (scope.position === "target" && scope.model.event) {
|
||||
setTargetPosition();
|
||||
|
||||
// update the position of the overlay on content changes
|
||||
// as these affect the layout/size of the overlay
|
||||
if ('ResizeObserver' in window)
|
||||
{
|
||||
var resizeObserver = new ResizeObserver(setTargetPosition);
|
||||
var contentArea = document.getElementById("contentwrapper");
|
||||
resizeObserver.observe(el[0]);
|
||||
if (contentArea) {
|
||||
resizeObserver.observe(contentArea);
|
||||
}
|
||||
unsubscribe.push(function () {
|
||||
resizeObserver.disconnect();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// this has to be done inside a timeout to ensure the destroy
|
||||
@@ -397,50 +413,44 @@ Opens an overlay to show a custom YSOD. </br>
|
||||
bottom: "inherit"
|
||||
};
|
||||
|
||||
// if mouse click position is know place element with mouse in center
|
||||
if (scope.model.event && scope.model.event) {
|
||||
// click position
|
||||
mousePositionClickX = scope.model.event.pageX;
|
||||
mousePositionClickY = scope.model.event.pageY;
|
||||
|
||||
// click position
|
||||
mousePositionClickX = scope.model.event.pageX;
|
||||
mousePositionClickY = scope.model.event.pageY;
|
||||
// element size
|
||||
elementHeight = el[0].clientHeight;
|
||||
elementWidth = el[0].clientWidth;
|
||||
|
||||
// element size
|
||||
elementHeight = el[0].clientHeight;
|
||||
elementWidth = el[0].clientWidth;
|
||||
|
||||
// move element to this position
|
||||
position.left = mousePositionClickX - (elementWidth / 2);
|
||||
position.top = mousePositionClickY - (elementHeight / 2);
|
||||
|
||||
// check to see if element is outside screen
|
||||
// outside right
|
||||
if (position.left + elementWidth > containerRight) {
|
||||
position.right = 10;
|
||||
position.left = "inherit";
|
||||
}
|
||||
|
||||
// outside bottom
|
||||
if (position.top + elementHeight > containerBottom) {
|
||||
position.bottom = 10;
|
||||
position.top = "inherit";
|
||||
}
|
||||
|
||||
// outside left
|
||||
if (position.left < containerLeft) {
|
||||
position.left = containerLeft + 10;
|
||||
position.right = "inherit";
|
||||
}
|
||||
|
||||
// outside top
|
||||
if (position.top < containerTop) {
|
||||
position.top = 10;
|
||||
position.bottom = "inherit";
|
||||
}
|
||||
|
||||
el.css(position);
|
||||
// move element to this position
|
||||
position.left = mousePositionClickX - (elementWidth / 2);
|
||||
position.top = mousePositionClickY - (elementHeight / 2);
|
||||
|
||||
// check to see if element is outside screen
|
||||
// outside right
|
||||
if (position.left + elementWidth > containerRight) {
|
||||
position.right = 10;
|
||||
position.left = "inherit";
|
||||
}
|
||||
|
||||
// outside bottom
|
||||
if (position.top + elementHeight > containerBottom) {
|
||||
position.bottom = 10;
|
||||
position.top = "inherit";
|
||||
}
|
||||
|
||||
// outside left
|
||||
if (position.left < containerLeft) {
|
||||
position.left = containerLeft + 10;
|
||||
position.right = "inherit";
|
||||
}
|
||||
|
||||
// outside top
|
||||
if (position.top < containerTop) {
|
||||
position.top = 10;
|
||||
position.bottom = "inherit";
|
||||
}
|
||||
|
||||
el.css(position);
|
||||
}
|
||||
|
||||
scope.submitForm = function(model) {
|
||||
|
||||
+14
-13
@@ -1,13 +1,13 @@
|
||||
/**
|
||||
@ngdoc directive
|
||||
@name umbraco.directives.directive:umbFlatpickr
|
||||
@name umbraco.directives.directive:umbDateTimePicker
|
||||
@restrict E
|
||||
@scope
|
||||
|
||||
@description
|
||||
<b>Added in Umbraco version 8.0</b>
|
||||
This directive is a wrapper of the flatpickr library. Use it to render a date time picker.
|
||||
For extra details about options and events take a look here: https://flatpickr.js.org/
|
||||
For extra details about options and events take a look here: https://flatpickr.js.org
|
||||
|
||||
Use this directive to render a date time picker
|
||||
|
||||
@@ -15,11 +15,11 @@ Use this directive to render a date time picker
|
||||
<pre>
|
||||
<div ng-controller="My.Controller as vm">
|
||||
|
||||
<umb-flatpickr
|
||||
<umb-date-time-picker
|
||||
ng-model="vm.date"
|
||||
options="vm.config"
|
||||
on-change="vm.datePickerChange(selectedDates, dateStr, instance)">
|
||||
</umb-flatpickr>
|
||||
</umb-date-time-picker>
|
||||
|
||||
</div>
|
||||
</pre>
|
||||
@@ -70,12 +70,12 @@ Use this directive to render a date time picker
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
var umbFlatpickr = {
|
||||
var umbDateTimePicker = {
|
||||
template: '<ng-transclude>' +
|
||||
'<input type="text" ng-if="!$ctrl.options.inline" ng-model="$ctrl.ngModel" placeholder="Select Date.."></input>' +
|
||||
'<div ng-if="$ctrl.options.inline"></div>' +
|
||||
'</ng-transclude>',
|
||||
controller: umbFlatpickrCtrl,
|
||||
controller: umbDateTimePickerCtrl,
|
||||
transclude: true,
|
||||
bindings: {
|
||||
ngModel: '<',
|
||||
@@ -92,9 +92,9 @@ Use this directive to render a date time picker
|
||||
}
|
||||
};
|
||||
|
||||
function umbFlatpickrCtrl($element, $timeout, $scope, assetsService, userService) {
|
||||
function umbDateTimePickerCtrl($element, $timeout, $scope, assetsService, userService) {
|
||||
|
||||
var ctrl = this;
|
||||
var loaded = false;
|
||||
var userLocale = null;
|
||||
|
||||
ctrl.$onInit = function() {
|
||||
@@ -102,14 +102,14 @@ Use this directive to render a date time picker
|
||||
// load css file for the date picker
|
||||
assetsService.loadCss('lib/flatpickr/flatpickr.css', $scope).then(function () {
|
||||
userService.getCurrentUser().then(function (user) {
|
||||
|
||||
// init date picker
|
||||
userLocale = user.locale;
|
||||
if (userLocale.indexOf('-') > -1) {
|
||||
userLocale = userLocale.split('-')[0];
|
||||
}
|
||||
loaded = true;
|
||||
grabElementAndRunFlatpickr();
|
||||
|
||||
grabElementAndRunFlatpickr();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -234,7 +234,8 @@ Use this directive to render a date time picker
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
angular.module('umbraco.directives').component('umbFlatpickr', umbFlatpickr);
|
||||
|
||||
|
||||
// umbFlatpickr (umb-flatpickr) is deprecated, but we keep it for backwards compatibility
|
||||
angular.module('umbraco.directives').component('umbFlatpickr', umbDateTimePicker);
|
||||
angular.module('umbraco.directives').component('umbDateTimePicker', umbDateTimePicker);
|
||||
})();
|
||||
+21
-3
@@ -17,6 +17,7 @@
|
||||
scope.sortableOptionsGroup = {};
|
||||
scope.sortableOptionsProperty = {};
|
||||
scope.sortingButtonKey = "general_reorder";
|
||||
scope.compositionsButtonState = "init";
|
||||
|
||||
function activate() {
|
||||
|
||||
@@ -47,6 +48,7 @@
|
||||
function setSortingOptions() {
|
||||
|
||||
scope.sortableOptionsGroup = {
|
||||
axis: 'y',
|
||||
distance: 10,
|
||||
tolerance: "pointer",
|
||||
opacity: 0.7,
|
||||
@@ -65,6 +67,7 @@
|
||||
};
|
||||
|
||||
scope.sortableOptionsProperty = {
|
||||
axis: 'y',
|
||||
distance: 10,
|
||||
tolerance: "pointer",
|
||||
connectWith: ".umb-group-builder__properties",
|
||||
@@ -246,8 +249,14 @@
|
||||
|
||||
};
|
||||
|
||||
scope.openCompositionsDialog = function() {
|
||||
|
||||
scope.isCompositionsDialogLoading = false;
|
||||
|
||||
scope.openCompositionsDialog = function() {
|
||||
if (scope.isCompositionsDialogLoading) {
|
||||
return;
|
||||
}
|
||||
scope.isCompositionsDialogLoading = true;
|
||||
scope.compositionsDialogModel = {
|
||||
contentType: scope.model,
|
||||
compositeContentTypes: scope.model.compositeContentTypes,
|
||||
@@ -264,7 +273,7 @@
|
||||
|
||||
// remove overlay
|
||||
editorService.close();
|
||||
|
||||
scope.isCompositionsDialogLoading = false;
|
||||
},
|
||||
close: function(oldModel) {
|
||||
|
||||
@@ -274,7 +283,7 @@
|
||||
|
||||
// remove overlay
|
||||
editorService.close();
|
||||
|
||||
scope.isCompositionsDialogLoading = false;
|
||||
},
|
||||
selectCompositeContentType: function (selectedContentType) {
|
||||
|
||||
@@ -335,6 +344,7 @@
|
||||
})), function(f) {
|
||||
return f !== null && f !== undefined;
|
||||
});
|
||||
scope.compositionsButtonState = "busy";
|
||||
$q.all([
|
||||
//get available composite types
|
||||
availableContentTypeResource(scope.model.id, [], propAliasesExisting).then(function (result) {
|
||||
@@ -352,8 +362,10 @@
|
||||
scope.compositionsDialogModel.totalContentTypes = parseInt(result, 10);
|
||||
})
|
||||
]).then(function() {
|
||||
scope.isCompositionsDialogLoading = false;
|
||||
//resolves when both other promises are done, now show it
|
||||
editorService.open(scope.compositionsDialogModel);
|
||||
scope.compositionsButtonState = "init";
|
||||
});
|
||||
|
||||
};
|
||||
@@ -530,6 +542,7 @@
|
||||
// push new init tab to the scope
|
||||
addInitGroup(scope.model.groups);
|
||||
|
||||
notifyChanged();
|
||||
},
|
||||
close: function() {
|
||||
if(_.isEqual(oldPropertyModel, propertyModel) === false) {
|
||||
@@ -586,8 +599,13 @@
|
||||
|
||||
}
|
||||
|
||||
notifyChanged();
|
||||
};
|
||||
|
||||
function notifyChanged() {
|
||||
eventsService.emit("editors.groupsBuilder.changed");
|
||||
}
|
||||
|
||||
function addInitProperty(group) {
|
||||
|
||||
var addInitPropertyBool = true;
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
@ngdoc directive
|
||||
@name umbraco.directives.directive:umbLoader
|
||||
@restrict E
|
||||
|
||||
@description
|
||||
Use this directive to generate a loading indicator.
|
||||
|
||||
<h3>Markup example</h3>
|
||||
<pre>
|
||||
<div ng-controller="My.Controller as vm">
|
||||
|
||||
<umb-loader
|
||||
ng-if="vm.loading">
|
||||
</umb-loader>
|
||||
|
||||
<div class="content" ng-if="!vm.loading">
|
||||
<p>{{content}}</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</pre>
|
||||
|
||||
<h3>Controller example</h3>
|
||||
<pre>
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
function Controller(myService) {
|
||||
|
||||
var vm = this;
|
||||
|
||||
vm.content = "";
|
||||
vm.loading = true;
|
||||
|
||||
myService.getContent().then(function(content){
|
||||
vm.content = content;
|
||||
vm.loading = false;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
angular.module("umbraco").controller("My.Controller", Controller);
|
||||
})();
|
||||
</pre>
|
||||
|
||||
@param {string=} position The loader position ("top", "bottom").
|
||||
|
||||
**/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
function UmbLoaderDirective() {
|
||||
|
||||
function link(scope, el, attr, ctrl) {
|
||||
|
||||
}
|
||||
|
||||
var directive = {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
templateUrl: 'views/components/umb-loader.html',
|
||||
scope: {
|
||||
position: "@?"
|
||||
},
|
||||
link: link
|
||||
};
|
||||
|
||||
return directive;
|
||||
}
|
||||
|
||||
angular.module('umbraco.directives').directive('umbLoader', UmbLoaderDirective);
|
||||
|
||||
})();
|
||||
+1
-1
@@ -39,7 +39,7 @@ Use this directive to generate a loading indicator.
|
||||
});
|
||||
|
||||
}
|
||||
½
|
||||
|
||||
angular.module("umbraco").controller("My.Controller", Controller);
|
||||
})();
|
||||
</pre>
|
||||
|
||||
+156
-151
@@ -1,167 +1,172 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
'use strict';
|
||||
|
||||
function ChangePasswordController($scope) {
|
||||
function ChangePasswordController($scope) {
|
||||
|
||||
function resetModel(isNew) {
|
||||
//the model config will contain an object, if it does not we'll create defaults
|
||||
//NOTE: We will not support doing the password regex on the client side because the regex on the server side
|
||||
//based on the membership provider cannot always be ported to js from .net directly.
|
||||
/*
|
||||
{
|
||||
hasPassword: true/false,
|
||||
requiresQuestionAnswer: true/false,
|
||||
enableReset: true/false,
|
||||
enablePasswordRetrieval: true/false,
|
||||
minPasswordLength: 10
|
||||
}
|
||||
*/
|
||||
var vm = this;
|
||||
|
||||
$scope.showReset = false;
|
||||
vm.$onInit = onInit;
|
||||
vm.$onDestroy = onDestroy;
|
||||
vm.doChange = doChange;
|
||||
vm.cancelChange = cancelChange;
|
||||
vm.showOldPass = showOldPass;
|
||||
vm.showCancelBtn = showCancelBtn;
|
||||
|
||||
//set defaults if they are not available
|
||||
if ($scope.config.disableToggle === undefined) {
|
||||
$scope.config.disableToggle = false;
|
||||
}
|
||||
if ($scope.config.hasPassword === undefined) {
|
||||
$scope.config.hasPassword = false;
|
||||
}
|
||||
if ($scope.config.enablePasswordRetrieval === undefined) {
|
||||
$scope.config.enablePasswordRetrieval = true;
|
||||
}
|
||||
if ($scope.config.requiresQuestionAnswer === undefined) {
|
||||
$scope.config.requiresQuestionAnswer = false;
|
||||
}
|
||||
//don't enable reset if it is new - that doesn't make sense
|
||||
if (isNew === "true") {
|
||||
$scope.config.enableReset = false;
|
||||
}
|
||||
else if ($scope.config.enableReset === undefined) {
|
||||
$scope.config.enableReset = true;
|
||||
}
|
||||
|
||||
if ($scope.config.minPasswordLength === undefined) {
|
||||
$scope.config.minPasswordLength = 0;
|
||||
}
|
||||
|
||||
//set the model defaults
|
||||
if (!angular.isObject($scope.passwordValues)) {
|
||||
//if it's not an object then just create a new one
|
||||
$scope.passwordValues = {
|
||||
newPassword: null,
|
||||
oldPassword: null,
|
||||
reset: null,
|
||||
answer: null
|
||||
};
|
||||
}
|
||||
else {
|
||||
//just reset the values
|
||||
var unsubscribe = [];
|
||||
|
||||
function resetModel(isNew) {
|
||||
//the model config will contain an object, if it does not we'll create defaults
|
||||
//NOTE: We will not support doing the password regex on the client side because the regex on the server side
|
||||
//based on the membership provider cannot always be ported to js from .net directly.
|
||||
/*
|
||||
{
|
||||
hasPassword: true/false,
|
||||
requiresQuestionAnswer: true/false,
|
||||
enableReset: true/false,
|
||||
enablePasswordRetrieval: true/false,
|
||||
minPasswordLength: 10
|
||||
}
|
||||
*/
|
||||
|
||||
vm.showReset = false;
|
||||
|
||||
//set defaults if they are not available
|
||||
if (vm.config.disableToggle === undefined) {
|
||||
vm.config.disableToggle = false;
|
||||
}
|
||||
if (vm.config.hasPassword === undefined) {
|
||||
vm.config.hasPassword = false;
|
||||
}
|
||||
if (vm.config.enablePasswordRetrieval === undefined) {
|
||||
vm.config.enablePasswordRetrieval = true;
|
||||
}
|
||||
if (vm.config.requiresQuestionAnswer === undefined) {
|
||||
vm.config.requiresQuestionAnswer = false;
|
||||
}
|
||||
//don't enable reset if it is new - that doesn't make sense
|
||||
if (isNew === "true") {
|
||||
vm.config.enableReset = false;
|
||||
}
|
||||
else if (vm.config.enableReset === undefined) {
|
||||
vm.config.enableReset = true;
|
||||
}
|
||||
|
||||
if (vm.config.minPasswordLength === undefined) {
|
||||
vm.config.minPasswordLength = 0;
|
||||
}
|
||||
|
||||
//set the model defaults
|
||||
if (!angular.isObject(vm.passwordValues)) {
|
||||
//if it's not an object then just create a new one
|
||||
vm.passwordValues = {
|
||||
newPassword: null,
|
||||
oldPassword: null,
|
||||
reset: null,
|
||||
answer: null
|
||||
};
|
||||
}
|
||||
else {
|
||||
//just reset the values
|
||||
|
||||
if (!isNew) {
|
||||
//if it is new, then leave the generated pass displayed
|
||||
vm.passwordValues.newPassword = null;
|
||||
vm.passwordValues.oldPassword = null;
|
||||
}
|
||||
vm.passwordValues.reset = null;
|
||||
vm.passwordValues.answer = null;
|
||||
}
|
||||
|
||||
//the value to compare to match passwords
|
||||
if (!isNew) {
|
||||
vm.passwordValues.confirm = "";
|
||||
}
|
||||
else if (vm.passwordValues.newPassword && vm.passwordValues.newPassword.length > 0) {
|
||||
//if it is new and a new password has been set, then set the confirm password too
|
||||
vm.passwordValues.confirm = vm.passwordValues.newPassword;
|
||||
}
|
||||
|
||||
if (!isNew) {
|
||||
//if it is new, then leave the generated pass displayed
|
||||
$scope.passwordValues.newPassword = null;
|
||||
$scope.passwordValues.oldPassword = null;
|
||||
}
|
||||
$scope.passwordValues.reset = null;
|
||||
$scope.passwordValues.answer = null;
|
||||
}
|
||||
|
||||
//the value to compare to match passwords
|
||||
if (!isNew) {
|
||||
$scope.passwordValues.confirm = "";
|
||||
}
|
||||
else if ($scope.passwordValues.newPassword && $scope.passwordValues.newPassword.length > 0) {
|
||||
//if it is new and a new password has been set, then set the confirm password too
|
||||
$scope.passwordValues.confirm = $scope.passwordValues.newPassword;
|
||||
}
|
||||
//when the scope is destroyed we need to unsubscribe
|
||||
function onDestroy() {
|
||||
for (var u in unsubscribe) {
|
||||
unsubscribe[u]();
|
||||
}
|
||||
}
|
||||
|
||||
function onInit() {
|
||||
//listen for the saved event, when that occurs we'll
|
||||
//change to changing = false;
|
||||
unsubscribe.push($scope.$on("formSubmitted", function () {
|
||||
if (vm.config.disableToggle === false) {
|
||||
vm.changing = false;
|
||||
}
|
||||
}));
|
||||
|
||||
unsubscribe.push($scope.$on("formSubmitting", function () {
|
||||
//if there was a previously generated password displaying, clear it
|
||||
if (vm.changing && vm.passwordValues) {
|
||||
vm.passwordValues.generatedPassword = null;
|
||||
}
|
||||
else if (!vm.changing) {
|
||||
//we are not changing, so the model needs to be null
|
||||
vm.passwordValues = null;
|
||||
}
|
||||
}));
|
||||
|
||||
resetModel(vm.isNew);
|
||||
|
||||
//if there is no password saved for this entity , it must be new so we do not allow toggling of the change password, it is always there
|
||||
//with validators turned on.
|
||||
vm.changing = vm.config.disableToggle === true || !vm.config.hasPassword;
|
||||
|
||||
//we're not currently changing so set the model to null
|
||||
if (!vm.changing) {
|
||||
vm.passwordValues = null;
|
||||
}
|
||||
}
|
||||
|
||||
function doChange() {
|
||||
resetModel();
|
||||
vm.changing = true;
|
||||
//if there was a previously generated password displaying, clear it
|
||||
vm.passwordValues.generatedPassword = null;
|
||||
vm.passwordValues.confirm = null;
|
||||
};
|
||||
|
||||
function cancelChange() {
|
||||
vm.changing = false;
|
||||
//set model to null
|
||||
vm.passwordValues = null;
|
||||
};
|
||||
|
||||
function showOldPass() {
|
||||
return vm.config.hasPassword &&
|
||||
!vm.config.allowManuallyChangingPassword &&
|
||||
!vm.config.enablePasswordRetrieval && !vm.showReset;
|
||||
};
|
||||
|
||||
// TODO: I don't think we need this or the cancel button, this can be up to the editor rendering this component
|
||||
function showCancelBtn() {
|
||||
return vm.config.disableToggle !== true && vm.config.hasPassword;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
resetModel($scope.isNew);
|
||||
|
||||
//if there is no password saved for this entity , it must be new so we do not allow toggling of the change password, it is always there
|
||||
//with validators turned on.
|
||||
$scope.changing = $scope.config.disableToggle === true || !$scope.config.hasPassword;
|
||||
|
||||
//we're not currently changing so set the model to null
|
||||
if (!$scope.changing) {
|
||||
$scope.passwordValues = null;
|
||||
}
|
||||
|
||||
$scope.doChange = function () {
|
||||
resetModel();
|
||||
$scope.changing = true;
|
||||
//if there was a previously generated password displaying, clear it
|
||||
$scope.passwordValues.generatedPassword = null;
|
||||
$scope.passwordValues.confirm = null;
|
||||
var component = {
|
||||
templateUrl: 'views/components/users/change-password.html',
|
||||
controller: ChangePasswordController,
|
||||
controllerAs: 'vm',
|
||||
bindings: {
|
||||
isNew: "<",
|
||||
passwordValues: "=", //TODO: Do we need bi-directional vals?
|
||||
config: "=" //TODO: Do we need bi-directional vals?
|
||||
//TODO: Do we need callbacks?
|
||||
}
|
||||
};
|
||||
|
||||
$scope.cancelChange = function () {
|
||||
$scope.changing = false;
|
||||
//set model to null
|
||||
$scope.passwordValues = null;
|
||||
};
|
||||
|
||||
var unsubscribe = [];
|
||||
|
||||
//listen for the saved event, when that occurs we'll
|
||||
//change to changing = false;
|
||||
unsubscribe.push($scope.$on("formSubmitted", function () {
|
||||
if ($scope.config.disableToggle === false) {
|
||||
$scope.changing = false;
|
||||
}
|
||||
}));
|
||||
unsubscribe.push($scope.$on("formSubmitting", function () {
|
||||
//if there was a previously generated password displaying, clear it
|
||||
if ($scope.changing && $scope.passwordValues) {
|
||||
$scope.passwordValues.generatedPassword = null;
|
||||
}
|
||||
else if (!$scope.changing) {
|
||||
//we are not changing, so the model needs to be null
|
||||
$scope.passwordValues = null;
|
||||
}
|
||||
}));
|
||||
|
||||
//when the scope is destroyed we need to unsubscribe
|
||||
$scope.$on('$destroy', function () {
|
||||
for (var u in unsubscribe) {
|
||||
unsubscribe[u]();
|
||||
}
|
||||
});
|
||||
|
||||
$scope.showOldPass = function () {
|
||||
return $scope.config.hasPassword &&
|
||||
!$scope.config.allowManuallyChangingPassword &&
|
||||
!$scope.config.enablePasswordRetrieval && !$scope.showReset;
|
||||
};
|
||||
|
||||
// TODO: I don't think we need this or the cancel button, this can be up to the editor rendering this directive
|
||||
$scope.showCancelBtn = function () {
|
||||
return $scope.config.disableToggle !== true && $scope.config.hasPassword;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
function ChangePasswordDirective() {
|
||||
|
||||
var directive = {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
templateUrl: 'views/components/users/change-password.html',
|
||||
controller: 'Umbraco.Editors.Users.ChangePasswordDirectiveController',
|
||||
scope: {
|
||||
isNew: "=?",
|
||||
passwordValues: "=",
|
||||
config: "="
|
||||
}
|
||||
};
|
||||
|
||||
return directive;
|
||||
|
||||
}
|
||||
|
||||
angular.module('umbraco.directives').controller('Umbraco.Editors.Users.ChangePasswordDirectiveController', ChangePasswordController);
|
||||
angular.module('umbraco.directives').directive('changePassword', ChangePasswordDirective);
|
||||
angular.module('umbraco.directives').component('changePassword', component);
|
||||
|
||||
|
||||
})();
|
||||
|
||||
@@ -104,21 +104,26 @@ function entityResource($q, $http, umbRequestHelper) {
|
||||
*
|
||||
* @param {Int} id Id of node to return the public url to
|
||||
* @param {string} type Object type name
|
||||
* @param {string} culture Culture
|
||||
* @returns {Promise} resourcePromise object containing the url.
|
||||
*
|
||||
*/
|
||||
getUrl: function (id, type) {
|
||||
getUrl: function (id, type, culture) {
|
||||
|
||||
if (id === -1 || id === "-1") {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (!culture) {
|
||||
culture = "";
|
||||
}
|
||||
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"entityApiBaseUrl",
|
||||
"GetUrl",
|
||||
[{ id: id }, {type: type }])),
|
||||
[{ id: id }, {type: type }, {culture: culture }])),
|
||||
'Failed to retrieve url for id:' + id);
|
||||
},
|
||||
|
||||
@@ -133,7 +138,7 @@ function entityResource($q, $http, umbRequestHelper) {
|
||||
* ##usage
|
||||
* <pre>
|
||||
* //get media by id
|
||||
* entityResource.getEntityById(0, "Media")
|
||||
* entityResource.getById(0, "Media")
|
||||
* .then(function(ent) {
|
||||
* var myDoc = ent;
|
||||
* alert('its here!');
|
||||
@@ -204,7 +209,7 @@ function entityResource($q, $http, umbRequestHelper) {
|
||||
* ##usage
|
||||
* <pre>
|
||||
* //Get templates for ids
|
||||
* entityResource.getEntitiesByIds( [1234,2526,28262], "Template")
|
||||
* entityResource.getByIds( [1234,2526,28262], "Template")
|
||||
* .then(function(templateArray) {
|
||||
* var myDoc = contentArray;
|
||||
* alert('they are here!');
|
||||
|
||||
@@ -7,64 +7,44 @@
|
||||
**/
|
||||
function logViewerResource($q, $http, umbRequestHelper) {
|
||||
|
||||
/**
|
||||
* verb => 'get', 'post',
|
||||
* method => API method to call
|
||||
* params => additional data to send
|
||||
* error => error message when things go wrong...
|
||||
*/
|
||||
const request = (verb, method, params, error) =>
|
||||
umbRequestHelper.resourcePromise(
|
||||
(verb === 'GET' ?
|
||||
$http.get(umbRequestHelper.getApiUrl("logViewerApiBaseUrl", method) + (params ? params : '')) :
|
||||
$http.post(umbRequestHelper.getApiUrl("logViewerApiBaseUrl", method), params)),
|
||||
error);
|
||||
|
||||
//the factory object returned
|
||||
return {
|
||||
|
||||
getNumberOfErrors: function (startDate, endDate) {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"logViewerApiBaseUrl",
|
||||
"GetNumberOfErrors")+ '?startDate='+startDate+ '&endDate='+ endDate ),
|
||||
'Failed to retrieve number of errors in logs');
|
||||
},
|
||||
getNumberOfErrors: (startDate, endDate) =>
|
||||
request('GET', 'GetNumberOfErrors', '?startDate=' + startDate + '&endDate=' + endDate, 'Failed to retrieve number of errors in logs'),
|
||||
|
||||
getLogLevelCounts: function (startDate, endDate) {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"logViewerApiBaseUrl",
|
||||
"GetLogLevelCounts")+ '?startDate='+startDate+ '&endDate='+ endDate ),
|
||||
'Failed to retrieve log level counts');
|
||||
},
|
||||
getLogLevel: () =>
|
||||
request('GET', 'GetLogLevel', null, 'Failed to retrieve log level'),
|
||||
|
||||
getMessageTemplates: function (startDate, endDate) {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"logViewerApiBaseUrl",
|
||||
"GetMessageTemplates")+ '?startDate='+startDate+ '&endDate='+ endDate ),
|
||||
'Failed to retrieve log templates');
|
||||
},
|
||||
getLogLevelCounts: (startDate, endDate) =>
|
||||
request('GET', 'GetLogLevelCounts', '?startDate=' + startDate + '&endDate=' + endDate, 'Failed to retrieve log level counts'),
|
||||
|
||||
getSavedSearches: function () {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"logViewerApiBaseUrl",
|
||||
"GetSavedSearches")),
|
||||
'Failed to retrieve saved searches');
|
||||
},
|
||||
getMessageTemplates: (startDate, endDate) =>
|
||||
request('GET', 'GetMessageTemplates', '?startDate=' + startDate + '&endDate=' + endDate, 'Failed to retrieve log templates'),
|
||||
|
||||
postSavedSearch: function (name, query) {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.post(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"logViewerApiBaseUrl",
|
||||
"PostSavedSearch"), { 'name': name, 'query': query }),
|
||||
'Failed to add new saved search');
|
||||
},
|
||||
getSavedSearches: () =>
|
||||
request('GET', 'GetSavedSearches', null, 'Failed to retrieve saved searches'),
|
||||
|
||||
deleteSavedSearch: function (name, query) {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.post(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"logViewerApiBaseUrl",
|
||||
"DeleteSavedSearch"), { 'name': name, 'query': query }),
|
||||
'Failed to delete saved search');
|
||||
},
|
||||
postSavedSearch: (name, query) =>
|
||||
request('POST', 'PostSavedSearch', { 'name': name, 'query': query }, 'Failed to add new saved search'),
|
||||
|
||||
getLogs: function (options) {
|
||||
deleteSavedSearch: (name, query) =>
|
||||
request('POST', 'DeleteSavedSearch', { 'name': name, 'query': query }, 'Failed to delete saved search'),
|
||||
|
||||
getLogs: options => {
|
||||
|
||||
var defaults = {
|
||||
pageSize: 100,
|
||||
@@ -83,7 +63,6 @@ function logViewerResource($q, $http, umbRequestHelper) {
|
||||
//now copy back to the options we will use
|
||||
options = defaults;
|
||||
|
||||
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
@@ -93,15 +72,8 @@ function logViewerResource($q, $http, umbRequestHelper) {
|
||||
'Failed to retrieve common log messages');
|
||||
},
|
||||
|
||||
canViewLogs: function (startDate, endDate) {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"logViewerApiBaseUrl",
|
||||
"GetCanViewLogs") + '?startDate='+startDate+ '&endDate='+ endDate ),
|
||||
'Failed to retrieve state if logs can be viewed');
|
||||
}
|
||||
|
||||
canViewLogs: (startDate, endDate) =>
|
||||
request('GET', 'GetCanViewLogs', '?startDate=' + startDate + '&endDate=' + endDate, 'Failed to retrieve state if logs can be viewed')
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name umbraco.services.contentEditingHelper
|
||||
@@ -53,6 +53,12 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt
|
||||
}
|
||||
if (args.showNotifications === undefined) {
|
||||
args.showNotifications = true;
|
||||
}
|
||||
// needed for infinite editing to create new items
|
||||
if (args.create === undefined) {
|
||||
if ($routeParams.create) {
|
||||
args.create = true;
|
||||
}
|
||||
}
|
||||
if (args.softRedirect === undefined) {
|
||||
//when true, the url will change but it won't actually re-route
|
||||
@@ -61,6 +67,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt
|
||||
args.softRedirect = false;
|
||||
}
|
||||
|
||||
|
||||
var self = this;
|
||||
|
||||
//we will use the default one for content if not specified
|
||||
@@ -68,7 +75,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt
|
||||
|
||||
if (formHelper.submitForm({ scope: args.scope, action: args.action })) {
|
||||
|
||||
return args.saveMethod(args.content, $routeParams.create, fileManager.getFiles(), args.showNotifications)
|
||||
return args.saveMethod(args.content, args.create, fileManager.getFiles(), args.showNotifications)
|
||||
.then(function (data) {
|
||||
|
||||
formHelper.resetForm({ scope: args.scope });
|
||||
@@ -484,7 +491,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt
|
||||
var savedVariants = [];
|
||||
if (origContent.variants) {
|
||||
isContent = true;
|
||||
//it's contnet so assign the variants as they exist
|
||||
//it's content so assign the variants as they exist
|
||||
origVariants = origContent.variants;
|
||||
savedVariants = savedContent.variants;
|
||||
}
|
||||
@@ -510,7 +517,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt
|
||||
|
||||
//special case for content, don't sync this variant if it wasn't tagged
|
||||
//for saving in the first place
|
||||
if (!origVariant.save) {
|
||||
if (isContent && !origVariant.save) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -261,7 +261,7 @@ When building a custom infinite editor view you can use the same components as a
|
||||
*/
|
||||
unbindKeyboardShortcuts();
|
||||
|
||||
// set flag so we know when the editor is open in "infinie mode"
|
||||
// set flag so we know when the editor is open in "infinite mode"
|
||||
editor.infiniteMode = true;
|
||||
|
||||
editors.push(editor);
|
||||
@@ -331,13 +331,17 @@ When building a custom infinite editor view you can use the same components as a
|
||||
* @methodOf umbraco.services.editorService
|
||||
*
|
||||
* @description
|
||||
* Opens a media editor in infinite editing, the submit callback returns the updated content item
|
||||
* Opens a content editor in infinite editing, the submit callback returns the updated content item
|
||||
* @param {Object} editor rendering options
|
||||
* @param {String} editor.id The id of the content item
|
||||
* @param {Boolean} editor.create Create new content item
|
||||
* @param {Function} editor.submit Callback function when the publish and close button is clicked. Returns the editor model object
|
||||
* @param {Function} editor.close Callback function when the close button is clicked.
|
||||
*
|
||||
* @param {String} editor.parentId If editor.create is true, provide parentId for the creation of the content item
|
||||
* @param {String} editor.documentTypeAlias If editor.create is true, provide document type alias for the creation of the content item
|
||||
* @param {Boolean} editor.allowSaveAndClose If editor is being used in infinite editing allows the editor to close when the save action is performed
|
||||
* @param {Boolean} editor.allowPublishAndClose If editor is being used in infinite editing allows the editor to close when the publish action is performed
|
||||
*
|
||||
* @returns {Object} editor object
|
||||
*/
|
||||
function contentEditor(editor) {
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
@import "components/umb-editor-navigation-item.less";
|
||||
@import "components/umb-editor-sub-views.less";
|
||||
@import "components/editor/subheader/umb-editor-sub-header.less";
|
||||
@import "components/umb-flatpickr.less";
|
||||
@import "components/umb-date-time-picker.less";
|
||||
@import "components/umb-grid-selector.less";
|
||||
@import "components/umb-child-selector.less";
|
||||
@import "components/umb-group-builder.less";
|
||||
@@ -124,6 +124,7 @@
|
||||
@import "components/umb-form-check.less";
|
||||
@import "components/umb-locked-field.less";
|
||||
@import "components/umb-tabs.less";
|
||||
@import "components/umb-loader.less";
|
||||
@import "components/umb-load-indicator.less";
|
||||
@import "components/umb-breadcrumbs.less";
|
||||
@import "components/umb-media-grid.less";
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
.umb-tour__loader {
|
||||
background: @white;
|
||||
z-index: @zindexTourModal;
|
||||
.umb-loader-wrapper.umb-tour__loader {
|
||||
margin: 0;
|
||||
position: fixed;
|
||||
height: 5px;
|
||||
z-index: @zindexTourModal;
|
||||
|
||||
.umb-loader {
|
||||
background-color: @white;
|
||||
height: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.umb-tour__pulse {
|
||||
|
||||
@@ -74,8 +74,11 @@
|
||||
}
|
||||
|
||||
.umb-editor-header__back {
|
||||
color: @gray-6;
|
||||
margin-bottom: 1px;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: @gray-6;
|
||||
margin: 0 0 1px 0;
|
||||
padding: 0;
|
||||
transition: color 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
@@ -373,14 +376,3 @@ a.umb-variant-switcher__toggle {
|
||||
margin-right: auto;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
/* Confirm */
|
||||
.umb-editor-confirm {
|
||||
background-color: @white;
|
||||
padding: 20px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 10;
|
||||
box-shadow: 0 -3px 12px 0px rgba(0,0,0,0.16);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user