Merge remote-tracking branch 'origin/v8/contrib' into v8/bugfix/removing-unnecessary-readerwriterlock

This commit is contained in:
Shannon
2021-07-20 10:05:32 -06:00
467 changed files with 28054 additions and 20256 deletions

View File

@@ -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/)

View 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

View File

@@ -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.
-->

View File

@@ -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.
-->

View File

@@ -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.
-->

View File

@@ -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?

View File

@@ -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>

View File

@@ -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")]

View File

@@ -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";
}
}

View File

@@ -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);
}
}
}

View File

@@ -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));
}
}

View File

@@ -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>();

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);
}
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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";
}

View File

@@ -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";
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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; }
}
}

View 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
{
}
}

View File

@@ -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)

View File

@@ -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()

View File

@@ -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
}
}

View File

@@ -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; }
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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,

View File

@@ -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 />

View File

@@ -4,6 +4,7 @@ using Umbraco.Core.Models.Entities;
namespace Umbraco.Core.Models
{
/// <summary>
/// Provides a base class for content items.
/// </summary>

View 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; }
}
}

View 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;
}
}

View File

@@ -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 });
}
}

View 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;
}
}

View File

@@ -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; }
}
}

View File

@@ -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;
}
}

View File

@@ -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; }
}
}

View File

@@ -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; }
}
}

View File

@@ -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")]

View File

@@ -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:

View File

@@ -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")]

View File

@@ -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")]

View File

@@ -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

View File

@@ -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

View File

@@ -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();

View File

@@ -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, ")");

View File

@@ -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();
}
}

View File

@@ -6,5 +6,6 @@ namespace Umbraco.Core.Persistence.Repositories
public interface IMediaRepository : IContentRepository<int, IMedia>, IReadRepository<Guid, IMedia>
{
IMedia GetMediaByPath(string mediaPath);
bool RecycleBinSmells();
}
}

View File

@@ -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 />

View File

@@ -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()

View File

@@ -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));
}
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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();
}
}
}

View File

@@ -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
{

View 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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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')";
}
}

View File

@@ -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
}
}

View File

@@ -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>

View File

@@ -2,6 +2,7 @@
namespace Umbraco.Core.PropertyEditors
{
/// <summary>
/// Marks a class that represents a data editor.
/// </summary>

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View 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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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; }
}
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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}'");
}

View File

@@ -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];
}
}
}

View File

@@ -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

View File

@@ -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));

View File

@@ -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));

View File

@@ -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)

View File

@@ -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>

View 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();
}
}

View 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;
}
}
}

View 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
}
}

View File

@@ -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" />

View File

@@ -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>

View File

@@ -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)));

View File

@@ -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);
}
}

View File

@@ -47,7 +47,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose
{
var assemblyNames = new[]
{
"Umbraco.ModelsBuider",
"Umbraco.ModelsBuilder",
"ModelsBuilder.Umbraco"
};

View 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()
{
}
}
}

View 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
});
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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" />

View File

@@ -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