Merge branch 'v8/contrib' into pr/7231

This commit is contained in:
emma burstow
2020-02-19 13:35:22 +00:00
769 changed files with 34764 additions and 13551 deletions
+10 -6
View File
@@ -13,7 +13,7 @@
*.png binary
*.gif binary
*.cs text=auto diff=csharp
*.cs text=auto diff=csharp
*.vb text=auto
*.c text=auto
*.cpp text=auto
@@ -41,9 +41,13 @@
*.fs text=auto
*.fsx text=auto
*.hs text=auto
*.json text=auto
*.xml text=auto
*.csproj text=auto merge=union
*.vbproj text=auto merge=union
*.fsproj text=auto merge=union
*.dbproj text=auto merge=union
*.sln text=auto eol=crlf merge=union
*.csproj text=auto merge=union
*.vbproj text=auto merge=union
*.fsproj text=auto merge=union
*.dbproj text=auto merge=union
*.sln text=auto eol=crlf merge=union
*.gitattributes text=auto
+2
View File
@@ -43,6 +43,8 @@ If you only see a build.bat-file, you're probably on the wrong branch. If you sw
You might run into [Powershell quirks](#powershell-quirks).
If it runs without errors; Hooray! Now you can continue with [the next step](CONTRIBUTING.md#how-do-i-begin) and open the solution and build it.
### Build Infrastructure
The Umbraco Build infrastructure relies on a PowerShell object. The object can be retrieved with:
+7 -7
View File
@@ -28,7 +28,7 @@ This project and everyone participating in it, is governed by the [our Code of C
[Working with the code](#working-with-the-code)
* [Building Umbraco from source code](#building-umbraco-from-source-code)
* [Working with the source code](#working-with-the-source-code)
* [Making changes after the PR was opened](#making-changes-after-the-pr-was-opened)
* [Making changes after the PR is open](#making-changes-after-the-pr-is-open)
* [Which branch should I target for my contributions?](#which-branch-should-i-target-for-my-contributions)
* [Keeping your Umbraco fork in sync with the main repository](#keeping-your-umbraco-fork-in-sync-with-the-main-repository)
@@ -60,12 +60,12 @@ Great question! The short version goes like this:
![Clone the fork](img/clonefork.png)
* **Switch to the correct branch** - switch to the v8-dev branch
* **Switch to the correct branch** - switch to the `v8/contrib` branch
* **Build** - build your fork of Umbraco locally as described in [building Umbraco from source code](BUILD.md)
* **Change** - make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will [happily give feedback](#questions)
* **Commit** - done? Yay! 🎉 **Important:** create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-12345`. This means it's a temporary branch for the particular issue you're working on, in this case `12345`. When you have a branch, commit your changes. Don't commit to `v8/dev`, create a new branch first.
* **Commit** - done? Yay! 🎉 **Important:** create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-12345`. This means it's a temporary branch for the particular issue you're working on, in this case `12345`. When you have a branch, commit your changes. Don't commit to `v8/contrib`, create a new branch first.
* **Push** - great, now you can push the changes up to your fork on GitHub
* **Create pull request** - exciting! You're ready to show us your changes (or not quite ready, you just need some feedback to progress - you can now make use of GitHub's draft pull request status, detailed [here] (https://github.blog/2019-02-14-introducing-draft-pull-requests/)). GitHub has picked up on the new branch you've pushed and will offer to create a Pull Request. Click that green button and away you go.
* **Create pull request** - exciting! You're ready to show us your changes (or not quite ready, you just need some feedback to progress - you can now make use of GitHub's draft pull request status, detailed [here](https://github.blog/2019-02-14-introducing-draft-pull-requests/)). GitHub has picked up on the new branch you've pushed and will offer to create a Pull Request. Click that green button and away you go.
![Create a pull request](img/createpullrequest.png)
@@ -158,7 +158,7 @@ To find the general areas for something you're looking to fix or improve, have a
### Which branch should I target for my contributions?
We like to use [Gitflow as much as possible](https://jeffkreeftmeijer.com/git-flow/), but don't worry if you are not familiar with it. The most important thing you need to know is that when you fork the Umbraco repository, the default branch is set to something, usually `v8/dev`. If you are working on v8, this is the branch you should be targetting. For v7 contributions, please target 'v7/dev'.
We like to use [Gitflow as much as possible](https://jeffkreeftmeijer.com/git-flow/), but don't worry if you are not familiar with it. The most important thing you need to know is that when you fork the Umbraco repository, the default branch is set to something, usually `v8/contrib`. If you are working on v8, this is the branch you should be targetting. For v7 contributions, please target 'v7/dev'.
Please note: we are no longer accepting features for v7 but will continue to merge bug fixes as and when they arise.
@@ -184,10 +184,10 @@ Then when you want to get the changes from the main repository:
```
git fetch upstream
git rebase upstream/v8/dev
git rebase upstream/v8/contrib
```
In this command we're syncing with the `v8/dev` branch, but you can of course choose another one if needed.
In this command we're syncing with the `v8/contrib` branch, but you can of course choose another one if needed.
(More info on how this works: [http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated](http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated))
+1 -1
View File
@@ -1,4 +1,4 @@
# [Umbraco CMS](https://umbraco.com) · [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](../LICENSE.md) [![Build status](https://umbraco.visualstudio.com/Umbraco%20Cms/_apis/build/status/Cms%208%20Continuous?branchName=v8/dev)](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=75) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md) [![Twitter](https://img.shields.io/twitter/follow/umbraco.svg?style=social&label=Follow)](https://twitter.com/intent/follow?screen_name=umbraco)
# [Umbraco CMS](https://umbraco.com) · [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](../LICENSE.md) [![Build status](https://umbraco.visualstudio.com/Umbraco%20Cms/_apis/build/status/Cms%208%20Continuous?branchName=v8/contrib)](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=75) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md) [![Twitter](https://img.shields.io/twitter/follow/umbraco.svg?style=social&label=Follow)](https://twitter.com/intent/follow?screen_name=umbraco)
Umbraco is the friendliest, most flexible and fastest growing ASP.NET CMS, and used by more than 500,000 websites worldwide. Our mission is to help you deliver delightful digital experiences by making Umbraco friendly, simpler and social.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 24 KiB

+1
View File
@@ -55,6 +55,7 @@ App_Data/TEMP/*
src/Umbraco.Web.UI/[Cc]ss/*
src/Umbraco.Web.UI/App_Code/*
src/Umbraco.Web.UI/App_Data/*
src/Umbraco.Web.UI/data/*
src/Umbraco.Tests/App_Data/*
src/Umbraco.Web.UI/[Mm]edia/*
src/Umbraco.Web.UI/[Mm]aster[Pp]ages/*
+4 -1
View File
@@ -28,7 +28,7 @@
<dependency id="ClientDependency" version="[1.9.8,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.0.1,1.999999)" />
<dependency id="Examine" version="[1.0.2,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)" />
@@ -52,14 +52,17 @@
<file src="$BuildTmp$\WebApp\bin\Umbraco.Web.dll" target="lib\net472\Umbraco.Web.dll" />
<file src="$BuildTmp$\WebApp\bin\Umbraco.Web.UI.dll" target="lib\net472\Umbraco.Web.UI.dll" />
<file src="$BuildTmp$\WebApp\bin\Umbraco.Examine.dll" target="lib\net472\Umbraco.Examine.dll" />
<file src="$BuildTmp$\WebApp\bin\Umbraco.ModelsBuilder.Embedded.dll" target="lib\net472\Umbraco.ModelsBuilder.Embedded.dll" />
<!-- docs -->
<file src="$BuildTmp$\WebApp\bin\Umbraco.Web.xml" target="lib\net472\Umbraco.Web.xml" />
<file src="$BuildTmp$\WebApp\bin\Umbraco.Web.UI.xml" target="lib\net472\Umbraco.Web.UI.xml" />
<file src="$BuildTmp$\WebApp\bin\Umbraco.Examine.xml" target="lib\net472\Umbraco.Examine.xml" />
<file src="$BuildTmp$\WebApp\bin\Umbraco.ModelsBuilder.Embedded.xml" target="lib\net472\Umbraco.ModelsBuilder.Embedded.xml" />
<!-- symbols -->
<file src="$BuildTmp$\bin\Umbraco.Web.pdb" target="lib\net472\Umbraco.Web.pdb" />
<file src="$BuildTmp$\bin\Umbraco.Examine.pdb" target="lib\net472\Umbraco.Examine.pdb" />
<file src="$BuildTmp$\bin\Umbraco.ModelsBuilder.Embedded.pdb" target="lib\net472\Umbraco.ModelsBuilder.Embedded.pdb" />
</files>
</package>
-1
View File
@@ -26,7 +26,6 @@
not want this to happen as the alpha of the next major is, really, the next major already.
-->
<dependency id="Microsoft.AspNet.SignalR.Core" version="[2.4.0, 2.999999)" />
<dependency id="Umbraco.ModelsBuilder.Ui" version="[8.1.0]" />
<dependency id="ImageProcessor.Web" version="[4.10.0.100,4.999999)" />
<dependency id="ImageProcessor.Web.Config" version="[2.5.0.100,2.999999)" />
<dependency id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="[2.0.1,2.999999)" />
+2 -2
View File
@@ -456,10 +456,10 @@
$ubuild.DefineMethod("PrepareAngularDocs",
{
Write-Host "Prepare Angular Documentation"
$src = "$($this.SolutionRoot)\src"
$out = $this.BuildOutput
"Moving to Umbraco.Web.UI.Docs folder"
cd ..\src\Umbraco.Web.UI.Docs
+2 -2
View File
@@ -18,5 +18,5 @@ using System.Resources;
[assembly: AssemblyVersion("8.0.0")]
// these are FYI and changed automatically
[assembly: AssemblyFileVersion("8.6.0")]
[assembly: AssemblyInformationalVersion("8.6.0")]
[assembly: AssemblyFileVersion("8.7.0")]
[assembly: AssemblyInformationalVersion("8.7.0")]
+1 -1
View File
@@ -86,7 +86,7 @@ namespace Umbraco.Core.Cache
protected override void EnterWriteLock()
{
_locker.EnterWriteLock();;
_locker.EnterWriteLock();
}
protected override void ExitReadLock()
@@ -70,7 +70,23 @@ namespace Umbraco.Core.Collections
/// The number of elements contained in the <see cref="T:System.Collections.ICollection"/>.
/// </returns>
/// <filterpriority>2</filterpriority>
public int Count => GetThreadSafeClone().Count;
public int Count
{
get
{
try
{
_instanceLocker.EnterReadLock();
return _innerSet.Count;
}
finally
{
if (_instanceLocker.IsReadLockHeld)
_instanceLocker.ExitReadLock();
}
}
}
/// <summary>
/// Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.
@@ -105,8 +121,7 @@ namespace Umbraco.Core.Collections
/// <returns></returns>
public bool TryAdd(T item)
{
var clone = GetThreadSafeClone();
if (clone.Contains(item)) return false;
if (Contains(item)) return false;
try
{
_instanceLocker.EnterWriteLock();
@@ -150,7 +165,16 @@ namespace Umbraco.Core.Collections
/// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param>
public bool Contains(T item)
{
return GetThreadSafeClone().Contains(item);
try
{
_instanceLocker.EnterReadLock();
return _innerSet.Contains(item);
}
finally
{
if (_instanceLocker.IsReadLockHeld)
_instanceLocker.ExitReadLock();
}
}
/// <summary>
@@ -168,13 +192,13 @@ namespace Umbraco.Core.Collections
HashSet<T> clone = null;
try
{
_instanceLocker.EnterWriteLock();
_instanceLocker.EnterReadLock();
clone = new HashSet<T>(_innerSet, _innerSet.Comparer);
}
finally
{
if (_instanceLocker.IsWriteLockHeld)
_instanceLocker.ExitWriteLock();
if (_instanceLocker.IsReadLockHeld)
_instanceLocker.ExitReadLock();
}
return clone;
}
+1 -1
View File
@@ -126,7 +126,7 @@ namespace Umbraco.Core.Collections
if (_items.TryGetValue(key, out value))
yield return value;
else if (throwOnMissing)
throw new Exception(MissingDependencyError);
throw new Exception($"{MissingDependencyError} Error in type {typeof(TItem).Name}, with key {key}");
}
}
}
@@ -26,10 +26,11 @@ namespace Umbraco.Core.Compose
if (relationType == null)
{
relationType = new RelationType(Constants.ObjectTypes.Document,
relationType = new RelationType(Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias,
Constants.Conventions.RelationTypes.RelateDocumentOnCopyName,
true,
Constants.ObjectTypes.Document,
Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias,
Constants.Conventions.RelationTypes.RelateDocumentOnCopyName) { IsBidirectional = true };
Constants.ObjectTypes.Document);
relationService.Save(relationType);
}
@@ -63,7 +63,7 @@ namespace Umbraco.Core.Compose
var documentObjectType = Constants.ObjectTypes.Document;
const string relationTypeName = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName;
relationType = new RelationType(documentObjectType, documentObjectType, relationTypeAlias, relationTypeName);
relationType = new RelationType(relationTypeName, relationTypeAlias, false, documentObjectType, documentObjectType);
relationService.Save(relationType);
}
@@ -106,7 +106,7 @@ namespace Umbraco.Core.Compose
{
var documentObjectType = Constants.ObjectTypes.Document;
const string relationTypeName = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName;
relationType = new RelationType(documentObjectType, documentObjectType, relationTypeAlias, relationTypeName);
relationType = new RelationType(relationTypeName, relationTypeAlias, false, documentObjectType, documentObjectType);
relationService.Save(relationType);
}
foreach (var item in e.MoveInfoCollection)
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Logging;
@@ -43,8 +44,15 @@ namespace Umbraco.Core.Composing
var componentType = component.GetType();
using (_logger.DebugDuration<ComponentCollection>($"Terminating {componentType.FullName}.", $"Terminated {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds))
{
component.Terminate();
component.DisposeIfDisposable();
try
{
component.Terminate();
component.DisposeIfDisposable();
}
catch (Exception ex)
{
_logger.Error<ComponentCollection>(ex, "Error while terminating component {ComponentType}.", componentType.FullName);
}
}
}
}
+46 -17
View File
@@ -18,19 +18,52 @@ namespace Umbraco.Core.Composing
private readonly Composition _composition;
private readonly IProfilingLogger _logger;
private readonly IEnumerable<Type> _composerTypes;
private readonly IEnumerable<Attribute> _enableDisableAttributes;
private const int LogThresholdMilliseconds = 100;
/// <summary>
/// Initializes a new instance of the <see cref="Composers"/> class.
/// Initializes a new instance of the <see cref="Composers" /> class.
/// </summary>
/// <param name="composition">The composition.</param>
/// <param name="composerTypes">The composer types.</param>
/// <param name="logger">A profiling logger.</param>
/// <param name="composerTypes">The <see cref="IComposer" /> types.</param>
/// <param name="logger">The profiling logger.</param>
[Obsolete("This overload only gets the EnableComposer/DisableComposer attributes from the composerTypes assemblies.")]
public Composers(Composition composition, IEnumerable<Type> composerTypes, IProfilingLogger logger)
: this(composition, composerTypes, Enumerable.Empty<Attribute>(), logger)
{
var enableDisableAttributes = new List<Attribute>();
var assemblies = composerTypes.Select(t => t.Assembly).Distinct();
foreach (var assembly in assemblies)
{
enableDisableAttributes.AddRange(assembly.GetCustomAttributes(typeof(EnableComposerAttribute)));
enableDisableAttributes.AddRange(assembly.GetCustomAttributes(typeof(DisableComposerAttribute)));
}
_enableDisableAttributes = enableDisableAttributes;
}
/// <summary>
/// Initializes a new instance of the <see cref="Composers" /> class.
/// </summary>
/// <param name="composition">The composition.</param>
/// <param name="composerTypes">The <see cref="IComposer" /> types.</param>
/// <param name="enableDisableAttributes">The <see cref="EnableComposerAttribute" /> and/or <see cref="DisableComposerAttribute" /> attributes.</param>
/// <param name="logger">The profiling logger.</param>
/// <exception cref="ArgumentNullException">composition
/// or
/// composerTypes
/// or
/// enableDisableAttributes
/// or
/// logger</exception>
public Composers(Composition composition, IEnumerable<Type> composerTypes, IEnumerable<Attribute> enableDisableAttributes, IProfilingLogger logger)
{
_composition = composition ?? throw new ArgumentNullException(nameof(composition));
_composerTypes = composerTypes ?? throw new ArgumentNullException(nameof(composerTypes));
_enableDisableAttributes = enableDisableAttributes ?? throw new ArgumentNullException(nameof(enableDisableAttributes));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
@@ -103,7 +136,7 @@ namespace Umbraco.Core.Composing
.ToList();
// enable or disable composers
EnableDisableComposers(composerTypeList);
EnableDisableComposers(_enableDisableAttributes, composerTypeList);
void GatherInterfaces<TAttribute>(Type type, Func<TAttribute, Type> getTypeInAttribute, HashSet<Type> iset, List<Type> set2)
where TAttribute : Attribute
@@ -218,7 +251,7 @@ namespace Umbraco.Core.Composing
return text.ToString();
}
private static void EnableDisableComposers(ICollection<Type> types)
private static void EnableDisableComposers(IEnumerable<Attribute> enableDisableAttributes, ICollection<Type> types)
{
var enabled = new Dictionary<Type, EnableInfo>();
@@ -240,20 +273,16 @@ namespace Umbraco.Core.Composing
enableInfo.Weight = weight2;
}
var assemblies = types.Select(x => x.Assembly).Distinct();
foreach (var assembly in assemblies)
foreach (var attr in enableDisableAttributes.OfType<EnableComposerAttribute>())
{
foreach (var attr in assembly.GetCustomAttributes<EnableComposerAttribute>())
{
var type = attr.EnabledType;
UpdateEnableInfo(type, 2, enabled, true);
}
var type = attr.EnabledType;
UpdateEnableInfo(type, 2, enabled, true);
}
foreach (var attr in assembly.GetCustomAttributes<DisableComposerAttribute>())
{
var type = attr.DisabledType;
UpdateEnableInfo(type, 2, enabled, false);
}
foreach (var attr in enableDisableAttributes.OfType<DisableComposerAttribute>())
{
var type = attr.DisabledType;
UpdateEnableInfo(type, 2, enabled, false);
}
foreach (var composerType in types)
+6
View File
@@ -5,6 +5,7 @@ using Umbraco.Core.Dictionary;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Mapping;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.PackageActions;
using Umbraco.Core.Packaging;
@@ -154,6 +155,9 @@ namespace Umbraco.Core.Composing
public static DataEditorCollection DataEditors
=> Factory.GetInstance<DataEditorCollection>();
public static DataValueReferenceFactoryCollection DataValueReferenceFactories
=> Factory.GetInstance<DataValueReferenceFactoryCollection>();
public static PropertyEditorCollection PropertyEditors
=> Factory.GetInstance<PropertyEditorCollection>();
@@ -205,6 +209,8 @@ namespace Umbraco.Core.Composing
public static IVariationContextAccessor VariationContextAccessor
=> Factory.GetInstance<IVariationContextAccessor>();
public static IImageUrlGenerator ImageUrlGenerator
=> Factory.GetInstance<IImageUrlGenerator>();
#endregion
}
}
+43
View File
@@ -506,6 +506,49 @@ namespace Umbraco.Core.Composing
#endregion
#region Get Assembly Attributes
/// <summary>
/// Gets the assembly attributes of the specified type <typeparamref name="T" />.
/// </summary>
/// <typeparam name="T">The attribute type.</typeparam>
/// <returns>
/// The assembly attributes of the specified type <typeparamref name="T" />.
/// </returns>
public IEnumerable<T> GetAssemblyAttributes<T>()
where T : Attribute
{
return AssembliesToScan.SelectMany(a => a.GetCustomAttributes<T>()).ToList();
}
/// <summary>
/// Gets all the assembly attributes.
/// </summary>
/// <returns>
/// All assembly attributes.
/// </returns>
public IEnumerable<Attribute> GetAssemblyAttributes()
{
return AssembliesToScan.SelectMany(a => a.GetCustomAttributes()).ToList();
}
/// <summary>
/// Gets the assembly attributes of the specified <paramref name="attributeTypes" />.
/// </summary>
/// <param name="attributeTypes">The attribute types.</param>
/// <returns>
/// The assembly attributes of the specified types.
/// </returns>
/// <exception cref="ArgumentNullException">attributeTypes</exception>
public IEnumerable<Attribute> GetAssemblyAttributes(params Type[] attributeTypes)
{
if (attributeTypes == null) throw new ArgumentNullException(nameof(attributeTypes));
return AssembliesToScan.SelectMany(a => attributeTypes.SelectMany(at => a.GetCustomAttributes(at))).ToList();
}
#endregion
#region Get Types
/// <summary>
@@ -49,6 +49,13 @@ namespace Umbraco.Core
public static DataEditorCollectionBuilder DataEditors(this Composition composition)
=> composition.WithCollectionBuilder<DataEditorCollectionBuilder>();
/// <summary>
/// Gets the data value reference factory collection builder.
/// </summary>
/// <param name="composition">The composition.</param>
public static DataValueReferenceFactoryCollectionBuilder DataValueReferenceFactories(this Composition composition)
=> composition.WithCollectionBuilder<DataValueReferenceFactoryCollectionBuilder>();
/// <summary>
/// Gets the property value converters collection builder.
/// </summary>
@@ -0,0 +1,8 @@
namespace Umbraco.Core.Configuration.UmbracoSettings
{
public interface IKeepAliveSection : IUmbracoConfigurationSection
{
bool DisableKeepAliveTask { get; }
string KeepAlivePingUrl { get; }
}
}
@@ -15,5 +15,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
ILoggingSection Logging { get; }
IWebRoutingSection WebRouting { get; }
IKeepAliveSection KeepAlive { get; }
}
}
@@ -0,0 +1,13 @@
using System.Configuration;
namespace Umbraco.Core.Configuration.UmbracoSettings
{
internal class KeepAliveElement : ConfigurationElement, IKeepAliveSection
{
[ConfigurationProperty("disableKeepAliveTask", DefaultValue = "false")]
public bool DisableKeepAliveTask => (bool)base["disableKeepAliveTask"];
[ConfigurationProperty("keepAlivePingUrl", DefaultValue = "{umbracoApplicationUrl}/api/keepalive/ping")]
public string KeepAlivePingUrl => (string)base["keepAlivePingUrl"];
}
}
@@ -19,10 +19,12 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
[ConfigurationProperty("logging")]
internal LoggingElement Logging => (LoggingElement)this["logging"];
[ConfigurationProperty("web.routing")]
internal WebRoutingElement WebRouting => (WebRoutingElement)this["web.routing"];
[ConfigurationProperty("keepAlive")]
internal KeepAliveElement KeepAlive => (KeepAliveElement)this["keepAlive"];
IContentSection IUmbracoSettingsSection.Content => Content;
ISecuritySection IUmbracoSettingsSection.Security => Security;
@@ -34,5 +36,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
ILoggingSection IUmbracoSettingsSection.Logging => Logging;
IWebRoutingSection IUmbracoSettingsSection.WebRouting => WebRouting;
IKeepAliveSection IUmbracoSettingsSection.KeepAlive => KeepAlive;
}
}
+17
View File
@@ -9,6 +9,8 @@ namespace Umbraco.Core
/// </summary>
public static class AppSettings
{
public const string MainDomLock = "Umbraco.Core.MainDom.Lock";
// TODO: Kill me - still used in Umbraco.Core.IO.SystemFiles:27
[Obsolete("We need to kill this appsetting as we do not use XML content cache umbraco.config anymore due to NuCache")]
public const string ContentXML = "Umbraco.Core.ContentXML"; //umbracoContentXML
@@ -41,6 +43,21 @@ namespace Umbraco.Core
/// </summary>
public const string Path = "Umbraco.Core.Path";
/// <summary>
/// Gets the path to the css directory (/css by default).
/// </summary>
public const string CssPath = "umbracoCssPath";
/// <summary>
/// Gets the path to the scripts directory (/scripts by default).
/// </summary>
public const string ScriptsPath = "umbracoScriptsPath";
/// <summary>
/// Gets the path to media directory (/media by default).
/// </summary>
public const string MediaPath = "umbracoMediaPath";
/// <summary>
/// The reserved urls from web.config.
/// </summary>
+37 -6
View File
@@ -315,34 +315,65 @@ namespace Umbraco.Core
public static class RelationTypes
{
/// <summary>
/// ContentType name for default relation type "Relate Document On Copy".
/// Name for default relation type "Related Media".
/// </summary>
public const string RelatedMediaName = "Related Media";
/// <summary>
/// Alias for default relation type "Related Media"
/// </summary>
public const string RelatedMediaAlias = "umbMedia";
/// <summary>
/// Name for default relation type "Related Document".
/// </summary>
public const string RelatedDocumentName = "Related Document";
/// <summary>
/// Alias for default relation type "Related Document"
/// </summary>
public const string RelatedDocumentAlias = "umbDocument";
/// <summary>
/// Name for default relation type "Relate Document On Copy".
/// </summary>
public const string RelateDocumentOnCopyName = "Relate Document On Copy";
/// <summary>
/// ContentType alias for default relation type "Relate Document On Copy".
/// Alias for default relation type "Relate Document On Copy".
/// </summary>
public const string RelateDocumentOnCopyAlias = "relateDocumentOnCopy";
/// <summary>
/// ContentType name for default relation type "Relate Parent Document On Delete".
/// Name for default relation type "Relate Parent Document On Delete".
/// </summary>
public const string RelateParentDocumentOnDeleteName = "Relate Parent Document On Delete";
/// <summary>
/// ContentType alias for default relation type "Relate Parent Document On Delete".
/// Alias for default relation type "Relate Parent Document On Delete".
/// </summary>
public const string RelateParentDocumentOnDeleteAlias = "relateParentDocumentOnDelete";
/// <summary>
/// ContentType name for default relation type "Relate Parent Media Folder On Delete".
/// Name for default relation type "Relate Parent Media Folder On Delete".
/// </summary>
public const string RelateParentMediaFolderOnDeleteName = "Relate Parent Media Folder On Delete";
/// <summary>
/// ContentType alias for default relation type "Relate Parent Media Folder On Delete".
/// Alias for default relation type "Relate Parent Media Folder On Delete".
/// </summary>
public const string RelateParentMediaFolderOnDeleteAlias = "relateParentMediaFolderOnDelete";
/// <summary>
/// Returns the types of relations that are automatically tracked
/// </summary>
/// <remarks>
/// Developers should not manually use these relation types since they will all be cleared whenever an entity
/// (content, media or member) is saved since they are auto-populated based on property values.
/// </remarks>
public static string[] AutomaticRelationTypes = new[] { RelatedMediaAlias, RelatedDocumentAlias };
//TODO: return a list of built in types so we can use that to prevent deletion in the uI
}
}
}
+8 -7
View File
@@ -13,6 +13,7 @@ using Umbraco.Core.IO;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement;
@@ -23,6 +24,8 @@ namespace Umbraco.Core
// this ain't pretty
private static IMediaFileSystem _mediaFileSystem;
private static IMediaFileSystem MediaFileSystem => _mediaFileSystem ?? (_mediaFileSystem = Current.MediaFileSystem);
private static readonly PropertyEditorCollection _propertyEditors;
private static PropertyEditorCollection PropertyEditors = _propertyEditors ?? (_propertyEditors = Current.PropertyEditors);
#region IContent
@@ -162,14 +165,12 @@ namespace Umbraco.Core
// Fixes https://github.com/umbraco/Umbraco-CMS/issues/3937 - Assigning a new file to an
// existing IMedia with extension SetValue causes exception 'Illegal characters in path'
string oldpath = null;
if (property.GetValue(culture, segment) is string svalue)
var value = property.GetValue(culture, segment);
if (PropertyEditors.TryGet(propertyTypeAlias, out var editor)
&& editor is IDataEditorWithMediaPath dataEditor)
{
if (svalue.DetectIsJson())
{
// the property value is a JSON serialized image crop data set - grab the "src" property as the file source
var jObject = JsonConvert.DeserializeObject<JObject>(svalue);
svalue = jObject != null ? jObject.GetValueAsString("src") : svalue;
}
var svalue = dataEditor.GetMediaPath(value);
oldpath = MediaFileSystem.GetRelativePath(svalue);
}
+226 -82
View File
@@ -12,121 +12,260 @@ namespace Umbraco.Core
/// <summary>
/// Determines whether the content type is invariant.
/// </summary>
/// <param name="contentType">The content type.</param>
/// <returns>
/// A value indicating whether the content type is invariant.
/// </returns>
public static bool VariesByNothing(this ISimpleContentType contentType) => contentType.Variations.VariesByNothing();
/// <summary>
/// Determines whether the content type varies by culture.
/// </summary>
public static bool VariesByCulture(this ISimpleContentType contentType) => contentType.Variations.VariesByCulture();
/// <summary>
/// Determines whether the content type is invariant.
/// </summary>
/// <param name="contentType">The content type.</param>
/// <returns>
/// A value indicating whether the content type is invariant.
/// </returns>
public static bool VariesByNothing(this IContentTypeBase contentType) => contentType.Variations.VariesByNothing();
/// <summary>
/// Determines whether the content type varies by culture.
/// </summary>
/// <remarks>And then it could also vary by segment.</remarks>
public static bool VariesByCulture(this IContentTypeBase contentType) => contentType.Variations.VariesByCulture();
/// <summary>
/// Determines whether the content type varies by segment.
/// </summary>
/// <remarks>And then it could also vary by culture.</remarks>
public static bool VariesBySegment(this IContentTypeBase contentType) => contentType.Variations.VariesBySegment();
/// <summary>
/// Determines whether the content type varies by culture and segment.
/// </summary>
public static bool VariesByCultureAndSegment(this IContentTypeBase contentType) => contentType.Variations.VariesByCultureAndSegment();
/// <summary>
/// Determines whether the property type is invariant.
/// </summary>
public static bool VariesByNothing(this PropertyType propertyType) => propertyType.Variations.VariesByNothing();
/// <summary>
/// Determines whether the property type varies by culture.
/// </summary>
/// <remarks>And then it could also vary by segment.</remarks>
public static bool VariesByCulture(this PropertyType propertyType) => propertyType.Variations.VariesByCulture();
/// <summary>
/// Determines whether the property type varies by segment.
/// </summary>
/// <remarks>And then it could also vary by culture.</remarks>
public static bool VariesBySegment(this PropertyType propertyType) => propertyType.Variations.VariesBySegment();
/// <summary>
/// Determines whether the property type varies by culture and segment.
/// </summary>
public static bool VariesByCultureAndSegment(this PropertyType propertyType) => propertyType.Variations.VariesByCultureAndSegment();
/// <summary>
/// Determines whether the content type is invariant.
/// </summary>
/// <param name="contentType">The content type.</param>
/// <returns>
/// A value indicating whether the content type is invariant.
/// </returns>
public static bool VariesByNothing(this IPublishedContentType contentType) => contentType.Variations.VariesByNothing();
/// <summary>
/// Determines whether the content type varies by culture.
/// Determines whether the property type is invariant.
/// </summary>
/// <remarks>And then it could also vary by segment.</remarks>
public static bool VariesByCulture(this IPublishedContentType contentType) => contentType.Variations.VariesByCulture();
/// <summary>
/// Determines whether the content type varies by segment.
/// </summary>
/// <remarks>And then it could also vary by culture.</remarks>
public static bool VariesBySegment(this IPublishedContentType contentType) => contentType.Variations.VariesBySegment();
/// <summary>
/// Determines whether the content type varies by culture and segment.
/// </summary>
public static bool VariesByCultureAndSegment(this IPublishedContentType contentType) => contentType.Variations.VariesByCultureAndSegment();
/// <param name="propertyType">The property type.</param>
/// <returns>
/// A value indicating whether the property type is invariant.
/// </returns>
public static bool VariesByNothing(this PropertyType propertyType) => propertyType.Variations.VariesByNothing();
/// <summary>
/// Determines whether the property type is invariant.
/// </summary>
/// <param name="propertyType">The property type.</param>
/// <returns>
/// A value indicating whether the property type is invariant.
/// </returns>
public static bool VariesByNothing(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesByNothing();
/// <summary>
/// Determines whether the property type varies by culture.
/// </summary>
public static bool VariesByCulture(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesByCulture();
/// <summary>
/// Determines whether the property type varies by segment.
/// </summary>
public static bool VariesBySegment(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesBySegment();
/// <summary>
/// Determines whether the property type varies by culture and segment.
/// </summary>
public static bool VariesByCultureAndSegment(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesByCultureAndSegment();
/// <summary>
/// Determines whether a variation is invariant.
/// </summary>
/// <param name="variation">The variation.</param>
/// <returns>
/// A value indicating whether the variation is invariant.
/// </returns>
public static bool VariesByNothing(this ContentVariation variation) => variation == ContentVariation.Nothing;
/// <summary>
/// Determines whether the content type varies by culture.
/// </summary>
/// <param name="contentType">The content type.</param>
/// <returns>
/// A value indicating whether the content type varies by culture.
/// </returns>
public static bool VariesByCulture(this ISimpleContentType contentType) => contentType.Variations.VariesByCulture();
/// <summary>
/// Determines whether the content type varies by culture.
/// </summary>
/// <param name="contentType">The content type.</param>
/// <returns>
/// A value indicating whether the content type varies by culture.
/// </returns>
public static bool VariesByCulture(this IContentTypeBase contentType) => contentType.Variations.VariesByCulture();
/// <summary>
/// Determines whether the content type varies by culture.
/// </summary>
/// <param name="contentType">The content type.</param>
/// <returns>
/// A value indicating whether the content type varies by culture.
/// </returns>
public static bool VariesByCulture(this IPublishedContentType contentType) => contentType.Variations.VariesByCulture();
/// <summary>
/// Determines whether the property type varies by culture.
/// </summary>
/// <param name="propertyType">The property type.</param>
/// <returns>
/// A value indicating whether the property type varies by culture.
/// </returns>
public static bool VariesByCulture(this PropertyType propertyType) => propertyType.Variations.VariesByCulture();
/// <summary>
/// Determines whether the property type varies by culture.
/// </summary>
/// <param name="propertyType">The property type.</param>
/// <returns>
/// A value indicating whether the property type varies by culture.
/// </returns>
public static bool VariesByCulture(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesByCulture();
/// <summary>
/// Determines whether a variation varies by culture.
/// </summary>
/// <remarks>And then it could also vary by segment.</remarks>
/// <param name="variation">The variation.</param>
/// <returns>
/// A value indicating whether the variation varies by culture.
/// </returns>
public static bool VariesByCulture(this ContentVariation variation) => (variation & ContentVariation.Culture) > 0;
/// <summary>
/// Determines whether the content type varies by segment.
/// </summary>
/// <param name="contentType">The content type.</param>
/// <returns>
/// A value indicating whether the content type varies by segment.
/// </returns>
public static bool VariesBySegment(this ISimpleContentType contentType) => contentType.Variations.VariesBySegment();
/// <summary>
/// Determines whether the content type varies by segment.
/// </summary>
/// <param name="contentType">The content type.</param>
/// <returns>
/// A value indicating whether the content type varies by segment.
/// </returns>
public static bool VariesBySegment(this IContentTypeBase contentType) => contentType.Variations.VariesBySegment();
/// <summary>
/// Determines whether the content type varies by segment.
/// </summary>
/// <param name="contentType">The content type.</param>
/// <returns>
/// A value indicating whether the content type varies by segment.
/// </returns>
public static bool VariesBySegment(this IPublishedContentType contentType) => contentType.Variations.VariesBySegment();
/// <summary>
/// Determines whether the property type varies by segment.
/// </summary>
/// <param name="propertyType">The property type.</param>
/// <returns>
/// A value indicating whether the property type varies by segment.
/// </returns>
public static bool VariesBySegment(this PropertyType propertyType) => propertyType.Variations.VariesBySegment();
/// <summary>
/// Determines whether the property type varies by segment.
/// </summary>
/// <param name="propertyType">The property type.</param>
/// <returns>
/// A value indicating whether the property type varies by segment.
/// </returns>
public static bool VariesBySegment(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesBySegment();
/// <summary>
/// Determines whether a variation varies by segment.
/// </summary>
/// <remarks>And then it could also vary by culture.</remarks>
/// <param name="variation">The variation.</param>
/// <returns>
/// A value indicating whether the variation varies by segment.
/// </returns>
public static bool VariesBySegment(this ContentVariation variation) => (variation & ContentVariation.Segment) > 0;
/// <summary>
/// Determines whether the content type varies by culture and segment.
/// </summary>
/// <param name="contentType">The content type.</param>
/// <returns>
/// A value indicating whether the content type varies by culture and segment.
/// </returns>
public static bool VariesByCultureAndSegment(this ISimpleContentType contentType) => contentType.Variations.VariesByCultureAndSegment();
/// <summary>
/// Determines whether the content type varies by culture and segment.
/// </summary>
/// <param name="contentType">The content type.</param>
/// <returns>
/// A value indicating whether the content type varies by culture and segment.
/// </returns>
public static bool VariesByCultureAndSegment(this IContentTypeBase contentType) => contentType.Variations.VariesByCultureAndSegment();
/// <summary>
/// Determines whether the content type varies by culture and segment.
/// </summary>
/// <param name="contentType">The content type.</param>
/// <returns>
/// A value indicating whether the content type varies by culture and segment.
/// </returns>
public static bool VariesByCultureAndSegment(this IPublishedContentType contentType) => contentType.Variations.VariesByCultureAndSegment();
/// <summary>
/// Determines whether the property type varies by culture and segment.
/// </summary>
/// <param name="propertyType">The property type.</param>
/// <returns>
/// A value indicating whether the property type varies by culture and segment.
/// </returns>
public static bool VariesByCultureAndSegment(this PropertyType propertyType) => propertyType.Variations.VariesByCultureAndSegment();
/// <summary>
/// Determines whether the property type varies by culture and segment.
/// </summary>
/// <param name="propertyType">The property type.</param>
/// <returns>
/// A value indicating whether the property type varies by culture and segment.
/// </returns>
public static bool VariesByCultureAndSegment(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesByCultureAndSegment();
/// <summary>
/// Determines whether a variation varies by culture and segment.
/// </summary>
/// <param name="variation">The variation.</param>
/// <returns>
/// A value indicating whether the variation varies by culture and segment.
/// </returns>
public static bool VariesByCultureAndSegment(this ContentVariation variation) => (variation & ContentVariation.CultureAndSegment) == ContentVariation.CultureAndSegment;
/// <summary>
/// Sets or removes the content type variation depending on the specified value.
/// </summary>
/// <param name="contentType">The content type.</param>
/// <param name="variation">The variation to set or remove.</param>
/// <param name="value">If set to <c>true</c> sets the variation; otherwise, removes the variation.</param>
/// <remarks>
/// This method does not support setting the variation to nothing.
/// </remarks>
public static void SetVariesBy(this IContentTypeBase contentType, ContentVariation variation, bool value = true) => contentType.Variations = contentType.Variations.SetFlag(variation, value);
/// <summary>
/// Sets or removes the property type variation depending on the specified value.
/// </summary>
/// <param name="propertyType">The property type.</param>
/// <param name="variation">The variation to set or remove.</param>
/// <param name="value">If set to <c>true</c> sets the variation; otherwise, removes the variation.</param>
/// <remarks>
/// This method does not support setting the variation to nothing.
/// </remarks>
public static void SetVariesBy(this PropertyType propertyType, ContentVariation variation, bool value = true) => propertyType.Variations = propertyType.Variations.SetFlag(variation, value);
/// <summary>
/// Returns the variations with the variation set or removed depending on the specified value.
/// </summary>
/// <param name="variations">The existing variations.</param>
/// <param name="variation">The variation to set or remove.</param>
/// <param name="value">If set to <c>true</c> sets the variation; otherwise, removes the variation.</param>
/// <returns>
/// The variations with the variation set or removed.
/// </returns>
/// <remarks>
/// This method does not support setting the variation to nothing.
/// </remarks>
public static ContentVariation SetFlag(this ContentVariation variations, ContentVariation variation, bool value = true)
{
return value
? variations | variation // Set flag using bitwise logical OR
: variations & ~variation; // Remove flag using bitwise logical AND with bitwise complement (reversing the bit)
}
/// <summary>
/// Validates that a combination of culture and segment is valid for the variation.
/// </summary>
@@ -135,16 +274,18 @@ namespace Umbraco.Core
/// <param name="segment">The segment.</param>
/// <param name="exact">A value indicating whether to perform exact validation.</param>
/// <param name="wildcards">A value indicating whether to support wildcards.</param>
/// <param name="throwIfInvalid">A value indicating whether to throw a <see cref="NotSupportedException"/> when the combination is invalid.</param>
/// <returns>True if the combination is valid; otherwise false.</returns>
/// <param name="throwIfInvalid">A value indicating whether to throw a <see cref="NotSupportedException" /> when the combination is invalid.</param>
/// <returns>
/// <c>true</c> if the combination is valid; otherwise <c>false</c>.
/// </returns>
/// <exception cref="NotSupportedException">Occurs when the combination is invalid, and <paramref name="throwIfInvalid" /> is true.</exception>
/// <remarks>
/// <para>When validation is exact, the combination must match the variation exactly. For instance, if the variation is Culture, then
/// a culture is required. When validation is not strict, the combination must be equivalent, or more restrictive: if the variation is
/// Culture, an invariant combination is ok.</para>
/// <para>Basically, exact is for one content type, or one property type, and !exact is for "all property types" of one content type.</para>
/// <para>Both <paramref name="culture"/> and <paramref name="segment"/> can be "*" to indicate "all of them".</para>
/// <para>Both <paramref name="culture" /> and <paramref name="segment" /> can be "*" to indicate "all of them".</para>
/// </remarks>
/// <exception cref="NotSupportedException">Occurs when the combination is invalid, and <paramref name="throwIfInvalid"/> is true.</exception>
public static bool ValidateVariation(this ContentVariation variation, string culture, string segment, bool exact, bool wildcards, bool throwIfInvalid)
{
culture = culture.NullOrWhiteSpaceAsNull();
@@ -161,13 +302,14 @@ namespace Umbraco.Core
if (variation.VariesByCulture())
{
// varies by culture
// in exact mode, the culture cannot be null
// in exact mode, the culture cannot be null
if (exact && culture == null)
{
if (throwIfInvalid)
throw new NotSupportedException($"Culture may not be null because culture variation is enabled.");
return false;
}
}
}
else
{
@@ -178,9 +320,10 @@ namespace Umbraco.Core
{
if (throwIfInvalid)
throw new NotSupportedException($"Culture \"{culture}\" is invalid because culture variation is disabled.");
return false;
}
}
}
// if it does not vary by segment
// the segment cannot have a value
@@ -190,6 +333,7 @@ namespace Umbraco.Core
{
if (throwIfInvalid)
throw new NotSupportedException($"Segment \"{segment}\" is invalid because segment variation is disabled.");
return false;
}
+22 -23
View File
@@ -3,43 +3,42 @@
namespace Umbraco.Core
{
/// <summary>
/// Provides extension methods to enums.
/// Provides extension methods to <see cref="Enum"/>.
/// </summary>
public static class EnumExtensions
{
// note:
// - no need to HasFlagExact, that's basically an == test
// - HasFlagAll cannot be named HasFlag because ext. methods never take priority over instance methods
/// <summary>
/// Determines whether a flag enum has all the specified values.
/// Determines whether all the flags/bits are set within the enum value.
/// </summary>
/// <remarks>
/// <para>True when all bits set in <paramref name="uses"/> are set in <paramref name="use"/>, though other bits may be set too.</para>
/// <para>This is the behavior of the original <see cref="Enum.HasFlag"/> method.</para>
/// </remarks>
public static bool HasFlagAll<T>(this T use, T uses)
/// <typeparam name="T">The enum type.</typeparam>
/// <param name="value">The enum value.</param>
/// <param name="flags">The flags.</param>
/// <returns>
/// <c>true</c> if all the flags/bits are set within the enum value; otherwise, <c>false</c>.
/// </returns>
[Obsolete("Use Enum.HasFlag() or bitwise operations (if performance is important) instead.")]
public static bool HasFlagAll<T>(this T value, T flags)
where T : Enum
{
var num = Convert.ToUInt64(use);
var nums = Convert.ToUInt64(uses);
return (num & nums) == nums;
return value.HasFlag(flags);
}
/// <summary>
/// Determines whether a flag enum has any of the specified values.
/// Determines whether any of the flags/bits are set within the enum value.
/// </summary>
/// <remarks>
/// <para>True when at least one of the bits set in <paramref name="uses"/> is set in <paramref name="use"/>.</para>
/// </remarks>
public static bool HasFlagAny<T>(this T use, T uses)
/// <typeparam name="T">The enum type.</typeparam>
/// <param name="value">The value.</param>
/// <param name="flags">The flags.</param>
/// <returns>
/// <c>true</c> if any of the flags/bits are set within the enum value; otherwise, <c>false</c>.
/// </returns>
public static bool HasFlagAny<T>(this T value, T flags)
where T : Enum
{
var num = Convert.ToUInt64(use);
var nums = Convert.ToUInt64(uses);
var v = Convert.ToUInt64(value);
var f = Convert.ToUInt64(flags);
return (num & nums) > 0;
return (v & f) > 0;
}
}
}
+1 -1
View File
@@ -41,7 +41,7 @@ namespace Umbraco.Core
internal void AddDateTime(DateTime d)
{
_writer.Write(d.Ticks);;
_writer.Write(d.Ticks);
}
internal void AddString(string s)
+4 -4
View File
@@ -29,13 +29,13 @@ namespace Umbraco.Core.IO
public static string MacroPartials => MvcViews + "/MacroPartials/";
public static string Media => IOHelper.ReturnPath("umbracoMediaPath", "~/media");
public static string Media => IOHelper.ReturnPath(Constants.AppSettings.MediaPath, "~/media");
public static string Scripts => IOHelper.ReturnPath("umbracoScriptsPath", "~/scripts");
public static string Scripts => IOHelper.ReturnPath(Constants.AppSettings.ScriptsPath, "~/scripts");
public static string Css => IOHelper.ReturnPath("umbracoCssPath", "~/css");
public static string Css => IOHelper.ReturnPath(Constants.AppSettings.CssPath, "~/css");
public static string Umbraco => IOHelper.ReturnPath("umbracoPath", "~/umbraco");
public static string Umbraco => IOHelper.ReturnPath(Constants.AppSettings.Path, "~/umbraco");
public static string Packages => Data + "/packages";
@@ -19,6 +19,8 @@ namespace Umbraco.Core.Manifest
// '-content/foo', // hide for content type 'foo'
// '+content/*', // show for all other content types
// '+media/*', // show for all media types
// '-member/foo' // hide for member type 'foo'
// '+member/*' // show for all member types
// '+role/admin' // show for admin users. Role based permissions will override others.
// ]
// },
@@ -56,6 +58,10 @@ namespace Umbraco.Core.Manifest
partA = "media";
partB = media.ContentType.Alias;
break;
case IMember member:
partA = "member";
partB = member.ContentType.Alias;
break;
default:
return null;
@@ -151,6 +151,8 @@ namespace Umbraco.Core.Migrations.Install
_database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.Domains, Name = "Domains" });
_database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.KeyValues, Name = "KeyValues" });
_database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.Languages, Name = "Languages" });
_database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.MainDom, Name = "MainDom" });
}
private void CreateContentTypeData()
@@ -310,14 +312,27 @@ namespace Umbraco.Core.Migrations.Install
private void CreateRelationTypeData()
{
var relationType = new RelationTypeDto { Id = 1, Alias = Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, ChildObjectType = Constants.ObjectTypes.Document, ParentObjectType = Constants.ObjectTypes.Document, Dual = true, Name = Constants.Conventions.RelationTypes.RelateDocumentOnCopyName };
relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid();
relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name);
_database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType);
relationType = new RelationTypeDto { Id = 2, Alias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias, ChildObjectType = Constants.ObjectTypes.Document, ParentObjectType = Constants.ObjectTypes.Document, Dual = false, Name = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName };
relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid();
relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name);
_database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType);
relationType = new RelationTypeDto { Id = 3, Alias = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias, ChildObjectType = Constants.ObjectTypes.Media, ParentObjectType = Constants.ObjectTypes.Media, Dual = false, Name = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName };
relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid();
relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name);
_database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType);
relationType = new RelationTypeDto { Id = 4, Alias = Constants.Conventions.RelationTypes.RelatedMediaAlias, ChildObjectType = null, ParentObjectType = null, Dual = false, Name = Constants.Conventions.RelationTypes.RelatedMediaName };
relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name);
_database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType);
relationType = new RelationTypeDto { Id = 5, Alias = Constants.Conventions.RelationTypes.RelatedDocumentAlias, ChildObjectType = null, ParentObjectType = null, Dual = false, Name = Constants.Conventions.RelationTypes.RelatedDocumentName };
relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name);
_database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType);
}
internal static Guid CreateUniqueRelationTypeId(string alias, string name)
{
return (alias + "____" + name).ToGuid();
}
private void CreateKeyValueData()
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.SqlSyntax;
@@ -84,10 +85,18 @@ namespace Umbraco.Core.Migrations
protected void ReplaceColumn<T>(string tableName, string currentName, string newName)
{
AddColumn<T>(tableName, newName, out var sqls);
Execute.Sql($"UPDATE {SqlSyntax.GetQuotedTableName(tableName)} SET {SqlSyntax.GetQuotedColumnName(newName)}={SqlSyntax.GetQuotedColumnName(currentName)}").Do();
foreach (var sql in sqls) Execute.Sql(sql).Do();
Delete.Column(currentName).FromTable(tableName).Do();
if (DatabaseType.IsSqlCe())
{
AddColumn<T>(tableName, newName, out var sqls);
Execute.Sql($"UPDATE {SqlSyntax.GetQuotedTableName(tableName)} SET {SqlSyntax.GetQuotedColumnName(newName)}={SqlSyntax.GetQuotedColumnName(currentName)}").Do();
foreach (var sql in sqls) Execute.Sql(sql).Do();
Delete.Column(currentName).FromTable(tableName).Do();
}
else
{
Execute.Sql(SqlSyntax.FormatColumnRename(tableName, currentName, newName)).Do();
AlterColumn<T>(tableName, newName);
}
}
protected bool TableExists(string tableName)
@@ -12,6 +12,7 @@ namespace Umbraco.Core.Migrations.Upgrade.Common
{
// remove those that may already have keys
Delete.KeysAndIndexes(Constants.DatabaseSchema.Tables.KeyValue).Do();
Delete.KeysAndIndexes(Constants.DatabaseSchema.Tables.PropertyData).Do();
// re-create *all* keys and indexes
foreach (var x in DatabaseSchemaCreator.OrderedTables)
@@ -183,8 +183,15 @@ namespace Umbraco.Core.Migrations.Upgrade
To<RenameUserLoginDtoDateIndex>("{0372A42B-DECF-498D-B4D1-6379E907EB94}");
To<FixContentNuCascade>("{5B1E0D93-F5A3-449B-84BA-65366B84E2D4}");
// to 8.6.0
// to 8.6.0...
To<UpdateRelationTypeTable>("{4759A294-9860-46BC-99F9-B4C975CAE580}");
To<AddNewRelationTypes>("{0BC866BC-0665-487A-9913-0290BD0169AD}");
To<AddPropertyTypeValidationMessageColumns>("{3D67D2C8-5E65-47D0-A9E1-DC2EE0779D6B}");
To<MissingContentVersionsIndexes>("{EE288A91-531B-4995-8179-1D62D9AA3E2E}");
To<AddMainDomLock>("{2AB29964-02A1-474D-BD6B-72148D2A53A2}");
To<MissingDictionaryIndex>("{a78e3369-8ea3-40ec-ad3f-5f76929d2b20}");
//FINAL
}
@@ -75,8 +75,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
var labelPropertyTypes = Database.Fetch<PropertyTypeDto>(Sql()
.Select<PropertyTypeDto>(x => x.Id, x => x.Alias)
.From<PropertyTypeDto>()
.Where<PropertyTypeDto>(x => x.DataTypeId == Constants.DataTypes.LabelString)
);
.Where<PropertyTypeDto>(x => x.DataTypeId == Constants.DataTypes.LabelString));
var intPropertyAliases = new[] { Constants.Conventions.Media.Width, Constants.Conventions.Media.Height, Constants.Conventions.Member.FailedPasswordAttempts };
var bigintPropertyAliases = new[] { Constants.Conventions.Media.Bytes };
@@ -101,16 +100,18 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
foreach (var value in values)
Database.Execute(Sql()
.Update<PropertyDataDto>(u => u
.Set(x => x.IntegerValue, string.IsNullOrWhiteSpace(value.VarcharValue) ? (int?) null : int.Parse(value.VarcharValue, NumberStyles.Any, CultureInfo.InvariantCulture))
.Set(x => x.TextValue, null))
.Set(x => x.IntegerValue, string.IsNullOrWhiteSpace(value.VarcharValue) ? (int?)null : int.Parse(value.VarcharValue, NumberStyles.Any, CultureInfo.InvariantCulture))
.Set(x => x.TextValue, null)
.Set(x => x.VarcharValue, null))
.Where<PropertyDataDto>(x => x.Id == value.Id));
values = Database.Fetch<PropertyDataValue>(Sql().Select<PropertyDataDto>(x => x.Id, x => x.VarcharValue).From<PropertyDataDto>().WhereIn<PropertyDataDto>(x => x.PropertyTypeId, dtPropertyTypes));
foreach (var value in values)
Database.Execute(Sql()
.Update<PropertyDataDto>(u => u
.Set(x => x.DateValue, string.IsNullOrWhiteSpace(value.VarcharValue) ? (DateTime?) null : DateTime.Parse(value.VarcharValue, CultureInfo.InvariantCulture, DateTimeStyles.None))
.Set(x => x.TextValue, null))
.Set(x => x.DateValue, string.IsNullOrWhiteSpace(value.VarcharValue) ? (DateTime?)null : DateTime.Parse(value.VarcharValue, CultureInfo.InvariantCulture, DateTimeStyles.None))
.Set(x => x.TextValue, null)
.Set(x => x.VarcharValue, null))
.Where<PropertyDataDto>(x => x.Id == value.Id));
// anything that's custom... ppl will have to figure it out manually, there isn't much we can do about it
@@ -4,8 +4,10 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NPoco;
using Umbraco.Core.Migrations.Upgrade.V_8_0_0.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.DatabaseAnnotations;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Dtos;
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
@@ -47,14 +49,14 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
}
}
var propertyTypes = Database.Fetch<PropertyTypeDto>(Sql().Select<PropertyTypeDto>().From<PropertyTypeDto>());
var propertyTypes = Database.Fetch<PropertyTypeDto80>(Sql().Select<PropertyTypeDto80>().From<PropertyTypeDto80>());
foreach (var dto in propertyTypes)
{
dto.Variations = GetNewValue(dto.Variations);
Database.Update(dto);
}
var contentTypes = Database.Fetch<ContentTypeDto>(Sql().Select<ContentTypeDto>().From<ContentTypeDto>());
var contentTypes = Database.Fetch<ContentTypeDto80>(Sql().Select<ContentTypeDto80>().From<ContentTypeDto80>());
foreach (var dto in contentTypes)
{
dto.Variations = GetNewValue(dto.Variations);
@@ -62,57 +64,11 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
}
}
// we *need* to use this private DTO here, which does *not* have extra properties, which would kill the migration
// we *need* to use these private DTOs here, which does *not* have extra properties, which would kill the migration
[TableName(TableName)]
[PrimaryKey("pk")]
[ExplicitColumns]
private class ContentTypeDto
{
public const string TableName = Constants.DatabaseSchema.Tables.ContentType;
[Column("pk")]
[PrimaryKeyColumn(IdentitySeed = 535)]
public int PrimaryKey { get; set; }
[Column("nodeId")]
[ForeignKey(typeof(NodeDto))]
[Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsContentType")]
public int NodeId { get; set; }
[Column("alias")]
[NullSetting(NullSetting = NullSettings.Null)]
public string Alias { get; set; }
[Column("icon")]
[Index(IndexTypes.NonClustered)]
[NullSetting(NullSetting = NullSettings.Null)]
public string Icon { get; set; }
[Column("thumbnail")]
[Constraint(Default = "folder.png")]
public string Thumbnail { get; set; }
[Column("description")]
[NullSetting(NullSetting = NullSettings.Null)]
[Length(1500)]
public string Description { get; set; }
[Column("isContainer")]
[Constraint(Default = "0")]
public bool IsContainer { get; set; }
[Column("allowAtRoot")]
[Constraint(Default = "0")]
public bool AllowAtRoot { get; set; }
[Column("variations")]
[Constraint(Default = "1" /*ContentVariation.InvariantNeutral*/)]
public byte Variations { get; set; }
[ResultColumn]
public NodeDto NodeDto { get; set; }
}
}
}
@@ -2,8 +2,7 @@
using System.Linq;
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Migrations.Upgrade.V_8_0_0.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Dtos;
@@ -34,11 +33,11 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
}
var sqlPropertyTpes = Sql()
.Select<PropertyTypeDto>()
.From<PropertyTypeDto>()
.Where<PropertyTypeDto>(x => dataTypeIds.Contains(x.DataTypeId));
.Select<PropertyTypeDto80>()
.From<PropertyTypeDto80>()
.Where<PropertyTypeDto80>(x => dataTypeIds.Contains(x.DataTypeId));
var propertyTypeIds = Database.Fetch<PropertyTypeDto>(sqlPropertyTpes).Select(x => x.Id).ToList();
var propertyTypeIds = Database.Fetch<PropertyTypeDto80>(sqlPropertyTpes).Select(x => x.Id).ToList();
if (propertyTypeIds.Count == 0) return;
@@ -32,7 +32,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
if (string.IsNullOrEmpty(dataType.Configuration))
{
config.Format = "YYYY-MM-DD";
};
}
}
catch (Exception ex)
{
@@ -0,0 +1,63 @@
using NPoco;
using Umbraco.Core.Persistence.DatabaseAnnotations;
using Umbraco.Core.Persistence.Dtos;
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.Models
{
/// <summary>
/// Snapshot of the <see cref="ContentTypeDto"/> as it was at version 8.0
/// </summary>
/// <remarks>
/// This is required during migrations the schema of this table changed and running SQL against the new table would result in errors
/// </remarks>
[TableName(TableName)]
[PrimaryKey("pk")]
[ExplicitColumns]
internal class ContentTypeDto80
{
public const string TableName = Constants.DatabaseSchema.Tables.ContentType;
[Column("pk")]
[PrimaryKeyColumn(IdentitySeed = 535)]
public int PrimaryKey { get; set; }
[Column("nodeId")]
[ForeignKey(typeof(NodeDto))]
[Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsContentType")]
public int NodeId { get; set; }
[Column("alias")]
[NullSetting(NullSetting = NullSettings.Null)]
public string Alias { get; set; }
[Column("icon")]
[Index(IndexTypes.NonClustered)]
[NullSetting(NullSetting = NullSettings.Null)]
public string Icon { get; set; }
[Column("thumbnail")]
[Constraint(Default = "folder.png")]
public string Thumbnail { get; set; }
[Column("description")]
[NullSetting(NullSetting = NullSettings.Null)]
[Length(1500)]
public string Description { get; set; }
[Column("isContainer")]
[Constraint(Default = "0")]
public bool IsContainer { get; set; }
[Column("allowAtRoot")]
[Constraint(Default = "0")]
public bool AllowAtRoot { get; set; }
[Column("variations")]
[Constraint(Default = "1" /*ContentVariation.InvariantNeutral*/)]
public byte Variations { get; set; }
[ResultColumn]
public NodeDto NodeDto { get; set; }
}
}
@@ -0,0 +1,142 @@
using NPoco;
using System;
using Umbraco.Core.Persistence.DatabaseAnnotations;
using Umbraco.Core.Persistence.Dtos;
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.Models
{
/// <summary>
/// Snapshot of the <see cref="PropertyDataDto"/> as it was at version 8.0
/// </summary>
/// <remarks>
/// This is required during migrations the schema of this table changed and running SQL against the new table would result in errors
/// </remarks>
[TableName(TableName)]
[PrimaryKey("id")]
[ExplicitColumns]
internal class PropertyDataDto80
{
public const string TableName = Constants.DatabaseSchema.Tables.PropertyData;
public const int VarcharLength = 512;
public const int SegmentLength = 256;
private decimal? _decimalValue;
// pk, not used at the moment (never updating)
[Column("id")]
[PrimaryKeyColumn]
public int Id { get; set; }
[Column("versionId")]
[ForeignKey(typeof(ContentVersionDto))]
[Index(IndexTypes.UniqueNonClustered, Name = "IX_" + TableName + "_VersionId", ForColumns = "versionId,propertyTypeId,languageId,segment")]
public int VersionId { get; set; }
[Column("propertyTypeId")]
[ForeignKey(typeof(PropertyTypeDto80))]
[Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_PropertyTypeId")]
public int PropertyTypeId { get; set; }
[Column("languageId")]
[ForeignKey(typeof(LanguageDto))]
[Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_LanguageId")]
[NullSetting(NullSetting = NullSettings.Null)]
public int? LanguageId { get; set; }
[Column("segment")]
[Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_Segment")]
[NullSetting(NullSetting = NullSettings.Null)]
[Length(SegmentLength)]
public string Segment { get; set; }
[Column("intValue")]
[NullSetting(NullSetting = NullSettings.Null)]
public int? IntegerValue { get; set; }
[Column("decimalValue")]
[NullSetting(NullSetting = NullSettings.Null)]
public decimal? DecimalValue
{
get => _decimalValue;
set => _decimalValue = value?.Normalize();
}
[Column("dateValue")]
[NullSetting(NullSetting = NullSettings.Null)]
public DateTime? DateValue { get; set; }
[Column("varcharValue")]
[NullSetting(NullSetting = NullSettings.Null)]
[Length(VarcharLength)]
public string VarcharValue { get; set; }
[Column("textValue")]
[NullSetting(NullSetting = NullSettings.Null)]
[SpecialDbType(SpecialDbTypes.NTEXT)]
public string TextValue { get; set; }
[ResultColumn]
[Reference(ReferenceType.OneToOne, ColumnName = "PropertyTypeId")]
public PropertyTypeDto80 PropertyTypeDto { get; set; }
[Ignore]
public object Value
{
get
{
if (IntegerValue.HasValue)
return IntegerValue.Value;
if (DecimalValue.HasValue)
return DecimalValue.Value;
if (DateValue.HasValue)
return DateValue.Value;
if (!string.IsNullOrEmpty(VarcharValue))
return VarcharValue;
if (!string.IsNullOrEmpty(TextValue))
return TextValue;
return null;
}
}
public PropertyDataDto80 Clone(int versionId)
{
return new PropertyDataDto80
{
VersionId = versionId,
PropertyTypeId = PropertyTypeId,
LanguageId = LanguageId,
Segment = Segment,
IntegerValue = IntegerValue,
DecimalValue = DecimalValue,
DateValue = DateValue,
VarcharValue = VarcharValue,
TextValue = TextValue,
PropertyTypeDto = PropertyTypeDto
};
}
protected bool Equals(PropertyDataDto other)
{
return Id == other.Id;
}
public override bool Equals(object other)
{
return
!ReferenceEquals(null, other) // other is not null
&& (ReferenceEquals(this, other) // and either ref-equals, or same id
|| other is PropertyDataDto pdata && pdata.Id == Id);
}
public override int GetHashCode()
{
// ReSharper disable once NonReadonlyMemberInGetHashCode
return Id;
}
}
}
@@ -0,0 +1,80 @@
using NPoco;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Umbraco.Core.Persistence.DatabaseAnnotations;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Dtos;
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.Models
{
/// <summary>
/// Snapshot of the <see cref="PropertyTypeDto"/> as it was at version 8.0
/// </summary>
/// <remarks>
/// This is required during migrations before 8.6 since the schema has changed and running SQL against the new table would result in errors
/// </remarks>
[TableName(Constants.DatabaseSchema.Tables.PropertyType)]
[PrimaryKey("id")]
[ExplicitColumns]
internal class PropertyTypeDto80
{
[Column("id")]
[PrimaryKeyColumn(IdentitySeed = 50)]
public int Id { get; set; }
[Column("dataTypeId")]
[ForeignKey(typeof(DataTypeDto), Column = "nodeId")]
public int DataTypeId { get; set; }
[Column("contentTypeId")]
[ForeignKey(typeof(ContentTypeDto), Column = "nodeId")]
public int ContentTypeId { get; set; }
[Column("propertyTypeGroupId")]
[NullSetting(NullSetting = NullSettings.Null)]
[ForeignKey(typeof(PropertyTypeGroupDto))]
public int? PropertyTypeGroupId { get; set; }
[Index(IndexTypes.NonClustered, Name = "IX_cmsPropertyTypeAlias")]
[Column("Alias")]
public string Alias { get; set; }
[Column("Name")]
[NullSetting(NullSetting = NullSettings.Null)]
public string Name { get; set; }
[Column("sortOrder")]
[Constraint(Default = "0")]
public int SortOrder { get; set; }
[Column("mandatory")]
[Constraint(Default = "0")]
public bool Mandatory { get; set; }
[Column("validationRegExp")]
[NullSetting(NullSetting = NullSettings.Null)]
public string ValidationRegExp { get; set; }
[Column("Description")]
[NullSetting(NullSetting = NullSettings.Null)]
[Length(2000)]
public string Description { get; set; }
[Column("variations")]
[Constraint(Default = "1" /*ContentVariation.InvariantNeutral*/)]
public byte Variations { get; set; }
[ResultColumn]
[Reference(ReferenceType.OneToOne, ColumnName = "DataTypeId")]
public DataTypeDto DataTypeDto { get; set; }
[Column("UniqueID")]
[NullSetting(NullSetting = NullSettings.NotNull)]
[Constraint(Default = SystemMethods.NewGuid)]
[Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsPropertyTypeUniqueID")]
public Guid UniqueId { get; set; }
}
}
@@ -1,4 +1,5 @@
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Dtos;
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
{
@@ -17,14 +18,24 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
AddColumn<MediaVersionDto>("id", out var sqls);
// SQLCE does not support UPDATE...FROM
var temp2 = Database.Fetch<dynamic>($@"SELECT v.versionId, v.id
if (Database.DatabaseType.IsSqlCe())
{
// SQLCE does not support UPDATE...FROM
var versions = Database.Fetch<dynamic>($@"SELECT v.versionId, v.id
FROM cmsContentVersion v
JOIN umbracoNode n on v.contentId=n.id
WHERE n.nodeObjectType='{Constants.ObjectTypes.Media}'");
foreach (var t in temp2)
Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.MediaVersion} SET id={t.id} WHERE versionId='{t.versionId}'").Do();
foreach (var t in versions)
Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.MediaVersion} SET id={t.id} WHERE versionId='{t.versionId}'").Do();
}
else
{
Database.Execute($@"UPDATE {Constants.DatabaseSchema.Tables.MediaVersion} SET id=v.id
FROM {Constants.DatabaseSchema.Tables.MediaVersion} m
JOIN cmsContentVersion v on m.versionId = v.versionId
JOIN umbracoNode n on v.contentId=n.id
WHERE n.nodeObjectType='{Constants.ObjectTypes.Media}'");
}
foreach (var sql in sqls)
Execute.Sql(sql).Do();
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Umbraco.Core.Migrations.Install;
@@ -19,6 +20,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
public override void Migrate()
{
MigratePropertyData();
CreatePropertyDataIndexes();
MigrateContentAndPropertyTypes();
MigrateContent();
MigrateVersions();
@@ -74,10 +76,20 @@ HAVING COUNT(v2.id) <> 1").Any())
{
Alter.Table(PreTables.PropertyData).AddColumn("versionId2").AsInt32().Nullable().Do();
// SQLCE does not support UPDATE...FROM
var temp = Database.Fetch<dynamic>($"SELECT id, versionId FROM {PreTables.ContentVersion}");
foreach (var t in temp)
Database.Execute($"UPDATE {PreTables.PropertyData} SET versionId2=@v2 WHERE versionId=@v1", new { v1 = t.versionId, v2 = t.id });
if (Database.DatabaseType.IsSqlCe())
{
// SQLCE does not support UPDATE...FROM
var versions = Database.Fetch<dynamic>($"SELECT id, versionId FROM {PreTables.ContentVersion}");
foreach (var t in versions)
Database.Execute($"UPDATE {PreTables.PropertyData} SET versionId2=@v2 WHERE versionId=@v1", new { v1 = t.versionId, v2 = t.id });
}
else
{
Database.Execute($@"UPDATE {PreTables.PropertyData} SET versionId2={PreTables.ContentVersion}.id
FROM {PreTables.ContentVersion}
INNER JOIN {PreTables.PropertyData} ON {PreTables.ContentVersion}.versionId = {PreTables.PropertyData}.versionId");
}
Delete.Column("versionId").FromTable(PreTables.PropertyData).Do();
ReplaceColumn<PropertyDataDto>(PreTables.PropertyData, "versionId2", "versionId");
}
@@ -90,6 +102,22 @@ HAVING COUNT(v2.id) <> 1").Any())
Rename.Table(PreTables.PropertyData).To(Constants.DatabaseSchema.Tables.PropertyData).Do();
}
private void CreatePropertyDataIndexes()
{
// Creates a temporary index on umbracoPropertyData to speed up other migrations which update property values.
// It will be removed in CreateKeysAndIndexes before the normal indexes for the table are created
var tableDefinition = Persistence.DatabaseModelDefinitions.DefinitionFactory.GetTableDefinition(typeof(PropertyDataDto), SqlSyntax);
Execute.Sql(SqlSyntax.FormatPrimaryKey(tableDefinition)).Do();
Create.Index("IX_umbracoPropertyData_Temp").OnTable(PropertyDataDto.TableName)
.WithOptions().Unique()
.WithOptions().NonClustered()
.OnColumn("versionId").Ascending()
.OnColumn("propertyTypeId").Ascending()
.OnColumn("languageId").Ascending()
.OnColumn("segment").Ascending()
.Do();
}
private void MigrateContentAndPropertyTypes()
{
if (!ColumnExists(PreTables.ContentType, "variations"))
@@ -153,22 +181,40 @@ HAVING COUNT(v2.id) <> 1").Any())
ReplaceColumn<ContentVersionDto>(PreTables.ContentVersion, "ContentId", "nodeId");
// populate contentVersion text, current and userId columns for documents
// SQLCE does not support UPDATE...FROM
var temp1 = Database.Fetch<dynamic>($"SELECT versionId, text, newest, documentUser FROM {PreTables.Document}");
foreach (var t in temp1)
Database.Execute($@"UPDATE {PreTables.ContentVersion} SET text=@text, {SqlSyntax.GetQuotedColumnName("current")}=@current, userId=@userId WHERE versionId=@versionId",
new { text = t.text, current = t.newest, userId=t.documentUser, versionId=t.versionId });
if (Database.DatabaseType.IsSqlCe())
{
// SQLCE does not support UPDATE...FROM
var documents = Database.Fetch<dynamic>($"SELECT versionId, text, published, newest, documentUser FROM {PreTables.Document}");
foreach (var t in documents)
Database.Execute($@"UPDATE {PreTables.ContentVersion} SET text=@text, {SqlSyntax.GetQuotedColumnName("current")}=@current, userId=@userId WHERE versionId=@versionId",
new { text = t.text, current = t.newest && !t.published, userId = t.documentUser, versionId = t.versionId });
}
else
{
Database.Execute($@"UPDATE {PreTables.ContentVersion} SET text=d.text, {SqlSyntax.GetQuotedColumnName("current")}=(d.newest & ~d.published), userId=d.documentUser
FROM {PreTables.ContentVersion} v INNER JOIN {PreTables.Document} d ON d.versionId = v.versionId");
}
// populate contentVersion text and current columns for non-documents, userId is default
// SQLCE does not support UPDATE...FROM
var temp2 = Database.Fetch<dynamic>($@"SELECT cver.versionId, n.text
if (Database.DatabaseType.IsSqlCe())
{
// SQLCE does not support UPDATE...FROM
var otherContent = Database.Fetch<dynamic>($@"SELECT cver.versionId, n.text
FROM {PreTables.ContentVersion} cver
JOIN {SqlSyntax.GetQuotedTableName(Constants.DatabaseSchema.Tables.Node)} n ON cver.nodeId=n.id
WHERE cver.versionId NOT IN (SELECT versionId FROM {SqlSyntax.GetQuotedTableName(PreTables.Document)})");
foreach (var t in temp2)
Database.Execute($@"UPDATE {PreTables.ContentVersion} SET text=@text, {SqlSyntax.GetQuotedColumnName("current")}=1, userId=0 WHERE versionId=@versionId",
new { text = t.text, versionId=t.versionId });
foreach (var t in otherContent)
Database.Execute($@"UPDATE {PreTables.ContentVersion} SET text=@text, {SqlSyntax.GetQuotedColumnName("current")}=1, userId=0 WHERE versionId=@versionId",
new { text = t.text, versionId = t.versionId });
}
else
{
Database.Execute($@"UPDATE {PreTables.ContentVersion} SET text=n.text, {SqlSyntax.GetQuotedColumnName("current")}=1, userId=0
FROM {PreTables.ContentVersion} cver
JOIN {SqlSyntax.GetQuotedTableName(Constants.DatabaseSchema.Tables.Node)} n ON cver.nodeId=n.id
WHERE cver.versionId NOT IN (SELECT versionId FROM {SqlSyntax.GetQuotedTableName(PreTables.Document)})");
}
// create table
Create.Table<DocumentVersionDto>(withoutKeysAndIndexes: true).Do();
@@ -179,36 +225,42 @@ SELECT cver.id, doc.templateId, doc.published
FROM {SqlSyntax.GetQuotedTableName(PreTables.ContentVersion)} cver
JOIN {SqlSyntax.GetQuotedTableName(PreTables.Document)} doc ON doc.nodeId=cver.nodeId AND doc.versionId=cver.versionId");
// need to add extra rows for where published=newest
// 'cos INSERT above has inserted the 'published' document version
// and v8 always has a 'edited' document version too
var temp3 = Database.Fetch<dynamic>($@"SELECT doc.nodeId, doc.updateDate, doc.documentUser, doc.text, doc.templateId, cver.id versionId
Database.Execute($@"
INSERT INTO {SqlSyntax.GetQuotedTableName(PreTables.ContentVersion)} (nodeId, versionId, versionDate, userId, {SqlSyntax.GetQuotedColumnName("current")}, text)
SELECT doc.nodeId, NEWID(), doc.updateDate, doc.documentUser, 1, doc.text
FROM {SqlSyntax.GetQuotedTableName(PreTables.Document)} doc
JOIN {SqlSyntax.GetQuotedTableName(PreTables.ContentVersion)} cver ON doc.nodeId=cver.nodeId AND doc.versionId=cver.versionId
WHERE doc.newest=1 AND doc.published=1");
var getIdentity = "@@@@IDENTITY";
foreach (var t in temp3)
{
Database.Execute($@"INSERT INTO {SqlSyntax.GetQuotedTableName(PreTables.ContentVersion)} (nodeId, versionId, versionDate, userId, {SqlSyntax.GetQuotedColumnName("current")}, text)
VALUES (@nodeId, @versionId, @versionDate, @userId, 1, @text)", new { nodeId=t.nodeId, versionId=Guid.NewGuid(), versionDate=t.updateDate, userId=t.documentUser, text=t.text });
var id = Database.ExecuteScalar<int>("SELECT " + getIdentity);
Database.Execute($"UPDATE {SqlSyntax.GetQuotedTableName(PreTables.ContentVersion)} SET {SqlSyntax.GetQuotedColumnName("current")}=0 WHERE nodeId=@0 AND id<>@1", (int) t.nodeId, id);
Database.Execute($@"INSERT INTO {SqlSyntax.GetQuotedTableName(Constants.DatabaseSchema.Tables.DocumentVersion)} (id, templateId, published)
VALUES (@id, @templateId, 0)", new { id=id, templateId=t.templateId });
var versionId = (int) t.versionId;
var pdatas = Database.Fetch<PropertyDataDto>(Sql().Select<PropertyDataDto>().From<PropertyDataDto>().Where<PropertyDataDto>(x => x.VersionId == versionId));
foreach (var pdata in pdatas)
{
pdata.VersionId = id;
Database.Insert(pdata);
}
}
Database.Execute($@"
INSERT INTO {SqlSyntax.GetQuotedTableName(Constants.DatabaseSchema.Tables.DocumentVersion)} (id, templateId, published)
SELECT cverNew.id, doc.templateId, 0
FROM {SqlSyntax.GetQuotedTableName(PreTables.Document)} doc
JOIN {SqlSyntax.GetQuotedTableName(PreTables.ContentVersion)} cverNew ON doc.nodeId = cverNew.nodeId
WHERE doc.newest=1 AND doc.published=1 AND cverNew.{SqlSyntax.GetQuotedColumnName("current")} = 1");
Database.Execute($@"
INSERT INTO {SqlSyntax.GetQuotedTableName(PropertyDataDto.TableName)} (propertytypeid,languageId,segment,textValue,varcharValue,decimalValue,intValue,dateValue,versionId)
SELECT propertytypeid,languageId,segment,textValue,varcharValue,decimalValue,intValue,dateValue,cverNew.id
FROM {SqlSyntax.GetQuotedTableName(PreTables.Document)} doc
JOIN {SqlSyntax.GetQuotedTableName(PreTables.ContentVersion)} cver ON doc.nodeId=cver.nodeId AND doc.versionId=cver.versionId
JOIN {SqlSyntax.GetQuotedTableName(PreTables.ContentVersion)} cverNew ON doc.nodeId = cverNew.nodeId
JOIN {SqlSyntax.GetQuotedTableName(PropertyDataDto.TableName)} pd ON pd.versionId=cver.id
WHERE doc.newest=1 AND doc.published=1 AND cverNew.{SqlSyntax.GetQuotedColumnName("current")} = 1");
// reduce document to 1 row per content
Database.Execute($@"DELETE FROM {PreTables.Document}
WHERE versionId NOT IN (SELECT (versionId) FROM {PreTables.ContentVersion} WHERE {SqlSyntax.GetQuotedColumnName("current")} = 1) AND (published<>1 OR newest<>1)");
// ensure that documents with a published version are marked as published
Database.Execute($@"UPDATE {PreTables.Document} SET published=1 WHERE nodeId IN (
SELECT nodeId FROM {PreTables.ContentVersion} cv INNER JOIN {Constants.DatabaseSchema.Tables.DocumentVersion} dv ON dv.id = cv.id WHERE dv.published=1)");
// drop some document columns
Delete.Column("text").FromTable(PreTables.Document).Do();
Delete.Column("templateId").FromTable(PreTables.Document).Do();
@@ -223,7 +275,7 @@ WHERE versionId NOT IN (SELECT (versionId) FROM {PreTables.ContentVersion} WHERE
if (!ColumnExists(PreTables.Document, "edited"))
{
AddColumn<DocumentDto>(PreTables.Document, "edited", out var sqls);
Database.Execute($"UPDATE {SqlSyntax.GetQuotedTableName(PreTables.Document)} SET edited=0");
Database.Execute($"UPDATE {SqlSyntax.GetQuotedTableName(PreTables.Document)} SET edited=~published");
foreach (var sql in sqls) Database.Execute(sql);
}
@@ -240,11 +292,15 @@ JOIN {Constants.DatabaseSchema.Tables.PropertyData} v1 ON cv1.id=v1.versionId
JOIN {PreTables.ContentVersion} cv2 ON n.id=cv2.nodeId
JOIN {Constants.DatabaseSchema.Tables.DocumentVersion} dv ON cv2.id=dv.id AND dv.published=1
JOIN {Constants.DatabaseSchema.Tables.PropertyData} v2 ON cv2.id=v2.versionId
WHERE v1.propertyTypeId=v2.propertyTypeId AND v1.languageId=v2.languageId AND v1.segment=v2.segment");
WHERE v1.propertyTypeId=v2.propertyTypeId
AND (v1.languageId=v2.languageId OR (v1.languageId IS NULL AND v2.languageId IS NULL))
AND (v1.segment=v2.segment OR (v1.segment IS NULL AND v2.segment IS NULL))");
var updatedIds = new HashSet<int>();
foreach (var t in temp)
if (t.intValue1 != t.intValue2 || t.decimalValue1 != t.decimalValue2 || t.dateValue1 != t.dateValue2 || t.varcharValue1 != t.varcharValue2 || t.textValue1 != t.textValue2)
Database.Execute("UPDATE {SqlSyntax.GetQuotedTableName(PreTables.Document)} SET edited=1 WHERE nodeId=@nodeIdd", new { t.id });
if (updatedIds.Add((int)t.id))
Database.Execute($"UPDATE {SqlSyntax.GetQuotedTableName(PreTables.Document)} SET edited=1 WHERE nodeId=@nodeId", new { nodeId = t.id });
// drop more columns
Delete.Column("versionId").FromTable(PreTables.ContentVersion).Do();
@@ -5,6 +5,7 @@ using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Umbraco.Core.Migrations.PostMigrations;
using Umbraco.Core.Migrations.Upgrade.V_8_0_0.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Services;
@@ -27,15 +28,15 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
var sqlPropertyData = Sql()
.Select<PropertyDataDto>(r => r.Select(x => x.PropertyTypeDto, r1 => r1.Select(x => x.DataTypeDto)))
.From<PropertyDataDto>()
.InnerJoin<PropertyTypeDto>().On<PropertyDataDto, PropertyTypeDto>((left, right) => left.PropertyTypeId == right.Id)
.InnerJoin<DataTypeDto>().On<PropertyTypeDto, DataTypeDto>((left, right) => left.DataTypeId == right.NodeId)
.Select<PropertyDataDto80>(r => r.Select(x => x.PropertyTypeDto, r1 => r1.Select(x => x.DataTypeDto)))
.From<PropertyDataDto80>()
.InnerJoin<PropertyTypeDto80>().On<PropertyDataDto80, PropertyTypeDto80>((left, right) => left.PropertyTypeId == right.Id)
.InnerJoin<DataTypeDto>().On<PropertyTypeDto80, DataTypeDto>((left, right) => left.DataTypeId == right.NodeId)
.Where<DataTypeDto>(x =>
x.EditorAlias == Constants.PropertyEditors.Aliases.TinyMce ||
x.EditorAlias == Constants.PropertyEditors.Aliases.Grid);
var properties = Database.Fetch<PropertyDataDto>(sqlPropertyData);
var properties = Database.Fetch<PropertyDataDto80>(sqlPropertyData);
var exceptions = new List<Exception>();
foreach (var property in properties)
@@ -43,6 +44,8 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0
var value = property.TextValue;
if (string.IsNullOrWhiteSpace(value)) continue;
bool propertyChanged = false;
if (property.PropertyTypeDto.DataTypeDto.EditorAlias == Constants.PropertyEditors.Aliases.Grid)
{
try
@@ -55,7 +58,8 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0
var controlValue = control["value"];
if (controlValue?.Type == JTokenType.String)
{
control["value"] = UpdateMediaUrls(mediaLinkPattern, controlValue.Value<string>());
control["value"] = UpdateMediaUrls(mediaLinkPattern, controlValue.Value<string>(), out var controlChanged);
propertyChanged |= controlChanged;
}
}
@@ -76,10 +80,11 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0
}
else
{
property.TextValue = UpdateMediaUrls(mediaLinkPattern, value);
property.TextValue = UpdateMediaUrls(mediaLinkPattern, value, out propertyChanged);
}
Database.Update(property);
if (propertyChanged)
Database.Update(property);
}
@@ -91,10 +96,14 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0
Context.AddPostMigration<RebuildPublishedSnapshot>();
}
private string UpdateMediaUrls(Regex mediaLinkPattern, string value)
private string UpdateMediaUrls(Regex mediaLinkPattern, string value, out bool changed)
{
return mediaLinkPattern.Replace(value, match =>
bool matched = false;
var result = mediaLinkPattern.Replace(value, match =>
{
matched = true;
// match groups:
// - 1 = from the beginning of the a tag until href attribute value begins
// - 2 = the href attribute value excluding the querystring (if present)
@@ -106,6 +115,10 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0
? match.Value
: $"{match.Groups[1].Value}/{{localLink:{media.GetUdi()}}}{match.Groups[3].Value}";
});
changed = matched;
return result;
}
}
}
@@ -0,0 +1,16 @@
using Umbraco.Core.Persistence.Dtos;
namespace Umbraco.Core.Migrations.Upgrade.V_8_6_0
{
public class AddMainDomLock : MigrationBase
{
public AddMainDomLock(IMigrationContext context)
: base(context)
{ }
public override void Migrate()
{
Database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.MainDom, Name = "MainDom" });
}
}
}
@@ -0,0 +1,33 @@
using Umbraco.Core.Migrations.Install;
namespace Umbraco.Core.Migrations.Upgrade.V_8_6_0
{
/// <summary>
/// Ensures the new relation types are created
/// </summary>
public class AddNewRelationTypes : MigrationBase
{
public AddNewRelationTypes(IMigrationContext context)
: base(context)
{ }
public override void Migrate()
{
CreateRelation(
Constants.Conventions.RelationTypes.RelatedMediaAlias,
Constants.Conventions.RelationTypes.RelatedMediaName);
CreateRelation(
Constants.Conventions.RelationTypes.RelatedDocumentAlias,
Constants.Conventions.RelationTypes.RelatedDocumentName);
}
private void CreateRelation(string alias, string name)
{
var uniqueId = DatabaseDataCreator.CreateUniqueRelationTypeId(alias ,name); //this is the same as how it installs so everything is consistent
Insert.IntoTable(Constants.DatabaseSchema.Tables.RelationType)
.Row(new { typeUniqueId = uniqueId, dual = 0, name, alias })
.Do();
}
}
}
@@ -3,6 +3,7 @@ using Umbraco.Core.Persistence.Dtos;
namespace Umbraco.Core.Migrations.Upgrade.V_8_6_0
{
public class AddPropertyTypeValidationMessageColumns : MigrationBase
{
public AddPropertyTypeValidationMessageColumns(IMigrationContext context)
@@ -0,0 +1,33 @@
using Umbraco.Core.Persistence.Dtos;
namespace Umbraco.Core.Migrations.Upgrade.V_8_6_0
{
public class MissingContentVersionsIndexes : MigrationBase
{
private const string IndexName = "IX_" + ContentVersionDto.TableName + "_NodeId";
public MissingContentVersionsIndexes(IMigrationContext context) : base(context)
{
}
public override void Migrate()
{
// We must check before we create an index because if we are upgrading from v7 we force re-create all
// indexes in the whole DB and then this would throw
if (!IndexExists(IndexName))
{
Create
.Index(IndexName)
.OnTable(ContentVersionDto.TableName)
.OnColumn("nodeId")
.Ascending()
.OnColumn("current")
.Ascending()
.WithOptions().NonClustered()
.Do();
}
}
}
}
@@ -0,0 +1,38 @@
using Umbraco.Core.Persistence.Dtos;
namespace Umbraco.Core.Migrations.Upgrade.V_8_6_0
{
public class UpdateRelationTypeTable : MigrationBase
{
public UpdateRelationTypeTable(IMigrationContext context)
: base(context)
{ }
public override void Migrate()
{
Alter.Table(Constants.DatabaseSchema.Tables.RelationType).AlterColumn("parentObjectType").AsGuid().Nullable().Do();
Alter.Table(Constants.DatabaseSchema.Tables.RelationType).AlterColumn("childObjectType").AsGuid().Nullable().Do();
//TODO: We have to update this field to ensure it's not null, we can just copy across the name since that is not nullable
//drop index before we can alter the column
if (IndexExists("IX_umbracoRelationType_alias"))
Delete
.Index("IX_umbracoRelationType_alias")
.OnTable(Constants.DatabaseSchema.Tables.RelationType)
.Do();
//change the column to non nullable
Alter.Table(Constants.DatabaseSchema.Tables.RelationType).AlterColumn("alias").AsString(100).NotNullable().Do();
//re-create the index
Create
.Index("IX_umbracoRelationType_alias")
.OnTable(Constants.DatabaseSchema.Tables.RelationType)
.OnColumn("alias")
.Ascending()
.WithOptions().Unique().WithOptions().NonClustered()
.Do();
}
}
}
@@ -0,0 +1,33 @@
using Umbraco.Core.Persistence.Dtos;
namespace Umbraco.Core.Migrations.Upgrade.V_8_6_0
{
public class MissingDictionaryIndex : MigrationBase
{
public MissingDictionaryIndex(IMigrationContext context)
: base(context)
{
}
/// <summary>
/// Adds an index to the foreign key column <c>parent</c> on <c>DictionaryDto</c>'s table
/// if it doesn't already exist
/// </summary>
public override void Migrate()
{
var indexName = "IX_" + DictionaryDto.TableName + "_Parent";
if (!IndexExists(indexName))
{
Create
.Index(indexName)
.OnTable(DictionaryDto.TableName)
.OnColumn("parent")
.Ascending()
.WithOptions().NonClustered()
.Do();
}
}
}
}
@@ -1,5 +1,6 @@
namespace Umbraco.Core.Models.Editors
{
/// <summary>
/// Represents an uploaded file for a property.
/// </summary>
@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
namespace Umbraco.Core.Models.Editors
{
/// <summary>
/// Used to track reference to other entities in a property value
/// </summary>
public struct UmbracoEntityReference : IEquatable<UmbracoEntityReference>
{
private static readonly UmbracoEntityReference _empty = new UmbracoEntityReference(Udi.UnknownTypeUdi.Instance, string.Empty);
public UmbracoEntityReference(Udi udi, string relationTypeAlias)
{
Udi = udi ?? throw new ArgumentNullException(nameof(udi));
RelationTypeAlias = relationTypeAlias ?? throw new ArgumentNullException(nameof(relationTypeAlias));
}
public UmbracoEntityReference(Udi udi)
{
Udi = udi ?? throw new ArgumentNullException(nameof(udi));
switch (udi.EntityType)
{
case Constants.UdiEntityType.Media:
RelationTypeAlias = Constants.Conventions.RelationTypes.RelatedMediaAlias;
break;
default:
RelationTypeAlias = Constants.Conventions.RelationTypes.RelatedDocumentAlias;
break;
}
}
public static UmbracoEntityReference Empty() => _empty;
public static bool IsEmpty(UmbracoEntityReference reference) => reference == Empty();
public Udi Udi { get; }
public string RelationTypeAlias { get; }
public override bool Equals(object obj)
{
return obj is UmbracoEntityReference reference && Equals(reference);
}
public bool Equals(UmbracoEntityReference other)
{
return EqualityComparer<Udi>.Default.Equals(Udi, other.Udi) &&
RelationTypeAlias == other.RelationTypeAlias;
}
public override int GetHashCode()
{
var hashCode = -487348478;
hashCode = hashCode * -1521134295 + EqualityComparer<Udi>.Default.GetHashCode(Udi);
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(RelationTypeAlias);
return hashCode;
}
public static bool operator ==(UmbracoEntityReference left, UmbracoEntityReference right)
{
return left.Equals(right);
}
public static bool operator !=(UmbracoEntityReference left, UmbracoEntityReference right)
{
return !(left == right);
}
}
}
@@ -24,7 +24,7 @@
/// Sets the parent entity.
/// </summary>
/// <remarks>Use this method to set the parent entity when the parent entity is known, but has not
/// been persistent and does not yet have an identity. The parent identifier will we retrieved
/// been persistent and does not yet have an identity. The parent identifier will be retrieved
/// from the parent entity when needed. If the parent entity still does not have an entity by that
/// time, an exception will be thrown by <see cref="ParentId"/> getter.</remarks>
void SetParent(ITreeEntity parent);
@@ -53,4 +53,4 @@
/// </remarks>
bool Trashed { get; }
}
}
}
@@ -1,13 +1,6 @@
namespace Umbraco.Core.Models.Entities
{
public class MemberEntitySlim : EntitySlim, IMemberEntitySlim
public class MemberEntitySlim : ContentEntitySlim, IMemberEntitySlim
{
public string ContentTypeAlias { get; set; }
/// <inheritdoc />
public string ContentTypeIcon { get; set; }
/// <inheritdoc />
public string ContentTypeThumbnail { get; set; }
}
}
}
@@ -0,0 +1,7 @@
namespace Umbraco.Core.Models
{
public interface IImageUrlGenerator
{
string GetImageUrl(ImageUrlGenerationOptions options);
}
}
+8 -1
View File
@@ -1,4 +1,5 @@
using System.Runtime.Serialization;
using System;
using System.Runtime.Serialization;
using Umbraco.Core.Models.Entities;
namespace Umbraco.Core.Models
@@ -11,12 +12,18 @@ namespace Umbraco.Core.Models
[DataMember]
int ParentId { get; set; }
[DataMember]
Guid ParentObjectType { get; set; }
/// <summary>
/// Gets or sets the Child Id of the Relation (Destination)
/// </summary>
[DataMember]
int ChildId { get; set; }
[DataMember]
Guid ChildObjectType { get; set; }
/// <summary>
/// Gets or sets the <see cref="RelationType"/> for the Relation
/// </summary>
+2 -2
View File
@@ -29,13 +29,13 @@ namespace Umbraco.Core.Models
/// </summary>
/// <remarks>Corresponds to the NodeObjectType in the umbracoNode table</remarks>
[DataMember]
Guid ParentObjectType { get; set; }
Guid? ParentObjectType { get; set; }
/// <summary>
/// Gets or sets the Childs object type id
/// </summary>
/// <remarks>Corresponds to the NodeObjectType in the umbracoNode table</remarks>
[DataMember]
Guid ChildObjectType { get; set; }
Guid? ChildObjectType { get; set; }
}
}
@@ -0,0 +1,66 @@
namespace Umbraco.Core.Models
{
/// <summary>
/// These are options that are passed to the IImageUrlGenerator implementation to determine
/// the propery URL that is needed
/// </summary>
public class ImageUrlGenerationOptions
{
public ImageUrlGenerationOptions (string imageUrl)
{
ImageUrl = imageUrl;
}
public string ImageUrl { get; }
public int? Width { get; set; }
public int? Height { get; set; }
public decimal? WidthRatio { get; set; }
public decimal? HeightRatio { get; set; }
public int? Quality { get; set; }
public string ImageCropMode { get; set; }
public string ImageCropAnchor { get; set; }
public bool DefaultCrop { get; set; }
public FocalPointPosition FocalPoint { get; set; }
public CropCoordinates Crop { get; set; }
public string CacheBusterValue { get; set; }
public string FurtherOptions { get; set; }
public bool UpScale { get; set; } = true;
public string AnimationProcessMode { get; set; }
/// <summary>
/// The focal point position, in whatever units the registered IImageUrlGenerator uses,
/// typically a percentage of the total image from 0.0 to 1.0.
/// </summary>
public class FocalPointPosition
{
public FocalPointPosition (decimal top, decimal left)
{
Left = left;
Top = top;
}
public decimal Left { get; }
public decimal Top { get; }
}
/// <summary>
/// The bounds of the crop within the original image, in whatever units the registered
/// IImageUrlGenerator uses, typically a percentage between 0 and 100.
/// </summary>
public class CropCoordinates
{
public CropCoordinates (decimal x1, decimal y1, decimal x2, decimal y2)
{
X1 = x1;
Y1 = y1;
X2 = x2;
Y2 = y2;
}
public decimal X1 { get; }
public decimal Y1 { get; }
public decimal X2 { get; }
public decimal Y2 { get; }
}
}
}
+8 -27
View File
@@ -1,10 +1,8 @@
using System;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Linq;
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Logging;
using Umbraco.Core.PropertyEditors.ValueConverters;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Core.Models
{
@@ -18,29 +16,12 @@ namespace Umbraco.Core.Models
if (!media.Properties.TryGetValue(propertyAlias, out var property))
return string.Empty;
// TODO: would need to be adjusted to variations, when media become variants
if (!(property.GetValue() is string jsonString))
return string.Empty;
if (property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.UploadField)
return jsonString;
if (property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.ImageCropper)
if (Current.PropertyEditors.TryGet(property.PropertyType.PropertyEditorAlias, out var editor)
&& editor is IDataEditorWithMediaPath dataEditor)
{
if (jsonString.DetectIsJson() == false)
return jsonString;
try
{
var json = JsonConvert.DeserializeObject<JObject>(jsonString);
if (json["src"] != null)
return json["src"].Value<string>();
}
catch (Exception ex)
{
logger.Error<ImageCropperValueConverter>(ex, "Could not parse the string '{JsonString}' to a json object", jsonString);
return string.Empty;
}
// TODO: would need to be adjusted to variations, when media become variants
var value = property.GetValue();
return dataEditor.GetMediaPath(value);
}
// Without knowing what it is, just adding a string here might not be very nice
@@ -0,0 +1,10 @@
namespace Umbraco.Core.Models
{
internal static class MediaTypeExtensions
{
internal static bool IsSystemMediaType(this IMediaType mediaType) =>
mediaType.Alias == Constants.Conventions.MediaTypes.File
|| mediaType.Alias == Constants.Conventions.MediaTypes.Folder
|| mediaType.Alias == Constants.Conventions.MediaTypes.Image;
}
}
+1 -1
View File
@@ -41,7 +41,7 @@ namespace Umbraco.Core.Models
/// </summary>
public static UmbracoObjectTypes GetUmbracoObjectType(string name)
{
return (UmbracoObjectTypes) Enum.Parse(typeof (UmbracoObjectTypes), name, false);
return (UmbracoObjectTypes) Enum.Parse(typeof (UmbracoObjectTypes), name, true);
}
#region Guid object type utilities
@@ -63,6 +63,7 @@ namespace Umbraco.Core.Models.PublishedContent
/// <summary>
/// Gets the name of the user who created the content item.
/// </summary>
[Obsolete("Use CreatorName(IUserService) extension instead")]
string CreatorName { get; }
/// <summary>
@@ -78,6 +79,7 @@ namespace Umbraco.Core.Models.PublishedContent
/// <summary>
/// Gets the name of the user who last updated the content item.
/// </summary>
[Obsolete("Use WriterName(IUserService) extension instead")]
string WriterName { get; }
/// <summary>
@@ -97,6 +99,7 @@ namespace Umbraco.Core.Models.PublishedContent
/// <para>The value of this property is contextual. It depends on the 'current' request uri,
/// if any.</para>
/// </remarks>
[Obsolete("Use the Url() extension instead")]
string Url { get; }
/// <summary>
@@ -94,6 +94,7 @@ namespace Umbraco.Core.Models.PublishedContent
public virtual DateTime UpdateDate => _content.UpdateDate;
/// <inheritdoc />
[Obsolete("Use the Url() extension instead")]
public virtual string Url => _content.Url;
/// <inheritdoc />
+30 -1
View File
@@ -17,13 +17,36 @@ namespace Umbraco.Core.Models
private IRelationType _relationType;
private string _comment;
/// <summary>
/// Constructor for constructing the entity to be created
/// </summary>
/// <param name="parentId"></param>
/// <param name="childId"></param>
/// <param name="relationType"></param>
public Relation(int parentId, int childId, IRelationType relationType)
{
_parentId = parentId;
_childId = childId;
_relationType = relationType;
}
/// <summary>
/// Constructor for reconstructing the entity from the data source
/// </summary>
/// <param name="parentId"></param>
/// <param name="childId"></param>
/// <param name="parentObjectType"></param>
/// <param name="childObjectType"></param>
/// <param name="relationType"></param>
public Relation(int parentId, int childId, Guid parentObjectType, Guid childObjectType, IRelationType relationType)
{
_parentId = parentId;
_childId = childId;
_relationType = relationType;
ParentObjectType = parentObjectType;
ChildObjectType = childObjectType;
}
/// <summary>
/// Gets or sets the Parent Id of the Relation (Source)
@@ -35,6 +58,9 @@ namespace Umbraco.Core.Models
set => SetPropertyValueAndDetectChanges(value, ref _parentId, nameof(ParentId));
}
[DataMember]
public Guid ParentObjectType { get; set; }
/// <summary>
/// Gets or sets the Child Id of the Relation (Destination)
/// </summary>
@@ -45,6 +71,9 @@ namespace Umbraco.Core.Models
set => SetPropertyValueAndDetectChanges(value, ref _childId, nameof(ChildId));
}
[DataMember]
public Guid ChildObjectType { get; set; }
/// <summary>
/// Gets or sets the <see cref="RelationType"/> for the Relation
/// </summary>
+15 -17
View File
@@ -14,28 +14,26 @@ namespace Umbraco.Core.Models
private string _name;
private string _alias;
private bool _isBidrectional;
private Guid _parentObjectType;
private Guid _childObjectType;
private Guid? _parentObjectType;
private Guid? _childObjectType;
public RelationType(Guid childObjectType, Guid parentObjectType, string alias)
//TODO: Should we put back the broken ctors with obsolete attributes?
public RelationType(string alias, string name)
: this(name, alias, false, null, null)
{
if (alias == null) throw new ArgumentNullException(nameof(alias));
if (string.IsNullOrWhiteSpace(alias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(alias));
}
_childObjectType = childObjectType;
_parentObjectType = parentObjectType;
public RelationType(string name, string alias, bool isBidrectional, Guid? parentObjectType, Guid? childObjectType)
{
_name = name;
_alias = alias;
Name = _alias;
_isBidrectional = isBidrectional;
_parentObjectType = parentObjectType;
_childObjectType = childObjectType;
}
public RelationType(Guid childObjectType, Guid parentObjectType, string alias, string name)
: this(childObjectType, parentObjectType, alias)
{
if (name == null) throw new ArgumentNullException(nameof(name));
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name));
Name = name;
}
/// <summary>
/// Gets or sets the Name of the RelationType
@@ -72,7 +70,7 @@ namespace Umbraco.Core.Models
/// </summary>
/// <remarks>Corresponds to the NodeObjectType in the umbracoNode table</remarks>
[DataMember]
public Guid ParentObjectType
public Guid? ParentObjectType
{
get => _parentObjectType;
set => SetPropertyValueAndDetectChanges(value, ref _parentObjectType, nameof(ParentObjectType));
@@ -83,7 +81,7 @@ namespace Umbraco.Core.Models
/// </summary>
/// <remarks>Corresponds to the NodeObjectType in the umbracoNode table</remarks>
[DataMember]
public Guid ChildObjectType
public Guid? ChildObjectType
{
get => _childObjectType;
set => SetPropertyValueAndDetectChanges(value, ref _childObjectType, nameof(ChildObjectType));
+6 -5
View File
@@ -106,13 +106,14 @@ namespace Umbraco.Core.Models
//use the custom avatar
var avatarUrl = Current.MediaFileSystem.GetUrl(user.Avatar);
var urlGenerator = Current.ImageUrlGenerator;
return new[]
{
avatarUrl + "?width=30&height=30&mode=crop",
avatarUrl + "?width=60&height=60&mode=crop",
avatarUrl + "?width=90&height=90&mode=crop",
avatarUrl + "?width=150&height=150&mode=crop",
avatarUrl + "?width=300&height=300&mode=crop"
urlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { ImageCropMode = "crop", Width = 30, Height = 30 }),
urlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { ImageCropMode = "crop", Width = 60, Height = 60 }),
urlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { ImageCropMode = "crop", Width = 90, Height = 90 }),
urlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { ImageCropMode = "crop", Width = 150, Height = 150 }),
urlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { ImageCropMode = "crop", Width = 300, Height = 300 })
};
}
@@ -7,7 +7,7 @@ using Umbraco.Core.Services;
namespace Umbraco.Core.Packaging
{
internal class ConflictingPackageData
internal class ConflictingPackageData
{
private readonly IMacroService _macroService;
private readonly IFileService _fileService;
@@ -23,7 +23,7 @@ namespace Umbraco.Core.Packaging
return stylesheetNodes
.Select(n =>
{
var xElement = n.Element("Name") ?? n.Element("name"); ;
var xElement = n.Element("Name") ?? n.Element("name");
if (xElement == null)
throw new FormatException("Missing \"Name\" element");
@@ -82,7 +82,7 @@ namespace Umbraco.Core.Packaging
{
var packagesXml = EnsureStorage(out _);
if (packagesXml?.Root == null)
yield break;;
yield break;
foreach (var packageXml in packagesXml.Root.Elements("package"))
yield return _parser.ToPackageDefinition(packageXml);
@@ -139,7 +139,7 @@ namespace Umbraco.Core.Packaging
var updatedXml = _parser.ToXml(definition);
packageXml.ReplaceWith(updatedXml);
}
packagesXml.Save(packagesFile);
return true;
@@ -212,7 +212,7 @@ namespace Umbraco.Core.Packaging
compiledPackageXml.Save(packageXmlFileName);
// check if there's a packages directory below media
if (Directory.Exists(IOHelper.MapPath(_mediaFolderPath)) == false)
Directory.CreateDirectory(IOHelper.MapPath(_mediaFolderPath));
@@ -510,7 +510,6 @@ namespace Umbraco.Core.Packaging
private XElement GetStylesheetXml(string name, bool includeProperties)
{
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(name));
;
var sts = _fileService.GetStylesheetByName(name);
if (sts == null) return null;
var stylesheetXml = new XElement("Stylesheet");
@@ -562,7 +561,7 @@ namespace Umbraco.Core.Packaging
package.Add(new XElement("url", definition.Url));
var requirements = new XElement("requirements");
requirements.Add(new XElement("major", definition.UmbracoVersion == null ? UmbracoVersion.SemanticVersion.Major.ToInvariantString() : definition.UmbracoVersion.Major.ToInvariantString()));
requirements.Add(new XElement("minor", definition.UmbracoVersion == null ? UmbracoVersion.SemanticVersion.Minor.ToInvariantString() : definition.UmbracoVersion.Minor.ToInvariantString()));
requirements.Add(new XElement("patch", definition.UmbracoVersion == null ? UmbracoVersion.SemanticVersion.Patch.ToInvariantString() : definition.UmbracoVersion.Build.ToInvariantString()));
@@ -589,7 +588,7 @@ namespace Umbraco.Core.Packaging
contributors.Add(new XElement("contributor", contributor));
}
}
info.Add(contributors);
info.Add(new XElement("readme", new XCData(definition.Readme)));
@@ -8,6 +8,11 @@ namespace Umbraco.Core
/// </summary>
public static class Locks
{
/// <summary>
/// The <see cref="IMainDom"/> lock
/// </summary>
public const int MainDom = -1000;
/// <summary>
/// All servers.
/// </summary>
@@ -19,6 +19,7 @@ namespace Umbraco.Core.Persistence.Dtos
[Column("nodeId")]
[ForeignKey(typeof(ContentDto))]
[Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_NodeId", ForColumns = "nodeId,current")]
public int NodeId { get; set; }
[Column("versionDate")] // TODO: db rename to 'updateDate'
@@ -30,7 +31,6 @@ namespace Umbraco.Core.Persistence.Dtos
[NullSetting(NullSetting = NullSettings.Null)]
public int? UserId { get => _userId == 0 ? null : _userId; set => _userId = value; } //return null if zero
// TODO: we need an index on this it is used almost always in querying and sorting
[Column("current")]
public bool Current { get; set; }
@@ -5,11 +5,12 @@ using Umbraco.Core.Persistence.DatabaseAnnotations;
namespace Umbraco.Core.Persistence.Dtos
{
[TableName(Constants.DatabaseSchema.Tables.DictionaryEntry)]
[TableName(TableName)]
[PrimaryKey("pk")]
[ExplicitColumns]
internal class DictionaryDto
{
public const string TableName = Constants.DatabaseSchema.Tables.DictionaryEntry;
[Column("pk")]
[PrimaryKeyColumn]
public int PrimaryKey { get; set; }
@@ -21,6 +22,7 @@ namespace Umbraco.Core.Persistence.Dtos
[Column("parent")]
[NullSetting(NullSetting = NullSettings.Null)]
[ForeignKey(typeof(DictionaryDto), Column = "id")]
[Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_Parent")]
public Guid? Parent { get; set; }
[Column("key")]
@@ -34,5 +34,13 @@ namespace Umbraco.Core.Persistence.Dtos
[Column("comment")]
[Length(1000)]
public string Comment { get; set; }
[ResultColumn]
[Column("parentObjectType")]
public Guid ParentObjectType { get; set; }
[ResultColumn]
[Column("childObjectType")]
public Guid ChildObjectType { get; set; }
}
}
@@ -9,7 +9,7 @@ namespace Umbraco.Core.Persistence.Dtos
[ExplicitColumns]
internal class RelationTypeDto
{
public const int NodeIdSeed = 4;
public const int NodeIdSeed = 10;
[Column("id")]
[PrimaryKeyColumn(IdentitySeed = NodeIdSeed)]
@@ -23,17 +23,20 @@ namespace Umbraco.Core.Persistence.Dtos
public bool Dual { get; set; }
[Column("parentObjectType")]
public Guid ParentObjectType { get; set; }
[NullSetting(NullSetting = NullSettings.Null)]
public Guid? ParentObjectType { get; set; }
[Column("childObjectType")]
public Guid ChildObjectType { get; set; }
[NullSetting(NullSetting = NullSettings.Null)]
public Guid? ChildObjectType { get; set; }
[Column("name")]
[NullSetting(NullSetting = NullSettings.NotNull)]
[Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelationType_name")]
public string Name { get; set; }
[Column("alias")]
[NullSetting(NullSetting = NullSettings.Null)]
[NullSetting(NullSetting = NullSettings.NotNull)]
[Length(100)]
[Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelationType_alias")]
public string Alias { get; set; }
@@ -1,17 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Core.Persistence.Factories
{
internal class ContentBaseFactory
{
private static readonly Regex MediaPathPattern = new Regex(@"(/media/.+?)(?:['""]|$)", RegexOptions.Compiled);
/// <summary>
/// Builds an IContent item from a dto and content type.
/// </summary>
@@ -189,7 +187,7 @@ namespace Umbraco.Core.Persistence.Factories
/// <summary>
/// Builds a dto from an IMedia item.
/// </summary>
public static MediaDto BuildDto(IMedia entity)
public static MediaDto BuildDto(PropertyEditorCollection propertyEditors, IMedia entity)
{
var contentDto = BuildContentDto(entity, Constants.ObjectTypes.Media);
@@ -197,7 +195,7 @@ namespace Umbraco.Core.Persistence.Factories
{
NodeId = entity.Id,
ContentDto = contentDto,
MediaVersionDto = BuildMediaVersionDto(entity, contentDto)
MediaVersionDto = BuildMediaVersionDto(propertyEditors, entity, contentDto)
};
return dto;
@@ -291,12 +289,20 @@ namespace Umbraco.Core.Persistence.Factories
return dto;
}
private static MediaVersionDto BuildMediaVersionDto(IMedia entity, ContentDto contentDto)
private static MediaVersionDto BuildMediaVersionDto(PropertyEditorCollection propertyEditors, IMedia entity, ContentDto contentDto)
{
// try to get a path from the string being stored for media
// TODO: only considering umbracoFile
TryMatch(entity.GetValue<string>("umbracoFile"), out var path);
string path = null;
if (entity.Properties.TryGetValue(Constants.Conventions.Media.File, out var property)
&& propertyEditors.TryGet(property.PropertyType.PropertyEditorAlias, out var editor)
&& editor is IDataEditorWithMediaPath dataEditor)
{
var value = property.GetValue();
path = dataEditor.GetMediaPath(value);
}
var dto = new MediaVersionDto
{
@@ -308,22 +314,5 @@ namespace Umbraco.Core.Persistence.Factories
return dto;
}
// TODO: this should NOT be here?!
// more dark magic ;-(
internal static bool TryMatch(string text, out string path)
{
// In v8 we should allow exposing this via the property editor in a much nicer way so that the property editor
// can tell us directly what any URL is for a given property if it contains an asset
path = null;
if (string.IsNullOrWhiteSpace(text)) return false;
var m = MediaPathPattern.Match(text);
if (!m.Success || m.Groups.Count != 2) return false;
path = m.Groups[1].Value;
return true;
}
}
}
@@ -3,20 +3,11 @@ using Umbraco.Core.Persistence.Dtos;
namespace Umbraco.Core.Persistence.Factories
{
internal class RelationFactory
internal static class RelationFactory
{
private readonly IRelationType _relationType;
public RelationFactory(IRelationType relationType)
public static IRelation BuildEntity(RelationDto dto, IRelationType relationType)
{
_relationType = relationType;
}
#region Implementation of IEntityFactory<Relation,RelationDto>
public IRelation BuildEntity(RelationDto dto)
{
var entity = new Relation(dto.ParentId, dto.ChildId, _relationType);
var entity = new Relation(dto.ParentId, dto.ChildId, dto.ParentObjectType, dto.ChildObjectType, relationType);
try
{
@@ -37,7 +28,7 @@ namespace Umbraco.Core.Persistence.Factories
}
}
public RelationDto BuildDto(IRelation entity)
public static RelationDto BuildDto(IRelation entity)
{
var dto = new RelationDto
{
@@ -54,6 +45,5 @@ namespace Umbraco.Core.Persistence.Factories
return dto;
}
#endregion
}
}
@@ -9,7 +9,7 @@ namespace Umbraco.Core.Persistence.Factories
public static IRelationType BuildEntity(RelationTypeDto dto)
{
var entity = new RelationType(dto.ChildObjectType, dto.ParentObjectType, dto.Alias);
var entity = new RelationType(dto.Name, dto.Alias, dto.Dual, dto.ChildObjectType, dto.ParentObjectType);
try
{
@@ -17,8 +17,6 @@ namespace Umbraco.Core.Persistence.Factories
entity.Id = dto.Id;
entity.Key = dto.UniqueId;
entity.IsBidirectional = dto.Dual;
entity.Name = dto.Name;
// reset dirty initial properties (U4-1946)
entity.ResetDirtyProperties(false);
@@ -14,7 +14,21 @@ namespace Umbraco.Core.Persistence
/// </summary>
public static partial class NPocoDatabaseExtensions
{
// TODO: review NPoco native InsertBulk to replace the code below
/// <summary>
/// Configures NPoco's SqlBulkCopyHelper to use the correct SqlConnection and SqlTransaction instances from the underlying RetryDbConnection and ProfiledDbTransaction
/// </summary>
/// <remarks>
/// This is required to use NPoco's own <see cref="Database.InsertBulk{T}(IEnumerable{T})" /> method because we use wrapped DbConnection and DbTransaction instances.
/// NPoco's InsertBulk method only caters for efficient bulk inserting records for Sql Server, it does not cater for bulk inserting of records for
/// any other database type and in which case will just insert records one at a time.
/// NPoco's InsertBulk method also deals with updating the passed in entity's PK/ID once it's inserted whereas our own BulkInsertRecords methods
/// do not handle this scenario.
/// </remarks>
public static void ConfigureNPocoBulkExtensions()
{
SqlBulkCopyHelper.SqlConnectionResolver = dbConn => GetTypedConnection<SqlConnection>(dbConn);
SqlBulkCopyHelper.SqlTransactionResolver = dbTran => GetTypedTransaction<SqlTransaction>(dbTran);
}
/// <summary>
/// Bulk-inserts records within a transaction.
@@ -235,7 +249,7 @@ namespace Umbraco.Core.Persistence
//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
//to the order in which they are declared in the model then this will not work, so instead we will add column mappings by name so that this explicitly uses
//the names instead of their ordering.
foreach(var col in bulkReader.ColumnMappings)
foreach (var col in bulkReader.ColumnMappings)
{
copy.ColumnMappings.Add(col.DestinationColumn, col.DestinationColumn);
}
@@ -26,5 +26,10 @@ namespace Umbraco.Core.Persistence.Repositories
/// <param name="contentPath"></param>
/// <returns></returns>
bool HasContainerInPath(string contentPath);
/// <summary>
/// Returns true or false depending on whether content nodes have been created based on the provided content type id.
/// </summary>
bool HasContentNodes(int id);
}
}
@@ -1,4 +1,5 @@
using System;
using NPoco;
using System;
using System.Collections.Generic;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
@@ -15,10 +16,22 @@ namespace Umbraco.Core.Persistence.Repositories
IEntitySlim Get(int id, Guid objectTypeId);
IEntitySlim Get(Guid key, Guid objectTypeId);
IEnumerable<IEntitySlim> GetAll(Guid objectType, params int[] ids);
IEnumerable<IEntitySlim> GetAll(Guid objectType, params int[] ids);
IEnumerable<IEntitySlim> GetAll(Guid objectType, params Guid[] keys);
/// <summary>
/// Gets entities for a query
/// </summary>
/// <param name="query"></param>
/// <returns></returns>
IEnumerable<IEntitySlim> GetByQuery(IQuery<IUmbracoEntity> query);
/// <summary>
/// Gets entities for a query and a specific object type allowing the query to be slightly more optimized
/// </summary>
/// <param name="query"></param>
/// <param name="objectType"></param>
/// <returns></returns>
IEnumerable<IEntitySlim> GetByQuery(IQuery<IUmbracoEntity> query, Guid objectType);
UmbracoObjectTypes GetObjectType(int id);
@@ -30,7 +43,41 @@ namespace Umbraco.Core.Persistence.Repositories
bool Exists(int id);
bool Exists(Guid key);
/// <summary>
/// Gets paged entities for a query and a subset of object types
/// </summary>
/// <param name="query"></param>
/// <param name="objectTypes"></param>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <param name="totalRecords"></param>
/// <param name="filter"></param>
/// <param name="ordering"></param>
/// <param name="sqlCustomization">
/// A callback providing the ability to customize the generated SQL used to retrieve entities
/// </param>
/// <returns>
/// A collection of mixed entity types which would be of type <see cref="IEntitySlim"/>, <see cref="IDocumentEntitySlim"/>, <see cref="IMediaEntitySlim"/>,
/// <see cref="IMemberEntitySlim"/>
/// </returns>
IEnumerable<IEntitySlim> GetPagedResultsByQuery(
IQuery<IUmbracoEntity> query, Guid[] objectTypes, long pageIndex, int pageSize, out long totalRecords,
IQuery<IUmbracoEntity> filter, Ordering ordering, Action<Sql<ISqlContext>> sqlCustomization = null);
/// <summary>
/// Gets paged entities for a query and a specific object type
/// </summary>
/// <param name="query"></param>
/// <param name="objectType"></param>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <param name="totalRecords"></param>
/// <param name="filter"></param>
/// <param name="ordering"></param>
/// <returns></returns>
IEnumerable<IEntitySlim> GetPagedResultsByQuery(IQuery<IUmbracoEntity> query, Guid objectType, long pageIndex, int pageSize, out long totalRecords,
IQuery<IUmbracoEntity> filter, Ordering ordering);
}
}
@@ -1,9 +1,33 @@
using Umbraco.Core.Models;
using System;
using System.Collections.Generic;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Services;
namespace Umbraco.Core.Persistence.Repositories
{
public interface IRelationRepository : IReadWriteQueryRepository<int, IRelation>
{
IEnumerable<IRelation> GetPagedRelationsByQuery(IQuery<IRelation> query, long pageIndex, int pageSize, out long totalRecords, Ordering ordering);
/// <summary>
/// Persist multiple <see cref="IRelation"/> at once
/// </summary>
/// <param name="relations"></param>
void Save(IEnumerable<IRelation> relations);
/// <summary>
/// Deletes all relations for a parent for any specified relation type alias
/// </summary>
/// <param name="parentId"></param>
/// <param name="relationTypeAliases">
/// A list of relation types to match for deletion, if none are specified then all relations for this parent id are deleted
/// </param>
void DeleteByParent(int parentId, params string[] relationTypeAliases);
IEnumerable<IUmbracoEntity> GetPagedParentEntitiesByChildId(int childId, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes);
IEnumerable<IUmbracoEntity> GetPagedChildEntitiesByParentId(int parentId, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes);
}
}
@@ -77,7 +77,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
public IEnumerable<IAuditItem> Get(AuditType type, IQuery<IAuditItem> query)
{
var sqlClause = GetBaseQuery(false)
.Where<LogDto>(x => x.Header == type.ToString());
.Where("(logHeader=@0)", type.ToString());
var translator = new SqlTranslator<IAuditItem>(sqlClause, query);
var sql = translator.Translate();
@@ -9,6 +9,7 @@ using Umbraco.Core.Composing;
using Umbraco.Core.Events;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Editors;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Dtos;
@@ -24,26 +25,48 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
internal sealed class ContentRepositoryBase
{
/// <summary>
///
/// This is used for unit tests ONLY
/// </summary>
public static bool ThrowOnWarning = false;
}
internal abstract class ContentRepositoryBase<TId, TEntity, TRepository> : NPocoRepositoryBase<TId, TEntity>, IContentRepository<TId, TEntity>
where TEntity : class, IUmbracoEntity
where TEntity : class, IContentBase
where TRepository : class, IRepository
{
protected ContentRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILanguageRepository languageRepository, ILogger logger)
private readonly Lazy<PropertyEditorCollection> _propertyEditors;
private readonly DataValueReferenceFactoryCollection _dataValueReferenceFactories;
/// <summary>
///
/// </summary>
/// <param name="scopeAccessor"></param>
/// <param name="cache"></param>
/// <param name="logger"></param>
/// <param name="languageRepository"></param>
/// <param name="propertyEditors">
/// Lazy property value collection - must be lazy because we have a circular dependency since some property editors require services, yet these services require property editors
/// </param>
protected ContentRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger,
ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository,
Lazy<PropertyEditorCollection> propertyEditors, DataValueReferenceFactoryCollection dataValueReferenceFactories)
: base(scopeAccessor, cache, logger)
{
LanguageRepository = languageRepository;
RelationRepository = relationRepository;
RelationTypeRepository = relationTypeRepository;
_propertyEditors = propertyEditors;
_dataValueReferenceFactories = dataValueReferenceFactories;
}
protected abstract TRepository This { get; }
protected ILanguageRepository LanguageRepository { get; }
protected IRelationRepository RelationRepository { get; }
protected IRelationTypeRepository RelationTypeRepository { get; }
protected PropertyEditorCollection PropertyEditors => Current.PropertyEditors; // TODO: inject
protected PropertyEditorCollection PropertyEditors => _propertyEditors.Value;
#region Versions
@@ -797,5 +820,56 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
}
#endregion
protected void PersistRelations(TEntity entity)
{
// Get all references from our core built in DataEditors/Property Editors
// Along with seeing if deverlopers want to collect additional references from the DataValueReferenceFactories collection
var trackedRelations = new List<UmbracoEntityReference>();
trackedRelations.AddRange(_dataValueReferenceFactories.GetAllReferences(entity.Properties, PropertyEditors));
//First delete all auto-relations for this entity
RelationRepository.DeleteByParent(entity.Id, Constants.Conventions.RelationTypes.AutomaticRelationTypes);
if (trackedRelations.Count == 0) return;
trackedRelations = trackedRelations.Distinct().ToList();
var udiToGuids = trackedRelations.Select(x => x.Udi as GuidUdi)
.ToDictionary(x => (Udi)x, x => x.Guid);
//lookup in the DB all INT ids for the GUIDs and chuck into a dictionary
var keyToIds = Database.Fetch<NodeIdKey>(Sql().Select<NodeDto>(x => x.NodeId, x => x.UniqueId).From<NodeDto>().WhereIn<NodeDto>(x => x.UniqueId, udiToGuids.Values))
.ToDictionary(x => x.UniqueId, x => x.NodeId);
var allRelationTypes = RelationTypeRepository.GetMany(Array.Empty<int>())
.ToDictionary(x => x.Alias, x => x);
var toSave = trackedRelations.Select(rel =>
{
if (!allRelationTypes.TryGetValue(rel.RelationTypeAlias, out var relationType))
throw new InvalidOperationException($"The relation type {rel.RelationTypeAlias} does not exist");
if (!udiToGuids.TryGetValue(rel.Udi, out var guid))
return null; // This shouldn't happen!
if (!keyToIds.TryGetValue(guid, out var id))
return null; // This shouldn't happen!
return new Relation(entity.Id, id, relationType);
}).WhereNotNull();
// Save bulk relations
RelationRepository.Save(toSave);
}
private class NodeIdKey
{
[Column("id")]
public int NodeId { get; set; }
[Column("uniqueId")]
public Guid UniqueId { get; set; }
}
}
}
@@ -16,7 +16,6 @@ using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics;
namespace Umbraco.Core.Persistence.Repositories.Implement
{
@@ -506,7 +505,7 @@ AND umbracoNode.id <> @id",
/// <summary>
/// Corrects the property type variations for the given entity
/// to make sure the property type variation is compatible with the
/// variation set on the entity itself.
/// variation set on the entity itself.
/// </summary>
/// <param name="entity">Entity to correct properties for</param>
private void CorrectPropertyTypeVariations(IContentTypeComposition entity)
@@ -754,7 +753,7 @@ AND umbracoNode.id <> @id",
//we don't need to move the names! this is because we always keep the invariant names with the name of the default language.
//however, if we were to move names, we could do this: BUT this doesn't work with SQLCE, for that we'd have to update row by row :(
// if we want these SQL statements back, look into GIT history
// if we want these SQL statements back, look into GIT history
}
}
@@ -1033,7 +1032,7 @@ AND umbracoNode.id <> @id",
//keep track of this node/lang to mark or unmark a culture as edited
var editedLanguageVersions = new Dictionary<(int nodeId, int? langId), bool>();
//keep track of which node to mark or unmark as edited
//keep track of which node to mark or unmark as edited
var editedDocument = new Dictionary<int, bool>();
var nodeId = -1;
var propertyTypeId = -1;
@@ -1324,6 +1323,17 @@ WHERE {Constants.DatabaseSchema.Tables.Content}.nodeId IN (@ids) AND cmsContentT
return Database.ExecuteScalar<int>(sql) > 0;
}
/// <summary>
/// Returns true or false depending on whether content nodes have been created based on the provided content type id.
/// </summary>
public bool HasContentNodes(int id)
{
var sql = new Sql(
$"SELECT CASE WHEN EXISTS (SELECT * FROM {Constants.DatabaseSchema.Tables.Content} WHERE contentTypeId = @id) THEN 1 ELSE 0 END",
new { id });
return Database.ExecuteScalar<int>(sql) == 1;
}
protected override IEnumerable<string> GetDeleteClauses()
{
// in theory, services should have ensured that content items of the given content type
@@ -2,6 +2,7 @@
using Umbraco.Core.Cache;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Logging;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
namespace Umbraco.Core.Persistence.Repositories.Implement
@@ -17,8 +18,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
/// </remarks>
internal class DocumentBlueprintRepository : DocumentRepository, IDocumentBlueprintRepository
{
public DocumentBlueprintRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository)
: base(scopeAccessor, appCaches, logger, contentTypeRepository, templateRepository, tagRepository, languageRepository)
public DocumentBlueprintRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger,
IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository,
Lazy<PropertyEditorCollection> propertyEditorCollection, DataValueReferenceFactoryCollection dataValueReferenceFactories)
: base(scopeAccessor, appCaches, logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditorCollection, dataValueReferenceFactories)
{
}
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Linq;
using NPoco;
using Umbraco.Core.Cache;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
@@ -12,6 +11,7 @@ using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
@@ -30,8 +30,23 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
private readonly ContentByGuidReadRepository _contentByGuidReadRepository;
private readonly IScopeAccessor _scopeAccessor;
public DocumentRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository)
: base(scopeAccessor, appCaches, languageRepository, logger)
/// <summary>
/// Constructor
/// </summary>
/// <param name="scopeAccessor"></param>
/// <param name="appCaches"></param>
/// <param name="logger"></param>
/// <param name="contentTypeRepository"></param>
/// <param name="templateRepository"></param>
/// <param name="tagRepository"></param>
/// <param name="languageRepository"></param>
/// <param name="propertyEditors">
/// Lazy property value collection - must be lazy because we have a circular dependency since some property editors require services, yet these services require property editors
/// </param>
public DocumentRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger,
IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository,
Lazy<PropertyEditorCollection> propertyEditors, DataValueReferenceFactoryCollection dataValueReferenceFactories)
: base(scopeAccessor, appCaches, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferenceFactories)
{
_contentTypeRepository = contentTypeRepository ?? throw new ArgumentNullException(nameof(contentTypeRepository));
_templateRepository = templateRepository ?? throw new ArgumentNullException(nameof(templateRepository));
@@ -229,6 +244,21 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
return MapDtosToContent(Database.Fetch<DocumentDto>(sql), true);
}
// TODO: This method needs to return a readonly version of IContent! The content returned
// from this method does not contain all of the data required to re-persist it and if that
// is attempted some odd things will occur.
// Either we create an IContentReadOnly (which ultimately we should for vNext so we can
// differentiate between methods that return entities that can be re-persisted or not), or
// in the meantime to not break API compatibility, we can add a property to IContentBase
// (or go further and have it on IUmbracoEntity): "IsReadOnly" and if that is true we throw
// an exception if that entity is passed to a Save method.
// Ideally we return "Slim" versions of content for all sorts of methods here and in ContentService.
// Perhaps another non-breaking alternative is to have new services like IContentServiceReadOnly
// which can return IContentReadOnly.
// We have the ability with `MapDtosToContent` to reduce the amount of data looked up for a
// content item. Ideally for paged data that populates list views, these would be ultra slim
// content items, there's no reason to populate those with really anything apart from property data,
// but until we do something like the above, we can't do that since it would be breaking and unclear.
public override IEnumerable<IContent> GetAllVersionsSlim(int nodeId, int skip, int take)
{
var sql = GetBaseQuery(QueryType.Many, false)
@@ -236,7 +266,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
.OrderByDescending<ContentVersionDto>(x => x.Current)
.AndByDescending<ContentVersionDto>(x => x.VersionDate);
return MapDtosToContent(Database.Fetch<DocumentDto>(sql), true, true).Skip(skip).Take(take);
return MapDtosToContent(Database.Fetch<DocumentDto>(sql), true,
// load bare minimum, need variants though since this is used to rollback with variants
false, false, false, true).Skip(skip).Take(take);
}
public override IContent GetVersion(int versionId)
@@ -468,6 +500,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
ClearEntityTags(entity, _tagRepository);
}
PersistRelations(entity);
entity.ResetDirtyProperties();
// troubleshooting
@@ -671,6 +705,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
ClearEntityTags(entity, _tagRepository);
}
PersistRelations(entity);
// TODO: note re. tags: explicitly unpublished entities have cleared tags, but masked or trashed entities *still* have tags in the db - so what?
entity.ResetDirtyProperties();
@@ -1056,7 +1092,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
return base.ApplySystemOrdering(ref sql, ordering);
}
private IEnumerable<IContent> MapDtosToContent(List<DocumentDto> dtos, bool withCache = false, bool slim = false)
private IEnumerable<IContent> MapDtosToContent(List<DocumentDto> dtos,
bool withCache = false,
bool loadProperties = true,
bool loadTemplates = true,
bool loadSchedule = true,
bool loadVariants = true)
{
var temps = new List<TempContent<Content>>();
var contentTypes = new Dictionary<int, IContentType>();
@@ -1089,7 +1130,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
var c = content[i] = ContentBaseFactory.BuildEntity(dto, contentType);
if (!slim)
if (loadTemplates)
{
// need templates
var templateId = dto.DocumentVersionDto.TemplateId;
@@ -1114,49 +1155,71 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
temps.Add(temp);
}
if (!slim)
Dictionary<int, ITemplate> templates = null;
if (loadTemplates)
{
// load all required templates in 1 query, and index
var templates = _templateRepository.GetMany(templateIds.ToArray())
templates = _templateRepository.GetMany(templateIds.ToArray())
.ToDictionary(x => x.Id, x => x);
}
IDictionary<int, PropertyCollection> properties = null;
if (loadProperties)
{
// load all properties for all documents from database in 1 query - indexed by version id
var properties = GetPropertyCollections(temps);
var schedule = GetContentSchedule(temps.Select(x => x.Content.Id).ToArray());
properties = GetPropertyCollections(temps);
}
// assign templates and properties
foreach (var temp in temps)
var schedule = GetContentSchedule(temps.Select(x => x.Content.Id).ToArray());
// assign templates and properties
foreach (var temp in temps)
{
if (loadTemplates)
{
// set the template ID if it matches an existing template
if (temp.Template1Id.HasValue && templates.ContainsKey(temp.Template1Id.Value))
temp.Content.TemplateId = temp.Template1Id;
if (temp.Template2Id.HasValue && templates.ContainsKey(temp.Template2Id.Value))
temp.Content.PublishTemplateId = temp.Template2Id;
}
// set properties
// set properties
if (loadProperties)
{
if (properties.ContainsKey(temp.VersionId))
temp.Content.Properties = properties[temp.VersionId];
else
throw new InvalidOperationException($"No property data found for version: '{temp.VersionId}'.");
}
if (loadSchedule)
{
// load in the schedule
if (schedule.TryGetValue(temp.Content.Id, out var s))
temp.Content.ContentSchedule = s;
}
}
// set variations, if varying
temps = temps.Where(x => x.ContentType.VariesByCulture()).ToList();
if (temps.Count > 0)
if (loadVariants)
{
// load all variations for all documents from database, in one query
var contentVariations = GetContentVariations(temps);
var documentVariations = GetDocumentVariations(temps);
foreach (var temp in temps)
SetVariations(temp.Content, contentVariations, documentVariations);
// set variations, if varying
temps = temps.Where(x => x.ContentType.VariesByCulture()).ToList();
if (temps.Count > 0)
{
// load all variations for all documents from database, in one query
var contentVariations = GetContentVariations(temps);
var documentVariations = GetDocumentVariations(temps);
foreach (var temp in temps)
SetVariations(temp.Content, contentVariations, documentVariations);
}
}
foreach(var c in content)
foreach (var c in content)
c.ResetDirtyProperties(false); // reset dirty initial properties (U4-1946)
return content;
@@ -35,21 +35,33 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected ISqlSyntaxProvider SqlSyntax => _scopeAccessor.AmbientScope.SqlContext.SqlSyntax;
#region Repository
// get a page of entities
public IEnumerable<IEntitySlim> GetPagedResultsByQuery(IQuery<IUmbracoEntity> query, Guid objectType, long pageIndex, int pageSize, out long totalRecords,
IQuery<IUmbracoEntity> filter, Ordering ordering)
{
var isContent = objectType == Constants.ObjectTypes.Document || objectType == Constants.ObjectTypes.DocumentBlueprint;
var isMedia = objectType == Constants.ObjectTypes.Media;
var isMember = objectType == Constants.ObjectTypes.Member;
return GetPagedResultsByQuery(query, new[] { objectType }, pageIndex, pageSize, out totalRecords, filter, ordering);
}
var sql = GetBaseWhere(isContent, isMedia, isMember, false, x =>
// get a page of entities
public IEnumerable<IEntitySlim> GetPagedResultsByQuery(IQuery<IUmbracoEntity> query, Guid[] objectTypes, long pageIndex, int pageSize, out long totalRecords,
IQuery<IUmbracoEntity> filter, Ordering ordering, Action<Sql<ISqlContext>> sqlCustomization = null)
{
var isContent = objectTypes.Any(objectType => objectType == Constants.ObjectTypes.Document || objectType == Constants.ObjectTypes.DocumentBlueprint);
var isMedia = objectTypes.Any(objectType => objectType == Constants.ObjectTypes.Media);
var isMember = objectTypes.Any(objectType => objectType == Constants.ObjectTypes.Member);
var sql = GetBaseWhere(isContent, isMedia, isMember, false, s =>
{
if (filter == null) return;
foreach (var filterClause in filter.GetWhereClauses())
x.Where(filterClause.Item1, filterClause.Item2);
}, objectType);
sqlCustomization?.Invoke(s);
if (filter != null)
{
foreach (var filterClause in filter.GetWhereClauses())
s.Where(filterClause.Item1, filterClause.Item2);
}
}, objectTypes);
ordering = ordering ?? Ordering.ByDefault();
@@ -70,35 +82,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// for content we must query for ContentEntityDto entities to produce the correct culture variant entity names
var pageIndexToFetch = pageIndex + 1;
IEnumerable<BaseDto> dtos;
if(isContent)
{
var page = Database.Page<ContentEntityDto>(pageIndexToFetch, pageSize, sql);
dtos = page.Items;
totalRecords = page.TotalItems;
}
else if (isMedia)
{
var page = Database.Page<MediaEntityDto>(pageIndexToFetch, pageSize, sql);
dtos = page.Items;
totalRecords = page.TotalItems;
}
else if (isMember)
{
var page = Database.Page<MemberEntityDto>(pageIndexToFetch, pageSize, sql);
dtos = page.Items;
totalRecords = page.TotalItems;
}
else
{
var page = Database.Page<BaseDto>(pageIndexToFetch, pageSize, sql);
dtos = page.Items;
totalRecords = page.TotalItems;
}
var page = Database.Page<GenericContentEntityDto>(pageIndexToFetch, pageSize, sql);
dtos = page.Items;
totalRecords = page.TotalItems;
var entities = dtos.Select(x => BuildEntity(isContent, isMedia, isMember, x)).ToArray();
var entities = dtos.Select(BuildEntity).ToArray();
if (isContent)
BuildVariants(entities.Cast<DocumentEntitySlim>());
BuildVariants(entities.OfType<DocumentEntitySlim>());
return entities;
}
@@ -107,7 +97,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
var sql = GetBaseWhere(false, false, false, false, key);
var dto = Database.FirstOrDefault<BaseDto>(sql);
return dto == null ? null : BuildEntity(false, false, false, dto);
return dto == null ? null : BuildEntity(dto);
}
@@ -116,7 +106,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
//isContent is going to return a 1:M result now with the variants so we need to do different things
if (isContent)
{
var cdtos = Database.Fetch<ContentEntityDto>(sql);
var cdtos = Database.Fetch<DocumentEntityDto>(sql);
return cdtos.Count == 0 ? null : BuildVariants(BuildDocumentEntity(cdtos[0]));
}
@@ -127,7 +117,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (dto == null) return null;
var entity = BuildEntity(false, isMedia, isMember, dto);
var entity = BuildEntity(dto);
return entity;
}
@@ -146,7 +136,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
var sql = GetBaseWhere(false, false, false, false, id);
var dto = Database.FirstOrDefault<BaseDto>(sql);
return dto == null ? null : BuildEntity(false, false, false, dto);
return dto == null ? null : BuildEntity(dto);
}
public IEntitySlim Get(int id, Guid objectTypeId)
@@ -178,7 +168,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
//isContent is going to return a 1:M result now with the variants so we need to do different things
if (isContent)
{
var cdtos = Database.Fetch<ContentEntityDto>(sql);
var cdtos = Database.Fetch<DocumentEntityDto>(sql);
return cdtos.Count == 0
? Enumerable.Empty<IEntitySlim>()
@@ -189,7 +179,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
? (IEnumerable<BaseDto>)Database.Fetch<MediaEntityDto>(sql)
: Database.Fetch<BaseDto>(sql);
var entities = dtos.Select(x => BuildEntity(false, isMedia, isMember, x)).ToArray();
var entities = dtos.Select(BuildEntity).ToArray();
return entities;
}
@@ -233,7 +223,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
var sql = translator.Translate();
sql = AddGroupBy(false, false, false, sql, true);
var dtos = Database.Fetch<BaseDto>(sql);
return dtos.Select(x => BuildEntity(false, false, false, x)).ToList();
return dtos.Select(BuildEntity).ToList();
}
public IEnumerable<IEntitySlim> GetByQuery(IQuery<IUmbracoEntity> query, Guid objectType)
@@ -242,7 +232,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
var isMedia = objectType == Constants.ObjectTypes.Media;
var isMember = objectType == Constants.ObjectTypes.Member;
var sql = GetBaseWhere(isContent, isMedia, isMember, false, null, objectType);
var sql = GetBaseWhere(isContent, isMedia, isMember, false, null, new[] { objectType });
var translator = new SqlTranslator<IUmbracoEntity>(sql, query);
sql = translator.Translate();
@@ -356,14 +346,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// gets the full sql for a given object type, with a given filter
protected Sql<ISqlContext> GetFullSqlForEntityType(bool isContent, bool isMedia, bool isMember, Guid objectType, Action<Sql<ISqlContext>> filter)
{
var sql = GetBaseWhere(isContent, isMedia, isMember, false, filter, objectType);
var sql = GetBaseWhere(isContent, isMedia, isMember, false, filter, new[] { objectType });
return AddGroupBy(isContent, isMedia, isMember, sql, true);
}
// gets the base SELECT + FROM [+ filter] sql
// always from the 'current' content version
protected Sql<ISqlContext> GetBase(bool isContent, bool isMedia, bool isMember, Action<Sql<ISqlContext>> filter, bool isCount = false)
{
{
var sql = Sql();
if (isCount)
@@ -401,15 +391,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (isContent || isMedia || isMember)
{
sql
.InnerJoin<ContentVersionDto>().On<NodeDto, ContentVersionDto>((left, right) => left.NodeId == right.NodeId && right.Current)
.InnerJoin<ContentDto>().On<NodeDto, ContentDto>((left, right) => left.NodeId == right.NodeId)
.InnerJoin<ContentTypeDto>().On<ContentDto, ContentTypeDto>((left, right) => left.ContentTypeId == right.NodeId);
.LeftJoin<ContentVersionDto>().On<NodeDto, ContentVersionDto>((left, right) => left.NodeId == right.NodeId && right.Current)
.LeftJoin<ContentDto>().On<NodeDto, ContentDto>((left, right) => left.NodeId == right.NodeId)
.LeftJoin<ContentTypeDto>().On<ContentDto, ContentTypeDto>((left, right) => left.ContentTypeId == right.NodeId);
}
if (isContent)
{
sql
.InnerJoin<DocumentDto>().On<NodeDto, DocumentDto>((left, right) => left.NodeId == right.NodeId);
.LeftJoin<DocumentDto>().On<NodeDto, DocumentDto>((left, right) => left.NodeId == right.NodeId);
}
if (isMedia)
@@ -433,10 +423,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// gets the base SELECT + FROM [+ filter] + WHERE sql
// for a given object type, with a given filter
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, Action<Sql<ISqlContext>> filter, Guid objectType)
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, Action<Sql<ISqlContext>> filter, Guid[] objectTypes)
{
return GetBase(isContent, isMedia, isMember, filter, isCount)
.Where<NodeDto>(x => x.NodeObjectType == objectType);
var sql = GetBase(isContent, isMedia, isMember, filter, isCount);
if (objectTypes.Length > 0)
{
sql.WhereIn<NodeDto>(x => x.NodeObjectType, objectTypes);
}
return sql;
}
// gets the base SELECT + FROM + WHERE sql
@@ -510,8 +504,19 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (sql == null) throw new ArgumentNullException(nameof(sql));
if (ordering == null) throw new ArgumentNullException(nameof(ordering));
// TODO: although this works for name, it probably doesn't work for others without an alias of some sort
var orderBy = ordering.OrderBy;
// TODO: although the default ordering string works for name, it wont work for others without a table or an alias of some sort
// As more things are attempted to be sorted we'll prob have to add more expressions here
string orderBy;
switch (ordering.OrderBy.ToUpperInvariant())
{
case "PATH":
orderBy = SqlSyntax.GetQuotedColumn(NodeDto.TableName, "path");
break;
default:
orderBy = ordering.OrderBy;
break;
}
if (ordering.Direction == Direction.Ascending)
sql.OrderBy(orderBy);
@@ -524,9 +529,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
#region Classes
/// <summary>
/// The DTO used to fetch results for a content item with its variation info
/// The DTO used to fetch results for a generic content item which could be either a document, media or a member
/// </summary>
private class ContentEntityDto : BaseDto
private class GenericContentEntityDto : DocumentEntityDto
{
public string MediaPath { get; set; }
}
/// <summary>
/// The DTO used to fetch results for a document item with its variation info
/// </summary>
private class DocumentEntityDto : BaseDto
{
public ContentVariation Variations { get; set; }
@@ -534,11 +547,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
public bool Edited { get; set; }
}
/// <summary>
/// The DTO used to fetch results for a media item with its media path info
/// </summary>
private class MediaEntityDto : BaseDto
{
public string MediaPath { get; set; }
}
/// <summary>
/// The DTO used to fetch results for a member item
/// </summary>
private class MemberEntityDto : BaseDto
{
}
@@ -589,13 +608,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
#region Factory
private EntitySlim BuildEntity(bool isContent, bool isMedia, bool isMember, BaseDto dto)
private EntitySlim BuildEntity(BaseDto dto)
{
if (isContent)
if (dto.NodeObjectType == Constants.ObjectTypes.Document)
return BuildDocumentEntity(dto);
if (isMedia)
if (dto.NodeObjectType == Constants.ObjectTypes.Media)
return BuildMediaEntity(dto);
if (isMember)
if (dto.NodeObjectType == Constants.ObjectTypes.Member)
return BuildMemberEntity(dto);
// EntitySlim does not track changes
@@ -650,7 +669,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
var entity = new DocumentEntitySlim();
BuildContentEntity(entity, dto);
if (dto is ContentEntityDto contentDto)
if (dto is DocumentEntityDto contentDto)
{
// fill in the invariant info
entity.Edited = contentDto.Edited;
@@ -40,7 +40,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (macroDto == null)
return null;
var entity = MacroFactory.BuildEntity(macroDto);
// reset dirty initial properties (U4-1946)
@@ -153,7 +153,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override void PersistUpdatedItem(IMacro entity)
{
entity.UpdatingEntity();
;
var dto = MacroFactory.BuildDto(entity);
Database.Update(dto);
@@ -12,6 +12,7 @@ using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics;
@@ -27,8 +28,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
private readonly ITagRepository _tagRepository;
private readonly MediaByGuidReadRepository _mediaByGuidReadRepository;
public MediaRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IMediaTypeRepository mediaTypeRepository, ITagRepository tagRepository, ILanguageRepository languageRepository)
: base(scopeAccessor, cache, languageRepository, logger)
public MediaRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IMediaTypeRepository mediaTypeRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository,
Lazy<PropertyEditorCollection> propertyEditorCollection, DataValueReferenceFactoryCollection dataValueReferenceFactories)
: base(scopeAccessor, cache, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditorCollection, dataValueReferenceFactories)
{
_mediaTypeRepository = mediaTypeRepository ?? throw new ArgumentNullException(nameof(mediaTypeRepository));
_tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository));
@@ -228,7 +230,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
entity.SanitizeEntityPropertiesForXmlStorage();
// create the dto
var dto = ContentBaseFactory.BuildDto(entity);
var dto = ContentBaseFactory.BuildDto(PropertyEditors, entity);
// derive path and level from parent
var parent = GetParentNodeDto(entity.ParentId);
@@ -287,6 +289,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// set tags
SetEntityTags(entity, _tagRepository);
PersistRelations(entity);
OnUowRefreshedEntity(new ScopedEntityEventArgs(AmbientScope, entity));
entity.ResetDirtyProperties();
@@ -317,7 +321,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
}
// create the dto
var dto = ContentBaseFactory.BuildDto(entity);
var dto = ContentBaseFactory.BuildDto(PropertyEditors, entity);
// update the node dto
var nodeDto = dto.ContentDto.NodeDto;
@@ -343,6 +347,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
SetEntityTags(entity, _tagRepository);
PersistRelations(entity);
OnUowRefreshedEntity(new ScopedEntityEventArgs(AmbientScope, entity));
entity.ResetDirtyProperties();
@@ -542,6 +548,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
media.ResetDirtyProperties(false);
return media;
}
}
}
@@ -10,6 +10,7 @@ using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics;
@@ -25,8 +26,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
private readonly ITagRepository _tagRepository;
private readonly IMemberGroupRepository _memberGroupRepository;
public MemberRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, ILanguageRepository languageRepository)
: base(scopeAccessor, cache, languageRepository, logger)
public MemberRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger,
IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository,
Lazy<PropertyEditorCollection> propertyEditors, DataValueReferenceFactoryCollection dataValueReferenceFactories)
: base(scopeAccessor, cache, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferenceFactories)
{
_memberTypeRepository = memberTypeRepository ?? throw new ArgumentNullException(nameof(memberTypeRepository));
_tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository));
@@ -321,6 +324,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
SetEntityTags(entity, _tagRepository);
PersistRelations(entity);
OnUowRefreshedEntity(new ScopedEntityEventArgs(AmbientScope, entity));
entity.ResetDirtyProperties();
@@ -386,6 +391,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
SetEntityTags(entity, _tagRepository);
PersistRelations(entity);
OnUowRefreshedEntity(new ScopedEntityEventArgs(AmbientScope, entity));
entity.ResetDirtyProperties();
@@ -6,10 +6,14 @@ using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics;
namespace Umbraco.Core.Persistence.Repositories.Implement
{
@@ -19,11 +23,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
internal class RelationRepository : NPocoRepositoryBase<int, IRelation>, IRelationRepository
{
private readonly IRelationTypeRepository _relationTypeRepository;
private readonly IEntityRepository _entityRepository;
public RelationRepository(IScopeAccessor scopeAccessor, ILogger logger, IRelationTypeRepository relationTypeRepository)
public RelationRepository(IScopeAccessor scopeAccessor, ILogger logger, IRelationTypeRepository relationTypeRepository, IEntityRepository entityRepository)
: base(scopeAccessor, AppCaches.NoCache, logger)
{
_relationTypeRepository = relationTypeRepository;
_entityRepository = entityRepository;
}
#region Overrides of RepositoryBase<int,Relation>
@@ -39,10 +45,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
var relationType = _relationTypeRepository.Get(dto.RelationType);
if (relationType == null)
throw new Exception(string.Format("RelationType with Id: {0} doesn't exist", dto.RelationType));
throw new InvalidOperationException(string.Format("RelationType with Id: {0} doesn't exist", dto.RelationType));
var factory = new RelationFactory(relationType);
return DtoToEntity(dto, factory);
return DtoToEntity(dto, relationType);
}
protected override IEnumerable<IRelation> PerformGetAll(params int[] ids)
@@ -67,26 +72,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
private IEnumerable<IRelation> DtosToEntities(IEnumerable<RelationDto> dtos)
{
// in most cases, the relation type will be the same for all of them,
// plus we've ordered the relations by type, so try to allocate as few
// factories as possible - bearing in mind that relation types are cached
RelationFactory factory = null;
var relationTypeId = -1;
//NOTE: This is N+1, BUT ALL relation types are cached so shouldn't matter
return dtos.Select(x =>
{
if (relationTypeId != x.RelationType)
factory = new RelationFactory(_relationTypeRepository.Get(relationTypeId = x.RelationType));
return DtoToEntity(x, factory);
}).ToList();
return dtos.Select(x => DtoToEntity(x, _relationTypeRepository.Get(x.RelationType))).ToList();
}
private static IRelation DtoToEntity(RelationDto dto, RelationFactory factory)
private static IRelation DtoToEntity(RelationDto dto, IRelationType relationType)
{
var entity = factory.BuildEntity(dto);
var entity = RelationFactory.BuildEntity(dto, relationType);
// reset dirty initial properties (U4-1946)
((BeingDirtyBase)entity).ResetDirtyProperties(false);
entity.ResetDirtyProperties(false);
return entity;
}
@@ -97,14 +93,18 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override Sql<ISqlContext> GetBaseQuery(bool isCount)
{
var sql = Sql();
if (isCount)
{
return Sql().SelectCount().From<RelationDto>();
}
sql = isCount
? sql.SelectCount()
: sql.Select<RelationDto>();
var sql = Sql().Select<RelationDto>()
.AndSelect<NodeDto>("uchild", x => Alias(x.NodeObjectType, "childObjectType"))
.AndSelect<NodeDto>("uparent", x => Alias(x.NodeObjectType, "parentObjectType"))
.From<RelationDto>()
.InnerJoin<NodeDto>("uchild").On<RelationDto, NodeDto>((rel, node) => rel.ChildId == node.NodeId, aliasRight: "uchild")
.InnerJoin<NodeDto>("uparent").On<RelationDto, NodeDto>((rel, node) => rel.ParentId == node.NodeId, aliasRight: "uparent");
sql
.From<RelationDto>();
return sql;
}
@@ -136,11 +136,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
entity.AddingEntity();
var factory = new RelationFactory(entity.RelationType);
var dto = factory.BuildDto(entity);
var dto = RelationFactory.BuildDto(entity);
var id = Convert.ToInt32(Database.Insert(dto));
entity.Id = id;
PopulateObjectTypes(entity);
entity.ResetDirtyProperties();
}
@@ -149,13 +150,192 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
entity.UpdatingEntity();
var factory = new RelationFactory(entity.RelationType);
var dto = factory.BuildDto(entity);
var dto = RelationFactory.BuildDto(entity);
Database.Update(dto);
PopulateObjectTypes(entity);
entity.ResetDirtyProperties();
}
#endregion
/// <summary>
/// Used for joining the entity query with relations for the paging methods
/// </summary>
/// <param name="sql"></param>
private void SqlJoinRelations(Sql<ISqlContext> sql)
{
// add left joins for relation tables (this joins on both child or parent, so beware that this will normally return entities for
// both sides of the relation type unless the IUmbracoEntity query passed in filters one side out).
sql.LeftJoin<RelationDto>().On<NodeDto, RelationDto>((left, right) => left.NodeId == right.ChildId || left.NodeId == right.ParentId);
sql.LeftJoin<RelationTypeDto>().On<RelationDto, RelationTypeDto>((left, right) => left.RelationType == right.Id);
}
public IEnumerable<IUmbracoEntity> GetPagedParentEntitiesByChildId(int childId, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes)
{
// var contentObjectTypes = new[] { Constants.ObjectTypes.Document, Constants.ObjectTypes.Media, Constants.ObjectTypes.Member }
// we could pass in the contentObjectTypes so that the entity repository sql is configured to do full entity lookups so that we get the full data
// required to populate content, media or members, else we get the bare minimum data needed to populate an entity. BUT if we do this it
// means that the SQL is less efficient and returns data that is probably not needed for what we need this lookup for. For the time being we
// will just return the bare minimum entity data.
return _entityRepository.GetPagedResultsByQuery(Query<IUmbracoEntity>(), entityTypes, pageIndex, pageSize, out totalRecords, null, null, sql =>
{
SqlJoinRelations(sql);
sql.Where<RelationDto>(rel => rel.ChildId == childId);
sql.Where<RelationDto, NodeDto>((rel, node) => rel.ParentId == childId || node.NodeId != childId);
});
}
public IEnumerable<IUmbracoEntity> GetPagedChildEntitiesByParentId(int parentId, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes)
{
// var contentObjectTypes = new[] { Constants.ObjectTypes.Document, Constants.ObjectTypes.Media, Constants.ObjectTypes.Member }
// we could pass in the contentObjectTypes so that the entity repository sql is configured to do full entity lookups so that we get the full data
// required to populate content, media or members, else we get the bare minimum data needed to populate an entity. BUT if we do this it
// means that the SQL is less efficient and returns data that is probably not needed for what we need this lookup for. For the time being we
// will just return the bare minimum entity data.
return _entityRepository.GetPagedResultsByQuery(Query<IUmbracoEntity>(), entityTypes, pageIndex, pageSize, out totalRecords, null, null, sql =>
{
SqlJoinRelations(sql);
sql.Where<RelationDto>(rel => rel.ParentId == parentId);
sql.Where<RelationDto, NodeDto>((rel, node) => rel.ChildId == parentId || node.NodeId != parentId);
});
}
public void Save(IEnumerable<IRelation> relations)
{
foreach (var hasIdentityGroup in relations.GroupBy(r => r.HasIdentity))
{
if (hasIdentityGroup.Key)
{
// Do updates, we can't really do a bulk update so this is still a 1 by 1 operation
// however we can bulk populate the object types. It might be possible to bulk update
// with SQL but would be pretty ugly and we're not really too worried about that for perf,
// it's the bulk inserts we care about.
var asArray = hasIdentityGroup.ToArray();
foreach (var relation in hasIdentityGroup)
{
relation.UpdatingEntity();
var dto = RelationFactory.BuildDto(relation);
Database.Update(dto);
}
PopulateObjectTypes(asArray);
}
else
{
// Do bulk inserts
var entitiesAndDtos = hasIdentityGroup.ToDictionary(
r => // key = entity
{
r.AddingEntity();
return r;
},
RelationFactory.BuildDto); // value = DTO
// Use NPoco's own InsertBulk command which will automatically re-populate the new Ids on the entities, our own
// BulkInsertRecords does not cater for this.
Database.InsertBulk(entitiesAndDtos.Values);
// All dtos now have IDs assigned
foreach (var de in entitiesAndDtos)
{
// re-assign ID to the entity
de.Key.Id = de.Value.Id;
}
PopulateObjectTypes(entitiesAndDtos.Keys.ToArray());
}
}
}
public IEnumerable<IRelation> GetPagedRelationsByQuery(IQuery<IRelation> query, long pageIndex, int pageSize, out long totalRecords, Ordering ordering)
{
var sql = GetBaseQuery(false);
if (ordering == null || ordering.IsEmpty)
ordering = Ordering.By(SqlSyntax.GetQuotedColumn(Constants.DatabaseSchema.Tables.Relation, "id"));
var translator = new SqlTranslator<IRelation>(sql, query);
sql = translator.Translate();
// apply ordering
ApplyOrdering(ref sql, ordering);
var pageIndexToFetch = pageIndex + 1;
var page = Database.Page<RelationDto>(pageIndexToFetch, pageSize, sql);
var dtos = page.Items;
totalRecords = page.TotalItems;
var relTypes = _relationTypeRepository.GetMany(dtos.Select(x => x.RelationType).Distinct().ToArray())
.ToDictionary(x => x.Id, x => x);
var result = dtos.Select(r =>
{
if (!relTypes.TryGetValue(r.RelationType, out var relType))
throw new InvalidOperationException(string.Format("RelationType with Id: {0} doesn't exist", r.RelationType));
return DtoToEntity(r, relType);
}).ToList();
return result;
}
public void DeleteByParent(int parentId, params string[] relationTypeAliases)
{
var subQuery = Sql().Select<RelationDto>(x => x.Id)
.From<RelationDto>()
.InnerJoin<RelationTypeDto>().On<RelationDto, RelationTypeDto>(x => x.RelationType, x => x.Id)
.Where<RelationDto>(x => x.ParentId == parentId);
if (relationTypeAliases.Length > 0)
{
subQuery.WhereIn<RelationTypeDto>(x => x.Alias, relationTypeAliases);
}
Database.Execute(Sql().Delete<RelationDto>().WhereIn<RelationDto>(x => x.Id, subQuery));
}
/// <summary>
/// Used to populate the object types after insert/update
/// </summary>
/// <param name="entities"></param>
private void PopulateObjectTypes(params IRelation[] entities)
{
var entityIds = entities.Select(x => x.ParentId).Concat(entities.Select(y => y.ChildId)).Distinct();
var nodes = Database.Fetch<NodeDto>(Sql().Select<NodeDto>().From<NodeDto>()
.WhereIn<NodeDto>(x => x.NodeId, entityIds))
.ToDictionary(x => x.NodeId, x => x.NodeObjectType);
foreach (var e in entities)
{
if (nodes.TryGetValue(e.ParentId, out var parentObjectType))
{
e.ParentObjectType = parentObjectType.GetValueOrDefault();
}
if (nodes.TryGetValue(e.ChildId, out var childObjectType))
{
e.ChildObjectType = childObjectType.GetValueOrDefault();
}
}
}
private void ApplyOrdering(ref Sql<ISqlContext> sql, Ordering ordering)
{
if (sql == null) throw new ArgumentNullException(nameof(sql));
if (ordering == null) throw new ArgumentNullException(nameof(ordering));
// TODO: although this works for name, it probably doesn't work for others without an alias of some sort
var orderBy = ordering.OrderBy;
if (ordering.Direction == Direction.Ascending)
sql.OrderBy(orderBy);
else
sql.OrderByDescending(orderBy);
}
}
}
@@ -134,7 +134,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override void PersistNewItem(IRelationType entity)
{
entity.AddingEntity();
CheckNullObjectTypeValues(entity);
var dto = RelationTypeFactory.BuildDto(entity);
var id = Convert.ToInt32(Database.Insert(dto));
@@ -146,7 +148,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override void PersistUpdatedItem(IRelationType entity)
{
entity.UpdatingEntity();
CheckNullObjectTypeValues(entity);
var dto = RelationTypeFactory.BuildDto(entity);
Database.Update(dto);
@@ -154,5 +158,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
}
#endregion
private void CheckNullObjectTypeValues(IRelationType entity)
{
if (entity.ParentObjectType.HasValue && entity.ParentObjectType == Guid.Empty)
entity.ParentObjectType = null;
if (entity.ChildObjectType.HasValue && entity.ChildObjectType == Guid.Empty)
entity.ChildObjectType = null;
}
}
}
@@ -215,7 +215,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
//Save updated entity to db
template.UpdateDate = DateTime.Now;
;
var dto = TemplateFactory.BuildDto(template, NodeObjectTypeId, templateDto.PrimaryKey);
Database.Update(dto.NodeDto);
@@ -557,6 +557,16 @@ ORDER BY colName";
}
}
// If userlogin or the email has changed then need to reset security stamp
if (changedCols.Contains("userLogin") || changedCols.Contains("userEmail"))
{
userDto.EmailConfirmedDate = null;
userDto.SecurityStampToken = entity.SecurityStamp = Guid.NewGuid().ToString();
changedCols.Add("emailConfirmedDate");
changedCols.Add("securityStampToken");
}
//only update the changed cols
if (changedCols.Count > 0)
{
@@ -250,6 +250,11 @@ where tbl.[name]=@0 and col.[name]=@1;", tableName, columnName)
}
public override void WriteLock(IDatabase db, params int[] lockIds)
{
WriteLock(db, TimeSpan.FromMilliseconds(1800), lockIds);
}
public void WriteLock(IDatabase db, TimeSpan timeout, params int[] lockIds)
{
// soon as we get Database, a transaction is started
@@ -260,7 +265,7 @@ where tbl.[name]=@0 and col.[name]=@1;", tableName, columnName)
// *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
foreach (var lockId in lockIds)
{
db.Execute(@"SET LOCK_TIMEOUT 1800;");
db.Execute($"SET LOCK_TIMEOUT {timeout.TotalMilliseconds};");
var i = db.Execute(@"UPDATE umbracoLock WITH (REPEATABLEREAD) SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id", new { id = lockId });
if (i == 0) // ensure we are actually locking!
throw new ArgumentException($"LockObject with id={lockId} does not exist.");
@@ -35,6 +35,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax
/// </remarks>
public static Sql GetDeleteSubquery(this ISqlSyntaxProvider sqlProvider, string tableName, string columnName, Sql subQuery, WhereInType whereInType = WhereInType.In)
{
//TODO: This is no longer necessary since this used to be a specific requirement for MySql!
// Now we can do a Delete<T> + sub query, see RelationRepository.DeleteByParent for example
return
new Sql(string.Format(
@@ -44,6 +44,8 @@ namespace Umbraco.Core.Persistence
_commandRetryPolicy = commandRetryPolicy;
EnableSqlTrace = EnableSqlTraceDefault;
NPocoDatabaseExtensions.ConfigureNPocoBulkExtensions();
}
/// <summary>
@@ -57,6 +59,8 @@ namespace Umbraco.Core.Persistence
_logger = logger;
EnableSqlTrace = EnableSqlTraceDefault;
NPocoDatabaseExtensions.ConfigureNPocoBulkExtensions();
}
#endregion
@@ -14,6 +14,7 @@ using System.Runtime.InteropServices;
[assembly: InternalsVisibleTo("Umbraco.Web")]
[assembly: InternalsVisibleTo("Umbraco.Web.UI")]
[assembly: InternalsVisibleTo("Umbraco.Examine")]
[assembly: InternalsVisibleTo("Umbraco.ModelsBuilder.Embedded")]
[assembly: InternalsVisibleTo("Umbraco.Tests")]
[assembly: InternalsVisibleTo("Umbraco.Tests.Benchmarks")]

Some files were not shown because too many files have changed in this diff Show More