Merge remote-tracking branch 'origin/v8/contrib' into v8/bugfix/removing-unnecessary-readerwriterlock
This commit is contained in:
14
.github/CONTRIBUTING.md
vendored
14
.github/CONTRIBUTING.md
vendored
@@ -16,6 +16,7 @@ This project and everyone participating in it, is governed by the [our Code of C
|
||||
|
||||
[Contributing code changes](#contributing-code-changes)
|
||||
* [Guidelines for contributions we welcome](#guidelines-for-contributions-we-welcome)
|
||||
* [Ownership and copyright](#ownership-and-copyright)
|
||||
* [What can I start with?](#what-can-i-start-with)
|
||||
* [How do I begin?](#how-do-i-begin)
|
||||
* [Pull requests](#pull-requests)
|
||||
@@ -44,6 +45,17 @@ We have [documented what we consider small and large changes](CONTRIBUTION_GUIDE
|
||||
|
||||
Remember, it is always worth working on an issue from the `Up for grabs` list or even asking for some feedback before you send us a PR. This way, your PR will not be closed as unwanted.
|
||||
|
||||
#### Ownership and copyright
|
||||
|
||||
It is your responsibility to make sure that you're allowed to share the code you're providing us.
|
||||
For example, you should have permission from your employer or customer to share code.
|
||||
|
||||
Similarly, if your contribution is copied or adapted from somewhere else, make sure that the license allows you to reuse that for a contribution to Umbraco-CMS.
|
||||
|
||||
If you're not sure, leave a note on your contribution and we will be happy to guide you.
|
||||
|
||||
When your contribution has been accepted, it will be [MIT licensed](https://github.com/umbraco/Umbraco-CMS/blob/v8/contrib/LICENSE.md) from that time onwards.
|
||||
|
||||
### What can I start with?
|
||||
|
||||
Unsure where to begin contributing to Umbraco? You can start by looking through [these `Up for grabs` issues](https://github.com/umbraco/Umbraco-CMS/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3Acommunity%2Fup-for-grabs+)
|
||||
@@ -124,7 +136,7 @@ You can get in touch with [the core contributors team](#the-core-contributors-te
|
||||
|
||||
In order to build the Umbraco source code locally, first make sure you have the following installed.
|
||||
|
||||
* [Visual Studio 2019 v16.3+ (with .NET Core 3.0)](https://visualstudio.microsoft.com/vs/)
|
||||
* [Visual Studio 2019 v16.8+ (with .NET Core 3.0)](https://visualstudio.microsoft.com/vs/)
|
||||
* [Node.js v10+](https://nodejs.org/en/download/)
|
||||
* npm v6.4.1+ (installed with Node.js)
|
||||
* [Git command line](https://git-scm.com/download/)
|
||||
|
||||
52
.github/ISSUE_TEMPLATE/01_bug_report.yml
vendored
Normal file
52
.github/ISSUE_TEMPLATE/01_bug_report.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
name: 🐛 Bug Report
|
||||
description: "File a bug report, if you've discovered a problem in Umbraco."
|
||||
labels: "type/bug"
|
||||
body:
|
||||
- type: input
|
||||
id: "version"
|
||||
attributes:
|
||||
label: "Which *exact* Umbraco version are you using? For example: 8.13.1 - don't just write v8"
|
||||
description: "Use the help icon in the Umbraco backoffice to find the version you're using"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: "summary"
|
||||
attributes:
|
||||
label: "Bug summary"
|
||||
description: "Write a short summary of the bug."
|
||||
placeholder: >
|
||||
Try to pinpoint it as much as possible.
|
||||
|
||||
Try to state the actual problem, and not just what you think the solution might be.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Specifics"
|
||||
id: "specifics"
|
||||
description: "Remember that you can format code and logs nicely with the `<>` button"
|
||||
placeholder: >
|
||||
Mention the URL where this bug occurs, if applicable
|
||||
|
||||
Please mention if you've checked it in other browsers as well
|
||||
|
||||
Please include full error messages and screenshots, gifs or mp4 videos if applicable
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Steps to reproduce"
|
||||
id: "reproduction"
|
||||
description: "How can we reproduce the problem on a clean Umbraco install?"
|
||||
placeholder: >
|
||||
Please include screenshots, gifs or mp4 videos if applicable
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Expected result / actual result"
|
||||
id: "result"
|
||||
description: "What did you expect that would happen on your Umbraco site and what is the actual result of the above steps?"
|
||||
placeholder: >
|
||||
Describe the intended/desired outcome after you did the steps mentioned.
|
||||
|
||||
Describe the behaviour of the bug
|
||||
66
.github/ISSUE_TEMPLATE/1_Bug.md
vendored
66
.github/ISSUE_TEMPLATE/1_Bug.md
vendored
@@ -1,66 +0,0 @@
|
||||
---
|
||||
name: 🐛 Bug Report
|
||||
about: File a bug report, if you've discovered a problem in Umbraco.
|
||||
---
|
||||
|
||||
A brief description of the issue goes here.
|
||||
|
||||
<!--
|
||||
|
||||
Please fill out the rest of the details in the issue template below.
|
||||
The more details you can give us, the easier it will be for us
|
||||
to determine the cause of a problem.
|
||||
|
||||
-->
|
||||
|
||||
## Umbraco version
|
||||
|
||||
I am seeing this issue on Umbraco version: <!-- please note the version here -->
|
||||
|
||||
|
||||
Reproduction
|
||||
------------
|
||||
|
||||
If you're filing a bug, please describe how to reproduce it. Include as much
|
||||
relevant information as possible, such as:
|
||||
|
||||
### Bug summary
|
||||
|
||||
<!--
|
||||
* Write a short summary of the bug
|
||||
* Try to pinpoint it as much as possible
|
||||
* Try to state the _actual problem_, and not just what you _think_ the
|
||||
solution might be.
|
||||
-->
|
||||
|
||||
### Specifics
|
||||
|
||||
<!--
|
||||
* Mention the URL where this bug occurs, if applicable
|
||||
* What version of Umbraco are you using (down to the very last digit!)
|
||||
* What browser and version you are using
|
||||
* Please mention if you've checked it in other browsers as well
|
||||
* Please include *full error messages* and *screenshots* if possible
|
||||
-->
|
||||
|
||||
### Steps to reproduce
|
||||
|
||||
<!--
|
||||
* Clearly mention the steps to reproduce the bug
|
||||
-->
|
||||
|
||||
### Expected result
|
||||
|
||||
<!--
|
||||
* What did you _expect_ that would happen on your Umbraco site?
|
||||
* Describe the intended/desired outcome after you did the steps mentioned.
|
||||
-->
|
||||
|
||||
### Actual result
|
||||
|
||||
<!--
|
||||
* What is the actual result of the above steps?
|
||||
* Describe the behaviour of the bug
|
||||
* Please, please include **error messages** and screenshots. They might mean
|
||||
nothing to you, but they are _very_ helpful to us.
|
||||
-->
|
||||
31
.github/ISSUE_TEMPLATE/2_Feature_request.md
vendored
31
.github/ISSUE_TEMPLATE/2_Feature_request.md
vendored
@@ -1,31 +0,0 @@
|
||||
---
|
||||
name: 📮 Feature Request
|
||||
about: Open a feature request, if you want to propose a new feature.
|
||||
---
|
||||
|
||||
A brief description of your feature request goes here.
|
||||
|
||||
|
||||
<!--
|
||||
If you want to discuss the feature you're imagining, please make sure to keep
|
||||
those discussions on the forum at https://our.umbraco.com/ (choose the
|
||||
category "Contributing to Umbraco", Umbraco HQ follows all new topics there).
|
||||
|
||||
Once you've come to a conclusion in the discussion, feel free to propose the
|
||||
new feature here.
|
||||
-->
|
||||
|
||||
How can you help?
|
||||
-------------------------------
|
||||
|
||||
<!--
|
||||
The resources (read: available time and effort) of Umbraco's core team are
|
||||
limited.
|
||||
|
||||
If we can not work on your suggestion, please don't take it personally. Most
|
||||
likely, it's either:
|
||||
|
||||
- We think your idea is valid, but we can't find the time to work on it.
|
||||
- Your idea might be better suited as a package, if it's not suitable for
|
||||
the majority of users.
|
||||
-->
|
||||
65
.github/ISSUE_TEMPLATE/3_BugNetCore.md
vendored
65
.github/ISSUE_TEMPLATE/3_BugNetCore.md
vendored
@@ -1,65 +0,0 @@
|
||||
---
|
||||
name: 🌟 .Net Core Bug Report
|
||||
about: For bugs specifically for the upcoming .NET Core release of Umbraco, don't use this if you're working with Umbraco version 7 or 8
|
||||
labels: project/net-core
|
||||
---
|
||||
|
||||
ℹ️ If this bug **also** appears on the current version 8 of Umbraco then please [report it as a regular bug](https://github.com/umbraco/Umbraco-CMS/issues/new?template=1_Bug.md), fixes in version 8 will be merged to the .NET Core version.
|
||||
|
||||
A brief description of the issue goes here.
|
||||
|
||||
<!--
|
||||
|
||||
Please fill out the rest of the details in the issue template below.
|
||||
The more details you can give us, the easier it will be for us
|
||||
to determine the cause of a problem.
|
||||
|
||||
-->
|
||||
|
||||
|
||||
Reproduction
|
||||
------------
|
||||
|
||||
If you're filing a bug, please describe how to reproduce it. Include as much
|
||||
relevant information as possible, such as:
|
||||
|
||||
### Bug summary
|
||||
|
||||
<!--
|
||||
* Write a short summary of the bug
|
||||
* Try to pinpoint it as much as possible
|
||||
* Try to state the _actual problem_, and not just what you _think_ the
|
||||
solution might be.
|
||||
-->
|
||||
|
||||
### Specifics
|
||||
|
||||
<!--
|
||||
* Mention the URL where this bug occurs, if applicable
|
||||
* What version of Umbraco are you using (down to the very last digit!)
|
||||
* What browser and version you are using
|
||||
* Please mention if you've checked it in other browsers as well
|
||||
* Please include *full error messages* and *screenshots* if possible
|
||||
-->
|
||||
|
||||
### Steps to reproduce
|
||||
|
||||
<!--
|
||||
* Clearly mention the steps to reproduce the bug
|
||||
-->
|
||||
|
||||
### Expected result
|
||||
|
||||
<!--
|
||||
* What did you _expect_ that would happen on your Umbraco site?
|
||||
* Describe the intended/desired outcome after you did the steps mentioned.
|
||||
-->
|
||||
|
||||
### Actual result
|
||||
|
||||
<!--
|
||||
* What is the actual result of the above steps?
|
||||
* Describe the behaviour of the bug
|
||||
* Please, please include **error messages** and screenshots. They might mean
|
||||
nothing to you, but they are _very_ helpful to us.
|
||||
-->
|
||||
7
.github/ISSUE_TEMPLATE/config.yml
vendored
7
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +1,8 @@
|
||||
blank_issues_enabled: true
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 💡 Features and ideas
|
||||
url: https://github.com/umbraco/Umbraco-CMS/discussions/new?category=features-and-ideas
|
||||
about: Start a new discussion when you have ideas or feature requests, eventually discussions can turn into plans
|
||||
- name: ⁉️ Support Question
|
||||
url: https://our.umbraco.com
|
||||
about: This issue tracker is NOT meant for support questions. If you have a question, please join us on the forum.
|
||||
@@ -8,4 +11,4 @@ contact_links:
|
||||
about: Documentation issues should be reported on the Umbraco documentation repository.
|
||||
- name: 🔐 Security Issue
|
||||
url: https://umbraco.com/about-us/trust-center/security-and-umbraco/how-to-report-a-vulnerability-in-umbraco/
|
||||
about: Discovered a Security Issue in Umbraco?
|
||||
about: Discovered a Security Issue in Umbraco?
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<dependency id="ClientDependency" version="[1.9.9,1.999999)" />
|
||||
<dependency id="ClientDependency-Mvc5" version="[1.9.3,1.999999)" />
|
||||
<dependency id="CSharpTest.Net.Collections" version="[14.906.1403.1082,14.999999)" />
|
||||
<dependency id="Examine" version="[1.1.0,1.999999)" />
|
||||
<dependency id="Examine" version="[1.2.0,1.999999)" />
|
||||
<dependency id="HtmlAgilityPack" version="[1.8.14,1.999999)" />
|
||||
<dependency id="ImageProcessor" version="[2.7.0.100,2.999999)" />
|
||||
<dependency id="LightInject.Mvc" version="[2.0.0,2.999999)" />
|
||||
@@ -43,6 +43,8 @@
|
||||
<dependency id="Microsoft.Owin.Security.OAuth" version="[4.0.1,4.999999)" />
|
||||
<dependency id="System.Threading.Tasks.Dataflow" version="[4.9.0,4.999999)" />
|
||||
<dependency id="System.Text.Encoding.CodePages" version="[4.7.1,4.999999)" />
|
||||
<dependency id="MessagePack" version="[2.2.85,2.999999)" />
|
||||
<dependency id="K4os.Compression.LZ4" version="[1.1.11,1.999999)" />
|
||||
|
||||
</group>
|
||||
|
||||
|
||||
@@ -18,5 +18,5 @@ using System.Resources;
|
||||
[assembly: AssemblyVersion("8.0.0")]
|
||||
|
||||
// these are FYI and changed automatically
|
||||
[assembly: AssemblyFileVersion("8.13.0")]
|
||||
[assembly: AssemblyInformationalVersion("8.13.0-rc")]
|
||||
[assembly: AssemblyFileVersion("8.15.0")]
|
||||
[assembly: AssemblyInformationalVersion("8.15.0")]
|
||||
|
||||
@@ -17,5 +17,8 @@
|
||||
public const string UserAllMediaStartNodesPrefix = "AllMediaStartNodes";
|
||||
public const string UserMediaStartNodePathsPrefix = "MediaStartNodePaths";
|
||||
public const string UserContentStartNodePathsPrefix = "ContentStartNodePaths";
|
||||
|
||||
public const string ContentRecycleBinCacheKey = "recycleBin_content";
|
||||
public const string MediaRecycleBinCacheKey = "recycleBin_media";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Umbraco.Core.Cache
|
||||
internal class DefaultRepositoryCachePolicy<TEntity, TId> : RepositoryCachePolicyBase<TEntity, TId>
|
||||
where TEntity : class, IEntity
|
||||
{
|
||||
private static readonly TEntity[] EmptyEntities = new TEntity[0]; // const
|
||||
private static readonly TEntity[] s_emptyEntities = new TEntity[0]; // const
|
||||
private readonly RepositoryCachePolicyOptions _options;
|
||||
|
||||
public DefaultRepositoryCachePolicy(IAppPolicyCache cache, IScopeAccessor scopeAccessor, RepositoryCachePolicyOptions options)
|
||||
@@ -29,16 +29,24 @@ namespace Umbraco.Core.Cache
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
protected string GetEntityCacheKey(object id)
|
||||
protected string GetEntityCacheKey(int id) => EntityTypeCacheKey + id;
|
||||
|
||||
protected string GetEntityCacheKey(TId id)
|
||||
{
|
||||
if (id == null) throw new ArgumentNullException(nameof(id));
|
||||
return GetEntityTypeCacheKey() + id;
|
||||
if (EqualityComparer<TId>.Default.Equals(id, default))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (typeof(TId).IsValueType)
|
||||
{
|
||||
return EntityTypeCacheKey + id;
|
||||
}
|
||||
|
||||
return EntityTypeCacheKey + id.ToString().ToUpperInvariant();
|
||||
}
|
||||
|
||||
protected string GetEntityTypeCacheKey()
|
||||
{
|
||||
return $"uRepo_{typeof (TEntity).Name}_";
|
||||
}
|
||||
protected string EntityTypeCacheKey { get; } = $"uRepo_{typeof(TEntity).Name}_";
|
||||
|
||||
protected virtual void InsertEntity(string cacheKey, TEntity entity)
|
||||
{
|
||||
@@ -52,7 +60,7 @@ namespace Umbraco.Core.Cache
|
||||
// getting all of them, and finding nothing.
|
||||
// if we can cache a zero count, cache an empty array,
|
||||
// for as long as the cache is not cleared (no expiration)
|
||||
Cache.Insert(GetEntityTypeCacheKey(), () => EmptyEntities);
|
||||
Cache.Insert(EntityTypeCacheKey, () => s_emptyEntities);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -81,7 +89,7 @@ namespace Umbraco.Core.Cache
|
||||
}
|
||||
|
||||
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
|
||||
Cache.Clear(GetEntityTypeCacheKey());
|
||||
Cache.Clear(EntityTypeCacheKey);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -91,7 +99,7 @@ namespace Umbraco.Core.Cache
|
||||
Cache.Clear(GetEntityCacheKey(entity.Id));
|
||||
|
||||
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
|
||||
Cache.Clear(GetEntityTypeCacheKey());
|
||||
Cache.Clear(EntityTypeCacheKey);
|
||||
|
||||
throw;
|
||||
}
|
||||
@@ -113,7 +121,7 @@ namespace Umbraco.Core.Cache
|
||||
}
|
||||
|
||||
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
|
||||
Cache.Clear(GetEntityTypeCacheKey());
|
||||
Cache.Clear(EntityTypeCacheKey);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -123,7 +131,7 @@ namespace Umbraco.Core.Cache
|
||||
Cache.Clear(GetEntityCacheKey(entity.Id));
|
||||
|
||||
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
|
||||
Cache.Clear(GetEntityTypeCacheKey());
|
||||
Cache.Clear(EntityTypeCacheKey);
|
||||
|
||||
throw;
|
||||
}
|
||||
@@ -144,7 +152,7 @@ namespace Umbraco.Core.Cache
|
||||
var cacheKey = GetEntityCacheKey(entity.Id);
|
||||
Cache.Clear(cacheKey);
|
||||
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
|
||||
Cache.Clear(GetEntityTypeCacheKey());
|
||||
Cache.Clear(EntityTypeCacheKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +203,7 @@ namespace Umbraco.Core.Cache
|
||||
else
|
||||
{
|
||||
// get everything we have
|
||||
var entities = Cache.GetCacheItemsByKeySearch<TEntity>(GetEntityTypeCacheKey())
|
||||
var entities = Cache.GetCacheItemsByKeySearch<TEntity>(EntityTypeCacheKey)
|
||||
.ToArray(); // no need for null checks, we are not caching nulls
|
||||
|
||||
if (entities.Length > 0)
|
||||
@@ -218,7 +226,7 @@ namespace Umbraco.Core.Cache
|
||||
{
|
||||
// if none of them were in the cache
|
||||
// and we allow zero count - check for the special (empty) entry
|
||||
var empty = Cache.GetCacheItem<TEntity[]>(GetEntityTypeCacheKey());
|
||||
var empty = Cache.GetCacheItem<TEntity[]>(EntityTypeCacheKey);
|
||||
if (empty != null) return empty;
|
||||
}
|
||||
}
|
||||
@@ -238,7 +246,7 @@ namespace Umbraco.Core.Cache
|
||||
/// <inheritdoc />
|
||||
public override void ClearAll()
|
||||
{
|
||||
Cache.ClearByKey(GetEntityTypeCacheKey());
|
||||
Cache.ClearByKey(EntityTypeCacheKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,8 @@ namespace Umbraco.Core.Compose
|
||||
item.Entity.Id,
|
||||
ObjectTypes.GetName(UmbracoObjectTypes.Document),
|
||||
string.Format(textService.Localize(
|
||||
"recycleBin/contentTrashed"),
|
||||
"recycleBin","contentTrashed"),
|
||||
|
||||
item.Entity.Id, originalParentId));
|
||||
}
|
||||
}
|
||||
@@ -132,7 +133,7 @@ namespace Umbraco.Core.Compose
|
||||
item.Entity.Id,
|
||||
ObjectTypes.GetName(UmbracoObjectTypes.Media),
|
||||
string.Format(textService.Localize(
|
||||
"recycleBin/mediaTrashed"),
|
||||
"recycleBin", "mediaTrashed"),
|
||||
item.Entity.Id, originalParentId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace Umbraco.Core.Composing
|
||||
public static class Current
|
||||
{
|
||||
private static IFactory _factory;
|
||||
private static IRuntimeState _state;
|
||||
|
||||
// TODO: get rid of these oddities
|
||||
// we don't want Umbraco tests to die because the container has not been properly initialized,
|
||||
@@ -125,7 +126,17 @@ namespace Umbraco.Core.Composing
|
||||
?? new ProfilingLogger(Logger, Profiler);
|
||||
|
||||
public static IRuntimeState RuntimeState
|
||||
=> Factory.GetInstance<IRuntimeState>();
|
||||
{
|
||||
get
|
||||
{
|
||||
return _state ?? Factory.GetInstance<IRuntimeState>();
|
||||
}
|
||||
internal set
|
||||
{
|
||||
// this is only used when the boot entirely fails, we need to manually set this so we can report
|
||||
_state = value;
|
||||
}
|
||||
}
|
||||
|
||||
public static TypeLoader TypeLoader
|
||||
=> Factory.GetInstance<TypeLoader>();
|
||||
|
||||
@@ -16,6 +16,8 @@ namespace Umbraco.Core.Composing
|
||||
{
|
||||
protected abstract TBuilder This { get; }
|
||||
|
||||
private readonly Dictionary<Type, int> _customWeights = new Dictionary<Type, int>();
|
||||
|
||||
/// <summary>
|
||||
/// Clears all types in the collection.
|
||||
/// </summary>
|
||||
@@ -107,6 +109,18 @@ namespace Umbraco.Core.Composing
|
||||
return This;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the default weight of an item
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of item</typeparam>
|
||||
/// <param name="weight">The new weight</param>
|
||||
/// <returns></returns>
|
||||
public TBuilder SetWeight<T>(int weight) where T : TItem
|
||||
{
|
||||
_customWeights[typeof(T)] = weight;
|
||||
return This;
|
||||
}
|
||||
|
||||
protected override IEnumerable<Type> GetRegisteringTypes(IEnumerable<Type> types)
|
||||
{
|
||||
var list = types.ToList();
|
||||
@@ -118,6 +132,8 @@ namespace Umbraco.Core.Composing
|
||||
|
||||
protected virtual int GetWeight(Type type)
|
||||
{
|
||||
if (_customWeights.ContainsKey(type))
|
||||
return _customWeights[type];
|
||||
var attr = type.GetCustomAttributes(typeof(WeightAttribute), false).OfType<WeightAttribute>().SingleOrDefault();
|
||||
return attr?.Weight ?? DefaultWeight;
|
||||
}
|
||||
|
||||
@@ -109,12 +109,17 @@ namespace Umbraco.Core
|
||||
/// A true or false indicating whether umbraco should force a secure (https) connection to the backoffice.
|
||||
/// </summary>
|
||||
public const string UseHttps = "Umbraco.Core.UseHttps";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A true/false value indicating whether the content dashboard should be visible for all user groups.
|
||||
/// </summary>
|
||||
public const string AllowContentDashboardAccessToAllUsers = "Umbraco.Core.AllowContentDashboardAccessToAllUsers";
|
||||
|
||||
/// <summary>
|
||||
/// The path to use when constructing the URL for retrieving data for the content dashboard.
|
||||
/// </summary>
|
||||
public const string ContentDashboardPath = "Umbraco.Core.ContentDashboardPath";
|
||||
|
||||
/// <summary>
|
||||
/// TODO: FILL ME IN
|
||||
/// </summary>
|
||||
|
||||
@@ -118,6 +118,46 @@ namespace Umbraco.Core
|
||||
/// </summary>
|
||||
public const string Image = "Image";
|
||||
|
||||
/// <summary>
|
||||
/// MediaType name for a video.
|
||||
/// </summary>
|
||||
public const string Video = "Video";
|
||||
|
||||
/// <summary>
|
||||
/// MediaType name for an audio.
|
||||
/// </summary>
|
||||
public const string Audio = "Audio";
|
||||
|
||||
/// <summary>
|
||||
/// MediaType name for an article.
|
||||
/// </summary>
|
||||
public const string Article = "Article";
|
||||
|
||||
/// <summary>
|
||||
/// MediaType name for vector graphics.
|
||||
/// </summary>
|
||||
public const string VectorGraphics = "VectorGraphics";
|
||||
|
||||
/// <summary>
|
||||
/// MediaType alias for a video.
|
||||
/// </summary>
|
||||
public const string VideoAlias = "umbracoMediaVideo";
|
||||
|
||||
/// <summary>
|
||||
/// MediaType alias for an audio.
|
||||
/// </summary>
|
||||
public const string AudioAlias = "umbracoMediaAudio";
|
||||
|
||||
/// <summary>
|
||||
/// MediaType alias for an article.
|
||||
/// </summary>
|
||||
public const string ArticleAlias = "umbracoMediaArticle";
|
||||
|
||||
/// <summary>
|
||||
/// MediaType alias for vector graphics.
|
||||
/// </summary>
|
||||
public const string VectorGraphicsAlias = "umbracoMediaVectorGraphics";
|
||||
|
||||
/// <summary>
|
||||
/// MediaType alias indicating allowing auto-selection.
|
||||
/// </summary>
|
||||
|
||||
@@ -25,6 +25,10 @@ namespace Umbraco.Core
|
||||
public const int DropDownSingle = -39;
|
||||
public const int DropDownMultiple = -42;
|
||||
public const int Upload = -90;
|
||||
public const int UploadVideo = -100;
|
||||
public const int UploadAudio = -101;
|
||||
public const int UploadArticle = -102;
|
||||
public const int UploadVectorGraphics = -103;
|
||||
|
||||
public const int DefaultContentListView = -95;
|
||||
public const int DefaultMediaListView = -96;
|
||||
@@ -42,7 +46,7 @@ namespace Umbraco.Core
|
||||
/// Defines the identifiers for Umbraco data types as constants for easy centralized access/management.
|
||||
/// </summary>
|
||||
public static class Guids
|
||||
{
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Guid for Content Picker as string
|
||||
@@ -88,6 +92,49 @@ namespace Umbraco.Core
|
||||
public static readonly Guid MultipleMediaPickerGuid = new Guid(MultipleMediaPicker);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Guid for Media Picker v3 as string
|
||||
/// </summary>
|
||||
public const string MediaPicker3 = "4309A3EA-0D78-4329-A06C-C80B036AF19A";
|
||||
|
||||
/// <summary>
|
||||
/// Guid for Media Picker v3
|
||||
/// </summary>
|
||||
public static readonly Guid MediaPicker3Guid = new Guid(MediaPicker3);
|
||||
|
||||
/// <summary>
|
||||
/// Guid for Media Picker v3 multiple as string
|
||||
/// </summary>
|
||||
public const string MediaPicker3Multiple = "1B661F40-2242-4B44-B9CB-3990EE2B13C0";
|
||||
|
||||
/// <summary>
|
||||
/// Guid for Media Picker v3 multiple
|
||||
/// </summary>
|
||||
public static readonly Guid MediaPicker3MultipleGuid = new Guid(MediaPicker3Multiple);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Guid for Media Picker v3 single-image as string
|
||||
/// </summary>
|
||||
public const string MediaPicker3SingleImage = "AD9F0CF2-BDA2-45D5-9EA1-A63CFC873FD3";
|
||||
|
||||
/// <summary>
|
||||
/// Guid for Media Picker v3 single-image
|
||||
/// </summary>
|
||||
public static readonly Guid MediaPicker3SingleImageGuid = new Guid(MediaPicker3SingleImage);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Guid for Media Picker v3 multi-image as string
|
||||
/// </summary>
|
||||
public const string MediaPicker3MultipleImages = "0E63D883-B62B-4799-88C3-157F82E83ECC";
|
||||
|
||||
/// <summary>
|
||||
/// Guid for Media Picker v3 multi-image
|
||||
/// </summary>
|
||||
public static readonly Guid MediaPicker3MultipleImagesGuid = new Guid(MediaPicker3MultipleImages);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Guid for Related Links as string
|
||||
/// </summary>
|
||||
@@ -307,6 +354,46 @@ namespace Umbraco.Core
|
||||
/// </summary>
|
||||
public static readonly Guid UploadGuid = new Guid(Upload);
|
||||
|
||||
/// <summary>
|
||||
/// Guid for UploadVideo as string
|
||||
/// </summary>
|
||||
public const string UploadVideo = "70575fe7-9812-4396-bbe1-c81a76db71b5";
|
||||
|
||||
/// <summary>
|
||||
/// Guid for UploadVideo
|
||||
/// </summary>
|
||||
public static readonly Guid UploadVideoGuid = new Guid(UploadVideo);
|
||||
|
||||
/// <summary>
|
||||
/// Guid for UploadAudio as string
|
||||
/// </summary>
|
||||
public const string UploadAudio = "8f430dd6-4e96-447e-9dc0-cb552c8cd1f3";
|
||||
|
||||
/// <summary>
|
||||
/// Guid for UploadAudio
|
||||
/// </summary>
|
||||
public static readonly Guid UploadAudioGuid = new Guid(UploadAudio);
|
||||
|
||||
/// <summary>
|
||||
/// Guid for UploadArticle as string
|
||||
/// </summary>
|
||||
public const string UploadArticle = "bc1e266c-dac4-4164-bf08-8a1ec6a7143d";
|
||||
|
||||
/// <summary>
|
||||
/// Guid for UploadArticle
|
||||
/// </summary>
|
||||
public static readonly Guid UploadArticleGuid = new Guid(UploadArticle);
|
||||
|
||||
/// <summary>
|
||||
/// Guid for UploadVectorGraphics as string
|
||||
/// </summary>
|
||||
public const string UploadVectorGraphics = "215cb418-2153-4429-9aef-8c0f0041191b";
|
||||
|
||||
/// <summary>
|
||||
/// Guid for UploadVectorGraphics
|
||||
/// </summary>
|
||||
public static readonly Guid UploadVectorGraphicsGuid = new Guid(UploadVectorGraphics);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Guid for Label as string
|
||||
@@ -367,8 +454,8 @@ namespace Umbraco.Core
|
||||
/// Guid for Label decimal
|
||||
/// </summary>
|
||||
public static readonly Guid LabelDecimalGuid = new Guid(LabelDecimal);
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,26 @@
|
||||
/// </summary>
|
||||
public const string MediaFile = "icon-document";
|
||||
|
||||
/// <summary>
|
||||
/// System media video icon
|
||||
/// </summary>
|
||||
public const string MediaVideo = "icon-video";
|
||||
|
||||
/// <summary>
|
||||
/// System media audio icon
|
||||
/// </summary>
|
||||
public const string MediaAudio = "icon-sound-waves";
|
||||
|
||||
/// <summary>
|
||||
/// System media article icon
|
||||
/// </summary>
|
||||
public const string MediaArticle = "icon-article";
|
||||
|
||||
/// <summary>
|
||||
/// System media vector icon
|
||||
/// </summary>
|
||||
public const string MediaVectorGraphics = "icon-picture";
|
||||
|
||||
/// <summary>
|
||||
/// System media folder icon
|
||||
/// </summary>
|
||||
@@ -93,7 +113,7 @@
|
||||
/// System packages icon
|
||||
/// </summary>
|
||||
public const string Packages = "icon-box";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// System property editor icon
|
||||
/// </summary>
|
||||
|
||||
@@ -95,12 +95,17 @@ namespace Umbraco.Core
|
||||
/// ListView.
|
||||
/// </summary>
|
||||
public const string ListView = "Umbraco.ListView";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Media Picker.
|
||||
/// </summary>
|
||||
public const string MediaPicker = "Umbraco.MediaPicker";
|
||||
|
||||
/// <summary>
|
||||
/// Media Picker v.3.
|
||||
/// </summary>
|
||||
public const string MediaPicker3 = "Umbraco.MediaPicker3";
|
||||
|
||||
/// <summary>
|
||||
/// Multiple Media Picker.
|
||||
/// </summary>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
public static class PropertyTypeGroups
|
||||
{
|
||||
/// <summary>
|
||||
/// Guid for a Image PropertyTypeGroup object.
|
||||
/// Guid for an Image PropertyTypeGroup object.
|
||||
/// </summary>
|
||||
public const string Image = "79ED4D07-254A-42CF-8FA9-EBE1C116A596";
|
||||
|
||||
@@ -18,7 +18,27 @@
|
||||
public const string File = "50899F9C-023A-4466-B623-ABA9049885FE";
|
||||
|
||||
/// <summary>
|
||||
/// Guid for a Image PropertyTypeGroup object.
|
||||
/// Guid for a Video PropertyTypeGroup object.
|
||||
/// </summary>
|
||||
public const string Video = "2F0A61B6-CF92-4FF4-B437-751AB35EB254";
|
||||
|
||||
/// <summary>
|
||||
/// Guid for an Audio PropertyTypeGroup object.
|
||||
/// </summary>
|
||||
public const string Audio = "335FB495-0A87-4E82-B902-30EB367B767C";
|
||||
|
||||
/// <summary>
|
||||
/// Guid for an Article PropertyTypeGroup object.
|
||||
/// </summary>
|
||||
public const string Article = "9AF3BD65-F687-4453-9518-5F180D1898EC";
|
||||
|
||||
/// <summary>
|
||||
/// Guid for a VectorGraphics PropertyTypeGroup object.
|
||||
/// </summary>
|
||||
public const string VectorGraphics = "F199B4D7-9E84-439F-8531-F87D9AF37711";
|
||||
|
||||
/// <summary>
|
||||
/// Guid for a Membership PropertyTypeGroup object.
|
||||
/// </summary>
|
||||
public const string Membership = "0756729D-D665-46E3-B84A-37ACEAA614F8";
|
||||
}
|
||||
|
||||
@@ -24,6 +24,20 @@
|
||||
{
|
||||
public const string EnsureUniqueNodeName = "Umbraco.Core.DataTypeDefinitionRepository.EnsureUniqueNodeName";
|
||||
}
|
||||
|
||||
internal static class NuCacheDatabaseDataSource
|
||||
{
|
||||
public const string WhereNodeId = "Umbraco.Web.PublishedCache.NuCache.DataSource.WhereNodeId";
|
||||
public const string WhereNodeIdX = "Umbraco.Web.PublishedCache.NuCache.DataSource.WhereNodeIdX";
|
||||
public const string SourcesSelectUmbracoNodeJoin = "Umbraco.Web.PublishedCache.NuCache.DataSource.SourcesSelectUmbracoNodeJoin";
|
||||
public const string ContentSourcesSelect = "Umbraco.Web.PublishedCache.NuCache.DataSource.ContentSourcesSelect";
|
||||
public const string ContentSourcesCount = "Umbraco.Web.PublishedCache.NuCache.DataSource.ContentSourcesCount";
|
||||
public const string MediaSourcesSelect = "Umbraco.Web.PublishedCache.NuCache.DataSource.MediaSourcesSelect";
|
||||
public const string MediaSourcesCount = "Umbraco.Web.PublishedCache.NuCache.DataSource.MediaSourcesCount";
|
||||
public const string ObjectTypeNotTrashedFilter = "Umbraco.Web.PublishedCache.NuCache.DataSource.ObjectTypeNotTrashedFilter";
|
||||
public const string OrderByLevelIdSortOrder = "Umbraco.Web.PublishedCache.NuCache.DataSource.OrderByLevelIdSortOrder";
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Umbraco.Core.Dashboards
|
||||
{
|
||||
public class ContentDashboardSettings: IContentDashboardSettings
|
||||
{
|
||||
private const string DefaultContentDashboardPath = "cms";
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the content dashboard should be available to all users.
|
||||
@@ -20,5 +21,14 @@ namespace Umbraco.Core.Dashboards
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to use when constructing the URL for retrieving data for the content dashboard.
|
||||
/// </summary>
|
||||
/// <value>The URL path.</value>
|
||||
public string ContentDashboardPath =>
|
||||
ConfigurationManager.AppSettings.ContainsKey(Constants.AppSettings.ContentDashboardPath)
|
||||
? ConfigurationManager.AppSettings[Constants.AppSettings.ContentDashboardPath]
|
||||
: DefaultContentDashboardPath;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,5 +10,11 @@
|
||||
/// and the default access rules for that dashboard will be in use.
|
||||
/// </value>
|
||||
bool AllowContentDashboardAccessToAllUsers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to use when constructing the URL for retrieving data for the content dashboard.
|
||||
/// </summary>
|
||||
/// <value>The URL path.</value>
|
||||
string ContentDashboardPath { get; }
|
||||
}
|
||||
}
|
||||
|
||||
9
src/Umbraco.Core/Events/UnattendedInstallEventArgs.cs
Normal file
9
src/Umbraco.Core/Events/UnattendedInstallEventArgs.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to notify that an Unattended install has completed
|
||||
/// </summary>
|
||||
public class UnattendedInstallEventArgs : System.ComponentModel.CancelEventArgs
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@ namespace Umbraco.Core.Migrations.Expressions.Create.Table
|
||||
public class CreateTableOfDtoBuilder : IExecutableBuilder
|
||||
{
|
||||
private readonly IMigrationContext _context;
|
||||
|
||||
// TODO: This doesn't do anything.
|
||||
private readonly DatabaseType[] _supportedDatabaseTypes;
|
||||
|
||||
public CreateTableOfDtoBuilder(IMigrationContext context, params DatabaseType[] supportedDatabaseTypes)
|
||||
|
||||
@@ -107,7 +107,11 @@ namespace Umbraco.Core.Migrations.Install
|
||||
InsertDataTypeNodeDto(Constants.DataTypes.LabelDateTime, 37, Constants.DataTypes.Guids.LabelDateTime, "Label (datetime)");
|
||||
InsertDataTypeNodeDto(Constants.DataTypes.LabelTime, 38, Constants.DataTypes.Guids.LabelTime, "Label (time)");
|
||||
InsertDataTypeNodeDto(Constants.DataTypes.LabelDecimal, 39, Constants.DataTypes.Guids.LabelDecimal, "Label (decimal)");
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.Upload, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.Upload}", SortOrder = 34, UniqueId = Constants.DataTypes.Guids.UploadGuid, Text = "Upload", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.Upload, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.Upload}", SortOrder = 34, UniqueId = Constants.DataTypes.Guids.UploadGuid, Text = "Upload File", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.UploadVideo, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.UploadVideo}", SortOrder = 35, UniqueId = Constants.DataTypes.Guids.UploadVideoGuid, Text = "Upload Video", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.UploadAudio, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.UploadAudio}", SortOrder = 36, UniqueId = Constants.DataTypes.Guids.UploadAudioGuid, Text = "Upload Audio", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.UploadArticle, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.UploadArticle}", SortOrder = 37, UniqueId = Constants.DataTypes.Guids.UploadArticleGuid, Text = "Upload Article", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.UploadVectorGraphics, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.UploadVectorGraphics}", SortOrder = 38, UniqueId = Constants.DataTypes.Guids.UploadVectorGraphicsGuid, Text = "Upload Vector Graphics", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.Textarea, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.Textarea}", SortOrder = 33, UniqueId = Constants.DataTypes.Guids.TextareaGuid, Text = "Textarea", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.Textbox, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.Textbox}", SortOrder = 32, UniqueId = Constants.DataTypes.Guids.TextstringGuid, Text = "Textstring", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.RichtextEditor, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.RichtextEditor}", SortOrder = 4, UniqueId = Constants.DataTypes.Guids.RichtextEditorGuid, Text = "Richtext editor", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
|
||||
@@ -126,6 +130,10 @@ namespace Umbraco.Core.Migrations.Install
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1031, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1031", SortOrder = 2, UniqueId = new Guid("f38bd2d7-65d0-48e6-95dc-87ce06ec2d3d"), Text = Constants.Conventions.MediaTypes.Folder, NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1032, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1032", SortOrder = 2, UniqueId = new Guid("cc07b313-0843-4aa8-bbda-871c8da728c8"), Text = Constants.Conventions.MediaTypes.Image, NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1033, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1033", SortOrder = 2, UniqueId = new Guid("4c52d8ab-54e6-40cd-999c-7a5f24903e4d"), Text = Constants.Conventions.MediaTypes.File, NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1034, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1034", SortOrder = 2, UniqueId = new Guid("f6c515bb-653c-4bdc-821c-987729ebe327"), Text = Constants.Conventions.MediaTypes.Video, NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1035, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1035", SortOrder = 2, UniqueId = new Guid("a5ddeee0-8fd8-4cee-a658-6f1fcdb00de3"), Text = Constants.Conventions.MediaTypes.Audio, NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1036, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1036", SortOrder = 2, UniqueId = new Guid("a43e3414-9599-4230-a7d3-943a21b20122"), Text = Constants.Conventions.MediaTypes.Article, NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1037, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1037", SortOrder = 2, UniqueId = new Guid("c4b1efcf-a9d5-41c4-9621-e9d273b52a9c"), Text = "Vector Graphics (SVG)", NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.Tags, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.Tags}", SortOrder = 2, UniqueId = new Guid("b6b73142-b9c1-4bf8-a16d-e1c23320b549"), Text = "Tags", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.ImageCropper, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.ImageCropper}", SortOrder = 2, UniqueId = new Guid("1df9f033-e6d4-451f-b8d2-e0cbc50a836f"), Text = "Image Cropper", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1044, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1044", SortOrder = 0, UniqueId = new Guid("d59be02f-1df9-4228-aa1e-01917d806cda"), Text = Constants.Conventions.MemberTypes.DefaultAlias, NodeObjectType = Constants.ObjectTypes.MemberType, CreateDate = DateTime.Now });
|
||||
@@ -133,9 +141,15 @@ namespace Umbraco.Core.Migrations.Install
|
||||
//New UDI pickers with newer Ids
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1046, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1046", SortOrder = 2, UniqueId = new Guid("FD1E0DA5-5606-4862-B679-5D0CF3A52A59"), Text = "Content Picker", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1047, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1047", SortOrder = 2, UniqueId = new Guid("1EA2E01F-EBD8-4CE1-8D71-6B1149E63548"), Text = "Member Picker", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1048, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1048", SortOrder = 2, UniqueId = new Guid("135D60E0-64D9-49ED-AB08-893C9BA44AE5"), Text = "Media Picker", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1049, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1049", SortOrder = 2, UniqueId = new Guid("9DBBCBBB-2327-434A-B355-AF1B84E5010A"), Text = "Multiple Media Picker", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1048, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1048", SortOrder = 2, UniqueId = new Guid("135D60E0-64D9-49ED-AB08-893C9BA44AE5"), Text = "Media Picker (legacy)", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1049, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1049", SortOrder = 2, UniqueId = new Guid("9DBBCBBB-2327-434A-B355-AF1B84E5010A"), Text = "Multiple Media Picker (legacy)", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1050, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1050", SortOrder = 2, UniqueId = new Guid("B4E3535A-1753-47E2-8568-602CF8CFEE6F"), Text = "Multi URL Picker", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
|
||||
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1051, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1051", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.MediaPicker3Guid, Text = "Media Picker", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1052, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1052", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.MediaPicker3MultipleGuid, Text = "Multiple Media Picker", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1053, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1053", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.MediaPicker3SingleImageGuid, Text = "Image Media Picker", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1054, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1054", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.MediaPicker3MultipleImagesGuid, Text = "Multiple Image Media Picker", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
|
||||
|
||||
}
|
||||
|
||||
private void CreateLockData()
|
||||
@@ -160,6 +174,10 @@ namespace Umbraco.Core.Migrations.Install
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 532, NodeId = 1031, Alias = Constants.Conventions.MediaTypes.Folder, Icon = Constants.Icons.MediaFolder, Thumbnail = Constants.Icons.MediaFolder, IsContainer = false, AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 533, NodeId = 1032, Alias = Constants.Conventions.MediaTypes.Image, Icon = Constants.Icons.MediaImage, Thumbnail = Constants.Icons.MediaImage, AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 534, NodeId = 1033, Alias = Constants.Conventions.MediaTypes.File, Icon = Constants.Icons.MediaFile, Thumbnail = Constants.Icons.MediaFile, AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 540, NodeId = 1034, Alias = Constants.Conventions.MediaTypes.VideoAlias, Icon = Constants.Icons.MediaVideo, Thumbnail = Constants.Icons.MediaVideo, AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 541, NodeId = 1035, Alias = Constants.Conventions.MediaTypes.AudioAlias, Icon = Constants.Icons.MediaAudio, Thumbnail = Constants.Icons.MediaAudio, AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 542, NodeId = 1036, Alias = Constants.Conventions.MediaTypes.ArticleAlias, Icon = Constants.Icons.MediaArticle, Thumbnail = Constants.Icons.MediaArticle, AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 543, NodeId = 1037, Alias = Constants.Conventions.MediaTypes.VectorGraphicsAlias, Icon = Constants.Icons.MediaVectorGraphics, Thumbnail = Constants.Icons.MediaVectorGraphics, AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 531, NodeId = 1044, Alias = Constants.Conventions.MemberTypes.DefaultAlias, Icon = Constants.Icons.Member, Thumbnail = Constants.Icons.Member, Variations = (byte) ContentVariation.Nothing });
|
||||
}
|
||||
|
||||
@@ -207,20 +225,44 @@ namespace Umbraco.Core.Migrations.Install
|
||||
{
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 3, ContentTypeNodeId = 1032, Text = "Image", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Image) });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 4, ContentTypeNodeId = 1033, Text = "File", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.File) });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 52, ContentTypeNodeId = 1034, Text = "Video", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Video) });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 53, ContentTypeNodeId = 1035, Text = "Audio", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Audio) });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 54, ContentTypeNodeId = 1036, Text = "Article", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Article) });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 55, ContentTypeNodeId = 1037, Text = "Vector Graphics", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.VectorGraphics) });
|
||||
//membership property group
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 11, ContentTypeNodeId = 1044, Text = "Membership", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Membership) });
|
||||
}
|
||||
|
||||
private void CreatePropertyTypeData()
|
||||
{
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 6, UniqueId = 6.ToGuid(), DataTypeId = Constants.DataTypes.ImageCropper, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.File, Name = "Upload image", SortOrder = 0, Mandatory = true, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 6, UniqueId = 6.ToGuid(), DataTypeId = Constants.DataTypes.ImageCropper, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.File, Name = "Image", SortOrder = 0, Mandatory = true, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 7, UniqueId = 7.ToGuid(), DataTypeId = Constants.DataTypes.LabelInt, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Width, Name = "Width", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in pixels", Variations = (byte) ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 8, UniqueId = 8.ToGuid(), DataTypeId = Constants.DataTypes.LabelInt, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Height, Name = "Height", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in pixels", Variations = (byte) ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 9, UniqueId = 9.ToGuid(), DataTypeId = Constants.DataTypes.LabelBigint, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in bytes", Variations = (byte) ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 10, UniqueId = 10.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 24, UniqueId = 24.ToGuid(), DataTypeId = Constants.DataTypes.Upload, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.File, Name = "Upload file", SortOrder = 0, Mandatory = true, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 24, UniqueId = 24.ToGuid(), DataTypeId = Constants.DataTypes.Upload, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.File, Name = "File", SortOrder = 0, Mandatory = true, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 25, UniqueId = 25.ToGuid(), DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 26, UniqueId = 26.ToGuid(), DataTypeId = Constants.DataTypes.LabelBigint, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in bytes", Variations = (byte) ContentVariation.Nothing });
|
||||
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 40, UniqueId = 40.ToGuid(), DataTypeId = Constants.DataTypes.UploadVideo, ContentTypeId = 1034, PropertyTypeGroupId = 52, Alias = Constants.Conventions.Media.File, Name = "Video", SortOrder = 0, Mandatory = true, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 41, UniqueId = 41.ToGuid(), DataTypeId = -92, ContentTypeId = 1034, PropertyTypeGroupId = 52, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 42, UniqueId = 42.ToGuid(), DataTypeId = Constants.DataTypes.LabelBigint, ContentTypeId = 1034, PropertyTypeGroupId = 52, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in bytes", Variations = (byte) ContentVariation.Nothing });
|
||||
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 43, UniqueId = 43.ToGuid(), DataTypeId = Constants.DataTypes.UploadAudio, ContentTypeId = 1035, PropertyTypeGroupId = 53, Alias = Constants.Conventions.Media.File, Name = "Audio", SortOrder = 0, Mandatory = true, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 44, UniqueId = 44.ToGuid(), DataTypeId = -92, ContentTypeId = 1035, PropertyTypeGroupId = 53, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 45, UniqueId = 45.ToGuid(), DataTypeId = Constants.DataTypes.LabelBigint, ContentTypeId = 1035, PropertyTypeGroupId = 53, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in bytes", Variations = (byte) ContentVariation.Nothing });
|
||||
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 46, UniqueId = 46.ToGuid(), DataTypeId = Constants.DataTypes.UploadArticle, ContentTypeId = 1036, PropertyTypeGroupId = 54, Alias = Constants.Conventions.Media.File, Name = "Article", SortOrder = 0, Mandatory = true, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 47, UniqueId = 47.ToGuid(), DataTypeId = -92, ContentTypeId = 1036, PropertyTypeGroupId = 54, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 48, UniqueId = 48.ToGuid(), DataTypeId = Constants.DataTypes.LabelBigint, ContentTypeId = 1036, PropertyTypeGroupId = 54, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in bytes", Variations = (byte) ContentVariation.Nothing });
|
||||
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 49, UniqueId = 49.ToGuid(), DataTypeId = Constants.DataTypes.UploadVectorGraphics, ContentTypeId = 1037, PropertyTypeGroupId = 55, Alias = Constants.Conventions.Media.File, Name = "Vector Graphics", SortOrder = 0, Mandatory = true, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 50, UniqueId = 50.ToGuid(), DataTypeId = -92, ContentTypeId = 1037, PropertyTypeGroupId = 55, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 51, UniqueId = 51.ToGuid(), DataTypeId = Constants.DataTypes.LabelBigint, ContentTypeId = 1037, PropertyTypeGroupId = 55, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in bytes", Variations = (byte) ContentVariation.Nothing });
|
||||
|
||||
|
||||
|
||||
|
||||
//membership property types
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 28, UniqueId = 28.ToGuid(), DataTypeId = Constants.DataTypes.Textarea, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.Comments, Name = Constants.Conventions.Member.CommentsLabel, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 29, UniqueId = 29.ToGuid(), DataTypeId = Constants.DataTypes.LabelInt, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.FailedPasswordAttempts, Name = Constants.Conventions.Member.FailedPasswordAttemptsLabel, SortOrder = 1, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
|
||||
@@ -244,6 +286,10 @@ namespace Umbraco.Core.Migrations.Install
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.ContentChildType, "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1031 });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.ContentChildType, "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1032 });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.ContentChildType, "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1033 });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.ContentChildType, "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1034 });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.ContentChildType, "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1035 });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.ContentChildType, "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1036 });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.ContentChildType, "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1037 });
|
||||
}
|
||||
|
||||
private void CreateDataTypeData()
|
||||
@@ -304,9 +350,65 @@ namespace Umbraco.Core.Migrations.Install
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1046, EditorAlias = Constants.PropertyEditors.Aliases.ContentPicker, DbType = "Nvarchar" });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1047, EditorAlias = Constants.PropertyEditors.Aliases.MemberPicker, DbType = "Nvarchar" });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1048, EditorAlias = Constants.PropertyEditors.Aliases.MediaPicker, DbType = "Ntext" });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1049, EditorAlias = Constants.PropertyEditors.Aliases.MediaPicker, DbType = "Ntext",
|
||||
Configuration = "{\"multiPicker\":1}" });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1049, EditorAlias = Constants.PropertyEditors.Aliases.MediaPicker, DbType = "Ntext", Configuration = "{\"multiPicker\":1}" });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1050, EditorAlias = Constants.PropertyEditors.Aliases.MultiUrlPicker, DbType = "Ntext" });
|
||||
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto
|
||||
{
|
||||
NodeId = Constants.DataTypes.UploadVideo,
|
||||
EditorAlias = Constants.PropertyEditors.Aliases.UploadField,
|
||||
DbType = "Nvarchar",
|
||||
Configuration = "{\"fileExtensions\":[{\"id\":0, \"value\":\"mp4\"}, {\"id\":1, \"value\":\"webm\"}, {\"id\":2, \"value\":\"ogv\"}]}"
|
||||
});
|
||||
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto
|
||||
{
|
||||
NodeId = Constants.DataTypes.UploadAudio,
|
||||
EditorAlias = Constants.PropertyEditors.Aliases.UploadField,
|
||||
DbType = "Nvarchar",
|
||||
Configuration = "{\"fileExtensions\":[{\"id\":0, \"value\":\"mp3\"}, {\"id\":1, \"value\":\"weba\"}, {\"id\":2, \"value\":\"oga\"}, {\"id\":3, \"value\":\"opus\"}]}"
|
||||
});
|
||||
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto
|
||||
{
|
||||
NodeId = Constants.DataTypes.UploadArticle,
|
||||
EditorAlias = Constants.PropertyEditors.Aliases.UploadField,
|
||||
DbType = "Nvarchar",
|
||||
Configuration = "{\"fileExtensions\":[{\"id\":0, \"value\":\"pdf\"}, {\"id\":1, \"value\":\"docx\"}, {\"id\":2, \"value\":\"doc\"}]}"
|
||||
});
|
||||
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto
|
||||
{
|
||||
NodeId = Constants.DataTypes.UploadVectorGraphics,
|
||||
EditorAlias = Constants.PropertyEditors.Aliases.UploadField,
|
||||
DbType = "Nvarchar",
|
||||
Configuration = "{\"fileExtensions\":[{\"id\":0, \"value\":\"svg\"}]}"
|
||||
});
|
||||
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto {
|
||||
NodeId = 1051,
|
||||
EditorAlias = Constants.PropertyEditors.Aliases.MediaPicker3,
|
||||
DbType = "Ntext",
|
||||
Configuration = "{\"multiple\": false, \"validationLimit\":{\"min\":0,\"max\":1}}"
|
||||
});
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto {
|
||||
NodeId = 1052,
|
||||
EditorAlias = Constants.PropertyEditors.Aliases.MediaPicker3,
|
||||
DbType = "Ntext",
|
||||
Configuration = "{\"multiple\": true}"
|
||||
});
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto {
|
||||
NodeId = 1053,
|
||||
EditorAlias = Constants.PropertyEditors.Aliases.MediaPicker3,
|
||||
DbType = "Ntext",
|
||||
Configuration = "{\"filter\":\"" + Constants.Conventions.MediaTypes.Image + "\", \"multiple\": false, \"validationLimit\":{\"min\":0,\"max\":1}}"
|
||||
});
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto {
|
||||
NodeId = 1054,
|
||||
EditorAlias = Constants.PropertyEditors.Aliases.MediaPicker3,
|
||||
DbType = "Ntext",
|
||||
Configuration = "{\"filter\":\"" + Constants.Conventions.MediaTypes.Image + "\", \"multiple\": true}"
|
||||
});
|
||||
}
|
||||
|
||||
private void CreateRelationTypeData()
|
||||
|
||||
@@ -9,6 +9,7 @@ using Umbraco.Core.Migrations.Upgrade.V_8_1_0;
|
||||
using Umbraco.Core.Migrations.Upgrade.V_8_6_0;
|
||||
using Umbraco.Core.Migrations.Upgrade.V_8_9_0;
|
||||
using Umbraco.Core.Migrations.Upgrade.V_8_10_0;
|
||||
using Umbraco.Core.Migrations.Upgrade.V_8_15_0;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Upgrade
|
||||
{
|
||||
@@ -198,10 +199,14 @@ namespace Umbraco.Core.Migrations.Upgrade
|
||||
|
||||
// to 8.9.0
|
||||
To<ExternalLoginTableUserData>("{B5838FF5-1D22-4F6C-BCEB-F83ACB14B575}");
|
||||
|
||||
|
||||
// to 8.10.0
|
||||
To<AddPropertyTypeLabelOnTopColumn>("{D6A8D863-38EC-44FB-91EC-ACD6A668BD18}");
|
||||
|
||||
// to 8.15.0...
|
||||
To<AddCmsContentNuByteColumn>("{8DDDCD0B-D7D5-4C97-BD6A-6B38CA65752F}");
|
||||
To<UpgradedIncludeIndexes>("{4695D0C9-0729-4976-985B-048D503665D8}");
|
||||
|
||||
//FINAL
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
using NPoco;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.DatabaseAnnotations;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Upgrade.V_8_15_0
|
||||
{
|
||||
public class AddCmsContentNuByteColumn : MigrationBase
|
||||
{
|
||||
public AddCmsContentNuByteColumn(IMigrationContext context)
|
||||
: base(context)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override void Migrate()
|
||||
{
|
||||
// allow null for the `data` field
|
||||
if (DatabaseType.IsSqlCe())
|
||||
{
|
||||
// SQLCE does not support altering NTEXT, so we have to jump through some hoops to do it
|
||||
// All column ordering must remain the same as what is defined in the DTO so we need to create a temp table,
|
||||
// drop orig and then re-create/copy.
|
||||
Create.Table<ContentNuDtoTemp>(withoutKeysAndIndexes: true).Do();
|
||||
Execute.Sql($"INSERT INTO [{TempTableName}] SELECT nodeId, published, data, rv FROM [{Constants.DatabaseSchema.Tables.NodeData}]").Do();
|
||||
Delete.Table(Constants.DatabaseSchema.Tables.NodeData).Do();
|
||||
Create.Table<ContentNuDto>().Do();
|
||||
Execute.Sql($"INSERT INTO [{Constants.DatabaseSchema.Tables.NodeData}] SELECT nodeId, published, data, rv, NULL FROM [{TempTableName}]").Do();
|
||||
}
|
||||
else
|
||||
{
|
||||
AlterColumn<ContentNuDto>(Constants.DatabaseSchema.Tables.NodeData, "data");
|
||||
}
|
||||
|
||||
var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToList();
|
||||
AddColumnIfNotExists<ContentNuDto>(columns, "dataRaw");
|
||||
}
|
||||
|
||||
private const string TempTableName = Constants.DatabaseSchema.TableNamePrefix + "cms" + "ContentNuTEMP";
|
||||
|
||||
[TableName(TempTableName)]
|
||||
[ExplicitColumns]
|
||||
private class ContentNuDtoTemp
|
||||
{
|
||||
[Column("nodeId")]
|
||||
public int NodeId { get; set; }
|
||||
|
||||
[Column("published")]
|
||||
public bool Published { get; set; }
|
||||
|
||||
[Column("data")]
|
||||
[SpecialDbType(SpecialDbTypes.NTEXT)]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public string Data { get; set; }
|
||||
|
||||
[Column("rv")]
|
||||
public long Rv { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Migrations.Expressions.Execute.Expressions;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Upgrade.V_8_15_0
|
||||
{
|
||||
public class UpgradedIncludeIndexes : MigrationBase
|
||||
{
|
||||
public UpgradedIncludeIndexes(IMigrationContext context)
|
||||
: base(context)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override void Migrate()
|
||||
{
|
||||
// Need to drop the FK for the redirect table before modifying the unique id index
|
||||
Delete.ForeignKey()
|
||||
.FromTable(Constants.DatabaseSchema.Tables.RedirectUrl)
|
||||
.ForeignColumn("contentKey")
|
||||
.ToTable(NodeDto.TableName)
|
||||
.PrimaryColumn("uniqueID")
|
||||
.Do();
|
||||
var nodeDtoIndexes = new[] { $"IX_{NodeDto.TableName}_UniqueId", $"IX_{NodeDto.TableName}_ObjectType", $"IX_{NodeDto.TableName}_Level" };
|
||||
DeleteIndexes<NodeDto>(nodeDtoIndexes); // delete existing ones
|
||||
CreateIndexes<NodeDto>(nodeDtoIndexes); // update/add
|
||||
// Now re-create the FK for the redirect table
|
||||
Create.ForeignKey()
|
||||
.FromTable(Constants.DatabaseSchema.Tables.RedirectUrl)
|
||||
.ForeignColumn("contentKey")
|
||||
.ToTable(NodeDto.TableName)
|
||||
.PrimaryColumn("uniqueID")
|
||||
.Do();
|
||||
|
||||
|
||||
var contentVersionIndexes = new[] { $"IX_{ContentVersionDto.TableName}_NodeId", $"IX_{ContentVersionDto.TableName}_Current" };
|
||||
DeleteIndexes<ContentVersionDto>(contentVersionIndexes); // delete existing ones
|
||||
CreateIndexes<ContentVersionDto>(contentVersionIndexes); // update/add
|
||||
}
|
||||
|
||||
private void DeleteIndexes<T>(params string[] toDelete)
|
||||
{
|
||||
var tableDef = DefinitionFactory.GetTableDefinition(typeof(T), Context.SqlContext.SqlSyntax);
|
||||
|
||||
foreach (var i in toDelete)
|
||||
if (IndexExists(i))
|
||||
Delete.Index(i).OnTable(tableDef.Name).Do();
|
||||
|
||||
}
|
||||
|
||||
private void CreateIndexes<T>(params string[] toCreate)
|
||||
{
|
||||
var tableDef = DefinitionFactory.GetTableDefinition(typeof(T), Context.SqlContext.SqlSyntax);
|
||||
|
||||
foreach (var c in toCreate)
|
||||
{
|
||||
// get the definition by name
|
||||
var index = tableDef.Indexes.First(x => x.Name == c);
|
||||
new ExecuteSqlStatementExpression(Context) { SqlStatement = Context.SqlContext.SqlSyntax.Format(index) }.Execute();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,10 @@ namespace Umbraco.Core.Models
|
||||
Constants.DataTypes.Guids.TextstringGuid,
|
||||
Constants.DataTypes.Guids.TextareaGuid,
|
||||
Constants.DataTypes.Guids.UploadGuid,
|
||||
Constants.DataTypes.Guids.UploadArticleGuid,
|
||||
Constants.DataTypes.Guids.UploadAudioGuid,
|
||||
Constants.DataTypes.Guids.UploadVectorGraphicsGuid,
|
||||
Constants.DataTypes.Guids.UploadVideoGuid,
|
||||
Constants.DataTypes.Guids.LabelStringGuid,
|
||||
Constants.DataTypes.Guids.LabelDecimalGuid,
|
||||
Constants.DataTypes.Guids.LabelDateTimeGuid,
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Umbraco.Core.Models.Entities
|
||||
/// <inheritdoc />
|
||||
public virtual bool IsPropertyDirty(string propertyName)
|
||||
{
|
||||
return _currentChanges != null && _currentChanges.Any(x => x.Key == propertyName);
|
||||
return _currentChanges != null && _currentChanges.ContainsKey(propertyName);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -61,7 +61,7 @@ namespace Umbraco.Core.Models.Entities
|
||||
/// <inheritdoc />
|
||||
public virtual bool WasPropertyDirty(string propertyName)
|
||||
{
|
||||
return _savedChanges != null && _savedChanges.Any(x => x.Key == propertyName);
|
||||
return _savedChanges != null && _savedChanges.ContainsKey(propertyName);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -4,6 +4,7 @@ using Umbraco.Core.Models.Entities;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Provides a base class for content items.
|
||||
/// </summary>
|
||||
|
||||
72
src/Umbraco.Core/Models/IReadOnlyContentBase.cs
Normal file
72
src/Umbraco.Core/Models/IReadOnlyContentBase.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
public interface IReadOnlyContentBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the integer identifier of the entity.
|
||||
/// </summary>
|
||||
int Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Guid unique identifier of the entity.
|
||||
/// </summary>
|
||||
Guid Key { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the creation date.
|
||||
/// </summary>
|
||||
DateTime CreateDate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last update date.
|
||||
/// </summary>
|
||||
DateTime UpdateDate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the entity.
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the identifier of the user who created this entity.
|
||||
/// </summary>
|
||||
int CreatorId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the identifier of the parent entity.
|
||||
/// </summary>
|
||||
int ParentId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the level of the entity.
|
||||
/// </summary>
|
||||
int Level { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the entity.
|
||||
/// </summary>
|
||||
string Path { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sort order of the entity.
|
||||
/// </summary>
|
||||
int SortOrder { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content type id
|
||||
/// </summary>
|
||||
int ContentTypeId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the identifier of the writer.
|
||||
/// </summary>
|
||||
int WriterId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the version identifier.
|
||||
/// </summary>
|
||||
int VersionId { get; }
|
||||
}
|
||||
}
|
||||
86
src/Umbraco.Core/Models/MediaWithCrops.cs
Normal file
86
src/Umbraco.Core/Models/MediaWithCrops.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.PropertyEditors.ValueConverters;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a media item with local crops.
|
||||
/// </summary>
|
||||
/// <seealso cref="Umbraco.Core.Models.PublishedContent.PublishedContentWrapped" />
|
||||
public class MediaWithCrops : PublishedContentWrapped
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the media item.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The media item.
|
||||
/// </value>
|
||||
[Obsolete("This instance now implements IPublishedContent by wrapping the media item, use the extension methods directly on MediaWithCrops or use the Content property to get the media item instead.")]
|
||||
public IPublishedContent MediaItem => Content;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content/media item.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The content/media item.
|
||||
/// </value>
|
||||
public IPublishedContent Content => Unwrap();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the local crops.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The local crops.
|
||||
/// </value>
|
||||
public ImageCropperValue LocalCrops { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MediaWithCrops" /> class.
|
||||
/// </summary>
|
||||
/// <param name="content">The content.</param>
|
||||
/// <param name="localCrops">The local crops.</param>
|
||||
public MediaWithCrops(IPublishedContent content, ImageCropperValue localCrops)
|
||||
: base(content)
|
||||
{
|
||||
LocalCrops = localCrops;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a media item with local crops.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the media item.</typeparam>
|
||||
/// <seealso cref="Umbraco.Core.Models.PublishedContent.PublishedContentWrapped" />
|
||||
public class MediaWithCrops<T> : MediaWithCrops
|
||||
where T : IPublishedContent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the media item.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The media item.
|
||||
/// </value>
|
||||
public new T Content { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MediaWithCrops{T}" /> class.
|
||||
/// </summary>
|
||||
/// <param name="content">The content.</param>
|
||||
/// <param name="localCrops">The local crops.</param>
|
||||
public MediaWithCrops(T content, ImageCropperValue localCrops)
|
||||
: base(content, localCrops)
|
||||
{
|
||||
Content = content;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs an implicit conversion from <see cref="MediaWithCrops{T}" /> to <see cref="T" />.
|
||||
/// </summary>
|
||||
/// <param name="mediaWithCrops">The media with crops.</param>
|
||||
/// <returns>
|
||||
/// The result of the conversion.
|
||||
/// </returns>
|
||||
public static implicit operator T(MediaWithCrops<T> mediaWithCrops) => mediaWithCrops.Content;
|
||||
}
|
||||
}
|
||||
@@ -71,5 +71,11 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public const int DisplayFallbackLanguage = 4;
|
||||
/// <summary>
|
||||
/// Gets the fallback to tree ancestors policy.
|
||||
/// </summary>
|
||||
public static Fallback ToDisplayFallbackLanguage => new Fallback(new[] { DisplayFallbackLanguage });
|
||||
}
|
||||
}
|
||||
|
||||
42
src/Umbraco.Core/Models/ReadOnlyContentBaseAdapter.cs
Normal file
42
src/Umbraco.Core/Models/ReadOnlyContentBaseAdapter.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
internal struct ReadOnlyContentBaseAdapter : IReadOnlyContentBase
|
||||
{
|
||||
private readonly IContentBase _content;
|
||||
|
||||
private ReadOnlyContentBaseAdapter(IContentBase content)
|
||||
{
|
||||
_content = content ?? throw new ArgumentNullException(nameof(content));
|
||||
}
|
||||
|
||||
public static ReadOnlyContentBaseAdapter Create(IContentBase content) => new ReadOnlyContentBaseAdapter(content);
|
||||
|
||||
public int Id => _content.Id;
|
||||
|
||||
public Guid Key => _content.Key;
|
||||
|
||||
public DateTime CreateDate => _content.CreateDate;
|
||||
|
||||
public DateTime UpdateDate => _content.UpdateDate;
|
||||
|
||||
public string Name => _content.Name;
|
||||
|
||||
public int CreatorId => _content.CreatorId;
|
||||
|
||||
public int ParentId => _content.ParentId;
|
||||
|
||||
public int Level => _content.Level;
|
||||
|
||||
public string Path => _content.Path;
|
||||
|
||||
public int SortOrder => _content.SortOrder;
|
||||
|
||||
public int ContentTypeId => _content.ContentTypeId;
|
||||
|
||||
public int WriterId => _content.WriterId;
|
||||
|
||||
public int VersionId => _content.VersionId;
|
||||
}
|
||||
}
|
||||
@@ -31,5 +31,10 @@ namespace Umbraco.Core.Persistence.DatabaseAnnotations
|
||||
/// Gets or sets the column name(s) for the current index
|
||||
/// </summary>
|
||||
public string ForColumns { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the column name(s) for the columns to include in the index
|
||||
/// </summary>
|
||||
public string IncludeColumns { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,6 +166,14 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions
|
||||
definition.Columns.Add(new IndexColumnDefinition {Name = column, Direction = Direction.Ascending});
|
||||
}
|
||||
}
|
||||
if (string.IsNullOrEmpty(attribute.IncludeColumns) == false)
|
||||
{
|
||||
var columns = attribute.IncludeColumns.Split(',').Select(p => p.Trim());
|
||||
foreach (var column in columns)
|
||||
{
|
||||
definition.IncludeColumns.Add(new IndexColumnDefinition { Name = column, Direction = Direction.Ascending });
|
||||
}
|
||||
}
|
||||
return definition;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,17 +6,13 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions
|
||||
{
|
||||
public class IndexDefinition
|
||||
{
|
||||
public IndexDefinition()
|
||||
{
|
||||
Columns = new List<IndexColumnDefinition>();
|
||||
}
|
||||
|
||||
public virtual string Name { get; set; }
|
||||
public virtual string SchemaName { get; set; }
|
||||
public virtual string TableName { get; set; }
|
||||
public virtual string ColumnName { get; set; }
|
||||
|
||||
public virtual ICollection<IndexColumnDefinition> Columns { get; set; }
|
||||
public virtual ICollection<IndexColumnDefinition> Columns { get; set; } = new List<IndexColumnDefinition>();
|
||||
public virtual ICollection<IndexColumnDefinition> IncludeColumns { get; set; } = new List<IndexColumnDefinition>();
|
||||
public IndexTypes IndexType { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,9 +25,16 @@ namespace Umbraco.Core.Persistence.Dtos
|
||||
/// </remarks>
|
||||
[Column("data")]
|
||||
[SpecialDbType(SpecialDbTypes.NTEXT)]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public string Data { get; set; }
|
||||
|
||||
[Column("rv")]
|
||||
public long Rv { get; set; }
|
||||
|
||||
[Column("dataRaw")]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public byte[] RawData { get; set; }
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Umbraco.Core.Persistence.Dtos
|
||||
public const string TableName = Constants.DatabaseSchema.Tables.ContentType;
|
||||
|
||||
[Column("pk")]
|
||||
[PrimaryKeyColumn(IdentitySeed = 535)]
|
||||
[PrimaryKeyColumn(IdentitySeed = 700)]
|
||||
public int PrimaryKey { get; set; }
|
||||
|
||||
[Column("nodeId")]
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Umbraco.Core.Persistence.Dtos
|
||||
|
||||
[Column("nodeId")]
|
||||
[ForeignKey(typeof(ContentDto))]
|
||||
[Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_NodeId", ForColumns = "nodeId,current")]
|
||||
[Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_NodeId", ForColumns = "nodeId,current", IncludeColumns = "id,versionDate,text,userId")]
|
||||
public int NodeId { get; set; }
|
||||
|
||||
[Column("versionDate")] // TODO: db rename to 'updateDate'
|
||||
@@ -32,6 +32,7 @@ namespace Umbraco.Core.Persistence.Dtos
|
||||
public int? UserId { get => _userId == 0 ? null : _userId; set => _userId = value; } //return null if zero
|
||||
|
||||
[Column("current")]
|
||||
[Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_Current", IncludeColumns = "nodeId")]
|
||||
public bool Current { get; set; }
|
||||
|
||||
// about current:
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Umbraco.Core.Persistence.Dtos
|
||||
|
||||
[Column("uniqueId")]
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
[Index(IndexTypes.UniqueNonClustered, Name = "IX_" + TableName + "_UniqueId")]
|
||||
[Index(IndexTypes.UniqueNonClustered, Name = "IX_" + TableName + "_UniqueId", IncludeColumns = "parentId,level,path,sortOrder,trashed,nodeUser,text,createDate")]
|
||||
[Constraint(Default = SystemMethods.NewGuid)]
|
||||
public Guid UniqueId { get; set; }
|
||||
|
||||
@@ -29,7 +29,9 @@ namespace Umbraco.Core.Persistence.Dtos
|
||||
[Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_ParentId")]
|
||||
public int ParentId { get; set; }
|
||||
|
||||
// NOTE: This index is primarily for the nucache data lookup, see https://github.com/umbraco/Umbraco-CMS/pull/8365#issuecomment-673404177
|
||||
[Column("level")]
|
||||
[Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_Level", ForColumns = "level,parentId,sortOrder,nodeObjectType,trashed", IncludeColumns = "nodeUser,path,uniqueId,createDate")]
|
||||
public short Level { get; set; }
|
||||
|
||||
[Column("path")]
|
||||
@@ -55,8 +57,8 @@ namespace Umbraco.Core.Persistence.Dtos
|
||||
public string Text { get; set; }
|
||||
|
||||
[Column("nodeObjectType")] // TODO: db rename to 'objectType'
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
[Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_ObjectType")]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
[Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_ObjectType", ForColumns = "nodeObjectType,trashed", IncludeColumns = "uniqueId,parentId,level,path,sortOrder,nodeUser,text,createDate")]
|
||||
public Guid? NodeObjectType { get; set; }
|
||||
|
||||
[Column("createDate")]
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Umbraco.Core.Persistence.Dtos
|
||||
internal class PropertyTypeDto
|
||||
{
|
||||
[Column("id")]
|
||||
[PrimaryKeyColumn(IdentitySeed = 50)]
|
||||
[PrimaryKeyColumn(IdentitySeed = 100)]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Column("dataTypeId")]
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.SqlClient;
|
||||
using System.Data.SqlServerCe;
|
||||
using System.Data.SqlTypes;
|
||||
using System.Linq;
|
||||
using NPoco;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
@@ -61,26 +62,33 @@ namespace Umbraco.Core.Persistence
|
||||
/// <returns>The number of records that were inserted.</returns>
|
||||
public static int BulkInsertRecords<T>(this IUmbracoDatabase database, IEnumerable<T> records, bool useNativeBulkInsert = true)
|
||||
{
|
||||
var recordsA = records.ToArray();
|
||||
if (recordsA.Length == 0) return 0;
|
||||
if (!records.Any()) return 0;
|
||||
|
||||
var pocoData = database.PocoDataFactory.ForType(typeof(T));
|
||||
if (pocoData == null) throw new InvalidOperationException("Could not find PocoData for " + typeof(T));
|
||||
|
||||
if (database.DatabaseType.IsSqlCe())
|
||||
{
|
||||
if (useNativeBulkInsert) return BulkInsertRecordsSqlCe(database, pocoData, recordsA);
|
||||
if (useNativeBulkInsert)
|
||||
{
|
||||
return BulkInsertRecordsSqlCe(database, pocoData, records);
|
||||
}
|
||||
|
||||
// else, no other choice
|
||||
foreach (var record in recordsA)
|
||||
var count = 0;
|
||||
foreach (var record in records)
|
||||
{
|
||||
database.Insert(record);
|
||||
return recordsA.Length;
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
if (database.DatabaseType.IsSqlServer())
|
||||
{
|
||||
return useNativeBulkInsert && database.DatabaseType.IsSqlServer2008OrLater()
|
||||
? BulkInsertRecordsSqlServer(database, pocoData, recordsA)
|
||||
: BulkInsertRecordsWithCommands(database, recordsA);
|
||||
? BulkInsertRecordsSqlServer(database, pocoData, records)
|
||||
: BulkInsertRecordsWithCommands(database, records.ToArray());
|
||||
}
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
@@ -95,7 +103,9 @@ namespace Umbraco.Core.Persistence
|
||||
private static int BulkInsertRecordsWithCommands<T>(IUmbracoDatabase database, T[] records)
|
||||
{
|
||||
foreach (var command in database.GenerateBulkInsertCommands(records))
|
||||
{
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
return records.Length; // what else?
|
||||
}
|
||||
@@ -210,7 +220,15 @@ namespace Umbraco.Core.Persistence
|
||||
if (IncludeColumn(pocoData, columns[i]))
|
||||
{
|
||||
var val = columns[i].Value.GetValue(record);
|
||||
updatableRecord.SetValue(i, val);
|
||||
if (val is byte[])
|
||||
{
|
||||
var bytes = val as byte[];
|
||||
updatableRecord.SetSqlBinary(i, new SqlBinary(bytes));
|
||||
}
|
||||
else
|
||||
{
|
||||
updatableRecord.SetValue(i, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
resultSet.Insert(updatableRecord);
|
||||
@@ -232,6 +250,10 @@ namespace Umbraco.Core.Persistence
|
||||
/// <returns>The number of records that were inserted.</returns>
|
||||
internal static int BulkInsertRecordsSqlServer<T>(IUmbracoDatabase database, PocoData pocoData, IEnumerable<T> records)
|
||||
{
|
||||
// TODO: The main reason this exists is because the NPoco InsertBulk method doesn't return the number of items.
|
||||
// It is worth investigating the performance of this vs NPoco's because we use a custom BulkDataReader
|
||||
// which in theory should be more efficient than NPocos way of building up an in-memory DataTable.
|
||||
|
||||
// create command against the original database.Connection
|
||||
using (var command = database.CreateCommand(database.Connection, CommandType.Text, string.Empty))
|
||||
{
|
||||
@@ -243,7 +265,13 @@ namespace Umbraco.Core.Persistence
|
||||
var syntax = database.SqlContext.SqlSyntax as SqlServerSyntaxProvider;
|
||||
if (syntax == null) throw new NotSupportedException("SqlSyntax must be SqlServerSyntaxProvider.");
|
||||
|
||||
using (var copy = new SqlBulkCopy(tConnection, SqlBulkCopyOptions.Default, tTransaction) { BulkCopyTimeout = 10000, DestinationTableName = tableName })
|
||||
using (var copy = new SqlBulkCopy(tConnection, SqlBulkCopyOptions.Default, tTransaction)
|
||||
{
|
||||
BulkCopyTimeout = 0, // 0 = no bulk copy timeout. If a timeout occurs it will be an connection/command timeout.
|
||||
DestinationTableName = tableName,
|
||||
// be consistent with NPoco: https://github.com/schotime/NPoco/blob/5117a55fde57547e928246c044fd40bd00b2d7d1/src/NPoco.SqlServer/SqlBulkCopyHelper.cs#L50
|
||||
BatchSize = 4096
|
||||
})
|
||||
using (var bulkReader = new PocoDataDataReader<T, SqlServerSyntaxProvider>(records, pocoData, syntax))
|
||||
{
|
||||
//we need to add column mappings here because otherwise columns will be matched by their order and if the order of them are different in the DB compared
|
||||
|
||||
@@ -27,12 +27,13 @@ namespace Umbraco.Core.Persistence
|
||||
/// The number of rows to load per page
|
||||
/// </param>
|
||||
/// <param name="sql"></param>
|
||||
/// <param name="sqlCount">Specify a custom Sql command to get the total count, if null is specified than the auto-generated sql count will be used</param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// NPoco's normal Page returns a List{T} but sometimes we don't want all that in memory and instead want to
|
||||
/// iterate over each row with a reader using Query vs Fetch.
|
||||
/// </remarks>
|
||||
internal static IEnumerable<T> QueryPaged<T>(this IDatabase database, long pageSize, Sql sql)
|
||||
internal static IEnumerable<T> QueryPaged<T>(this IDatabase database, long pageSize, Sql sql, Sql sqlCount)
|
||||
{
|
||||
var sqlString = sql.SQL;
|
||||
var sqlArgs = sql.Arguments;
|
||||
@@ -42,12 +43,12 @@ namespace Umbraco.Core.Persistence
|
||||
do
|
||||
{
|
||||
// Get the paged queries
|
||||
database.BuildPageQueries<T>(pageIndex * pageSize, pageSize, sqlString, ref sqlArgs, out var sqlCount, out var sqlPage);
|
||||
database.BuildPageQueries<T>(pageIndex * pageSize, pageSize, sqlString, ref sqlArgs, out var generatedSqlCount, out var sqlPage);
|
||||
|
||||
// get the item count once
|
||||
if (itemCount == null)
|
||||
{
|
||||
itemCount = database.ExecuteScalar<int>(sqlCount, sqlArgs);
|
||||
itemCount = database.ExecuteScalar<int>(sqlCount?.SQL ?? generatedSqlCount, sqlCount?.Arguments ?? sqlArgs);
|
||||
}
|
||||
pageIndex++;
|
||||
|
||||
@@ -60,6 +61,22 @@ namespace Umbraco.Core.Persistence
|
||||
} while ((pageIndex * pageSize) < itemCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Iterates over the result of a paged data set with a db reader
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="database"></param>
|
||||
/// <param name="pageSize">
|
||||
/// The number of rows to load per page
|
||||
/// </param>
|
||||
/// <param name="sql"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// NPoco's normal Page returns a List{T} but sometimes we don't want all that in memory and instead want to
|
||||
/// iterate over each row with a reader using Query vs Fetch.
|
||||
/// </remarks>
|
||||
internal static IEnumerable<T> QueryPaged<T>(this IDatabase database, long pageSize, Sql sql) => database.QueryPaged<T>(pageSize, sql, null);
|
||||
|
||||
// NOTE
|
||||
//
|
||||
// proper way to do it with TSQL and SQLCE
|
||||
|
||||
@@ -40,9 +40,10 @@ namespace Umbraco.Core.Persistence
|
||||
_tableDefinition = DefinitionFactory.GetTableDefinition(pd.Type, sqlSyntaxProvider);
|
||||
if (_tableDefinition == null) throw new InvalidOperationException("No table definition found for type " + pd.Type);
|
||||
|
||||
// only real columns, exclude result columns
|
||||
// only real columns, exclude result/computed columns
|
||||
// Like NPoco does: https://github.com/schotime/NPoco/blob/5117a55fde57547e928246c044fd40bd00b2d7d1/src/NPoco.SqlServer/SqlBulkCopyHelper.cs#L59
|
||||
_readerColumns = pd.Columns
|
||||
.Where(x => x.Value.ResultColumn == false)
|
||||
.Where(x => x.Value.ResultColumn == false && x.Value.ComputedColumn == false)
|
||||
.Select(x => x.Value)
|
||||
.ToArray();
|
||||
|
||||
|
||||
@@ -328,8 +328,11 @@ namespace Umbraco.Core.Persistence.Querying
|
||||
{
|
||||
case ExpressionType.MemberAccess:
|
||||
// false property , i.e. x => !Trashed
|
||||
SqlParameters.Add(true);
|
||||
return Visited ? string.Empty : $"NOT ({o} = @{SqlParameters.Count - 1})";
|
||||
// BUT we don't want to do a NOT SQL statement since this generally results in indexes not being used
|
||||
// so we want to do an == false
|
||||
SqlParameters.Add(false);
|
||||
return Visited ? string.Empty : $"{o} = @{SqlParameters.Count - 1}";
|
||||
//return Visited ? string.Empty : $"NOT ({o} = @{SqlParameters.Count - 1})";
|
||||
default:
|
||||
// could be anything else, such as: x => !x.Path.StartsWith("-20")
|
||||
return Visited ? string.Empty : string.Concat("NOT (", o, ")");
|
||||
|
||||
@@ -73,5 +73,10 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
/// </summary>
|
||||
/// <param name="permission"></param>
|
||||
void AddOrUpdatePermissions(ContentPermissionSet permission);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if there is any content in the recycle bin
|
||||
/// </summary>
|
||||
bool RecycleBinSmells();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,5 +6,6 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
public interface IMediaRepository : IContentRepository<int, IMedia>, IReadRepository<Guid, IMedia>
|
||||
{
|
||||
IMedia GetMediaByPath(string mediaPath);
|
||||
bool RecycleBinSmells();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
Database.Update(dto);
|
||||
entity.ResetDirtyProperties();
|
||||
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IConsent>(entity.Id));
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IConsent, int>(entity.Id));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -630,6 +630,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
}
|
||||
if (versions.Count == 0) return new Dictionary<int, PropertyCollection>();
|
||||
|
||||
// TODO: This is a bugger of a query and I believe is the main issue with regards to SQL performance drain when querying content
|
||||
// which is done when rebuilding caches/indexes/etc... in bulk. We are using an "IN" query on umbracoPropertyData.VersionId
|
||||
// which then performs a Clustered Index Scan on PK_umbracoPropertyData which means it iterates the entire table which can be enormous!
|
||||
// especially if there are both a lot of content but worse if there is a lot of versions of that content.
|
||||
// So is it possible to return this property data without doing an index scan on PK_umbracoPropertyData and without iterating every row
|
||||
// in the table?
|
||||
|
||||
// get all PropertyDataDto for all definitions / versions
|
||||
var allPropertyDataDtos = Database.FetchByGroups<PropertyDataDto, int>(versions, 2000, batch =>
|
||||
SqlContext.Sql()
|
||||
|
||||
@@ -130,7 +130,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
foreach (var translation in dictionaryItem.Translations)
|
||||
translation.Value = translation.Value.ToValidXmlString();
|
||||
|
||||
|
||||
var dto = DictionaryItemFactory.BuildDto(dictionaryItem);
|
||||
|
||||
var id = Convert.ToInt32(Database.Insert(dto));
|
||||
@@ -152,7 +152,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
foreach (var translation in entity.Translations)
|
||||
translation.Value = translation.Value.ToValidXmlString();
|
||||
|
||||
|
||||
var dto = DictionaryItemFactory.BuildDto(entity);
|
||||
|
||||
Database.Update(dto);
|
||||
@@ -174,8 +174,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
entity.ResetDirtyProperties();
|
||||
|
||||
//Clear the cache entries that exist by uniqueid/item key
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem>(entity.ItemKey));
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem>(entity.Key));
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, string>(entity.ItemKey));
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, Guid>(entity.Key));
|
||||
}
|
||||
|
||||
protected override void PersistDeletedItem(IDictionaryItem entity)
|
||||
@@ -186,8 +186,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
Database.Delete<DictionaryDto>("WHERE id = @Id", new { Id = entity.Key });
|
||||
|
||||
//Clear the cache entries that exist by uniqueid/item key
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem>(entity.ItemKey));
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem>(entity.Key));
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, string>(entity.ItemKey));
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, Guid>(entity.Key));
|
||||
|
||||
entity.DeleteDate = DateTime.Now;
|
||||
}
|
||||
@@ -203,8 +203,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
Database.Delete<DictionaryDto>("WHERE id = @Id", new { Id = dto.UniqueId });
|
||||
|
||||
//Clear the cache entries that exist by uniqueid/item key
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem>(dto.Key));
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem>(dto.UniqueId));
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, string>(dto.Key));
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, Guid>(dto.UniqueId));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -912,6 +912,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
public override int RecycleBinId => Constants.System.RecycleBinContent;
|
||||
|
||||
public bool RecycleBinSmells()
|
||||
{
|
||||
var cache = _appCaches.RuntimeCache;
|
||||
var cacheKey = CacheKeys.ContentRecycleBinCacheKey;
|
||||
|
||||
// always cache either true or false
|
||||
return cache.GetCacheItem<bool>(cacheKey, () => CountChildren(RecycleBinId) > 0);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Read Repository implementation for Guid keys
|
||||
@@ -1154,7 +1163,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
if (withCache)
|
||||
{
|
||||
// if the cache contains the (proper version of the) item, use it
|
||||
var cached = IsolatedCache.GetCacheItem<IContent>(RepositoryCacheKeys.GetKey<IContent>(dto.NodeId));
|
||||
var cached = IsolatedCache.GetCacheItem<IContent>(RepositoryCacheKeys.GetKey<IContent, int>(dto.NodeId));
|
||||
if (cached != null && cached.VersionId == dto.DocumentVersionDto.ContentVersionDto.Id)
|
||||
{
|
||||
content[i] = (Content)cached;
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
/// </summary>
|
||||
internal class MediaRepository : ContentRepositoryBase<int, IMedia, MediaRepository>, IMediaRepository
|
||||
{
|
||||
private readonly AppCaches _cache;
|
||||
private readonly IMediaTypeRepository _mediaTypeRepository;
|
||||
private readonly ITagRepository _tagRepository;
|
||||
private readonly MediaByGuidReadRepository _mediaByGuidReadRepository;
|
||||
@@ -32,6 +33,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
Lazy<PropertyEditorCollection> propertyEditorCollection, DataValueReferenceFactoryCollection dataValueReferenceFactories)
|
||||
: base(scopeAccessor, cache, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditorCollection, dataValueReferenceFactories)
|
||||
{
|
||||
_cache = cache;
|
||||
_mediaTypeRepository = mediaTypeRepository ?? throw new ArgumentNullException(nameof(mediaTypeRepository));
|
||||
_tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository));
|
||||
_mediaByGuidReadRepository = new MediaByGuidReadRepository(this, scopeAccessor, cache, logger);
|
||||
@@ -369,6 +371,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
public override int RecycleBinId => Constants.System.RecycleBinMedia;
|
||||
|
||||
public bool RecycleBinSmells()
|
||||
{
|
||||
var cache = _cache.RuntimeCache;
|
||||
var cacheKey = CacheKeys.MediaRecycleBinCacheKey;
|
||||
|
||||
// always cache either true or false
|
||||
return cache.GetCacheItem<bool>(cacheKey, () => CountChildren(RecycleBinId) > 0);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Read Repository implementation for Guid keys
|
||||
@@ -497,10 +508,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
if (withCache)
|
||||
{
|
||||
// if the cache contains the (proper version of the) item, use it
|
||||
var cached = IsolatedCache.GetCacheItem<IMedia>(RepositoryCacheKeys.GetKey<IMedia>(dto.NodeId));
|
||||
var cached = IsolatedCache.GetCacheItem<IMedia>(RepositoryCacheKeys.GetKey<IMedia, int>(dto.NodeId));
|
||||
if (cached != null && cached.VersionId == dto.ContentVersionDto.Id)
|
||||
{
|
||||
content[i] = (Models.Media) cached;
|
||||
content[i] = (Models.Media)cached;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,7 +331,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
}
|
||||
|
||||
protected override void PersistUpdatedItem(IMember entity)
|
||||
{
|
||||
{
|
||||
// update
|
||||
entity.UpdatingEntity();
|
||||
|
||||
@@ -534,7 +534,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
var sqlSelectTemplateVersion = SqlContext.Templates.Get("Umbraco.Core.MemberRepository.SetLastLogin2", s => s
|
||||
.Select<ContentVersionDto>(x => x.Id)
|
||||
.From<ContentVersionDto>()
|
||||
.From<ContentVersionDto>()
|
||||
.InnerJoin<NodeDto>().On<NodeDto, ContentVersionDto>((l, r) => l.NodeId == r.NodeId)
|
||||
.InnerJoin<MemberDto>().On<MemberDto, NodeDto>((l, r) => l.NodeId == r.NodeId)
|
||||
.Where<NodeDto>(x => x.NodeObjectType == SqlTemplate.Arg<Guid>("nodeObjectType"))
|
||||
@@ -606,7 +606,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
if (withCache)
|
||||
{
|
||||
// if the cache contains the (proper version of the) item, use it
|
||||
var cached = IsolatedCache.GetCacheItem<IMember>(RepositoryCacheKeys.GetKey<IMember>(dto.NodeId));
|
||||
var cached = IsolatedCache.GetCacheItem<IMember>(RepositoryCacheKeys.GetKey<IMember, int>(dto.NodeId));
|
||||
if (cached != null && cached.VersionId == dto.ContentVersionDto.Id)
|
||||
{
|
||||
content[i] = (Member) cached;
|
||||
|
||||
@@ -8,14 +8,27 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
/// </summary>
|
||||
internal static class RepositoryCacheKeys
|
||||
{
|
||||
private static readonly Dictionary<Type, string> Keys = new Dictionary<Type, string>();
|
||||
private static readonly Dictionary<Type, string> s_keys = new Dictionary<Type, string>();
|
||||
|
||||
public static string GetKey<T>()
|
||||
{
|
||||
var type = typeof(T);
|
||||
return Keys.TryGetValue(type, out var key) ? key : (Keys[type] = "uRepo_" + type.Name + "_");
|
||||
return s_keys.TryGetValue(type, out var key) ? key : (s_keys[type] = "uRepo_" + type.Name + "_");
|
||||
}
|
||||
|
||||
public static string GetKey<T>(object id) => GetKey<T>() + id;
|
||||
public static string GetKey<T, TId>(TId id)
|
||||
{
|
||||
if (EqualityComparer<TId>.Default.Equals(id, default))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (typeof(TId).IsValueType)
|
||||
{
|
||||
return GetKey<T>() + id;
|
||||
}
|
||||
|
||||
return GetKey<T>() + id.ToString().ToUpperInvariant();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
protected override IUser PerformGet(int id)
|
||||
{
|
||||
// This will never resolve to a user, yet this is asked
|
||||
// for all of the time (especially in cases of members).
|
||||
// Don't issue a SQL call for this, we know it will not exist.
|
||||
if (id == default || id < -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var sql = SqlContext.Sql()
|
||||
.Select<UserDto>()
|
||||
.From<UserDto>()
|
||||
@@ -168,7 +176,7 @@ ORDER BY colName";
|
||||
}
|
||||
|
||||
public Guid CreateLoginSession(int userId, string requestingIpAddress, bool cleanStaleSessions = true)
|
||||
{
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var dto = new UserLoginDto
|
||||
{
|
||||
|
||||
59
src/Umbraco.Core/Persistence/SqlCeImageMapper.cs
Normal file
59
src/Umbraco.Core/Persistence/SqlCeImageMapper.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Data.SqlServerCe;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using NPoco;
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Core.Persistence
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom NPoco mapper for SqlCe
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Work arounds to handle special columns
|
||||
/// </remarks>
|
||||
internal class SqlCeImageMapper : DefaultMapper
|
||||
{
|
||||
public override Func<object, object> GetToDbConverter(Type destType, MemberInfo sourceMemberInfo)
|
||||
{
|
||||
if (sourceMemberInfo.GetMemberInfoType() == typeof(byte[]))
|
||||
{
|
||||
return x =>
|
||||
{
|
||||
var pd = Current.SqlContext.PocoDataFactory.ForType(sourceMemberInfo.DeclaringType);
|
||||
if (pd == null) return null;
|
||||
var col = pd.AllColumns.FirstOrDefault(x => x.MemberInfoData.MemberInfo == sourceMemberInfo);
|
||||
if (col == null) return null;
|
||||
|
||||
return new SqlCeParameter
|
||||
{
|
||||
SqlDbType = SqlDbType.Image,
|
||||
Value = x ?? Array.Empty<byte>()
|
||||
};
|
||||
};
|
||||
}
|
||||
return base.GetToDbConverter(destType, sourceMemberInfo);
|
||||
}
|
||||
|
||||
public override Func<object, object> GetParameterConverter(DbCommand dbCommand, Type sourceType)
|
||||
{
|
||||
if (sourceType == typeof(byte[]))
|
||||
{
|
||||
return x =>
|
||||
{
|
||||
var param = new SqlCeParameter
|
||||
{
|
||||
SqlDbType = SqlDbType.Image,
|
||||
Value = x
|
||||
};
|
||||
return param;
|
||||
};
|
||||
|
||||
}
|
||||
return base.GetParameterConverter(dbCommand, sourceType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,14 @@ namespace Umbraco.Core.Persistence.SqlSyntax
|
||||
/// </summary>
|
||||
public class SqlCeSyntaxProvider : MicrosoftSqlSyntaxProviderBase<SqlCeSyntaxProvider>
|
||||
{
|
||||
public SqlCeSyntaxProvider()
|
||||
{
|
||||
BlobColumnDefinition = "IMAGE";
|
||||
// This is silly to have to do this but the way these inherited classes are structured it's the easiest
|
||||
// way without an overhaul in type map initialization
|
||||
DbTypeMap.Set<byte[]>(DbType.Binary, BlobColumnDefinition);
|
||||
}
|
||||
|
||||
public override Sql<ISqlContext> SelectTop(Sql<ISqlContext> sql, int top)
|
||||
{
|
||||
return new Sql<ISqlContext>(sql.SqlContext, sql.SQL.Insert(sql.SQL.IndexOf(' '), " TOP " + top), sql.Arguments);
|
||||
@@ -265,15 +273,36 @@ where table_name=@0 and column_name=@1", tableName, columnName).FirstOrDefault()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public override string DropIndex { get { return "DROP INDEX {1}.{0}"; } }
|
||||
public override string CreateIndex => "CREATE {0}{1}INDEX {2} ON {3} ({4})";
|
||||
public override string Format(IndexDefinition index)
|
||||
{
|
||||
var name = string.IsNullOrEmpty(index.Name)
|
||||
? $"IX_{index.TableName}_{index.ColumnName}"
|
||||
: index.Name;
|
||||
|
||||
var columns = index.Columns.Any()
|
||||
? string.Join(",", index.Columns.Select(x => GetQuotedColumnName(x.Name)))
|
||||
: GetQuotedColumnName(index.ColumnName);
|
||||
|
||||
|
||||
return string.Format(CreateIndex, GetIndexType(index.IndexType), " ", GetQuotedName(name),
|
||||
GetQuotedTableName(index.TableName), columns);
|
||||
}
|
||||
|
||||
public override string GetSpecialDbType(SpecialDbTypes dbTypes)
|
||||
{
|
||||
if (dbTypes == SpecialDbTypes.NVARCHARMAX) // SqlCE does not have nvarchar(max) for now
|
||||
return "NTEXT";
|
||||
return base.GetSpecialDbType(dbTypes);
|
||||
}
|
||||
public override SqlDbType GetSqlDbType(DbType dbType)
|
||||
{
|
||||
if (DbType.Binary == dbType)
|
||||
{
|
||||
return SqlDbType.Image;
|
||||
}
|
||||
return base.GetSqlDbType(dbType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,5 +372,24 @@ where tbl.[name]=@0 and col.[name]=@1;", tableName, columnName)
|
||||
public override string DropIndex => "DROP INDEX {0} ON {1}";
|
||||
|
||||
public override string RenameColumn => "sp_rename '{0}.{1}', '{2}', 'COLUMN'";
|
||||
|
||||
public override string CreateIndex => "CREATE {0}{1}INDEX {2} ON {3} ({4}){5}";
|
||||
public override string Format(IndexDefinition index)
|
||||
{
|
||||
var name = string.IsNullOrEmpty(index.Name)
|
||||
? $"IX_{index.TableName}_{index.ColumnName}"
|
||||
: index.Name;
|
||||
|
||||
var columns = index.Columns.Any()
|
||||
? string.Join(",", index.Columns.Select(x => GetQuotedColumnName(x.Name)))
|
||||
: GetQuotedColumnName(index.ColumnName);
|
||||
|
||||
var includeColumns = index.IncludeColumns?.Any() ?? false
|
||||
? $" INCLUDE ({string.Join(",", index.IncludeColumns.Select(x => GetQuotedColumnName(x.Name)))})"
|
||||
: string.Empty;
|
||||
|
||||
return string.Format(CreateIndex, GetIndexType(index.IndexType), " ", GetQuotedName(name),
|
||||
GetQuotedTableName(index.TableName), columns, includeColumns);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,7 +352,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax
|
||||
sql.Append(" ");
|
||||
sql.Append(FormatIdentity(column));
|
||||
|
||||
var isNullable = column.IsNullable;
|
||||
//var isNullable = column.IsNullable;
|
||||
|
||||
//var constraint = FormatConstraint(column)?.TrimStart("CONSTRAINT ");
|
||||
//var hasConstraint = !string.IsNullOrWhiteSpace(constraint);
|
||||
@@ -360,11 +360,14 @@ namespace Umbraco.Core.Persistence.SqlSyntax
|
||||
//var defaultValue = FormatDefaultValue(column);
|
||||
//var hasDefaultValue = !string.IsNullOrWhiteSpace(defaultValue);
|
||||
|
||||
if (isNullable /*&& !hasConstraint && !hasDefaultValue*/)
|
||||
{
|
||||
sqls = Enumerable.Empty<string>();
|
||||
return sql.ToString();
|
||||
}
|
||||
// TODO: This used to exit if nullable but that means this would never work
|
||||
// to return SQL if the column was nullable?!? I don't get it. This was here
|
||||
// 4 years ago, I've removed it so that this works for nullable columns.
|
||||
//if (isNullable /*&& !hasConstraint && !hasDefaultValue*/)
|
||||
//{
|
||||
// sqls = Enumerable.Empty<string>();
|
||||
// return sql.ToString();
|
||||
//}
|
||||
|
||||
var msql = new List<string>();
|
||||
sqls = msql;
|
||||
@@ -571,7 +574,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax
|
||||
public virtual string CreateDefaultConstraint => "ALTER TABLE {0} ADD CONSTRAINT {1} DEFAULT ({2}) FOR {3}";
|
||||
|
||||
public virtual string ConvertIntegerToOrderableString => "REPLACE(STR({0}, 8), SPACE(1), '0')";
|
||||
public virtual string ConvertDateToOrderableString => "CONVERT(nvarchar, {0}, 102)";
|
||||
public virtual string ConvertDateToOrderableString => "CONVERT(nvarchar, {0}, 120)";
|
||||
public virtual string ConvertDecimalToOrderableString => "REPLACE(STR({0}, 20, 9), SPACE(1), '0')";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ using Umbraco.Core.Persistence.FaultHandling;
|
||||
|
||||
namespace Umbraco.Core.Persistence
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Extends NPoco Database for Umbraco.
|
||||
/// </summary>
|
||||
@@ -38,14 +39,10 @@ namespace Umbraco.Core.Persistence
|
||||
: base(connectionString, sqlContext.DatabaseType, provider, sqlContext.SqlSyntax.DefaultIsolationLevel)
|
||||
{
|
||||
SqlContext = sqlContext;
|
||||
|
||||
_logger = logger;
|
||||
_connectionRetryPolicy = connectionRetryPolicy;
|
||||
_commandRetryPolicy = commandRetryPolicy;
|
||||
|
||||
EnableSqlTrace = EnableSqlTraceDefault;
|
||||
|
||||
NPocoDatabaseExtensions.ConfigureNPocoBulkExtensions();
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -57,10 +54,17 @@ namespace Umbraco.Core.Persistence
|
||||
{
|
||||
SqlContext = sqlContext;
|
||||
_logger = logger;
|
||||
Init();
|
||||
}
|
||||
|
||||
private void Init()
|
||||
{
|
||||
EnableSqlTrace = EnableSqlTraceDefault;
|
||||
|
||||
NPocoDatabaseExtensions.ConfigureNPocoBulkExtensions();
|
||||
if (SqlContext.DatabaseType == DatabaseType.SQLCe)
|
||||
{
|
||||
Mappers.Add(new SqlCeImageMapper());
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -257,5 +261,6 @@ namespace Umbraco.Core.Persistence
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace Umbraco.Core.PropertyEditors
|
||||
public class DataEditor : IDataEditor
|
||||
{
|
||||
private IDictionary<string, object> _defaultConfiguration;
|
||||
private IDataValueEditor _reusableEditor;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DataEditor"/> class.
|
||||
@@ -90,7 +91,8 @@ namespace Umbraco.Core.PropertyEditors
|
||||
/// simple enough for now.</para>
|
||||
/// </remarks>
|
||||
// TODO: point of that one? shouldn't we always configure?
|
||||
public IDataValueEditor GetValueEditor() => ExplicitValueEditor ?? CreateValueEditor();
|
||||
public IDataValueEditor GetValueEditor() => ExplicitValueEditor ?? (_reusableEditor ?? (_reusableEditor = CreateValueEditor()));
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Umbraco.Core.PropertyEditors
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Marks a class that represents a data editor.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.PropertyEditors
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines if a property type's value should be compressed in memory
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
///
|
||||
/// </remarks>
|
||||
public interface IPropertyCacheCompression
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether a property on the content is/should be compressed
|
||||
/// </summary>
|
||||
/// <param name="content">The content</param>
|
||||
/// <param name="propertyTypeAlias">The property to compress or not</param>
|
||||
/// <param name="published">Whether this content is the published version</param>
|
||||
bool IsCompressed(IReadOnlyContentBase content, string propertyTypeAlias, bool published);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.PropertyEditors
|
||||
{
|
||||
public interface IPropertyCacheCompressionOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether a property on the content is/should be compressed
|
||||
/// </summary>
|
||||
/// <param name="content">The content</param>
|
||||
/// <param name="propertyType">The property to compress or not</param>
|
||||
/// <param name="dataEditor">The datatype of the property to compress or not</param>
|
||||
/// <param name="published">Whether this content is the published version</param>
|
||||
bool IsCompressed(IReadOnlyContentBase content, PropertyType propertyType, IDataEditor dataEditor, bool published);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.PropertyEditors.ValueConverters;
|
||||
using static Umbraco.Core.PropertyEditors.ValueConverters.ImageCropperValue;
|
||||
|
||||
namespace Umbraco.Core.PropertyEditors
|
||||
{
|
||||
@@ -22,4 +26,35 @@ namespace Umbraco.Core.PropertyEditors
|
||||
public int Height { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
internal static class ImageCropperConfigurationExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Applies the configuration to ensure only valid crops are kept and have the correct width/height.
|
||||
/// </summary>
|
||||
/// <param name="configuration">The configuration.</param>
|
||||
public static void ApplyConfiguration(this ImageCropperValue imageCropperValue, ImageCropperConfiguration configuration)
|
||||
{
|
||||
var crops = new List<ImageCropperCrop>();
|
||||
|
||||
var configuredCrops = configuration?.Crops;
|
||||
if (configuredCrops != null)
|
||||
{
|
||||
foreach (var configuredCrop in configuredCrops)
|
||||
{
|
||||
var crop = imageCropperValue.Crops?.FirstOrDefault(x => x.Alias == configuredCrop.Alias);
|
||||
|
||||
crops.Add(new ImageCropperCrop
|
||||
{
|
||||
Alias = configuredCrop.Alias,
|
||||
Width = configuredCrop.Width,
|
||||
Height = configuredCrop.Height,
|
||||
Coordinates = crop?.Coordinates
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
imageCropperValue.Crops = crops;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.PropertyEditors
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation for <see cref="IPropertyCacheCompressionOptions"/> which does not compress any property data
|
||||
/// </summary>
|
||||
internal class NoopPropertyCacheCompressionOptions : IPropertyCacheCompressionOptions
|
||||
{
|
||||
public bool IsCompressed(IReadOnlyContentBase content, PropertyType propertyType, IDataEditor dataEditor, bool published) => false;
|
||||
}
|
||||
}
|
||||
49
src/Umbraco.Core/PropertyEditors/PropertyCacheCompression.cs
Normal file
49
src/Umbraco.Core/PropertyEditors/PropertyCacheCompression.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.PropertyEditors
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Compresses property data based on config
|
||||
/// </summary>
|
||||
internal class PropertyCacheCompression : IPropertyCacheCompression
|
||||
{
|
||||
private readonly IPropertyCacheCompressionOptions _compressionOptions;
|
||||
private readonly IReadOnlyDictionary<int, IContentTypeComposition> _contentTypes;
|
||||
private readonly PropertyEditorCollection _propertyEditors;
|
||||
private readonly ConcurrentDictionary<(int contentTypeId, string propertyAlias, bool published), bool> _isCompressedCache;
|
||||
|
||||
public PropertyCacheCompression(
|
||||
IPropertyCacheCompressionOptions compressionOptions,
|
||||
IReadOnlyDictionary<int, IContentTypeComposition> contentTypes,
|
||||
PropertyEditorCollection propertyEditors,
|
||||
ConcurrentDictionary<(int, string, bool), bool> compressedStoragePropertyEditorCache)
|
||||
{
|
||||
_compressionOptions = compressionOptions;
|
||||
_contentTypes = contentTypes ?? throw new System.ArgumentNullException(nameof(contentTypes));
|
||||
_propertyEditors = propertyEditors ?? throw new System.ArgumentNullException(nameof(propertyEditors));
|
||||
_isCompressedCache = compressedStoragePropertyEditorCache;
|
||||
}
|
||||
|
||||
public bool IsCompressed(IReadOnlyContentBase content, string alias, bool published)
|
||||
{
|
||||
var compressedStorage = _isCompressedCache.GetOrAdd((content.ContentTypeId, alias, published), x =>
|
||||
{
|
||||
if (!_contentTypes.TryGetValue(x.contentTypeId, out var ct))
|
||||
return false;
|
||||
|
||||
var propertyType = ct.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == alias);
|
||||
if (propertyType == null) return false;
|
||||
|
||||
if (!_propertyEditors.TryGet(propertyType.PropertyEditorAlias, out var propertyEditor)) return false;
|
||||
|
||||
return _compressionOptions.IsCompressed(content, propertyType, propertyEditor, published);
|
||||
});
|
||||
|
||||
return compressedStorage;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.PropertyEditors
|
||||
{
|
||||
/// <summary>
|
||||
/// Compress large, non published text properties
|
||||
/// </summary>
|
||||
internal class UnPublishedContentPropertyCacheCompressionOptions : IPropertyCacheCompressionOptions
|
||||
{
|
||||
public bool IsCompressed(IReadOnlyContentBase content, PropertyType propertyType, IDataEditor dataEditor, bool published)
|
||||
{
|
||||
if (!published && propertyType.SupportsPublishing && propertyType.ValueStorageType == ValueStorageType.Ntext)
|
||||
{
|
||||
//Only compress non published content that supports publishing and the property is text
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,7 +140,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters
|
||||
/// Determines whether the value has a specified crop.
|
||||
/// </summary>
|
||||
public bool HasCrop(string alias)
|
||||
=> Crops.Any(x => x.Alias == alias);
|
||||
=> Crops != null && Crops.Any(x => x.Alias == alias);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the value has a source image.
|
||||
@@ -148,46 +148,35 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters
|
||||
public bool HasImage()
|
||||
=> !string.IsNullOrWhiteSpace(Src);
|
||||
|
||||
/// <summary>
|
||||
/// Applies a configuration.
|
||||
/// </summary>
|
||||
/// <remarks>Ensures that all crops defined in the configuration exists in the value.</remarks>
|
||||
internal void ApplyConfiguration(ImageCropperConfiguration configuration)
|
||||
internal ImageCropperValue Merge(ImageCropperValue imageCropperValue)
|
||||
{
|
||||
// merge the crop values - the alias + width + height comes from
|
||||
// configuration, but each crop can store its own coordinates
|
||||
|
||||
var configuredCrops = configuration?.Crops;
|
||||
if (configuredCrops == null) return;
|
||||
|
||||
//Use Crops if it's not null, otherwise create a new list
|
||||
var crops = Crops?.ToList() ?? new List<ImageCropperCrop>();
|
||||
|
||||
foreach (var configuredCrop in configuredCrops)
|
||||
var incomingCrops = imageCropperValue?.Crops;
|
||||
if (incomingCrops != null)
|
||||
{
|
||||
var crop = crops.FirstOrDefault(x => x.Alias == configuredCrop.Alias);
|
||||
if (crop != null)
|
||||
foreach (var incomingCrop in incomingCrops)
|
||||
{
|
||||
// found, apply the height & width
|
||||
crop.Width = configuredCrop.Width;
|
||||
crop.Height = configuredCrop.Height;
|
||||
}
|
||||
else
|
||||
{
|
||||
// not found, add
|
||||
crops.Add(new ImageCropperCrop
|
||||
var crop = crops.FirstOrDefault(x => x.Alias == incomingCrop.Alias);
|
||||
if (crop == null)
|
||||
{
|
||||
Alias = configuredCrop.Alias,
|
||||
Width = configuredCrop.Width,
|
||||
Height = configuredCrop.Height
|
||||
});
|
||||
// Add incoming crop
|
||||
crops.Add(incomingCrop);
|
||||
}
|
||||
else if (crop.Coordinates == null)
|
||||
{
|
||||
// Use incoming crop coordinates
|
||||
crop.Coordinates = incomingCrop.Coordinates;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// assume we don't have to remove the crops in value, that
|
||||
// are not part of configuration anymore?
|
||||
|
||||
Crops = crops;
|
||||
return new ImageCropperValue()
|
||||
{
|
||||
Src = !string.IsNullOrWhiteSpace(Src) ? Src : imageCropperValue?.Src,
|
||||
Crops = crops,
|
||||
FocalPoint = FocalPoint ?? imageCropperValue?.FocalPoint
|
||||
};
|
||||
}
|
||||
|
||||
#region IEquatable
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Core.Composing;
|
||||
@@ -18,6 +19,8 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters
|
||||
{
|
||||
private readonly PropertyEditorCollection _propertyEditors;
|
||||
|
||||
string[] ExcludedPropertyEditors = new string[] { Constants.PropertyEditors.Aliases.MediaPicker3 };
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonValueConverter"/> class.
|
||||
/// </summary>
|
||||
@@ -28,13 +31,16 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters
|
||||
|
||||
/// <summary>
|
||||
/// It is a converter for any value type that is "JSON"
|
||||
/// Unless it's in the Excluded Property Editors list
|
||||
/// The new MediaPicker 3 stores JSON but we want to use its own ValueConvertor
|
||||
/// </summary>
|
||||
/// <param name="propertyType"></param>
|
||||
/// <returns></returns>
|
||||
public override bool IsConverter(IPublishedPropertyType propertyType)
|
||||
{
|
||||
return _propertyEditors.TryGet(propertyType.EditorAlias, out var editor)
|
||||
&& editor.GetValueEditor().ValueType.InvariantEquals(ValueTypes.Json);
|
||||
&& editor.GetValueEditor().ValueType.InvariantEquals(ValueTypes.Json)
|
||||
&& ExcludedPropertyEditors.Contains(propertyType.EditorAlias) == false;
|
||||
}
|
||||
|
||||
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
using System.Web.Hosting;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
@@ -16,6 +20,9 @@ using Umbraco.Core.Migrations.Install;
|
||||
using Umbraco.Core.Migrations.Upgrade;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.Mappers;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Sync;
|
||||
|
||||
namespace Umbraco.Core.Runtime
|
||||
@@ -119,6 +126,9 @@ namespace Umbraco.Core.Runtime
|
||||
|
||||
try
|
||||
{
|
||||
// Setup event listener
|
||||
UnattendedInstalled += CoreRuntime_UnattendedInstalled;
|
||||
|
||||
// throws if not full-trust
|
||||
new AspNetHostingPermission(AspNetHostingPermissionLevel.Unrestricted).Demand();
|
||||
|
||||
@@ -162,8 +172,7 @@ namespace Umbraco.Core.Runtime
|
||||
// run handlers
|
||||
RuntimeOptions.DoRuntimeEssentials(composition, appCaches, typeLoader, databaseFactory);
|
||||
|
||||
// determines if unattended install is enabled and performs it if required
|
||||
DoUnattendedInstall(databaseFactory);
|
||||
|
||||
|
||||
// register runtime-level services
|
||||
// there should be none, really - this is here "just in case"
|
||||
@@ -190,6 +199,13 @@ namespace Umbraco.Core.Runtime
|
||||
// create the factory
|
||||
_factory = Current.Factory = composition.CreateFactory();
|
||||
|
||||
// determines if unattended install is enabled and performs it if required
|
||||
DoUnattendedInstall(databaseFactory);
|
||||
|
||||
// determine our runtime level (AFTER UNATTENDED INSTALL)
|
||||
// TODO: Feels kinda weird to call this again
|
||||
DetermineRuntimeLevel(databaseFactory, ProfilingLogger);
|
||||
|
||||
// if level is Run and reason is UpgradeMigrations, that means we need to perform an unattended upgrade
|
||||
if (_state.Reason == RuntimeLevelReason.UpgradeMigrations && _state.Level == RuntimeLevel.Run)
|
||||
{
|
||||
@@ -203,8 +219,6 @@ namespace Umbraco.Core.Runtime
|
||||
// create & initialize the components
|
||||
_components = _factory.GetInstance<ComponentCollection>();
|
||||
_components.Initialize();
|
||||
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -227,7 +241,13 @@ namespace Umbraco.Core.Runtime
|
||||
{
|
||||
_factory = Current.Factory = composition?.CreateFactory();
|
||||
}
|
||||
catch { /* yea */ }
|
||||
catch
|
||||
{
|
||||
// In this case we are basically dead, we do not have a factory but we need
|
||||
// to report on the state so we need to manually set that, this is the only time
|
||||
// we ever do this.
|
||||
Current.RuntimeState = _state;
|
||||
}
|
||||
}
|
||||
|
||||
Debugger.Break();
|
||||
@@ -242,6 +262,93 @@ namespace Umbraco.Core.Runtime
|
||||
return _factory;
|
||||
}
|
||||
|
||||
private void CoreRuntime_UnattendedInstalled(IRuntime sender, UnattendedInstallEventArgs e)
|
||||
{
|
||||
var unattendedName = Environment.GetEnvironmentVariable("UnattendedUserName");
|
||||
var unattendedEmail = Environment.GetEnvironmentVariable("UnattendedUserEmail");
|
||||
var unattendedPassword = Environment.GetEnvironmentVariable("UnattendedUserPassword");
|
||||
|
||||
var fileExists = false;
|
||||
var filePath = IOHelper.MapPath("~/App_Data/unattended.user.json");
|
||||
|
||||
// No values store in ENV vars - try fallback file of /app_data/unattended.user.json
|
||||
if (unattendedName.IsNullOrWhiteSpace()
|
||||
|| unattendedEmail.IsNullOrWhiteSpace()
|
||||
|| unattendedPassword.IsNullOrWhiteSpace())
|
||||
{
|
||||
|
||||
fileExists = File.Exists(filePath);
|
||||
if (fileExists == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Attempt to deserialize JSON
|
||||
try
|
||||
{
|
||||
var fileContents = File.ReadAllText(filePath);
|
||||
var credentials = JsonConvert.DeserializeObject<UnattendedUserConfig>(fileContents);
|
||||
|
||||
unattendedName = credentials.Name;
|
||||
unattendedEmail = credentials.Email;
|
||||
unattendedPassword = credentials.Password;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// ENV Variables & JSON still empty
|
||||
if (unattendedName.IsNullOrWhiteSpace()
|
||||
|| unattendedEmail.IsNullOrWhiteSpace()
|
||||
|| unattendedPassword.IsNullOrWhiteSpace())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Update user details
|
||||
var currentProvider = MembershipProviderExtensions.GetUsersMembershipProvider();
|
||||
var admin = Current.Services.UserService.GetUserById(Constants.Security.SuperUserId);
|
||||
if (admin == null)
|
||||
{
|
||||
throw new InvalidOperationException("Could not find the super user!");
|
||||
}
|
||||
|
||||
var membershipUser = currentProvider.GetUser(Constants.Security.SuperUserId, true);
|
||||
if (membershipUser == null)
|
||||
{
|
||||
throw new InvalidOperationException($"No user found in membership provider with id of {Constants.Security.SuperUserId}.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var success = membershipUser.ChangePassword("default", unattendedPassword.Trim());
|
||||
if (success == false)
|
||||
{
|
||||
throw new FormatException("Password must be at least " + currentProvider.MinRequiredPasswordLength + " characters long and contain at least " + currentProvider.MinRequiredNonAlphanumericCharacters + " symbols");
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new FormatException("Password must be at least " + currentProvider.MinRequiredPasswordLength + " characters long and contain at least " + currentProvider.MinRequiredNonAlphanumericCharacters + " symbols");
|
||||
}
|
||||
|
||||
admin.Email = unattendedEmail.Trim();
|
||||
admin.Name = unattendedName.Trim();
|
||||
admin.Username = unattendedEmail.Trim();
|
||||
|
||||
Current.Services.UserService.Save(admin);
|
||||
|
||||
// Delete JSON file if it existed to tidy
|
||||
if (fileExists)
|
||||
{
|
||||
File.Delete(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
private void DoUnattendedInstall(IUmbracoDatabaseFactory databaseFactory)
|
||||
{
|
||||
// unattended install is not enabled
|
||||
@@ -285,6 +392,11 @@ namespace Umbraco.Core.Runtime
|
||||
var creator = new DatabaseSchemaCreator(database, Logger);
|
||||
creator.InitializeDatabaseSchema();
|
||||
database.CompleteTransaction();
|
||||
|
||||
// Emit an event that unattended install completed
|
||||
// Then this event can be listened for and create an unattended user
|
||||
UnattendedInstalled?.Invoke(this, new UnattendedInstallEventArgs());
|
||||
|
||||
Logger.Info<CoreRuntime>("Unattended install completed.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -397,6 +509,7 @@ namespace Umbraco.Core.Runtime
|
||||
public virtual void Terminate()
|
||||
{
|
||||
_components?.Terminate();
|
||||
UnattendedInstalled -= CoreRuntime_UnattendedInstalled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -404,7 +517,7 @@ namespace Umbraco.Core.Runtime
|
||||
/// </summary>
|
||||
public virtual void Compose(Composition composition)
|
||||
{
|
||||
// nothing
|
||||
// Nothing
|
||||
}
|
||||
|
||||
#region Getters
|
||||
@@ -465,5 +578,23 @@ namespace Umbraco.Core.Runtime
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Event to be used to notify when the Unattended Install has finished
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IRuntime, UnattendedInstallEventArgs> UnattendedInstalled;
|
||||
|
||||
[DataContract]
|
||||
public class UnattendedUserConfig
|
||||
{
|
||||
[DataMember(Name = "name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[DataMember(Name = "email")]
|
||||
public string Email { get; set; }
|
||||
|
||||
[DataMember(Name = "password")]
|
||||
public string Password { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Umbraco.Core.Serialization
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// When applied to a string or string collection field will ensure the deserialized strings are interned
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Borrowed from https://stackoverflow.com/a/34906004/694494
|
||||
/// On the same page an interesting approach of using a local intern pool https://stackoverflow.com/a/39605620/694494 which re-uses .NET System.Xml.NameTable
|
||||
/// </remarks>
|
||||
internal class AutoInterningStringConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
// CanConvert is not called when a converter is applied directly to a property.
|
||||
throw new NotImplementedException($"{nameof(AutoInterningStringConverter)} should not be used globally");
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType == JsonToken.Null)
|
||||
return null;
|
||||
// Check is in case the value is a non-string literal such as an integer.
|
||||
var s = reader.TokenType == JsonToken.String
|
||||
? string.Intern((string)reader.Value)
|
||||
: string.Intern((string)JToken.Load(reader));
|
||||
return s;
|
||||
}
|
||||
|
||||
public override bool CanWrite => false;
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace Umbraco.Core.Serialization
|
||||
{
|
||||
/// <summary>
|
||||
/// When applied to a dictionary with a string key, will ensure the deserialized string keys are interned
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <remarks>
|
||||
/// borrowed from https://stackoverflow.com/a/36116462/694494
|
||||
/// </remarks>
|
||||
internal class AutoInterningStringKeyCaseInsensitiveDictionaryConverter<TValue> : CaseInsensitiveDictionaryConverter<TValue>
|
||||
{
|
||||
public AutoInterningStringKeyCaseInsensitiveDictionaryConverter()
|
||||
{
|
||||
}
|
||||
public AutoInterningStringKeyCaseInsensitiveDictionaryConverter(StringComparer comparer) : base(comparer)
|
||||
{
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType == JsonToken.StartObject)
|
||||
{
|
||||
var dictionary = new Dictionary<string, TValue>();
|
||||
while (reader.Read())
|
||||
{
|
||||
switch (reader.TokenType)
|
||||
{
|
||||
case JsonToken.PropertyName:
|
||||
var key = string.Intern(reader.Value.ToString());
|
||||
|
||||
if (!reader.Read())
|
||||
throw new Exception("Unexpected end when reading object.");
|
||||
|
||||
var v = serializer.Deserialize<TValue>(reader);
|
||||
dictionary[key] = v;
|
||||
break;
|
||||
case JsonToken.Comment:
|
||||
break;
|
||||
case JsonToken.EndObject:
|
||||
return dictionary;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -14,12 +14,24 @@ namespace Umbraco.Core.Serialization
|
||||
/// </example>
|
||||
public class CaseInsensitiveDictionaryConverter<T> : CustomCreationConverter<IDictionary>
|
||||
{
|
||||
private readonly StringComparer _comparer;
|
||||
|
||||
public CaseInsensitiveDictionaryConverter()
|
||||
: this(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
}
|
||||
|
||||
public CaseInsensitiveDictionaryConverter(StringComparer comparer)
|
||||
{
|
||||
_comparer = comparer ?? throw new ArgumentNullException(nameof(comparer));
|
||||
}
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override bool CanRead => true;
|
||||
|
||||
public override bool CanConvert(Type objectType) => typeof(IDictionary<string,T>).IsAssignableFrom(objectType);
|
||||
|
||||
public override IDictionary Create(Type objectType) => new Dictionary<string, T>(StringComparer.OrdinalIgnoreCase);
|
||||
public override IDictionary Create(Type objectType) => new Dictionary<string, T>(_comparer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,25 +89,5 @@ namespace Umbraco.Core.Services
|
||||
{
|
||||
contentService.SetPermissions(new EntityPermissionSet(contentId, new EntityPermissionCollection()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if there is any content in the recycle bin
|
||||
/// </summary>
|
||||
/// <param name="contentService"></param>
|
||||
/// <returns></returns>
|
||||
public static bool RecycleBinSmells(this IContentService contentService)
|
||||
{
|
||||
return contentService.CountChildren(Constants.System.RecycleBinContent) > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if there is any media in the recycle bin
|
||||
/// </summary>
|
||||
/// <param name="mediaService"></param>
|
||||
/// <returns></returns>
|
||||
public static bool RecycleBinSmells(this IMediaService mediaService)
|
||||
{
|
||||
return mediaService.CountChildren(Constants.System.RecycleBinMedia) > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,9 +323,14 @@ namespace Umbraco.Core.Services
|
||||
/// <summary>
|
||||
/// Empties the Recycle Bin by deleting all <see cref="IContent"/> that resides in the bin
|
||||
/// </summary>
|
||||
/// <param name="userId">Optional Id of the User emptying the Recycle Bin</param>
|
||||
/// <param name="userId">Optional Id of the User emptying the Recycle Bin</param>
|
||||
OperationResult EmptyRecycleBin(int userId = Constants.Security.SuperUserId);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if there is any content in the recycle bin
|
||||
/// </summary>
|
||||
bool RecycleBinSmells();
|
||||
|
||||
/// <summary>
|
||||
/// Sorts documents.
|
||||
/// </summary>
|
||||
@@ -494,6 +499,11 @@ namespace Umbraco.Core.Services
|
||||
/// </summary>
|
||||
IContent Create(string name, int parentId, string documentTypeAlias, int userId = Constants.Security.SuperUserId);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a document
|
||||
/// </summary>
|
||||
IContent Create(string name, int parentId, IContentType contentType, int userId = Constants.Security.SuperUserId);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a document.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,9 +1,34 @@
|
||||
using System.Collections;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Umbraco.Core.Services
|
||||
{
|
||||
// TODO: This needs to be merged into one interface in v9, but better yet
|
||||
// the Localize method should just the based on area + alias and we should remove
|
||||
// the one with the 'key' (the concatenated area/alias) to ensure that we never use that again.
|
||||
|
||||
public interface ILocalizedTextService2 : ILocalizedTextService
|
||||
{
|
||||
/// <summary>
|
||||
/// Localize a key with variables
|
||||
/// </summary>
|
||||
/// <param name="area"></param>
|
||||
/// <param name="alias"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <param name="tokens">This can be null</param>
|
||||
/// <returns></returns>
|
||||
string Localize(string area, string alias, CultureInfo culture, IDictionary<string, string> tokens = null);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns all key/values in storage for the given culture
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
IDictionary<string, IDictionary<string, string>> GetAllStoredValuesByAreaAndAlias(CultureInfo culture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The entry point to localize any key in the text storage source for a given culture
|
||||
/// </summary>
|
||||
@@ -20,6 +45,7 @@ namespace Umbraco.Core.Services
|
||||
/// <param name="culture"></param>
|
||||
/// <param name="tokens">This can be null</param>
|
||||
/// <returns></returns>
|
||||
[Obsolete("Use LocalizedTextServiceExtensions.Localize or ILocalizedTextService2.Localize instead")]
|
||||
string Localize(string key, CultureInfo culture, IDictionary<string, string> tokens = null);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -172,6 +172,11 @@ namespace Umbraco.Core.Services
|
||||
/// <param name="userId">Optional Id of the User emptying the Recycle Bin</param>
|
||||
OperationResult EmptyRecycleBin(int userId = Constants.Security.SuperUserId);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if there is any media in the recycle bin
|
||||
/// </summary>
|
||||
bool RecycleBinSmells();
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all media of specified type. All children of deleted media is moved to Recycle Bin.
|
||||
/// </summary>
|
||||
|
||||
@@ -188,11 +188,34 @@ namespace Umbraco.Core.Services.Implement
|
||||
// TODO: what about culture?
|
||||
|
||||
var contentType = GetContentType(contentTypeAlias);
|
||||
if (contentType == null)
|
||||
throw new ArgumentException("No content type with that alias.", nameof(contentTypeAlias));
|
||||
return Create(name, parentId, contentType, userId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="IContent"/> object of a specified content type.
|
||||
/// </summary>
|
||||
/// <remarks>This method simply returns a new, non-persisted, IContent without any identity. It
|
||||
/// is intended as a shortcut to creating new content objects that does not invoke a save
|
||||
/// operation against the database.
|
||||
/// </remarks>
|
||||
/// <param name="name">The name of the content object.</param>
|
||||
/// <param name="parentId">The identifier of the parent, or -1.</param>
|
||||
/// <param name="contentType">The content type of the content</param>
|
||||
/// <param name="userId">The optional id of the user creating the content.</param>
|
||||
/// <returns>The content object.</returns>
|
||||
public IContent Create(string name, int parentId, IContentType contentType,
|
||||
int userId = Constants.Security.SuperUserId)
|
||||
{
|
||||
if (contentType is null)
|
||||
{
|
||||
throw new ArgumentException("Content type must be specified", nameof(contentType));
|
||||
}
|
||||
|
||||
var parent = parentId > 0 ? GetById(parentId) : null;
|
||||
if (parentId > 0 && parent == null)
|
||||
if (parentId > 0 && parent is null)
|
||||
{
|
||||
throw new ArgumentException("No content with that id.", nameof(parentId));
|
||||
}
|
||||
|
||||
var content = new Content(name, parentId, contentType);
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
@@ -1088,7 +1111,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
/// <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...
|
||||
/// 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,
|
||||
@@ -1415,7 +1438,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
var result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs.Value, d.WriterId);
|
||||
if (result.Success == false)
|
||||
Logger.Error<ContentService, int, PublishResultType>(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
|
||||
Logger.Error<ContentService,int,PublishResultType>(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
|
||||
results.Add(result);
|
||||
|
||||
}
|
||||
@@ -2118,6 +2141,15 @@ namespace Umbraco.Core.Services.Implement
|
||||
return OperationResult.Succeed(evtMsgs);
|
||||
}
|
||||
|
||||
public bool RecycleBinSmells()
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(Constants.Locks.ContentTree);
|
||||
return _documentRepository.RecycleBinSmells();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Others
|
||||
@@ -2201,7 +2233,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
while (page * pageSize < total)
|
||||
{
|
||||
var descendants = GetPagedDescendants(content.Id, page++, pageSize, out total);
|
||||
foreach (var descendant in descendants.OrderBy(x => x.Level).ThenBy(y => y.SortOrder))
|
||||
foreach (var descendant in descendants)
|
||||
{
|
||||
// if parent has not been copied, skip, else gets its copy id
|
||||
if (idmap.TryGetValue(descendant.ParentId, out parentId) == false) continue;
|
||||
@@ -2420,7 +2452,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
if (report.FixedIssues.Count > 0)
|
||||
{
|
||||
//The event args needs a content item so we'll make a fake one with enough properties to not cause a null ref
|
||||
var root = new Content("root", -1, new ContentType(-1)) { Id = -1, Key = Guid.Empty };
|
||||
var root = new Content("root", -1, new ContentType(-1)) {Id = -1, Key = Guid.Empty};
|
||||
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IContent>.EventArgs(new TreeChange<IContent>(root, TreeChangeTypes.RefreshAll)));
|
||||
}
|
||||
|
||||
@@ -3169,7 +3201,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
if (rollbackSaveResult.Success == false)
|
||||
{
|
||||
//Log the error/warning
|
||||
Logger.Error<ContentService, int, int, int>("User '{UserId}' was unable to rollback content '{ContentId}' to version '{VersionId}'", userId, id, versionId);
|
||||
Logger.Error<ContentService,int,int,int>("User '{UserId}' was unable to rollback content '{ContentId}' to version '{VersionId}'", userId, id, versionId);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -3178,7 +3210,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
scope.Events.Dispatch(RolledBack, this, rollbackEventArgs);
|
||||
|
||||
//Logging & Audit message
|
||||
Logger.Info<ContentService, int, int, int>("User '{UserId}' rolled back content '{ContentId}' to version '{VersionId}'", userId, id, versionId);
|
||||
Logger.Info<ContentService,int,int,int>("User '{UserId}' rolled back content '{ContentId}' to version '{VersionId}'", userId, id, versionId);
|
||||
Audit(AuditType.RollBack, userId, id, $"Content '{content.Name}' was rolled back to version '{versionId}'");
|
||||
}
|
||||
|
||||
|
||||
@@ -8,15 +8,15 @@ using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
// TODO: Convert all of this over to Niels K's localization framework one day
|
||||
|
||||
public class LocalizedTextService : ILocalizedTextService
|
||||
public class LocalizedTextService : ILocalizedTextService2
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly Lazy<LocalizedTextServiceFileSources> _fileSources;
|
||||
private readonly IDictionary<CultureInfo, IDictionary<string, IDictionary<string, string>>> _dictionarySource;
|
||||
private readonly IDictionary<CultureInfo, Lazy<XDocument>> _xmlSource;
|
||||
|
||||
private IDictionary<CultureInfo, IDictionary<string, IDictionary<string, string>>> _dictionarySource => _dictionarySourceLazy.Value;
|
||||
private IDictionary<CultureInfo, IDictionary<string, string>> _noAreaDictionarySource => _noAreaDictionarySourceLazy.Value;
|
||||
private readonly Lazy<IDictionary<CultureInfo, IDictionary<string, IDictionary<string, string>>>> _dictionarySourceLazy;
|
||||
private readonly Lazy<IDictionary<CultureInfo, IDictionary<string, string>>> _noAreaDictionarySourceLazy;
|
||||
private readonly char[] _splitter = new[] { '/' };
|
||||
/// <summary>
|
||||
/// Initializes with a file sources instance
|
||||
/// </summary>
|
||||
@@ -24,12 +24,50 @@ namespace Umbraco.Core.Services.Implement
|
||||
/// <param name="logger"></param>
|
||||
public LocalizedTextService(Lazy<LocalizedTextServiceFileSources> fileSources, ILogger logger)
|
||||
{
|
||||
if (logger == null) throw new ArgumentNullException("logger");
|
||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||
_logger = logger;
|
||||
if (fileSources == null) throw new ArgumentNullException("fileSources");
|
||||
if (fileSources == null) throw new ArgumentNullException(nameof(fileSources));
|
||||
_dictionarySourceLazy = new Lazy<IDictionary<CultureInfo, IDictionary<string, IDictionary<string, string>>>>(() => FileSourcesToAreaDictionarySources(fileSources.Value));
|
||||
_noAreaDictionarySourceLazy = new Lazy<IDictionary<CultureInfo, IDictionary<string, string>>>(() => FileSourcesToNoAreaDictionarySources(fileSources.Value));
|
||||
_fileSources = fileSources;
|
||||
}
|
||||
|
||||
private IDictionary<CultureInfo, IDictionary<string, string>> FileSourcesToNoAreaDictionarySources(LocalizedTextServiceFileSources fileSources)
|
||||
{
|
||||
var xmlSources = fileSources.GetXmlSources();
|
||||
|
||||
return XmlSourceToNoAreaDictionary(xmlSources);
|
||||
}
|
||||
|
||||
private IDictionary<CultureInfo, IDictionary<string, string>> XmlSourceToNoAreaDictionary(IDictionary<CultureInfo, Lazy<XDocument>> xmlSources)
|
||||
{
|
||||
var cultureNoAreaDictionary = new Dictionary<CultureInfo, IDictionary<string, string>>();
|
||||
foreach (var xmlSource in xmlSources)
|
||||
{
|
||||
var noAreaAliasValue = GetNoAreaStoredTranslations(xmlSources, xmlSource.Key);
|
||||
cultureNoAreaDictionary.Add(xmlSource.Key, noAreaAliasValue);
|
||||
}
|
||||
return cultureNoAreaDictionary;
|
||||
}
|
||||
|
||||
private IDictionary<CultureInfo, IDictionary<string, IDictionary<string, string>>> FileSourcesToAreaDictionarySources(LocalizedTextServiceFileSources fileSources)
|
||||
{
|
||||
var xmlSources = fileSources.GetXmlSources();
|
||||
return XmlSourcesToAreaDictionary(xmlSources);
|
||||
}
|
||||
|
||||
private IDictionary<CultureInfo, IDictionary<string, IDictionary<string, string>>> XmlSourcesToAreaDictionary(IDictionary<CultureInfo, Lazy<XDocument>> xmlSources)
|
||||
{
|
||||
var cultureDictionary = new Dictionary<CultureInfo, IDictionary<string, IDictionary<string, string>>>();
|
||||
foreach (var xmlSource in xmlSources)
|
||||
{
|
||||
var areaAliaValue = GetAreaStoredTranslations(xmlSources, xmlSource.Key);
|
||||
cultureDictionary.Add(xmlSource.Key, areaAliaValue);
|
||||
|
||||
}
|
||||
return cultureDictionary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes with an XML source
|
||||
/// </summary>
|
||||
@@ -37,12 +75,15 @@ namespace Umbraco.Core.Services.Implement
|
||||
/// <param name="logger"></param>
|
||||
public LocalizedTextService(IDictionary<CultureInfo, Lazy<XDocument>> source, ILogger logger)
|
||||
{
|
||||
if (source == null) throw new ArgumentNullException("source");
|
||||
if (logger == null) throw new ArgumentNullException("logger");
|
||||
_xmlSource = source;
|
||||
_logger = logger;
|
||||
if (source == null) throw new ArgumentNullException(nameof(source));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
_dictionarySourceLazy = new Lazy<IDictionary<CultureInfo, IDictionary<string, IDictionary<string, string>>>>(() => XmlSourcesToAreaDictionary(source));
|
||||
_noAreaDictionarySourceLazy = new Lazy<IDictionary<CultureInfo, IDictionary<string, string>>>(() => XmlSourceToNoAreaDictionary(source));
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes with a source of a dictionary of culture -> areas -> sub dictionary of keys/values
|
||||
/// </summary>
|
||||
@@ -50,37 +91,54 @@ namespace Umbraco.Core.Services.Implement
|
||||
/// <param name="logger"></param>
|
||||
public LocalizedTextService(IDictionary<CultureInfo, IDictionary<string, IDictionary<string, string>>> source, ILogger logger)
|
||||
{
|
||||
_dictionarySource = source ?? throw new ArgumentNullException(nameof(source));
|
||||
var dictionarySource = source ?? throw new ArgumentNullException(nameof(source));
|
||||
_dictionarySourceLazy = new Lazy<IDictionary<CultureInfo, IDictionary<string, IDictionary<string, string>>>>(() => dictionarySource);
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
var cultureNoAreaDictionary = new Dictionary<CultureInfo, IDictionary<string, string>>();
|
||||
foreach (var cultureDictionary in dictionarySource)
|
||||
{
|
||||
var areaAliaValue = GetAreaStoredTranslations(source, cultureDictionary.Key);
|
||||
var aliasValue = new Dictionary<string, string>();
|
||||
foreach (var area in areaAliaValue)
|
||||
{
|
||||
foreach (var alias in area.Value)
|
||||
{
|
||||
if (!aliasValue.ContainsKey(alias.Key))
|
||||
{
|
||||
aliasValue.Add(alias.Key, alias.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
cultureNoAreaDictionary.Add(cultureDictionary.Key, aliasValue);
|
||||
}
|
||||
_noAreaDictionarySourceLazy = new Lazy<IDictionary<CultureInfo, IDictionary<string, string>>>(() => cultureNoAreaDictionary);
|
||||
}
|
||||
|
||||
public string Localize(string key, CultureInfo culture, IDictionary<string, string> tokens = null)
|
||||
{
|
||||
if (culture == null) throw new ArgumentNullException(nameof(culture));
|
||||
|
||||
// TODO: Hack, see notes on ConvertToSupportedCultureWithRegionCode
|
||||
culture = ConvertToSupportedCultureWithRegionCode(culture);
|
||||
|
||||
//This is what the legacy ui service did
|
||||
if (string.IsNullOrEmpty(key))
|
||||
return string.Empty;
|
||||
|
||||
var keyParts = key.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries);
|
||||
var keyParts = key.Split(_splitter, StringSplitOptions.RemoveEmptyEntries);
|
||||
var area = keyParts.Length > 1 ? keyParts[0] : null;
|
||||
var alias = keyParts.Length > 1 ? keyParts[1] : keyParts[0];
|
||||
return Localize(area, alias, culture, tokens);
|
||||
}
|
||||
public string Localize(string area, string alias, CultureInfo culture, IDictionary<string, string> tokens = null)
|
||||
{
|
||||
if (culture == null) throw new ArgumentNullException(nameof(culture));
|
||||
|
||||
var xmlSource = _xmlSource ?? (_fileSources != null
|
||||
? _fileSources.Value.GetXmlSources()
|
||||
: null);
|
||||
//This is what the legacy ui service did
|
||||
if (string.IsNullOrEmpty(alias))
|
||||
return string.Empty;
|
||||
|
||||
if (xmlSource != null)
|
||||
{
|
||||
return GetFromXmlSource(xmlSource, culture, area, alias, tokens);
|
||||
}
|
||||
else
|
||||
{
|
||||
return GetFromDictionarySource(culture, area, alias, tokens);
|
||||
}
|
||||
// TODO: Hack, see notes on ConvertToSupportedCultureWithRegionCode
|
||||
culture = ConvertToSupportedCultureWithRegionCode(culture);
|
||||
|
||||
return GetFromDictionarySource(culture, area, alias, tokens);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -89,74 +147,105 @@ namespace Umbraco.Core.Services.Implement
|
||||
/// <returns></returns>
|
||||
public IDictionary<string, string> GetAllStoredValues(CultureInfo culture)
|
||||
{
|
||||
if (culture == null) throw new ArgumentNullException("culture");
|
||||
if (culture == null) throw new ArgumentNullException(nameof(culture));
|
||||
|
||||
// TODO: Hack, see notes on ConvertToSupportedCultureWithRegionCode
|
||||
culture = ConvertToSupportedCultureWithRegionCode(culture);
|
||||
|
||||
var result = new Dictionary<string, string>();
|
||||
|
||||
var xmlSource = _xmlSource ?? (_fileSources != null
|
||||
? _fileSources.Value.GetXmlSources()
|
||||
: null);
|
||||
|
||||
if (xmlSource != null)
|
||||
if (_dictionarySource.ContainsKey(culture) == false)
|
||||
{
|
||||
if (xmlSource.ContainsKey(culture) == false)
|
||||
{
|
||||
_logger.Warn<LocalizedTextService, CultureInfo>("The culture specified {Culture} was not found in any configured sources for this service", culture);
|
||||
return result;
|
||||
}
|
||||
|
||||
//convert all areas + keys to a single key with a '/'
|
||||
result = GetStoredTranslations(xmlSource, culture);
|
||||
|
||||
//merge with the English file in case there's keys in there that don't exist in the local file
|
||||
var englishCulture = new CultureInfo("en-US");
|
||||
if (culture.Equals(englishCulture) == false)
|
||||
{
|
||||
var englishResults = GetStoredTranslations(xmlSource, englishCulture);
|
||||
foreach (var englishResult in englishResults.Where(englishResult => result.ContainsKey(englishResult.Key) == false))
|
||||
result.Add(englishResult.Key, englishResult.Value);
|
||||
}
|
||||
_logger.Warn<LocalizedTextService>("The culture specified {Culture} was not found in any configured sources for this service", culture);
|
||||
return new Dictionary<string, string>(0);
|
||||
}
|
||||
else
|
||||
IDictionary<string, string> result = new Dictionary<string, string>();
|
||||
//convert all areas + keys to a single key with a '/'
|
||||
foreach (var area in _dictionarySource[culture])
|
||||
{
|
||||
if (_dictionarySource.ContainsKey(culture) == false)
|
||||
foreach (var key in area.Value)
|
||||
{
|
||||
_logger.Warn<LocalizedTextService,CultureInfo>("The culture specified {Culture} was not found in any configured sources for this service", culture);
|
||||
return result;
|
||||
}
|
||||
|
||||
//convert all areas + keys to a single key with a '/'
|
||||
foreach (var area in _dictionarySource[culture])
|
||||
{
|
||||
foreach (var key in area.Value)
|
||||
var dictionaryKey = string.Format("{0}/{1}", area.Key, key.Key);
|
||||
//i don't think it's possible to have duplicates because we're dealing with a dictionary in the first place, but we'll double check here just in case.
|
||||
if (result.ContainsKey(dictionaryKey) == false)
|
||||
{
|
||||
var dictionaryKey = string.Format("{0}/{1}", area.Key, key.Key);
|
||||
//i don't think it's possible to have duplicates because we're dealing with a dictionary in the first place, but we'll double check here just in case.
|
||||
if (result.ContainsKey(dictionaryKey) == false)
|
||||
{
|
||||
result.Add(dictionaryKey, key.Value);
|
||||
}
|
||||
result.Add(dictionaryKey, key.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Dictionary<string, string> GetStoredTranslations(IDictionary<CultureInfo, Lazy<XDocument>> xmlSource, CultureInfo cult)
|
||||
private Dictionary<string, IDictionary<string, string>> GetAreaStoredTranslations(IDictionary<CultureInfo, Lazy<XDocument>> xmlSource, CultureInfo cult)
|
||||
{
|
||||
var result = new Dictionary<string, string>();
|
||||
var overallResult = new Dictionary<string, IDictionary<string, string>>(StringComparer.InvariantCulture);
|
||||
var areas = xmlSource[cult].Value.XPathSelectElements("//area");
|
||||
foreach (var area in areas)
|
||||
{
|
||||
var result = new Dictionary<string, string>(StringComparer.InvariantCulture);
|
||||
var keys = area.XPathSelectElements("./key");
|
||||
foreach (var key in keys)
|
||||
{
|
||||
var dictionaryKey = string.Format("{0}/{1}", (string)area.Attribute("alias"),
|
||||
(string)key.Attribute("alias"));
|
||||
var dictionaryKey =
|
||||
(string)key.Attribute("alias");
|
||||
//there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files
|
||||
if (result.ContainsKey(dictionaryKey) == false)
|
||||
result.Add(dictionaryKey, key.Value);
|
||||
}
|
||||
overallResult.Add(area.Attribute("alias").Value, result);
|
||||
}
|
||||
|
||||
//Merge English Dictionary
|
||||
var englishCulture = new CultureInfo("en-US");
|
||||
if (!cult.Equals(englishCulture))
|
||||
{
|
||||
var enUS = xmlSource[englishCulture].Value.XPathSelectElements("//area");
|
||||
foreach (var area in enUS)
|
||||
{
|
||||
IDictionary<string, string> result = new Dictionary<string, string>(StringComparer.InvariantCulture);
|
||||
if (overallResult.ContainsKey(area.Attribute("alias").Value))
|
||||
{
|
||||
result = overallResult[area.Attribute("alias").Value];
|
||||
}
|
||||
var keys = area.XPathSelectElements("./key");
|
||||
foreach (var key in keys)
|
||||
{
|
||||
var dictionaryKey =
|
||||
(string)key.Attribute("alias");
|
||||
//there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files
|
||||
if (result.ContainsKey(dictionaryKey) == false)
|
||||
result.Add(dictionaryKey, key.Value);
|
||||
}
|
||||
if (!overallResult.ContainsKey(area.Attribute("alias").Value))
|
||||
{
|
||||
overallResult.Add(area.Attribute("alias").Value, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
return overallResult;
|
||||
}
|
||||
private Dictionary<string, string> GetNoAreaStoredTranslations(IDictionary<CultureInfo, Lazy<XDocument>> xmlSource, CultureInfo cult)
|
||||
{
|
||||
var result = new Dictionary<string, string>(StringComparer.InvariantCulture);
|
||||
var keys = xmlSource[cult].Value.XPathSelectElements("//key");
|
||||
|
||||
foreach (var key in keys)
|
||||
{
|
||||
var dictionaryKey =
|
||||
(string)key.Attribute("alias");
|
||||
//there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files
|
||||
if (result.ContainsKey(dictionaryKey) == false)
|
||||
result.Add(dictionaryKey, key.Value);
|
||||
}
|
||||
|
||||
//Merge English Dictionary
|
||||
var englishCulture = new CultureInfo("en-US");
|
||||
if (!cult.Equals(englishCulture))
|
||||
{
|
||||
var keysEn = xmlSource[englishCulture].Value.XPathSelectElements("//key");
|
||||
|
||||
foreach (var key in keys)
|
||||
{
|
||||
var dictionaryKey =
|
||||
(string)key.Attribute("alias");
|
||||
//there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files
|
||||
if (result.ContainsKey(dictionaryKey) == false)
|
||||
result.Add(dictionaryKey, key.Value);
|
||||
@@ -164,6 +253,25 @@ namespace Umbraco.Core.Services.Implement
|
||||
}
|
||||
return result;
|
||||
}
|
||||
private Dictionary<string, IDictionary<string, string>> GetAreaStoredTranslations(IDictionary<CultureInfo, IDictionary<string, IDictionary<string, string>>> dictionarySource, CultureInfo cult)
|
||||
{
|
||||
var overallResult = new Dictionary<string, IDictionary<string, string>>(StringComparer.InvariantCulture);
|
||||
var areaDict = dictionarySource[cult];
|
||||
|
||||
foreach (var area in areaDict)
|
||||
{
|
||||
var result = new Dictionary<string, string>(StringComparer.InvariantCulture);
|
||||
var keys = area.Value.Keys;
|
||||
foreach (var key in keys)
|
||||
{
|
||||
//there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files
|
||||
if (result.ContainsKey(key) == false)
|
||||
result.Add(key, area.Value[key]);
|
||||
}
|
||||
overallResult.Add(area.Key, result);
|
||||
}
|
||||
return overallResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of all currently supported cultures
|
||||
@@ -171,11 +279,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
/// <returns></returns>
|
||||
public IEnumerable<CultureInfo> GetSupportedCultures()
|
||||
{
|
||||
var xmlSource = _xmlSource ?? (_fileSources != null
|
||||
? _fileSources.Value.GetXmlSources()
|
||||
: null);
|
||||
|
||||
return xmlSource != null ? xmlSource.Keys : _dictionarySource.Keys;
|
||||
return _dictionarySource.Keys;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -207,31 +311,29 @@ namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
if (_dictionarySource.ContainsKey(culture) == false)
|
||||
{
|
||||
_logger.Warn<LocalizedTextService,CultureInfo>("The culture specified {Culture} was not found in any configured sources for this service", culture);
|
||||
_logger.Warn<LocalizedTextService, CultureInfo>("The culture specified {Culture} was not found in any configured sources for this service", culture);
|
||||
return "[" + key + "]";
|
||||
}
|
||||
|
||||
var cultureSource = _dictionarySource[culture];
|
||||
|
||||
string found;
|
||||
if (area.IsNullOrWhiteSpace())
|
||||
string found = null;
|
||||
if (string.IsNullOrWhiteSpace(area))
|
||||
{
|
||||
found = cultureSource
|
||||
.SelectMany(x => x.Value)
|
||||
.Where(keyvals => keyvals.Key.InvariantEquals(key))
|
||||
.Select(x => x.Value)
|
||||
.FirstOrDefault();
|
||||
_noAreaDictionarySource[culture].TryGetValue(key, out found);
|
||||
}
|
||||
else
|
||||
{
|
||||
found = cultureSource
|
||||
.Where(areas => areas.Key.InvariantEquals(area))
|
||||
.SelectMany(a => a.Value)
|
||||
.Where(keyvals => keyvals.Key.InvariantEquals(key))
|
||||
.Select(x => x.Value)
|
||||
.FirstOrDefault();
|
||||
if (_dictionarySource[culture].TryGetValue(area, out var areaDictionary))
|
||||
{
|
||||
areaDictionary.TryGetValue(key, out found);
|
||||
}
|
||||
if (found == null)
|
||||
{
|
||||
_noAreaDictionarySource[culture].TryGetValue(key, out found);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (found != null)
|
||||
{
|
||||
return ParseTokens(found, tokens);
|
||||
@@ -240,44 +342,6 @@ namespace Umbraco.Core.Services.Implement
|
||||
//NOTE: Based on how legacy works, the default text does not contain the area, just the key
|
||||
return "[" + key + "]";
|
||||
}
|
||||
|
||||
private string GetFromXmlSource(IDictionary<CultureInfo, Lazy<XDocument>> xmlSource, CultureInfo culture, string area, string key, IDictionary<string, string> tokens)
|
||||
{
|
||||
if (xmlSource.ContainsKey(culture) == false)
|
||||
{
|
||||
_logger.Warn<LocalizedTextService,CultureInfo>("The culture specified {Culture} was not found in any configured sources for this service", culture);
|
||||
return "[" + key + "]";
|
||||
}
|
||||
|
||||
var found = FindTranslation(xmlSource, culture, area, key);
|
||||
|
||||
if (found != null)
|
||||
{
|
||||
return ParseTokens(found.Value, tokens);
|
||||
}
|
||||
|
||||
// Fall back to English by default if we can't find the key
|
||||
found = FindTranslation(xmlSource, new CultureInfo("en-US"), area, key);
|
||||
if (found != null)
|
||||
return ParseTokens(found.Value, tokens);
|
||||
|
||||
// If it can't be found in either file, fall back to the default, showing just the key in square brackets
|
||||
// NOTE: Based on how legacy works, the default text does not contain the area, just the key
|
||||
return "[" + key + "]";
|
||||
}
|
||||
|
||||
private XElement FindTranslation(IDictionary<CultureInfo, Lazy<XDocument>> xmlSource, CultureInfo culture, string area, string key)
|
||||
{
|
||||
var cultureSource = xmlSource[culture].Value;
|
||||
|
||||
var xpath = area.IsNullOrWhiteSpace()
|
||||
? string.Format("//key [@alias = '{0}']", key)
|
||||
: string.Format("//area [@alias = '{0}']/key [@alias = '{1}']", area, key);
|
||||
|
||||
var found = cultureSource.XPathSelectElement(xpath);
|
||||
return found;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the tokens in the value
|
||||
/// </summary>
|
||||
@@ -301,11 +365,26 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
foreach (var token in tokens)
|
||||
{
|
||||
value = value.Replace(string.Format("{0}{1}{0}", "%", token.Key), token.Value);
|
||||
value = value.Replace(string.Concat("%", token.Key, "%"), token.Value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public IDictionary<string, IDictionary<string, string>> GetAllStoredValuesByAreaAndAlias(CultureInfo culture)
|
||||
{
|
||||
if (culture == null) throw new ArgumentNullException("culture");
|
||||
|
||||
// TODO: Hack, see notes on ConvertToSupportedCultureWithRegionCode
|
||||
culture = ConvertToSupportedCultureWithRegionCode(culture);
|
||||
|
||||
if (_dictionarySource.ContainsKey(culture) == false)
|
||||
{
|
||||
_logger.Warn<LocalizedTextService>("The culture specified {Culture} was not found in any configured sources for this service", culture);
|
||||
return new Dictionary<string, IDictionary<string, string>>(0);
|
||||
}
|
||||
|
||||
return _dictionarySource[culture];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1088,6 +1088,15 @@ namespace Umbraco.Core.Services.Implement
|
||||
return OperationResult.Succeed(evtMsgs);
|
||||
}
|
||||
|
||||
public bool RecycleBinSmells()
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(Constants.Locks.MediaTree);
|
||||
return _mediaRepository.RecycleBinSmells();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Others
|
||||
|
||||
@@ -626,7 +626,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
query.Where(member => member.Username.EndsWith(login));
|
||||
break;
|
||||
case StringPropertyMatchType.Wildcard:
|
||||
query.Where(member => member.Email.SqlWildcard(login, TextColumnType.NVarchar));
|
||||
query.Where(member => member.Username.SqlWildcard(login, TextColumnType.NVarchar));
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(matchType));
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
@@ -12,10 +13,136 @@ namespace Umbraco.Core.Services
|
||||
/// </summary>
|
||||
public static class LocalizedTextServiceExtensions
|
||||
{
|
||||
public static string Localize(this ILocalizedTextService manager, string area, string key)
|
||||
// TODO: Remove these extension methods checking for ILocalizedTextService2 in v9 when these interfaces merge
|
||||
|
||||
public static string Localize(this ILocalizedTextService manager, string area, string alias, CultureInfo culture)
|
||||
{
|
||||
var fullKey = string.Join("/", area, key);
|
||||
if(manager is ILocalizedTextService2 manager2)
|
||||
{
|
||||
return manager2.Localize(area, alias, culture);
|
||||
}
|
||||
var fullKey = alias;
|
||||
if (area != null)
|
||||
{
|
||||
fullKey = string.Concat(area, "/", alias);
|
||||
}
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
return manager.Localize(fullKey, culture);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
|
||||
public static string Localize(this ILocalizedTextService manager, string area, string alias)
|
||||
{
|
||||
if (manager is ILocalizedTextService2 manager2)
|
||||
{
|
||||
return manager2.Localize(area, alias, Thread.CurrentThread.CurrentUICulture);
|
||||
}
|
||||
var fullKey = alias;
|
||||
if (area != null)
|
||||
{
|
||||
fullKey = string.Concat(area, "/", alias);
|
||||
}
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
return manager.Localize(fullKey, Thread.CurrentThread.CurrentUICulture);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Localize using the current thread culture
|
||||
/// </summary>
|
||||
/// <param name="manager"></param>
|
||||
/// <param name="area"></param>
|
||||
/// <param name="alias"></param>
|
||||
/// <param name="tokens"></param>
|
||||
/// <returns></returns>
|
||||
public static string Localize(this ILocalizedTextService manager, string area, string alias, string[] tokens)
|
||||
{
|
||||
if (manager is ILocalizedTextService2 manager2)
|
||||
{
|
||||
return manager2.Localize(area, alias, Thread.CurrentThread.CurrentUICulture, ConvertToDictionaryVars(tokens));
|
||||
}
|
||||
var fullKey = alias;
|
||||
if (area != null)
|
||||
{
|
||||
fullKey = string.Concat(area, "/", alias);
|
||||
}
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
return manager.Localize(fullKey, Thread.CurrentThread.CurrentUICulture, tokens);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Localize using the current thread culture
|
||||
/// </summary>
|
||||
/// <param name="manager"></param>
|
||||
/// <param name="area"></param>
|
||||
/// <param name="alias"></param>
|
||||
/// <param name="tokens"></param>
|
||||
/// <returns></returns>
|
||||
public static string Localize(this ILocalizedTextService manager, string area, string alias, IDictionary<string, string> tokens = null)
|
||||
{
|
||||
if (manager is ILocalizedTextService2 manager2)
|
||||
{
|
||||
return manager2.Localize(area, alias, Thread.CurrentThread.CurrentUICulture, tokens);
|
||||
}
|
||||
var fullKey = alias;
|
||||
if (area != null)
|
||||
{
|
||||
fullKey = string.Concat(area, "/", alias);
|
||||
}
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
return manager.Localize(fullKey, Thread.CurrentThread.CurrentUICulture, tokens);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Localize a key without any variables
|
||||
/// </summary>
|
||||
/// <param name="manager"></param>
|
||||
/// <param name="area"></param>
|
||||
/// <param name="alias"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <param name="tokens"></param>
|
||||
/// <returns></returns>
|
||||
public static string Localize(this ILocalizedTextService manager, string area, string alias, CultureInfo culture, string[] tokens)
|
||||
{
|
||||
if (manager is ILocalizedTextService2 manager2)
|
||||
{
|
||||
return manager2.Localize(area, alias, Thread.CurrentThread.CurrentUICulture, tokens);
|
||||
}
|
||||
var fullKey = alias;
|
||||
if (area != null)
|
||||
{
|
||||
fullKey = string.Concat(area, "/", alias);
|
||||
}
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
return manager.Localize(fullKey, culture, ConvertToDictionaryVars(tokens));
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Localize a key without any variables
|
||||
/// </summary>
|
||||
/// <param name="manager"></param>
|
||||
/// <param name="area"></param>
|
||||
/// <param name="alias"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <param name="tokens"></param>
|
||||
/// <returns></returns>
|
||||
public static string Localize(this ILocalizedTextService manager, string area, string alias, CultureInfo culture, IDictionary<string, string> tokens)
|
||||
{
|
||||
if (manager is ILocalizedTextService2 manager2)
|
||||
{
|
||||
return manager2.Localize(area, alias, culture, tokens);
|
||||
}
|
||||
var fullKey = alias;
|
||||
if (area != null)
|
||||
{
|
||||
fullKey = string.Concat(area, "/", alias);
|
||||
}
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
return manager.Localize(fullKey, culture, tokens);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -25,6 +152,7 @@ namespace Umbraco.Core.Services
|
||||
/// <param name="key"></param>
|
||||
/// <param name="tokens"></param>
|
||||
/// <returns></returns>
|
||||
[Obsolete("Use the overload specifying an area and alias instead of key")]
|
||||
public static string Localize(this ILocalizedTextService manager, string key, string[] tokens)
|
||||
{
|
||||
return manager.Localize(key, Thread.CurrentThread.CurrentUICulture, tokens);
|
||||
@@ -37,6 +165,7 @@ namespace Umbraco.Core.Services
|
||||
/// <param name="key"></param>
|
||||
/// <param name="tokens"></param>
|
||||
/// <returns></returns>
|
||||
[Obsolete("Use the overload specifying an area and alias instead of key")]
|
||||
public static string Localize(this ILocalizedTextService manager, string key, IDictionary<string, string> tokens = null)
|
||||
{
|
||||
return manager.Localize(key, Thread.CurrentThread.CurrentUICulture, tokens);
|
||||
@@ -50,6 +179,7 @@ namespace Umbraco.Core.Services
|
||||
/// <param name="culture"></param>
|
||||
/// <param name="tokens"></param>
|
||||
/// <returns></returns>
|
||||
[Obsolete("Use the overload specifying an area and alias instead of key")]
|
||||
public static string Localize(this ILocalizedTextService manager, string key, CultureInfo culture, string[] tokens)
|
||||
{
|
||||
return manager.Localize(key, culture, ConvertToDictionaryVars(tokens));
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace Umbraco.Core.Sync
|
||||
// but only processes instructions coming from remote servers,
|
||||
// thus ensuring that instructions run only once
|
||||
//
|
||||
public class DatabaseServerMessenger : ServerMessengerBase
|
||||
public class DatabaseServerMessenger : ServerMessengerBase, ISyncBootStateAccessor
|
||||
{
|
||||
private readonly IRuntimeState _runtime;
|
||||
private readonly ManualResetEvent _syncIdle;
|
||||
@@ -39,9 +39,9 @@ namespace Umbraco.Core.Sync
|
||||
private int _lastId = -1;
|
||||
private DateTime _lastSync;
|
||||
private DateTime _lastPruned;
|
||||
private bool _initialized;
|
||||
private bool _syncing;
|
||||
private bool _released;
|
||||
private readonly Lazy<SyncBootState> _getSyncBootState;
|
||||
|
||||
public DatabaseServerMessengerOptions Options { get; }
|
||||
|
||||
@@ -59,6 +59,7 @@ namespace Umbraco.Core.Sync
|
||||
_lastPruned = _lastSync = DateTime.UtcNow;
|
||||
_syncIdle = new ManualResetEvent(true);
|
||||
_distCacheFilePath = new Lazy<string>(() => GetDistCacheFilePath(globalSettings));
|
||||
_getSyncBootState = new Lazy<SyncBootState>(BootInternal);
|
||||
}
|
||||
|
||||
protected ILogger Logger { get; }
|
||||
@@ -75,7 +76,7 @@ namespace Umbraco.Core.Sync
|
||||
{
|
||||
// we don't care if there's servers listed or not,
|
||||
// if distributed call is enabled we will make the call
|
||||
return _initialized && DistributedEnabled;
|
||||
return _getSyncBootState.IsValueCreated && DistributedEnabled;
|
||||
}
|
||||
|
||||
protected override void DeliverRemote(
|
||||
@@ -110,14 +111,14 @@ namespace Umbraco.Core.Sync
|
||||
|
||||
#region Sync
|
||||
|
||||
/// <summary>
|
||||
/// Boots the messenger.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Thread safety: this is NOT thread safe. Because it is NOT meant to run multi-threaded.
|
||||
/// Callers MUST ensure thread-safety.
|
||||
/// </remarks>
|
||||
[Obsolete("This is no longer used and will be removed in future versions")]
|
||||
protected void Boot()
|
||||
{
|
||||
// if called, just forces the boot logic
|
||||
_ = GetSyncBootState();
|
||||
}
|
||||
|
||||
private SyncBootState BootInternal()
|
||||
{
|
||||
// weight:10, must release *before* the published snapshot service, because once released
|
||||
// the service will *not* be able to properly handle our notifications anymore
|
||||
@@ -139,7 +140,7 @@ namespace Umbraco.Core.Sync
|
||||
// properly releasing MainDom - a timeout here means that one refresher
|
||||
// is taking too much time processing, however when it's done we will
|
||||
// not update lastId and stop everything
|
||||
var idle =_syncIdle.WaitOne(5000);
|
||||
var idle = _syncIdle.WaitOne(5000);
|
||||
if (idle == false)
|
||||
{
|
||||
Logger.Warn<DatabaseServerMessenger>("The wait lock timed out, application is shutting down. The current instruction batch will be re-processed.");
|
||||
@@ -147,17 +148,23 @@ namespace Umbraco.Core.Sync
|
||||
},
|
||||
weight);
|
||||
|
||||
SyncBootState bootState = SyncBootState.Unknown;
|
||||
|
||||
if (registered == false)
|
||||
return;
|
||||
{
|
||||
return bootState;
|
||||
}
|
||||
|
||||
ReadLastSynced(); // get _lastId
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
EnsureInstructions(scope.Database); // reset _lastId if instructions are missing
|
||||
Initialize(scope.Database); // boot
|
||||
bootState = Initialize(scope.Database); // boot
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
return bootState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -167,60 +174,62 @@ namespace Umbraco.Core.Sync
|
||||
/// Thread safety: this is NOT thread safe. Because it is NOT meant to run multi-threaded.
|
||||
/// Callers MUST ensure thread-safety.
|
||||
/// </remarks>
|
||||
private void Initialize(IUmbracoDatabase database)
|
||||
private SyncBootState Initialize(IUmbracoDatabase database)
|
||||
{
|
||||
lock (_locko)
|
||||
{
|
||||
if (_released) return;
|
||||
// could occur if shutting down immediately once starting up and before we've initialized
|
||||
if (_released) return SyncBootState.Unknown;
|
||||
|
||||
var coldboot = false;
|
||||
if (_lastId < 0) // never synced before
|
||||
var coldboot = false;
|
||||
if (_lastId < 0) // never synced before
|
||||
{
|
||||
// we haven't synced - in this case we aren't going to sync the whole thing, we will assume this is a new
|
||||
// server and it will need to rebuild it's own caches, eg Lucene or the xml cache file.
|
||||
Logger.Warn<DatabaseServerMessenger>("No last synced Id found, this generally means this is a new server/install."
|
||||
+ " The server will build its caches and indexes, and then adjust its last synced Id to the latest found in"
|
||||
+ " the database and maintain cache updates based on that Id.");
|
||||
|
||||
coldboot = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//check for how many instructions there are to process, each row contains a count of the number of instructions contained in each
|
||||
//row so we will sum these numbers to get the actual count.
|
||||
var count = database.ExecuteScalar<int>("SELECT SUM(instructionCount) FROM umbracoCacheInstruction WHERE id > @lastId", new { lastId = _lastId });
|
||||
if (count > Options.MaxProcessingInstructionCount)
|
||||
{
|
||||
// we haven't synced - in this case we aren't going to sync the whole thing, we will assume this is a new
|
||||
// server and it will need to rebuild it's own caches, eg Lucene or the xml cache file.
|
||||
Logger.Warn<DatabaseServerMessenger>("No last synced Id found, this generally means this is a new server/install."
|
||||
+ " The server will build its caches and indexes, and then adjust its last synced Id to the latest found in"
|
||||
+ " the database and maintain cache updates based on that Id.");
|
||||
//too many instructions, proceed to cold boot
|
||||
Logger.Warn<DatabaseServerMessenger, int, int>(
|
||||
"The instruction count ({InstructionCount}) exceeds the specified MaxProcessingInstructionCount ({MaxProcessingInstructionCount})."
|
||||
+ " The server will skip existing instructions, rebuild its caches and indexes entirely, adjust its last synced Id"
|
||||
+ " to the latest found in the database and maintain cache updates based on that Id.",
|
||||
count, Options.MaxProcessingInstructionCount);
|
||||
|
||||
coldboot = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//check for how many instructions there are to process, each row contains a count of the number of instructions contained in each
|
||||
//row so we will sum these numbers to get the actual count.
|
||||
var count = database.ExecuteScalar<int>("SELECT SUM(instructionCount) FROM umbracoCacheInstruction WHERE id > @lastId", new {lastId = _lastId});
|
||||
if (count > Options.MaxProcessingInstructionCount)
|
||||
{
|
||||
//too many instructions, proceed to cold boot
|
||||
Logger.Warn<DatabaseServerMessenger,int,int>(
|
||||
"The instruction count ({InstructionCount}) exceeds the specified MaxProcessingInstructionCount ({MaxProcessingInstructionCount})."
|
||||
+ " The server will skip existing instructions, rebuild its caches and indexes entirely, adjust its last synced Id"
|
||||
+ " to the latest found in the database and maintain cache updates based on that Id.",
|
||||
count, Options.MaxProcessingInstructionCount);
|
||||
}
|
||||
|
||||
coldboot = true;
|
||||
if (coldboot)
|
||||
{
|
||||
// go get the last id in the db and store it
|
||||
// note: do it BEFORE initializing otherwise some instructions might get lost
|
||||
// when doing it before, some instructions might run twice - not an issue
|
||||
var maxId = database.ExecuteScalar<int>("SELECT MAX(id) FROM umbracoCacheInstruction");
|
||||
|
||||
//if there is a max currently, or if we've never synced
|
||||
if (maxId > 0 || _lastId < 0)
|
||||
SaveLastSynced(maxId);
|
||||
|
||||
// execute initializing callbacks
|
||||
if (Options.InitializingCallbacks != null)
|
||||
{
|
||||
foreach (var callback in Options.InitializingCallbacks)
|
||||
{
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
if (coldboot)
|
||||
{
|
||||
// go get the last id in the db and store it
|
||||
// note: do it BEFORE initializing otherwise some instructions might get lost
|
||||
// when doing it before, some instructions might run twice - not an issue
|
||||
var maxId = database.ExecuteScalar<int>("SELECT MAX(id) FROM umbracoCacheInstruction");
|
||||
|
||||
//if there is a max currently, or if we've never synced
|
||||
if (maxId > 0 || _lastId < 0)
|
||||
SaveLastSynced(maxId);
|
||||
|
||||
// execute initializing callbacks
|
||||
if (Options.InitializingCallbacks != null)
|
||||
foreach (var callback in Options.InitializingCallbacks)
|
||||
callback();
|
||||
}
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
return coldboot ? SyncBootState.ColdBoot : SyncBootState.WarmBoot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -352,7 +361,7 @@ namespace Umbraco.Core.Sync
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
Logger.Error<DatabaseServerMessenger,int, string>(ex, "Failed to deserialize instructions ({DtoId}: '{DtoInstructions}').",
|
||||
Logger.Error<DatabaseServerMessenger, int, string>(ex, "Failed to deserialize instructions ({DtoId}: '{DtoInstructions}').",
|
||||
dto.Id,
|
||||
dto.Instructions);
|
||||
|
||||
@@ -410,11 +419,11 @@ namespace Umbraco.Core.Sync
|
||||
//}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error<DatabaseServerMessenger,int, string> (
|
||||
ex,
|
||||
"DISTRIBUTED CACHE IS NOT UPDATED. Failed to execute instructions ({DtoId}: '{DtoInstructions}'). Instruction is being skipped/ignored",
|
||||
dto.Id,
|
||||
dto.Instructions);
|
||||
Logger.Error<DatabaseServerMessenger, int, string>(
|
||||
ex,
|
||||
"DISTRIBUTED CACHE IS NOT UPDATED. Failed to execute instructions ({DtoId}: '{DtoInstructions}'). Instruction is being skipped/ignored",
|
||||
dto.Id,
|
||||
dto.Instructions);
|
||||
|
||||
//we cannot throw here because this invalid instruction will just keep getting processed over and over and errors
|
||||
// will be thrown over and over. The only thing we can do is ignore and move on.
|
||||
@@ -548,6 +557,8 @@ namespace Umbraco.Core.Sync
|
||||
|
||||
#endregion
|
||||
|
||||
public virtual SyncBootState GetSyncBootState() => _getSyncBootState.Value;
|
||||
|
||||
#region Notify refreshers
|
||||
|
||||
private static ICacheRefresher GetRefresher(Guid id)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Umbraco.Core.Sync
|
||||
{
|
||||
@@ -24,13 +25,8 @@ namespace Umbraco.Core.Sync
|
||||
/// </summary>
|
||||
public int MaxProcessingInstructionCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of callbacks that will be invoked if the lastsynced.txt file does not exist.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// These callbacks will typically be for eg rebuilding the xml cache file, or examine indexes, based on
|
||||
/// the data in the database to get this particular server node up to date.
|
||||
/// </remarks>
|
||||
[Obsolete("This should not be used. If initialization calls need to be invoked on a cold boot, use the ISyncBootStateAccessor.Booting event.")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public IEnumerable<Action> InitializingCallbacks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
16
src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs
Normal file
16
src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Sync
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieve the state of the sync service
|
||||
/// </summary>
|
||||
public interface ISyncBootStateAccessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the boot state
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
SyncBootState GetSyncBootState();
|
||||
}
|
||||
}
|
||||
21
src/Umbraco.Core/Sync/NonRuntimeLevelBootStateAccessor.cs
Normal file
21
src/Umbraco.Core/Sync/NonRuntimeLevelBootStateAccessor.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Core.Sync
|
||||
{
|
||||
/// <summary>
|
||||
/// Boot state implementation for when umbraco is not in the run state
|
||||
/// </summary>
|
||||
public class NonRuntimeLevelBootStateAccessor : ISyncBootStateAccessor
|
||||
{
|
||||
public event EventHandler<SyncBootState> Booting;
|
||||
|
||||
public SyncBootState GetSyncBootState()
|
||||
{
|
||||
return SyncBootState.Unknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
src/Umbraco.Core/Sync/SyncBootState.cs
Normal file
20
src/Umbraco.Core/Sync/SyncBootState.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace Umbraco.Core.Sync
|
||||
{
|
||||
public enum SyncBootState
|
||||
{
|
||||
/// <summary>
|
||||
/// Unknown state. Treat as WarmBoot
|
||||
/// </summary>
|
||||
Unknown = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Cold boot. No Sync state
|
||||
/// </summary>
|
||||
ColdBoot = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Warm boot. Sync state present
|
||||
/// </summary>
|
||||
WarmBoot = 2
|
||||
}
|
||||
}
|
||||
@@ -131,6 +131,7 @@
|
||||
<Compile Include="Constants-CharArrays.cs" />
|
||||
<Compile Include="Collections\EventClearingObservableCollection.cs" />
|
||||
<Compile Include="Constants-SqlTemplates.cs" />
|
||||
<Compile Include="Events\UnattendedInstallEventArgs.cs" />
|
||||
<Compile Include="Logging\ILogger2.cs" />
|
||||
<Compile Include="Logging\Logger2Extensions.cs" />
|
||||
<Compile Include="Dashboards\ContentDashboardSettings.cs" />
|
||||
@@ -139,6 +140,8 @@
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\Models\ContentTypeDto80.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\Models\PropertyDataDto80.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\Models\PropertyTypeDto80.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_15_0\AddCmsContentNuByteColumn.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_15_0\UpgradedIncludeIndexes.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_10_0\AddPropertyTypeLabelOnTopColumn.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_9_0\ExternalLoginTableUserData.cs" />
|
||||
<Compile Include="Models\Blocks\BlockEditorDataConverter.cs" />
|
||||
@@ -156,10 +159,21 @@
|
||||
<Compile Include="Models\Identity\IExternalLogin.cs" />
|
||||
<Compile Include="Models\IconModel.cs" />
|
||||
<Compile Include="Models\InstallLog.cs" />
|
||||
<Compile Include="Models\IReadOnlyContentBase.cs" />
|
||||
<Compile Include="Models\MediaWithCrops.cs" />
|
||||
<Compile Include="Models\PublishedContent\PublishedContentTypeExtensions.cs" />
|
||||
<Compile Include="Models\ReadOnlyContentBaseAdapter.cs" />
|
||||
<Compile Include="Models\RelationTypeExtensions.cs" />
|
||||
<Compile Include="Persistence\Repositories\IInstallationRepository.cs" />
|
||||
<Compile Include="Persistence\Repositories\Implement\InstallationRepository.cs" />
|
||||
<Compile Include="Persistence\SqlCeImageMapper.cs" />
|
||||
<Compile Include="PropertyEditors\IPropertyCacheCompressionOptions.cs" />
|
||||
<Compile Include="PropertyEditors\NoopPropertyCacheCompressionOptions.cs" />
|
||||
<Compile Include="PropertyEditors\PropertyCacheCompression.cs" />
|
||||
<Compile Include="PropertyEditors\IPropertyCacheCompression.cs" />
|
||||
<Compile Include="PropertyEditors\UnPublishedContentPropertyCacheCompressionOptions.cs" />
|
||||
<Compile Include="Serialization\AutoInterningStringConverter.cs" />
|
||||
<Compile Include="Serialization\AutoInterningStringKeyCaseInsensitiveDictionaryConverter.cs" />
|
||||
<Compile Include="PropertyEditors\EyeDropperColorPickerConfiguration.cs" />
|
||||
<Compile Include="PropertyEditors\ComplexPropertyEditorContentEventHandler.cs" />
|
||||
<Compile Include="Services\IIconService.cs" />
|
||||
@@ -179,6 +193,9 @@
|
||||
<Compile Include="Migrations\Upgrade\V_8_7_0\MissingDictionaryIndex.cs" />
|
||||
<Compile Include="Services\IInstallationService.cs" />
|
||||
<Compile Include="Services\IUpgradeService.cs" />
|
||||
<Compile Include="Sync\ISyncBootStateAccessor.cs" />
|
||||
<Compile Include="Sync\NonRuntimeLevelBootStateAccessor.cs" />
|
||||
<Compile Include="Sync\SyncBootState.cs" />
|
||||
<Compile Include="SystemLock.cs" />
|
||||
<Compile Include="Attempt.cs" />
|
||||
<Compile Include="AttemptOfTResult.cs" />
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<!-- note: NuGet deals with transitive references now -->
|
||||
<PackageReference Include="Examine" Version="1.1.0" />
|
||||
<PackageReference Include="Examine" Version="1.2.0" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub">
|
||||
<Version>1.0.0-beta2-19324-01</Version>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -11,7 +11,7 @@ using Lucene.Net.Store;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Logging;
|
||||
using Examine.LuceneEngine;
|
||||
|
||||
using Examine.Search;
|
||||
namespace Umbraco.Examine
|
||||
{
|
||||
/// <summary>
|
||||
@@ -21,7 +21,7 @@ namespace Umbraco.Examine
|
||||
{
|
||||
public const string VariesByCultureFieldName = SpecialFieldPrefix + "VariesByCulture";
|
||||
protected ILocalizationService LanguageService { get; }
|
||||
|
||||
private readonly ISet<string> _idOnlyFieldSet = new HashSet<string> { "id" };
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
@@ -131,9 +131,10 @@ namespace Umbraco.Examine
|
||||
var searcher = GetSearcher();
|
||||
var c = searcher.CreateQuery();
|
||||
var filtered = c.NativeQuery(rawQuery);
|
||||
var results = filtered.Execute();
|
||||
|
||||
ProfilingLogger.Debug<string,long>(GetType(), "DeleteFromIndex with query: {Query} (found {TotalItems} results)", rawQuery, results.TotalItemCount);
|
||||
var selectedFields = filtered.SelectFields(_idOnlyFieldSet);
|
||||
var results = selectedFields.Execute();
|
||||
ProfilingLogger.Debug<string, long>(GetType(), "DeleteFromIndex with query: {Query} (found {TotalItems} results)", rawQuery, results.TotalItemCount);
|
||||
|
||||
//need to queue a delete item for each one found
|
||||
QueueIndexOperation(results.Select(r => new IndexOperation(new ValueSet(r.Id), IndexOperationType.Delete)));
|
||||
|
||||
@@ -25,8 +25,7 @@ namespace Umbraco.Examine
|
||||
// wrapping all operations that end up calling base.SafelyProcessQueueItems in a safe call
|
||||
// context because they will fork a thread/task/whatever which should *not* capture our
|
||||
// call context (and the database it can contain)!
|
||||
// TODO: FIX Examine to not flow the ExecutionContext so callers don't need to worry about this!
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Used to store the path of a content object
|
||||
/// </summary>
|
||||
@@ -99,13 +98,7 @@ namespace Umbraco.Examine
|
||||
{
|
||||
if (CanInitialize())
|
||||
{
|
||||
// Use SafeCallContext to prevent the current CallContext flow to child
|
||||
// tasks executed in the base class so we don't leak Scopes.
|
||||
// TODO: See notes at the top of this class
|
||||
using (new SafeCallContext())
|
||||
{
|
||||
base.PerformDeleteFromIndex(itemIds, onComplete);
|
||||
}
|
||||
base.PerformDeleteFromIndex(itemIds, onComplete);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,13 +106,7 @@ namespace Umbraco.Examine
|
||||
{
|
||||
if (CanInitialize())
|
||||
{
|
||||
// Use SafeCallContext to prevent the current CallContext flow to child
|
||||
// tasks executed in the base class so we don't leak Scopes.
|
||||
// TODO: See notes at the top of this class
|
||||
using (new SafeCallContext())
|
||||
{
|
||||
base.PerformIndexItems(values, onComplete);
|
||||
}
|
||||
base.PerformIndexItems(values, onComplete);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose
|
||||
{
|
||||
var assemblyNames = new[]
|
||||
{
|
||||
"Umbraco.ModelsBuider",
|
||||
"Umbraco.ModelsBuilder",
|
||||
"ModelsBuilder.Umbraco"
|
||||
};
|
||||
|
||||
|
||||
35
src/Umbraco.TestData/LoadTestComponent.cs
Normal file
35
src/Umbraco.TestData/LoadTestComponent.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Web.Mvc;
|
||||
using System.Web.Routing;
|
||||
using Umbraco.Core.Composing;
|
||||
using System.Configuration;
|
||||
|
||||
// see https://github.com/Shazwazza/UmbracoScripts/tree/master/src/LoadTesting
|
||||
|
||||
namespace Umbraco.TestData
|
||||
{
|
||||
public class LoadTestComponent : IComponent
|
||||
{
|
||||
public void Initialize()
|
||||
{
|
||||
if (ConfigurationManager.AppSettings["Umbraco.TestData.Enabled"] != "true")
|
||||
return;
|
||||
|
||||
|
||||
|
||||
RouteTable.Routes.MapRoute(
|
||||
name: "LoadTest",
|
||||
url: "LoadTest/{action}",
|
||||
defaults: new
|
||||
{
|
||||
controller = "LoadTest",
|
||||
action = "Index"
|
||||
},
|
||||
namespaces: new[] { "Umbraco.TestData" }
|
||||
);
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/Umbraco.TestData/LoadTestComposer.cs
Normal file
29
src/Umbraco.TestData/LoadTestComposer.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Umbraco.Core.Composing;
|
||||
using System.Configuration;
|
||||
using Umbraco.Web.PublishedCache.NuCache;
|
||||
|
||||
// see https://github.com/Shazwazza/UmbracoScripts/tree/master/src/LoadTesting
|
||||
|
||||
namespace Umbraco.TestData
|
||||
{
|
||||
public class LoadTestComposer : ComponentComposer<LoadTestComponent>, IUserComposer
|
||||
{
|
||||
public override void Compose(Composition composition)
|
||||
{
|
||||
base.Compose(composition);
|
||||
|
||||
if (ConfigurationManager.AppSettings["Umbraco.TestData.Enabled"] != "true")
|
||||
return;
|
||||
|
||||
composition.Register(typeof(LoadTestController), Lifetime.Request);
|
||||
|
||||
if (ConfigurationManager.AppSettings["Umbraco.TestData.IgnoreLocalDb"] == "true")
|
||||
{
|
||||
composition.Register(factory => new PublishedSnapshotServiceOptions
|
||||
{
|
||||
IgnoreLocalDb = true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,9 @@ using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Models;
|
||||
using System.Web;
|
||||
using System.Web.Hosting;
|
||||
using System.Web.Routing;
|
||||
using System.Diagnostics;
|
||||
using Umbraco.Core.Composing;
|
||||
using System.Configuration;
|
||||
using Umbraco.Core.IO;
|
||||
using System.IO;
|
||||
|
||||
// see https://github.com/Shazwazza/UmbracoScripts/tree/master/src/LoadTesting
|
||||
|
||||
@@ -261,6 +260,15 @@ namespace Umbraco.TestData
|
||||
HttpRuntime.UnloadAppDomain();
|
||||
}
|
||||
|
||||
public ActionResult ColdBootRestart()
|
||||
{
|
||||
Directory.Delete(IOHelper.MapPath("~/App_Data/TEMP/DistCache"), true);
|
||||
|
||||
DoRestart();
|
||||
|
||||
return Content("Cold Boot Restarted.");
|
||||
}
|
||||
|
||||
public ActionResult Restart()
|
||||
{
|
||||
DoRestart();
|
||||
@@ -331,41 +339,4 @@ namespace Umbraco.TestData
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
public class TestComponent : IComponent
|
||||
{
|
||||
public void Initialize()
|
||||
{
|
||||
if (ConfigurationManager.AppSettings["Umbraco.TestData.Enabled"] != "true")
|
||||
return;
|
||||
|
||||
RouteTable.Routes.MapRoute(
|
||||
name: "LoadTest",
|
||||
url: "LoadTest/{action}",
|
||||
defaults: new
|
||||
{
|
||||
controller = "LoadTest",
|
||||
action = "Index"
|
||||
},
|
||||
namespaces: new[] { "Umbraco.TestData" }
|
||||
);
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class TestComposer : ComponentComposer<TestComponent>, IUserComposer
|
||||
{
|
||||
public override void Compose(Composition composition)
|
||||
{
|
||||
base.Compose(composition);
|
||||
|
||||
if (ConfigurationManager.AppSettings["Umbraco.TestData.Enabled"] != "true")
|
||||
return;
|
||||
|
||||
composition.Register(typeof(LoadTestController), Lifetime.Request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="LoadTestComponent.cs" />
|
||||
<Compile Include="LoadTestComposer.cs" />
|
||||
<Compile Include="LoadTestController.cs" />
|
||||
<Compile Include="SegmentTestController.cs" />
|
||||
<Compile Include="UmbracoTestDataController.cs" />
|
||||
|
||||
@@ -208,7 +208,8 @@ namespace Umbraco.TestData
|
||||
var docType = GetOrCreateContentType();
|
||||
|
||||
var parent = Services.ContentService.Create(company, -1, docType.Alias);
|
||||
parent.SetValue("review", faker.Rant.Review());
|
||||
// give it some reasonable data (100 reviews)
|
||||
parent.SetValue("review", string.Join(" ", Enumerable.Range(0, 100).Select(x => faker.Rant.Review())));
|
||||
parent.SetValue("desc", company);
|
||||
parent.SetValue("media", imageIds[random.Next(0, imageIds.Count - 1)]);
|
||||
Services.ContentService.Save(parent);
|
||||
@@ -218,7 +219,8 @@ namespace Umbraco.TestData
|
||||
return CreateHierarchy(parent, count, depth, currParent =>
|
||||
{
|
||||
var content = Services.ContentService.Create(faker.Commerce.ProductName(), currParent, docType.Alias);
|
||||
content.SetValue("review", faker.Rant.Review());
|
||||
// give it some reasonable data (100 reviews)
|
||||
content.SetValue("review", string.Join(" ", Enumerable.Range(0, 100).Select(x => faker.Rant.Review())));
|
||||
content.SetValue("desc", string.Join(", ", Enumerable.Range(0, 5).Select(x => faker.Commerce.ProductAdjective())));
|
||||
content.SetValue("media", imageIds[random.Next(0, imageIds.Count - 1)]);
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user