diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md
index d21ee5ea02..0e79851c0b 100644
--- a/.github/CODE_OF_CONDUCT.md
+++ b/.github/CODE_OF_CONDUCT.md
@@ -17,7 +17,7 @@ Assume positive intent and try to understand before being understood.
Treat others as you would like to be treated.
-This also goes for treating the HQ with respect. For example: don’t promote products on [our.umbraco.org](https://our.umbraco.org) that directly compete with our commercial offerings which enables us to work for a sustainable Umbraco.
+This also goes for treating the HQ with respect. For example: don’t promote products on [our.umbraco.com](https://our.umbraco.com) that directly compete with our commercial offerings which enables us to work for a sustainable Umbraco.
## Open
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index c257600769..6e421da5d7 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -82,7 +82,7 @@ These wonderful volunteers will provide you with a first reply to your PR, revie
You can get in touch with [the PR team](#the-pr-team) in multiple ways, we love open conversations and we are a friendly bunch. No question you have is stupid. Any questions you have usually helps out multiple people with the same question. Ask away:
- If there's an existing issue on the issue tracker then that's a good place to leave questions and discuss how to start or move forward
-- Unsure where to start? Did something not work as expected? Try leaving a note in the ["Contributing to Umbraco"](https://our.umbraco.org/forum/contributing-to-umbraco-cms/) forum, the team monitors that one closely
+- Unsure where to start? Did something not work as expected? Try leaving a note in the ["Contributing to Umbraco"](https://our.umbraco.com/forum/contributing-to-umbraco-cms/) forum, the team monitors that one closely
- We're also [active in the Gitter chatroom](https://gitter.im/umbraco/Umbraco-CMS)
## Code of Conduct
diff --git a/.github/CONTRIBUTING_DETAILED.md b/.github/CONTRIBUTING_DETAILED.md
index 020346dc5e..a07539da6a 100644
--- a/.github/CONTRIBUTING_DETAILED.md
+++ b/.github/CONTRIBUTING_DETAILED.md
@@ -31,7 +31,7 @@ Before creating bug reports, please check [this list](#before-submitting-a-bug-r
##### Before Submitting A Bug Report
- * Most importantly, check **if you can reproduce the problem** in the [latest version of Umbraco](https://our.umbraco.org/download/). We might have already fixed your particular problem.
+ * Most importantly, check **if you can reproduce the problem** in the [latest version of Umbraco](https://our.umbraco.com/download/). We might have already fixed your particular problem.
* It also helps tremendously to check if the issue you're experiencing is present in **a clean install** of the Umbraco version you're currently using. Custom code can have side-effects that don't occur in a clean install.
* **Use the Google**. Whatever you're experiencing, Google it plus "Umbraco" - usually you can get some pretty good hints from the search results, including open issues and further troubleshooting hints.
* If you do find and existing issue has **and the issue is still open**, add a comment to the existing issue if you have additional information. If you have the same problem and no new info to add, just "star" the issue.
@@ -65,13 +65,11 @@ Most of the suggestions in the [reporting bugs](#reporting-bugs) section also co
Some additional hints that may be helpful:
* **Include screenshots and animated GIFs** which help you demonstrate the steps or point out the part of Umbraco which the suggestion is related to.
- * **Explain why this enhancement would be useful to most Umbraco users** and isn't something that can or should be implemented as a [community package](https://our.umbraco.org/projects/).
+ * **Explain why this enhancement would be useful to most Umbraco users** and isn't something that can or should be implemented as a [community package](https://our.umbraco.com/projects/).
### Your First Code Contribution
-Unsure where to begin contributing to Umbraco? You can start by looking through [these `Up for grabs` and issues](http://issues.umbraco.org/issues/U4?q=%28project%3A+%7BU4%7D+Difficulty%3A+%7BVery+Easy%7D+%23Easy+%23Unresolved+Priority%3A+Normal+%23Major+%23Show-stopper+State%3A+-%7BIn+Progress%7D+sort+by%3A+votes+Affected+versions%3A+-6.*+Affected+versions%3A+-4.*%29+OR+%28tag%3A+%7BUp+For+Grabs%7D+%23Unresolved+%29).
-
-The issue list is sorted by total number of upvotes. While not perfect, number of upvotes is a reasonable proxy for impact a given change will have.
+Unsure where to begin contributing to Umbraco? You can start by looking through [these `Up for grabs` and issues](https://issues.umbraco.org/issues?q=&project=U4&tagValue=upforgrabs&release=&issueType=&search=search) or on the [new issue tracker](https://github.com/umbraco/Umbraco-CMS/issues?q=is%3Aopen+is%3Aissue+label%3Acommunity%2Fup-for-grabs).
### Pull Requests
@@ -80,7 +78,7 @@ The most successful pull requests usually look a like this:
* Fill in the required template
* Include screenshots and animated GIFs in your pull request whenever possible.
* Unit tests, while optional are awesome, thank you!
- * New code is commented with documentation from which [the reference documentation](https://our.umbraco.org/documentation/Reference/) is generated
+ * New code is commented with documentation from which [the reference documentation](https://our.umbraco.com/documentation/Reference/) is generated
Again, these are guidelines, not strict requirements.
@@ -116,8 +114,8 @@ There's two big areas that you should know about:
To find the general areas of something you're looking to fix or improve, have a look at the following two parts of the API documentation.
- * [The AngularJS based backoffice files](https://our.umbraco.org/apidocs/ui/#/api) (to be found in `src\Umbraco.Web.UI.Client\src`)
- * [The rest](https://our.umbraco.org/apidocs/csharp/)
+ * [The AngularJS based backoffice files](https://our.umbraco.com/apidocs/ui/#/api) (to be found in `src\Umbraco.Web.UI.Client\src`)
+ * [The rest](https://our.umbraco.com/apidocs/csharp/)
### What branch should I target for my contributions?
diff --git a/.github/README.md b/.github/README.md
index 83de27c859..cf29f4e527 100644
--- a/.github/README.md
+++ b/.github/README.md
@@ -1,9 +1,18 @@
-_You are browsing the Umbraco v8 branch._
+_You are browsing the Umbraco v8 branch. Umbraco 8 is currently under development._
_Looking for Umbraco version 7? [Click here](https://github.com/umbraco/Umbraco-CMS) to go to the v7 branch._
_Ready to try out Version 8? [See the quick start guide](V8_GETTING_STARTED.md)._
+When is Umbraco 8 coming?
+=========================
+When it's ready. We're done with the major parts of the architecture work and are focusing on three seperate tracks to prepare Umbraco 8 for release:
+1) Editor Track (_currently in progress_). Without editors, there's no market for Umbraco. So we want to make sure that Umbraco 8 is full of love for editors.
+2) Partner Track. Without anyone implementing Umbraco, there's nothing for editors to update. So we want to make sure that Umbraco 8 is a joy to implement
+3) Contributor Track. Without our fabulous ecosystem of both individual Umbracians and 3rd party ISVs, Umbraco wouldn't be as rich a platform as it is today. We want to make sure that it's easy, straight forward and as backwards-compatible as possible to create packages for Umbraco
+
+Once a track is done, we start releasing previews where we ask people to test the features we believe are ready. While the testing is going on and we gather feedback, we move on to the next track. This doesn't mean that there hasn't already been work done in the area, but that a track focuses on finalizing, polishing and preparing the features for release.
+
Umbraco CMS
===========
The friendliest, most flexible and fastest growing ASP.NET CMS used by more than 443,000 websites worldwide: [https://umbraco.com](https://umbraco.com)
diff --git a/.github/V8_GETTING_STARTED.md b/.github/V8_GETTING_STARTED.md
index 8cd792aa71..62b376b0e7 100644
--- a/.github/V8_GETTING_STARTED.md
+++ b/.github/V8_GETTING_STARTED.md
@@ -23,7 +23,7 @@ We recommend running the site with the Visual Studio since you'll be able to rem
### Making code changes
-* _[The process for making code changes in v8 is the same as v7](https://github.com/umbraco/Umbraco-CMS/blob/dev-v7/docs/CONTRIBUTING.md)_
+* _[The process for making code changes in v8 is the same as v7](https://github.com/umbraco/Umbraco-CMS/blob/dev-v7/.github/CONTRIBUTING.md)_
* Any .NET changes you make you just need to compile
* Any Angular/JS changes you make you will need to make sure you are running the Gulp build. Easiest way to do this is from within Visual Studio in the `Task Runner Explorer`. You can find this window by pressing `ctrl + q` and typing in `Task Runner Explorer`. In this window you'll see all Gulp tasks, double click on the `dev` task, this will compile the angular solution and start a file watcher, then any html/js changes you make are automatically built.
* When making js changes, you should have the chrome developer tools open to ensure that cache is disabled
@@ -33,5 +33,5 @@ We recommend running the site with the Visual Studio since you'll be able to rem
We are keeping track of [known issues and limitations here](http://issues.umbraco.org/issue/U4-11279). These line items will eventually be turned into actual tasks to be worked on. Feel free to help us keep this list updated if you find issues and even help fix some of these items. If there is a particular item you'd like to help fix please mention this on the task and we'll create a sub task for the item to continue discussion there.
-There's [a list of tasks for v8 that haven't been completed](http://issues.umbraco.org/issues/U4?q=Due+in+version%3A+8.0.0+%23Unresolved+). If you are interested in helping out with any of these please mention this on the task. This list will be constantly updated as we begin to document and design some of the other tasks that still need to get done.
+There's [a list of tasks for v8 that haven't been completed](https://issues.umbraco.org/issues?q=&project=U4&tagValue=&release=8.0.0&issueType=&resolvedState=open&search=search). If you are interested in helping out with any of these please mention this on the task. This list will be constantly updated as we begin to document and design some of the other tasks that still need to get done.
diff --git a/LICENSE.md b/LICENSE.md
index c5560c3ce1..fa83dba963 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,6 +1,6 @@
# The MIT License (MIT) #
-Copyright (c) 2013 Umbraco
+Copyright (c) 2013-present Umbraco
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
diff --git a/build/build.ps1 b/build/build.ps1
index c7deb59f1e..8548cbb1ac 100644
--- a/build/build.ps1
+++ b/build/build.ps1
@@ -119,10 +119,6 @@
&npm install >> $log 2>&1
Write-Output ">> $? $($error.Count)" >> $log 2>&1
- Write-Output "### install bower" >> $log 2>&1
- &npm install -g bower >> $log 2>&1
- $error.Clear() # that one fails 'cos bower is deprecated - ignore
-
Write-Output "### install gulp" >> $log 2>&1
&npm install -g gulp >> $log 2>&1
$error.Clear() # that one fails 'cos deprecated stuff - ignore
diff --git a/src/ApiDocs/umbracotemplate/partials/head.tmpl.partial b/src/ApiDocs/umbracotemplate/partials/head.tmpl.partial
index 591e1c1885..ccc4d50229 100644
--- a/src/ApiDocs/umbracotemplate/partials/head.tmpl.partial
+++ b/src/ApiDocs/umbracotemplate/partials/head.tmpl.partial
@@ -8,7 +8,7 @@
{{#_description}}{{/_description}}
-
+
diff --git a/src/ApiDocs/umbracotemplate/styles/main.css b/src/ApiDocs/umbracotemplate/styles/main.css
index 7756b2f7d4..d74d51b150 100644
--- a/src/ApiDocs/umbracotemplate/styles/main.css
+++ b/src/ApiDocs/umbracotemplate/styles/main.css
@@ -63,7 +63,7 @@ a:focus {
}
.navbar-header .navbar-brand {
- background: url(https://our.umbraco.org/assets/images/logo.svg) left center no-repeat;
+ background: url(https://our.umbraco.com/assets/images/logo.svg) left center no-repeat;
background-size: 40px auto;
width:50px;
}
diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs
index d7f81c1bb1..b5af335791 100644
--- a/src/SolutionInfo.cs
+++ b/src/SolutionInfo.cs
@@ -2,7 +2,7 @@
using System.Resources;
[assembly: AssemblyCompany("Umbraco")]
-[assembly: AssemblyCopyright("Copyright © Umbraco 2017")]
+[assembly: AssemblyCopyright("Copyright © Umbraco 2018")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
diff --git a/src/Umbraco.Core/Configuration/GlobalSettings.cs b/src/Umbraco.Core/Configuration/GlobalSettings.cs
index 7c274089f7..c00ab795d2 100644
--- a/src/Umbraco.Core/Configuration/GlobalSettings.cs
+++ b/src/Umbraco.Core/Configuration/GlobalSettings.cs
@@ -61,7 +61,8 @@ namespace Umbraco.Core.Configuration
var config = WebConfigurationManager.OpenWebConfiguration(appPath);
var settings = (MailSettingsSectionGroup)config.GetSectionGroup("system.net/mailSettings");
- if (settings == null || settings.Smtp == null) return false;
+ // note: "noreply@example.com" is/was the sample SMTP from email - we'll regard that as "not configured"
+ if (settings == null || settings.Smtp == null || "noreply@example.com".Equals(settings.Smtp.From, StringComparison.OrdinalIgnoreCase)) return false;
if (settings.Smtp.SpecifiedPickupDirectory != null && string.IsNullOrEmpty(settings.Smtp.SpecifiedPickupDirectory.PickupDirectoryLocation) == false)
return true;
if (settings.Smtp.Network != null && string.IsNullOrEmpty(settings.Smtp.Network.Host) == false)
diff --git a/src/Umbraco.Core/Configuration/UmbracoConfig.cs b/src/Umbraco.Core/Configuration/UmbracoConfig.cs
index 6dd5617992..6a1203313e 100644
--- a/src/Umbraco.Core/Configuration/UmbracoConfig.cs
+++ b/src/Umbraco.Core/Configuration/UmbracoConfig.cs
@@ -193,4 +193,4 @@ namespace Umbraco.Core.Configuration
//TODO: Add other configurations here !
}
-}
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs
index 39861ac4e9..d2236bab70 100644
--- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs
+++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs
@@ -81,12 +81,6 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
[ConfigurationProperty("loginBackgroundImage")]
internal InnerTextConfigurationElement LoginBackgroundImage => GetOptionalTextElement("loginBackgroundImage", string.Empty);
- [ConfigurationProperty("StripUdiAttributes")]
- internal InnerTextConfigurationElement StripUdiAttributes
- {
- get { return GetOptionalTextElement("StripUdiAttributes", true); }
- }
-
string IContentSection.NotificationEmailAddress => Notifications.NotificationEmailAddress;
@@ -142,7 +136,6 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
bool IContentSection.EnableInheritedMediaTypes => EnableInheritedMediaTypes;
- bool IContentSection.StripUdiAttributes => StripUdiAttributes;
string IContentSection.LoginBackgroundImage => LoginBackgroundImage;
}
diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs
index ef9ffeb014..fe2eea5d91 100644
--- a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs
+++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs
@@ -66,7 +66,5 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
bool EnableInheritedMediaTypes { get; }
string LoginBackgroundImage { get; }
- bool StripUdiAttributes { get; }
-
}
-}
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Constants-Applications.cs b/src/Umbraco.Core/Constants-Applications.cs
index ac42274a71..4aa1760a45 100644
--- a/src/Umbraco.Core/Constants-Applications.cs
+++ b/src/Umbraco.Core/Constants-Applications.cs
@@ -145,6 +145,15 @@
public const string PartialViewMacros = "partialViewMacros";
+ public static class Groups
+ {
+ public const string Settings = "settingsGroup";
+
+ public const string Templating = "templatingGroup";
+
+ public const string ThirdParty = "thirdPartyGroup";
+ }
+
//TODO: Fill in the rest!
}
}
diff --git a/src/Umbraco.Core/Constants-DataTypes.cs b/src/Umbraco.Core/Constants-DataTypes.cs
index c9a33ba04d..f2b31be28f 100644
--- a/src/Umbraco.Core/Constants-DataTypes.cs
+++ b/src/Umbraco.Core/Constants-DataTypes.cs
@@ -13,7 +13,9 @@
public const int Textbox = -88;
public const int Boolean = -49;
- public const int Datetime = -36;
+ public const int DateTime = -36;
+ public const int DropDownSingle = -39;
+ public const int DropDownMultiple = -42;
public const int DefaultContentListView = -95;
public const int DefaultMediaListView = -96;
diff --git a/src/Umbraco.Core/IO/SystemDirectories.cs b/src/Umbraco.Core/IO/SystemDirectories.cs
index c8eedb1614..a0fa732c8c 100644
--- a/src/Umbraco.Core/IO/SystemDirectories.cs
+++ b/src/Umbraco.Core/IO/SystemDirectories.cs
@@ -6,10 +6,7 @@ namespace Umbraco.Core.IO
//all paths has a starting but no trailing /
public class SystemDirectories
{
- //TODO: Why on earth is this even configurable? You cannot change the /Bin folder in ASP.Net
- public static string Bin => IOHelper.ReturnPath("umbracoBinDirectory", "~/bin");
-
- public static string Base => IOHelper.ReturnPath("umbracoBaseDirectory", "~/base");
+ public static string Bin => "~/bin";
public static string Config => IOHelper.ReturnPath("umbracoConfigDirectory", "~/config");
@@ -19,6 +16,7 @@ namespace Umbraco.Core.IO
public static string Install => IOHelper.ReturnPath("umbracoInstallPath", "~/install");
+ //TODO: Consider removing this
public static string Masterpages => IOHelper.ReturnPath("umbracoMasterPagesPath", "~/masterpages");
//NOTE: this is not configurable and shouldn't need to be
@@ -40,9 +38,7 @@ namespace Umbraco.Core.IO
public static string Umbraco => IOHelper.ReturnPath("umbracoPath", "~/umbraco");
- [Obsolete("This will be removed, there is no more umbraco_client folder")]
- public static string UmbracoClient => IOHelper.ReturnPath("umbracoClientPath", "~/umbraco_client");
-
+ //TODO: Consider removing this
public static string UserControls => IOHelper.ReturnPath("umbracoUsercontrolsPath", "~/usercontrols");
public static string WebServices => IOHelper.ReturnPath("umbracoWebservicesPath", Umbraco.EnsureEndsWith("/") + "webservices");
diff --git a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs
index 6b8534a88f..d5f6c2b8c4 100644
--- a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs
+++ b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs
@@ -6,6 +6,7 @@ using System.Text.RegularExpressions;
using Umbraco.Core.IO;
using Umbraco.Core.Models;
using Umbraco.Core.Models.ContentEditing;
+using Umbraco.Core.Models.Membership;
namespace Umbraco.Core.Manifest
{
@@ -19,7 +20,8 @@ namespace Umbraco.Core.Manifest
// show: [ // optional, default is always show
// '-content/foo', // hide for content type 'foo'
// '+content/*', // show for all other content types
- // '+media/*' // show for all media types
+ // '+media/*', // show for all media types
+ // '+role/admin' // show for admin users. Role based permissions will override others.
// ]
// },
// ...
@@ -82,7 +84,7 @@ namespace Umbraco.Core.Manifest
public string[] Show { get; set; } = Array.Empty();
///
- public ContentApp GetContentAppFor(object o)
+ public ContentApp GetContentAppFor(object o, IEnumerable userGroups)
{
string partA, partB;
@@ -103,15 +105,49 @@ namespace Umbraco.Core.Manifest
}
var rules = _showRules ?? (_showRules = ShowRule.Parse(Show).ToArray());
+ var userGroupsList = userGroups.ToList();
- // if no 'show' is specified, then always display the content app
- if (rules.Length > 0)
+ var okRole = false;
+ var hasRole = false;
+ var okType = false;
+ var hasType = false;
+
+ foreach (var rule in rules)
{
- var ok = false;
-
- // else iterate over each entry
- foreach (var rule in rules)
+ if (rule.PartA.InvariantEquals("role"))
{
+ // if roles have been ok-ed already, skip the rule
+ if (okRole)
+ continue;
+
+ // remember we have role rules
+ hasRole = true;
+
+ foreach (var group in userGroupsList)
+ {
+ // if the entry does not apply, skip
+ if (!rule.Matches("role", group.Alias))
+ continue;
+
+ // if the entry applies,
+ // if it's an exclude entry, exit, do not display the content app
+ if (!rule.Show)
+ return null;
+
+ // else ok to display, remember roles are ok, break from userGroupsList
+ okRole = rule.Show;
+ break;
+ }
+ }
+ else // it is a type rule
+ {
+ // if type has been ok-ed already, skip the rule
+ if (okType)
+ continue;
+
+ // remember we have type rules
+ hasType = true;
+
// if the entry does not apply, skip it
if (!rule.Matches(partA, partB))
continue;
@@ -121,16 +157,18 @@ namespace Umbraco.Core.Manifest
if (!rule.Show)
return null;
- // else break - ok to display
- ok = true;
- break;
+ // else ok to display, remember type rules are ok
+ okType = true;
}
-
- // when 'show' is specified, default is to *not* show the content app
- if (!ok)
- return null;
}
+ // if roles rules are specified but not ok,
+ // or if type roles are specified but not ok,
+ // cannot display the content app
+ if ((hasRole && !okRole) || (hasType && !okType))
+ return null;
+
+ // else
// content app can be displayed
return _app ?? (_app = new ContentApp
{
diff --git a/src/Umbraco.Core/Migrations/Expressions/Alter/Expressions/AlterColumnExpression.cs b/src/Umbraco.Core/Migrations/Expressions/Alter/Expressions/AlterColumnExpression.cs
index f19fc94c97..1b00b03ca2 100644
--- a/src/Umbraco.Core/Migrations/Expressions/Alter/Expressions/AlterColumnExpression.cs
+++ b/src/Umbraco.Core/Migrations/Expressions/Alter/Expressions/AlterColumnExpression.cs
@@ -18,7 +18,6 @@ namespace Umbraco.Core.Migrations.Expressions.Alter.Expressions
protected override string GetSql()
{
-
return string.Format(SqlSyntax.AlterColumn,
SqlSyntax.GetQuotedTableName(TableName),
SqlSyntax.Format(Column));
diff --git a/src/Umbraco.Core/Migrations/Expressions/Alter/Table/AlterTableBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Alter/Table/AlterTableBuilder.cs
index 989ce95002..8d95d9c14c 100644
--- a/src/Umbraco.Core/Migrations/Expressions/Alter/Table/AlterTableBuilder.cs
+++ b/src/Umbraco.Core/Migrations/Expressions/Alter/Table/AlterTableBuilder.cs
@@ -2,6 +2,8 @@
using NPoco;
using Umbraco.Core.Migrations.Expressions.Alter.Expressions;
using Umbraco.Core.Migrations.Expressions.Common.Expressions;
+using Umbraco.Core.Migrations.Expressions.Create.Expressions;
+using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
namespace Umbraco.Core.Migrations.Expressions.Alter.Table
@@ -87,6 +89,21 @@ namespace Umbraco.Core.Migrations.Expressions.Alter.Table
public IAlterTableColumnOptionBuilder PrimaryKey()
{
CurrentColumn.IsPrimaryKey = true;
+
+ // see notes in CreateTableBuilder
+ if (Expression.DatabaseType.IsMySql() == false)
+ {
+ var expression = new CreateConstraintExpression(_context, ConstraintType.PrimaryKey)
+ {
+ Constraint =
+ {
+ TableName = Expression.TableName,
+ Columns = new[] { CurrentColumn.Name }
+ }
+ };
+ Expression.Expressions.Add(expression);
+ }
+
return this;
}
@@ -94,6 +111,22 @@ namespace Umbraco.Core.Migrations.Expressions.Alter.Table
{
CurrentColumn.IsPrimaryKey = true;
CurrentColumn.PrimaryKeyName = primaryKeyName;
+
+ // see notes in CreateTableBuilder
+ if (Expression.DatabaseType.IsMySql() == false)
+ {
+ var expression = new CreateConstraintExpression(_context, ConstraintType.PrimaryKey)
+ {
+ Constraint =
+ {
+ ConstraintName = primaryKeyName,
+ TableName = Expression.TableName,
+ Columns = new[] { CurrentColumn.Name }
+ }
+ };
+ Expression.Expressions.Add(expression);
+ }
+
return this;
}
diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs
index ba491fb5e1..c4ebfb664b 100644
--- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs
+++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs
@@ -114,15 +114,15 @@ namespace Umbraco.Core.Migrations.Install
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -51, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-51", SortOrder = 2, UniqueId = new Guid("2e6d3631-066e-44b8-aec4-96f09099b2b5"), Text = "Numeric", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -49, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-49", SortOrder = 2, UniqueId = new Guid("92897bc6-a5f3-4ffe-ae27-f2e7e33dda49"), Text = "True/false", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -43, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-43", SortOrder = 2, UniqueId = new Guid("fbaf13a8-4036-41f2-93a3-974f678c312a"), Text = "Checkbox list", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
- _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -42, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-42", SortOrder = 2, UniqueId = new Guid("0b6a45e7-44ba-430d-9da5-4e46060b9e03"), Text = "Dropdown", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
+ _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DropDownMultiple, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DropDownMultiple}", SortOrder = 2, UniqueId = new Guid("0b6a45e7-44ba-430d-9da5-4e46060b9e03"), Text = "Dropdown", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -41, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-41", SortOrder = 2, UniqueId = new Guid("5046194e-4237-453c-a547-15db3a07c4e1"), Text = "Date Picker", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -40, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-40", SortOrder = 2, UniqueId = new Guid("bb5f57c9-ce2b-4bb9-b697-4caca783a805"), Text = "Radiobox", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -39, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-39", SortOrder = 2, UniqueId = new Guid("f38f0ac7-1d27-439c-9f3f-089cd8825a53"), Text = "Dropdown multiple", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -37, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-37", SortOrder = 2, UniqueId = new Guid("0225af17-b302-49cb-9176-b9f35cab9c17"), Text = "Approved Color", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -36, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-36", SortOrder = 2, UniqueId = new Guid("e4d66c0f-b935-4200-81f0-025f7256b89a"), Text = "Date Picker with time", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
- _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultContentListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-95", SortOrder = 2, UniqueId = new Guid("C0808DD3-8133-4E4B-8CE8-E2BEA84A96A4"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Content", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
- _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultMediaListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-96", SortOrder = 2, UniqueId = new Guid("3A0156C4-3B8C-4803-BDC1-6871FAA83FFF"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Media", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
- _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultMembersListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-97", SortOrder = 2, UniqueId = new Guid("AA2C52A0-CE87-4E65-A47C-7DF09358585D"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Members", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
+ _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultContentListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DefaultContentListView}", SortOrder = 2, UniqueId = new Guid("C0808DD3-8133-4E4B-8CE8-E2BEA84A96A4"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Content", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
+ _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultMediaListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DefaultMediaListView}", SortOrder = 2, UniqueId = new Guid("3A0156C4-3B8C-4803-BDC1-6871FAA83FFF"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Media", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
+ _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultMembersListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DefaultMembersListView}", SortOrder = 2, UniqueId = new Guid("AA2C52A0-CE87-4E65-A47C-7DF09358585D"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Members", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now });
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1031, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1031", SortOrder = 2, UniqueId = new Guid("f38bd2d7-65d0-48e6-95dc-87ce06ec2d3d"), Text = Constants.Conventions.MediaTypes.Folder, NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now });
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1032, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1032", SortOrder = 2, UniqueId = new Guid("cc07b313-0843-4aa8-bbda-871c8da728c8"), Text = Constants.Conventions.MediaTypes.Image, NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now });
_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1033, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1033", SortOrder = 2, UniqueId = new Guid("4c52d8ab-54e6-40cd-999c-7a5f24903e4d"), Text = Constants.Conventions.MediaTypes.File, NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now });
@@ -149,6 +149,7 @@ namespace Umbraco.Core.Migrations.Install
_database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.MemberTypes, Name = "MemberTypes" });
_database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.MemberTree, Name = "MemberTree" });
_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" });
}
@@ -242,12 +243,12 @@ namespace Umbraco.Core.Migrations.Install
private void CreateDataTypeData()
{
- void InsertDataTypeDto(int id, string dbType, string configuration = null)
+ void InsertDataTypeDto(int id, string editorAlias, string dbType, string configuration = null)
{
var dataTypeDto = new DataTypeDto
{
NodeId = id,
- EditorAlias = Constants.PropertyEditors.Aliases.NoEdit,
+ EditorAlias = editorAlias,
DbType = dbType
};
@@ -270,18 +271,18 @@ namespace Umbraco.Core.Migrations.Install
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -88, EditorAlias = Constants.PropertyEditors.Aliases.TextBox, DbType = "Nvarchar" });
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -89, EditorAlias = Constants.PropertyEditors.Aliases.TextArea, DbType = "Ntext" });
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -90, EditorAlias = Constants.PropertyEditors.Aliases.UploadField, DbType = "Nvarchar" });
- InsertDataTypeDto(Constants.DataTypes.LabelString, "Nvarchar", "{\"umbracoDataValueType\":\"STRING\"}");
- InsertDataTypeDto(Constants.DataTypes.LabelInt,"Integer", "{\"umbracoDataValueType\":\"INT\"}");
- InsertDataTypeDto(Constants.DataTypes.LabelBigint, "Nvarchar", "{\"umbracoDataValueType\":\"BIGINT\"}");
- InsertDataTypeDto(Constants.DataTypes.LabelDateTime, "Date", "{\"umbracoDataValueType\":\"DATETIME\"}");
- InsertDataTypeDto(Constants.DataTypes.LabelDecimal, "Decimal", "{\"umbracoDataValueType\":\"DECIMAL\"}");
- InsertDataTypeDto(Constants.DataTypes.LabelTime, "Date", "{\"umbracoDataValueType\":\"TIME\"}");
+ InsertDataTypeDto(Constants.DataTypes.LabelString, Constants.PropertyEditors.Aliases.NoEdit, "Nvarchar", "{\"umbracoDataValueType\":\"STRING\"}");
+ InsertDataTypeDto(Constants.DataTypes.LabelInt, Constants.PropertyEditors.Aliases.NoEdit, "Integer", "{\"umbracoDataValueType\":\"INT\"}");
+ InsertDataTypeDto(Constants.DataTypes.LabelBigint, Constants.PropertyEditors.Aliases.NoEdit, "Nvarchar", "{\"umbracoDataValueType\":\"BIGINT\"}");
+ InsertDataTypeDto(Constants.DataTypes.LabelDateTime, Constants.PropertyEditors.Aliases.NoEdit, "Date", "{\"umbracoDataValueType\":\"DATETIME\"}");
+ InsertDataTypeDto(Constants.DataTypes.LabelDecimal, Constants.PropertyEditors.Aliases.NoEdit, "Decimal", "{\"umbracoDataValueType\":\"DECIMAL\"}");
+ InsertDataTypeDto(Constants.DataTypes.LabelTime, Constants.PropertyEditors.Aliases.NoEdit, "Date", "{\"umbracoDataValueType\":\"TIME\"}");
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -36, EditorAlias = Constants.PropertyEditors.Aliases.DateTime, DbType = "Date" });
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -37, EditorAlias = Constants.PropertyEditors.Aliases.ColorPicker, DbType = "Nvarchar" });
- _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -39, EditorAlias = Constants.PropertyEditors.Aliases.DropDownListMultiple, DbType = "Nvarchar" });
+ InsertDataTypeDto(Constants.DataTypes.DropDownSingle, Constants.PropertyEditors.Aliases.DropDownListFlexible, "Nvarchar", "{\"multiple\":false}");
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -40, EditorAlias = Constants.PropertyEditors.Aliases.RadioButtonList, DbType = "Nvarchar" });
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -41, EditorAlias = Constants.PropertyEditors.Aliases.Date, DbType = "Date" });
- _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -42, EditorAlias = Constants.PropertyEditors.Aliases.DropDownList, DbType = "Integer" });
+ InsertDataTypeDto(Constants.DataTypes.DropDownMultiple, Constants.PropertyEditors.Aliases.DropDownListFlexible, "Nvarchar", "{\"multiple\":true}");
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -43, EditorAlias = Constants.PropertyEditors.Aliases.CheckBoxList, DbType = "Nvarchar" });
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1041, EditorAlias = Constants.PropertyEditors.Aliases.Tags, DbType = "Ntext",
Configuration = "{\"group\":\"default\", \"storageType\":\"Json\"}"
diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs
index d45126f07f..28a31f23d5 100644
--- a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs
+++ b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs
@@ -334,16 +334,60 @@ namespace Umbraco.Core.Migrations.Install
#region Utilities
+ ///
+ /// Returns whether a table with the specified exists in the database.
+ ///
+ /// The name of the table.
+ /// true if the table exists; otherwise false.
+ ///
+ ///
+ /// if (schemaHelper.TableExist("MyTable"))
+ /// {
+ /// // do something when the table exists
+ /// }
+ ///
+ ///
public bool TableExists(string tableName)
{
return SqlSyntax.DoesTableExist(_database, tableName);
}
+ ///
+ /// Returns whether the table for the specified exists in the database.
+ ///
+ /// The type representing the DTO/table.
+ /// true if the table exists; otherwise false.
+ ///
+ ///
+ /// if (schemaHelper.TableExist<MyDto>)
+ /// {
+ /// // do something when the table exists
+ /// }
+ ///
+ ///
+ ///
+ /// If has been decorated with an , the name from that
+ /// attribute will be used for the table name. If the attribute is not present, the name
+ /// will be used instead.
+ ///
public bool TableExists()
{
var table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax);
return table != null && TableExists(table.Name);
}
- // this is used in tests
+ ///
+ /// Creates a new table in the database based on the type of .
+ ///
+ /// The type representing the DTO/table.
+ /// Whether the table should be overwritten if it already exists.
+ ///
+ /// If has been decorated with an , the name from that
+ /// attribute will be used for the table name. If the attribute is not present, the name
+ /// will be used instead.
+ ///
+ /// If a table with the same name already exists, the parameter will determine
+ /// whether the table is overwritten. If true, the table will be overwritten, whereas this method will
+ /// not do anything if the parameter is false.
+ ///
internal void CreateTable(bool overwrite = false)
where T : new()
{
@@ -351,6 +395,21 @@ namespace Umbraco.Core.Migrations.Install
CreateTable(overwrite, tableType, new DatabaseDataCreator(_database, _logger));
}
+ ///
+ /// Creates a new table in the database for the specified .
+ ///
+ /// Whether the table should be overwritten if it already exists.
+ /// The the representing the table.
+ ///
+ ///
+ /// If has been decorated with an , the name from
+ /// that attribute will be used for the table name. If the attribute is not present, the name
+ /// will be used instead.
+ ///
+ /// If a table with the same name already exists, the parameter will determine
+ /// whether the table is overwritten. If true, the table will be overwritten, whereas this method will
+ /// not do anything if the parameter is false.
+ ///
public void CreateTable(bool overwrite, Type modelType, DatabaseDataCreator dataCreation)
{
var tableDefinition = DefinitionFactory.GetTableDefinition(modelType, SqlSyntax);
@@ -364,6 +423,8 @@ namespace Umbraco.Core.Migrations.Install
var tableExist = TableExists(tableName);
if (overwrite && tableExist)
{
+ _logger.Info($"Table '{tableName}' already exists, but will be recreated");
+
DropTable(tableName);
tableExist = false;
}
@@ -417,12 +478,38 @@ namespace Umbraco.Core.Migrations.Install
}
transaction.Complete();
+
+ if (overwrite)
+ {
+ _logger.Info($"Table '{tableName}' was recreated");
+ }
+ else
+ {
+ _logger.Info($"New table '{tableName}' was created");
+ }
}
}
-
- _logger.Info("Created table '{TableName}'", tableName);
+ else
+ {
+ // The table exists and was not recreated/overwritten.
+ _logger.Info($"Table '{tableName}' already exists - no changes were made");
+ }
}
+ ///
+ /// Drops the table for the specified .
+ ///
+ /// The type representing the DTO/table.
+ ///
+ ///
+ /// schemaHelper.DropTable<MyDto>);
+ ///
+ ///
+ ///
+ /// If has been decorated with an , the name from that
+ /// attribute will be used for the table name. If the attribute is not present, the name
+ /// will be used instead.
+ ///
public void DropTable(string tableName)
{
var sql = new Sql(string.Format(SqlSyntax.DropTable, SqlSyntax.GetQuotedTableName(tableName)));
diff --git a/src/Umbraco.Core/Migrations/MigrationExpressionBase.cs b/src/Umbraco.Core/Migrations/MigrationExpressionBase.cs
index f1c535b466..4c5a1f1aa7 100644
--- a/src/Umbraco.Core/Migrations/MigrationExpressionBase.cs
+++ b/src/Umbraco.Core/Migrations/MigrationExpressionBase.cs
@@ -55,7 +55,7 @@ namespace Umbraco.Core.Migrations
if (string.IsNullOrWhiteSpace(sql))
{
- Logger.Info(GetType(), "SQL [{ContextIndex}: ", Context.Index);
+ Logger.Info(GetType(), "SQL [{ContextIndex}]: ", Context.Index);
}
else
{
diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs
index eeaf7533a9..5b0838573e 100644
--- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs
+++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs
@@ -137,6 +137,7 @@ namespace Umbraco.Core.Migrations.Upgrade
// resume at {290C18EE-B3DE-4769-84F1-1F467F3F76DA}...
Chain("{6A2C7C1B-A9DB-4EA9-B6AB-78E7D5B722A7}");
+ Chain("{77874C77-93E5-4488-A404-A630907CEEF0}");
//FINAL
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/FixLockTablePrimaryKey.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/FixLockTablePrimaryKey.cs
new file mode 100644
index 0000000000..fbb233927b
--- /dev/null
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/FixLockTablePrimaryKey.cs
@@ -0,0 +1,23 @@
+using System.Linq;
+
+namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
+{
+ public class FixLockTablePrimaryKey : MigrationBase
+ {
+ public FixLockTablePrimaryKey(IMigrationContext context)
+ : base(context)
+ { }
+
+ public override void Migrate()
+ {
+ // at some point, the KeyValueService dropped the PK and failed to re-create it,
+ // so the PK is gone - make sure we have one, and create if needed
+
+ var constraints = SqlSyntax.GetConstraintsPerTable(Database);
+ var exists = constraints.Any(x => x.Item2 == "PK_umbracoLock");
+
+ if (!exists)
+ Create.PrimaryKey("PK_umbracoLock").OnTable(Constants.DatabaseSchema.Tables.Lock).Column("id").Do();
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs
index 238d87b186..5d8a4f7222 100644
--- a/src/Umbraco.Core/Models/Content.cs
+++ b/src/Umbraco.Core/Models/Content.cs
@@ -227,7 +227,27 @@ namespace Umbraco.Core.Models
public bool WasCulturePublished(string culture)
// just check _publishInfosOrig - a copy of _publishInfos
// a non-available culture could not become published anyways
- => _publishInfosOrig != null && _publishInfosOrig.ContainsKey(culture);
+ => _publishInfosOrig != null && _publishInfosOrig.ContainsKey(culture);
+
+ // adjust dates to sync between version, cultures etc
+ // used by the repo when persisting
+ internal void AdjustDates(DateTime date)
+ {
+ foreach (var culture in PublishedCultures.ToList())
+ {
+ if (_publishInfos == null || !_publishInfos.TryGetValue(culture, out var publishInfos))
+ continue;
+
+ if (_publishInfosOrig != null && _publishInfosOrig.TryGetValue(culture, out var publishInfosOrig)
+ && publishInfosOrig.Date == publishInfos.Date)
+ continue;
+
+ _publishInfos[culture] = (publishInfos.Name, date);
+
+ if (CultureNames.TryGetValue(culture, out var name))
+ SetCultureInfo(culture, name, date);
+ }
+ }
///
public bool IsCultureEdited(string culture)
@@ -343,6 +363,12 @@ namespace Umbraco.Core.Models
SetPublishInfo(c, name, DateTime.Now);
}
}
+ else if (culture == null) // invariant culture
+ {
+ if (string.IsNullOrWhiteSpace(Name))
+ return false;
+ // PublishName set by repository - nothing to do here
+ }
else // one single culture
{
var name = GetCultureName(culture);
diff --git a/src/Umbraco.Core/Models/ContentEditing/IContentAppDefinition.cs b/src/Umbraco.Core/Models/ContentEditing/IContentAppDefinition.cs
index 5e0c421742..2d30fc6ba9 100644
--- a/src/Umbraco.Core/Models/ContentEditing/IContentAppDefinition.cs
+++ b/src/Umbraco.Core/Models/ContentEditing/IContentAppDefinition.cs
@@ -1,4 +1,7 @@
-namespace Umbraco.Core.Models.ContentEditing
+using System.Collections.Generic;
+using Umbraco.Core.Models.Membership;
+
+namespace Umbraco.Core.Models.ContentEditing
{
///
/// Represents a content app definition.
@@ -15,6 +18,6 @@
/// the content app should be displayed or not, and return either a
/// instance, or null.
///
- ContentApp GetContentAppFor(object source);
+ ContentApp GetContentAppFor(object source, IEnumerable userGroups);
}
}
diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs
index 6f90b5201d..9e73205c36 100644
--- a/src/Umbraco.Core/Models/ContentTypeBase.cs
+++ b/src/Umbraco.Core/Models/ContentTypeBase.cs
@@ -363,22 +363,28 @@ namespace Umbraco.Core.Models
/// Alias of the to remove
public void RemovePropertyType(string propertyTypeAlias)
{
- //check if the property exist in one of our collections
- if (PropertyGroups.Any(group => group.PropertyTypes.Any(pt => pt.Alias == propertyTypeAlias))
- || _propertyTypes.Any(x => x.Alias == propertyTypeAlias))
- {
- //set the flag that a property has been removed
- HasPropertyTypeBeenRemoved = true;
- }
-
+ //check through each property group to see if we can remove the property type by alias from it
foreach (var propertyGroup in PropertyGroups)
{
- propertyGroup.PropertyTypes.RemoveItem(propertyTypeAlias);
+ if (propertyGroup.PropertyTypes.RemoveItem(propertyTypeAlias))
+ {
+ if (!HasPropertyTypeBeenRemoved)
+ {
+ HasPropertyTypeBeenRemoved = true;
+ OnPropertyChanged(Ps.Value.PropertyTypeCollectionSelector);
+ }
+ break;
+ }
}
- if (_propertyTypes.Any(x => x.Alias == propertyTypeAlias))
+ //check through each local property type collection (not assigned to a tab)
+ if (_propertyTypes.RemoveItem(propertyTypeAlias))
{
- _propertyTypes.RemoveItem(propertyTypeAlias);
+ if (!HasPropertyTypeBeenRemoved)
+ {
+ HasPropertyTypeBeenRemoved = true;
+ OnPropertyChanged(Ps.Value.PropertyTypeCollectionSelector);
+ }
}
}
@@ -408,23 +414,9 @@ namespace Umbraco.Core.Models
/// PropertyTypes that are not part of a PropertyGroup
///
[IgnoreDataMember]
+ //fixme should we mark this as EditorBrowsable hidden since it really isn't ever used?
internal PropertyTypeCollection PropertyTypeCollection => _propertyTypes;
- ///
- /// Indicates whether a specific property on the current entity is dirty.
- ///
- /// Name of the property to check
- /// True if Property is dirty, otherwise False
- public override bool IsPropertyDirty(string propertyName)
- {
- bool existsInEntity = base.IsPropertyDirty(propertyName);
-
- bool anyDirtyGroups = PropertyGroups.Any(x => x.IsPropertyDirty(propertyName));
- bool anyDirtyTypes = PropertyTypes.Any(x => x.IsPropertyDirty(propertyName));
-
- return existsInEntity || anyDirtyGroups || anyDirtyTypes;
- }
-
///
/// Indicates whether the current entity is dirty.
///
diff --git a/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs b/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs
index ef55f0d469..0d2f817660 100644
--- a/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs
+++ b/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs
@@ -1,4 +1,8 @@
-using Umbraco.Core.Models.PublishedContent;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Umbraco.Core.Models.Entities;
+using Umbraco.Core.Models.PublishedContent;
namespace Umbraco.Core.Models
{
@@ -16,5 +20,63 @@ namespace Umbraco.Core.Models
else if (typeof(IMemberType).IsAssignableFrom(type)) itemType = PublishedItemType.Member;
return itemType;
}
+
+ ///
+ /// Used to check if any property type was changed between variant/invariant
+ ///
+ ///
+ ///
+ internal static bool WasPropertyTypeVariationChanged(this IContentTypeBase contentType)
+ {
+ return contentType.WasPropertyTypeVariationChanged(out var _);
+ }
+
+ ///
+ /// Used to check if any property type was changed between variant/invariant
+ ///
+ ///
+ ///
+ internal static bool WasPropertyTypeVariationChanged(this IContentTypeBase contentType, out IReadOnlyCollection aliases)
+ {
+ var a = new List();
+
+ // property variation change?
+ var hasAnyPropertyVariationChanged = contentType.PropertyTypes.Any(propertyType =>
+ {
+ if (!(propertyType is IRememberBeingDirty dirtyProperty))
+ throw new Exception("oops");
+
+ // skip new properties
+ //TODO: This used to be WasPropertyDirty("HasIdentity") but i don't think that actually worked for detecting new entities this does seem to work properly
+ var isNewProperty = dirtyProperty.WasPropertyDirty("Id");
+ if (isNewProperty) return false;
+
+ // variation change?
+ var dirty = dirtyProperty.WasPropertyDirty("Variations");
+ if (dirty)
+ a.Add(propertyType.Alias);
+
+ return dirty;
+
+ });
+
+ aliases = a;
+ return hasAnyPropertyVariationChanged;
+ }
+
+ ///
+ /// Returns the list of content types the composition is used in
+ ///
+ ///
+ ///
+ ///
+ internal static IEnumerable GetWhereCompositionIsUsedInContentTypes(this IContentTypeComposition source,
+ IContentTypeComposition[] allContentTypes)
+ {
+ var sourceId = source != null ? source.Id : 0;
+
+ // find which content types are using this composition
+ return allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == sourceId)).ToArray();
+ }
}
}
diff --git a/src/Umbraco.Core/Models/IContentTypeBase.cs b/src/Umbraco.Core/Models/IContentTypeBase.cs
index ef5988344e..a1d4aee02f 100644
--- a/src/Umbraco.Core/Models/IContentTypeBase.cs
+++ b/src/Umbraco.Core/Models/IContentTypeBase.cs
@@ -21,7 +21,12 @@ namespace Umbraco.Core.Models
string Description { get; set; }
///
- /// Gets or Sets the Icon for the ContentType
+ /// Gets or sets the icon for the content type. The value is a CSS class name representing
+ /// the icon (eg. icon-home) along with an optional CSS class name representing the
+ /// color (eg. icon-blue). Put together, the value for this scenario would be
+ /// icon-home color-blue.
+ ///
+ /// If a class name for the color isn't specified, the icon color will default to black.
///
string Icon { get; set; }
diff --git a/src/Umbraco.Core/Models/IContentTypeComposition.cs b/src/Umbraco.Core/Models/IContentTypeComposition.cs
index b5277a23be..36ace19f0f 100644
--- a/src/Umbraco.Core/Models/IContentTypeComposition.cs
+++ b/src/Umbraco.Core/Models/IContentTypeComposition.cs
@@ -10,6 +10,7 @@ namespace Umbraco.Core.Models
///
/// Gets or sets the content types that compose this content type.
///
+ //fixme: we should be storing key references, not the object else we are caching way too much
IEnumerable ContentTypeComposition { get; set; }
///
diff --git a/src/Umbraco.Core/Models/PropertyTypeCollection.cs b/src/Umbraco.Core/Models/PropertyTypeCollection.cs
index 53b75f7e48..47710e04cb 100644
--- a/src/Umbraco.Core/Models/PropertyTypeCollection.cs
+++ b/src/Umbraco.Core/Models/PropertyTypeCollection.cs
@@ -124,10 +124,11 @@ namespace Umbraco.Core.Models
return this.Any(x => x.Alias == propertyAlias);
}
- public void RemoveItem(string propertyTypeAlias)
+ public bool RemoveItem(string propertyTypeAlias)
{
var key = IndexOfKey(propertyTypeAlias);
if (key != -1) RemoveItem(key);
+ return key != -1;
}
public int IndexOfKey(string key)
diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs
index e611ded6c8..f1937c1c0c 100644
--- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs
@@ -94,10 +94,10 @@ namespace Umbraco.Core.Models.PublishedContent
{ "Comments", (Constants.DataTypes.Textbox, Constants.PropertyEditors.Aliases.TextBox) },
{ "IsApproved", (Constants.DataTypes.Boolean, Constants.PropertyEditors.Aliases.Boolean) },
{ "IsLockedOut", (Constants.DataTypes.Boolean, Constants.PropertyEditors.Aliases.Boolean) },
- { "LastLockoutDate", (Constants.DataTypes.Datetime, Constants.PropertyEditors.Aliases.DateTime) },
- { "CreateDate", (Constants.DataTypes.Datetime, Constants.PropertyEditors.Aliases.DateTime) },
- { "LastLoginDate", (Constants.DataTypes.Datetime, Constants.PropertyEditors.Aliases.DateTime) },
- { "LastPasswordChangeDate", (Constants.DataTypes.Datetime, Constants.PropertyEditors.Aliases.DateTime) },
+ { "LastLockoutDate", (Constants.DataTypes.DateTime, Constants.PropertyEditors.Aliases.DateTime) },
+ { "CreateDate", (Constants.DataTypes.DateTime, Constants.PropertyEditors.Aliases.DateTime) },
+ { "LastLoginDate", (Constants.DataTypes.DateTime, Constants.PropertyEditors.Aliases.DateTime) },
+ { "LastPasswordChangeDate", (Constants.DataTypes.DateTime, Constants.PropertyEditors.Aliases.DateTime) },
};
#region Content type
diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs
index c72a89c1f2..67758c1c69 100644
--- a/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs
@@ -71,7 +71,7 @@ namespace Umbraco.Core.Models.PublishedContent
throw new InvalidOperationException($"Both types {type.FullName} and {modelInfo.ModelType.FullName} want to be a model type for content type with alias \"{typeName}\".");
// have to use an unsafe ctor because we don't know the types, really
- var modelCtor = ReflectionUtilities.EmitCtorUnsafe>(constructor);
+ var modelCtor = ReflectionUtilities.EmitConstructorUnsafe>(constructor);
modelInfos[typeName] = new ModelInfo { ParameterType = parameterType, ModelType = type, Ctor = modelCtor };
modelTypeMap[typeName] = type;
}
@@ -112,7 +112,7 @@ namespace Umbraco.Core.Models.PublishedContent
if (ctor != null) return ctor();
var listType = typeof(List<>).MakeGenericType(modelInfo.ModelType);
- ctor = modelInfo.ListCtor = ReflectionUtilities.EmitCtor>(declaring: listType);
+ ctor = modelInfo.ListCtor = ReflectionUtilities.EmitConstuctor>(declaring: listType);
return ctor();
}
diff --git a/src/Umbraco.Core/Packaging/PackageInstallation.cs b/src/Umbraco.Core/Packaging/PackageInstallation.cs
index a6f1dd0f25..556c5203ec 100644
--- a/src/Umbraco.Core/Packaging/PackageInstallation.cs
+++ b/src/Umbraco.Core/Packaging/PackageInstallation.cs
@@ -576,7 +576,6 @@ namespace Umbraco.Core.Packaging
{
//this is experimental and undocumented...
path = path.Replace("[$UMBRACO]", SystemDirectories.Umbraco);
- path = path.Replace("[$UMBRACOCLIENT]", SystemDirectories.UmbracoClient);
path = path.Replace("[$CONFIG]", SystemDirectories.Config);
path = path.Replace("[$DATA]", SystemDirectories.Data);
}
diff --git a/src/Umbraco.Core/Persistence/Dtos/LockDto.cs b/src/Umbraco.Core/Persistence/Dtos/LockDto.cs
index 833d262e26..b5878141f3 100644
--- a/src/Umbraco.Core/Persistence/Dtos/LockDto.cs
+++ b/src/Umbraco.Core/Persistence/Dtos/LockDto.cs
@@ -4,12 +4,12 @@ using Umbraco.Core.Persistence.DatabaseAnnotations;
namespace Umbraco.Core.Persistence.Dtos
{
[TableName(Constants.DatabaseSchema.Tables.Lock)]
- [PrimaryKey("id")]
+ [PrimaryKey("id", AutoIncrement = false)]
[ExplicitColumns]
internal class LockDto
{
[Column("id")]
- [PrimaryKeyColumn(Name = "PK_umbracoLock")]
+ [PrimaryKeyColumn(Name = "PK_umbracoLock", AutoIncrement = false)]
public int Id { get; set; }
[Column("value")]
diff --git a/src/Umbraco.Core/Persistence/Dtos/PropertyDataDto.cs b/src/Umbraco.Core/Persistence/Dtos/PropertyDataDto.cs
index cb47784d92..a3b28b5b54 100644
--- a/src/Umbraco.Core/Persistence/Dtos/PropertyDataDto.cs
+++ b/src/Umbraco.Core/Persistence/Dtos/PropertyDataDto.cs
@@ -9,7 +9,7 @@ namespace Umbraco.Core.Persistence.Dtos
[ExplicitColumns]
internal class PropertyDataDto
{
- private const string TableName = Constants.DatabaseSchema.Tables.PropertyData;
+ public const string TableName = Constants.DatabaseSchema.Tables.PropertyData;
public const int VarcharLength = 512;
public const int SegmentLength = 256;
diff --git a/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs b/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs
index d97c748b6f..a5ab62d25f 100644
--- a/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs
+++ b/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs
@@ -73,7 +73,7 @@ namespace Umbraco.Core.Persistence
/// The Sql statement.
public static Sql Where(this Sql sql, Expression> predicate, string alias = null)
{
- var (s, a) = sql.SqlContext.Visit(predicate, alias);
+ var (s, a) = sql.SqlContext.VisitDto(predicate, alias);
return sql.Where(s, a);
}
@@ -89,7 +89,7 @@ namespace Umbraco.Core.Persistence
/// The Sql statement.
public static Sql Where(this Sql sql, Expression> predicate, string alias1 = null, string alias2 = null)
{
- var (s, a) = sql.SqlContext.Visit(predicate, alias1, alias2);
+ var (s, a) = sql.SqlContext.VisitDto(predicate, alias1, alias2);
return sql.Where(s, a);
}
@@ -321,9 +321,9 @@ namespace Umbraco.Core.Persistence
/// Appends an ORDER BY DESC clause to the Sql statement.
///
/// The Sql statement.
- /// Expression specifying the fields.
+ /// Fields.
/// The Sql statement.
- public static Sql OrderByDescending(this Sql sql, params object[] fields)
+ public static Sql OrderByDescending(this Sql sql, params string[] fields)
{
return sql.Append("ORDER BY " + string.Join(", ", fields.Select(x => x + " DESC")));
}
@@ -660,6 +660,18 @@ namespace Umbraco.Core.Persistence
return sql.Select(sql.GetColumns(tableAlias: tableAlias, columnExpressions: fields));
}
+ ///
+ /// Adds columns to a SELECT Sql statement.
+ ///
+ /// The origin sql.
+ /// Columns to select.
+ /// The Sql statement.
+ public static Sql AndSelect(this Sql sql, params string[] fields)
+ {
+ if (sql == null) throw new ArgumentNullException(nameof(sql));
+ return sql.Append(", " + string.Join(", ", fields));
+ }
+
///
/// Adds columns to a SELECT Sql statement.
///
@@ -676,7 +688,6 @@ namespace Umbraco.Core.Persistence
return sql.Append(", " + string.Join(", ", sql.GetColumns(columnExpressions: fields)));
}
-
///
/// Adds columns to a SELECT Sql statement.
///
@@ -1036,7 +1047,7 @@ namespace Umbraco.Core.Persistence
{
var pd = sql.SqlContext.PocoDataFactory.ForType(typeof (TDto));
var tableName = tableAlias ?? pd.TableInfo.TableName;
- var queryColumns = pd.QueryColumns;
+ var queryColumns = pd.QueryColumns.ToList();
Dictionary aliases = null;
@@ -1056,7 +1067,11 @@ namespace Umbraco.Core.Persistence
return fieldName;
}).ToArray();
- queryColumns = queryColumns.Where(x => names.Contains(x.Key)).ToArray();
+ //only get the columns that exist in the selected names
+ queryColumns = queryColumns.Where(x => names.Contains(x.Key)).ToList();
+
+ //ensure the order of the columns in the expressions is the order in the result
+ queryColumns.Sort((a, b) => names.IndexOf(a.Key).CompareTo(names.IndexOf(b.Key)));
}
string GetAlias(PocoColumn column)
diff --git a/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs
index f7341d112b..217719e144 100644
--- a/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs
@@ -19,6 +19,12 @@ namespace Umbraco.Core.Persistence.Repositories
/// Current version is first, and then versions are ordered with most recent first.
IEnumerable GetAllVersions(int nodeId);
+ ///
+ /// Gets versions.
+ ///
+ /// Current version is first, and then versions are ordered with most recent first.
+ IEnumerable GetAllVersionsSlim(int nodeId, int skip, int take);
+
///
/// Gets version identifiers.
///
diff --git a/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs
index f5e9013082..3bb1ac38ca 100644
--- a/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs
@@ -10,6 +10,12 @@ namespace Umbraco.Core.Persistence.Repositories
{
TItem Get(string alias);
IEnumerable> Move(TItem moving, EntityContainer container);
+
+ ///
+ /// Returns the content types that are direct compositions of the content type
+ ///
+ /// The content type id
+ ///
IEnumerable GetTypesDirectlyComposedOf(int id);
///
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs
index 6ace73fbc3..36b7ab07f1 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
@@ -10,7 +9,6 @@ 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;
@@ -19,7 +17,6 @@ using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
-using Umbraco.Core.Services.Implement;
using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics;
namespace Umbraco.Core.Persistence.Repositories.Implement
@@ -56,6 +53,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// gets all versions, current first
public abstract IEnumerable GetAllVersions(int nodeId);
+ // gets all versions, current first
+ public virtual IEnumerable GetAllVersionsSlim(int nodeId, int skip, int take)
+ => GetAllVersions(nodeId).Skip(skip).Take(take);
+
// gets all version ids, current first
public virtual IEnumerable GetVersionIds(int nodeId, int maxRows)
{
@@ -255,8 +256,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// if we do not do this then we end up with issues where we are ordering by a field that has duplicate values (i.e. the 'text' column
// is empty for many nodes) - see: http://issues.umbraco.org/issue/U4-8831
- var dbfield = GetQuotedFieldName("umbracoNode", "id");
- (dbfield, _) = SqlContext.Visit(x => x.NodeId); // fixme?!
+ var (dbfield, _) = SqlContext.VisitDto(x => x.NodeId);
if (ordering.IsCustomField || !ordering.OrderBy.InvariantEquals("id"))
{
psql.OrderBy(GetAliasedField(dbfield, sql)); // fixme why aliased?
@@ -265,6 +265,19 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// create prepared sql
// ensure it's single-line as NPoco PagingHelper has issues with multi-lines
psql = Sql(psql.SQL.ToSingleLine(), psql.Arguments);
+
+ // replace the magic culture parameter (see DocumentRepository.GetBaseQuery())
+ if (!ordering.Culture.IsNullOrWhiteSpace())
+ {
+ for (var i = 0; i < psql.Arguments.Length; i++)
+ {
+ if (psql.Arguments[i] is string s && s == "[[[ISOCODE]]]")
+ {
+ psql.Arguments[i] = ordering.Culture;
+ break;
+ }
+ }
+ }
return psql;
}
@@ -342,20 +355,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (ordering.Culture.IsNullOrWhiteSpace())
return GetAliasedField(SqlSyntax.GetFieldName(x => x.Text), sql);
- // culture = must work on variant name ?? invariant name
- // insert proper join and return coalesced ordering field
-
- var joins = Sql()
- .LeftJoin(nested =>
- nested.InnerJoin("lang").On((ccv, lang) => ccv.LanguageId == lang.Id && lang.IsoCode == ordering.Culture, "ccv", "lang"), "ccv")
- .On((version, ccv) => version.Id == ccv.VersionId, aliasRight: "ccv");
-
- // see notes in ApplyOrdering: the field MUST be selected + aliased
- sql = Sql(InsertBefore(sql, "FROM", ", " + SqlContext.Visit((ccv, node) => ccv.Name ?? node.Text, "ccv").Sql + " AS ordering "), sql.Arguments);
-
- sql = InsertJoins(sql, joins);
-
- return "ordering";
+ // "variantName" alias is defined in DocumentRepository.GetBaseQuery
+ // fixme - what if it is NOT a document but a ... media or whatever?
+ // previously, we inserted the join+select *here* so we were sure to have it,
+ // but now that's not the case anymore!
+ return "variantName";
}
// previously, we'd accept anything and just sanitize it - not anymore
@@ -608,6 +612,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
#region UnitOfWork Events
+ //fixme: The reason these events are in the repository is for legacy, the events should exist at the service
+ // level now since we can fire these events within the transaction... so move the events to service level
+
public class ScopedEntityEventArgs : EventArgs
{
public ScopedEntityEventArgs(IScope scope, TEntity entity)
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
index 555fc56157..3f1ea3116e 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
@@ -274,6 +274,8 @@ AND umbracoNode.id <> @id",
if (compositionBase != null && compositionBase.RemovedContentTypeKeyTracker != null &&
compositionBase.RemovedContentTypeKeyTracker.Any())
{
+ //TODO: Could we do the below with bulk SQL statements instead of looking everything up and then manipulating?
+
// find Content based on the current ContentType
var sql = Sql()
.SelectAll()
@@ -292,6 +294,7 @@ AND umbracoNode.id <> @id",
// based on the PropertyTypes that belong to the removed ContentType.
foreach (var contentDto in contentDtos)
{
+ //TODO: This could be done with bulk SQL statements
foreach (var propertyType in propertyTypes)
{
var nodeId = contentDto.NodeId;
@@ -323,9 +326,7 @@ AND umbracoNode.id <> @id",
});
}
- // fixme below, manage the property type
-
- // delete ??? fixme wtf is this?
+ // delete property types
// ... by excepting entries from db with entries from collections
if (entity.IsPropertyDirty("PropertyTypes") || entity.PropertyTypes.Any(x => x.IsDirty()))
{
@@ -404,10 +405,49 @@ AND umbracoNode.id <> @id",
propertyType.PropertyGroupId = new Lazy(() => groupId);
}
+ //check if the content type variation has been changed
+ var ctVariationChanging = entity.IsPropertyDirty("Variations");
+ if (ctVariationChanging)
+ {
+ //we've already looked up the previous version of the content type so we know it's previous variation state
+ MoveVariantData(entity, (ContentVariation)dtoPk.Variations, entity.Variations);
+ Clear301Redirects(entity);
+ ClearScheduledPublishing(entity);
+ }
+
+ //track any content type/property types that are changing variation which will require content updates
+ var propertyTypeVariationChanges = new Dictionary();
+
// insert or update properties
// all of them, no-group and in-groups
foreach (var propertyType in entity.PropertyTypes)
{
+ //if the content type variation isn't changing track if any property type is changing
+ if (!ctVariationChanging)
+ {
+ if (propertyType.IsPropertyDirty("Variations"))
+ {
+ propertyTypeVariationChanges[propertyType.Id] = propertyType.Variations;
+ }
+ }
+ else
+ {
+ switch(entity.Variations)
+ {
+ case ContentVariation.Nothing:
+ //if the content type is changing to Nothing, then all property type's must change to nothing
+ propertyType.Variations = ContentVariation.Nothing;
+ break;
+ case ContentVariation.Culture:
+ //we don't need to modify the property type in this case
+ break;
+ case ContentVariation.CultureAndSegment:
+ case ContentVariation.Segment:
+ default:
+ throw new NotSupportedException(); //TODO: Support this
+ }
+ }
+
var groupId = propertyType.PropertyGroupId?.Value ?? default(int);
// if the Id of the DataType is not set, we resolve it from the db by its PropertyEditorAlias
if (propertyType.DataTypeId == 0 || propertyType.DataTypeId == default(int))
@@ -431,6 +471,28 @@ AND umbracoNode.id <> @id",
orphanPropertyTypeIds.Remove(typeId);
}
+ //check if any property types were changing variation
+ if (propertyTypeVariationChanges.Count > 0)
+ {
+ var changes = new Dictionary();
+
+ //now get the current property type variations for the changed ones so that we know which variation they
+ //are going from and to
+ var from = Database.Dictionary(Sql()
+ .Select(x => x.Id, x => x.Variations)
+ .From()
+ .WhereIn(x => x.Id, propertyTypeVariationChanges.Keys));
+
+ foreach (var f in from)
+ {
+ changes[f.Key] = (propertyTypeVariationChanges[f.Key], (ContentVariation)f.Value);
+ }
+
+ //perform the move
+ MoveVariantData(changes);
+ }
+
+
// deal with orphan properties: those that were in a deleted tab,
// and have not been re-mapped to another tab or to 'generic properties'
if (orphanPropertyTypeIds != null)
@@ -438,6 +500,221 @@ AND umbracoNode.id <> @id",
DeletePropertyType(entity.Id, id);
}
+ ///
+ /// Clear any redirects associated with content for a content type
+ ///
+ private void Clear301Redirects(IContentTypeComposition contentType)
+ {
+ //first clear out any existing property data that might already exists under the default lang
+ var sqlSelect = Sql().Select(x => x.UniqueId)
+ .From()
+ .InnerJoin().On(x => x.NodeId, x => x.NodeId)
+ .Where(x => x.ContentTypeId == contentType.Id);
+ var sqlDelete = Sql()
+ .Delete()
+ .WhereIn((System.Linq.Expressions.Expression>)(x => x.ContentKey), sqlSelect);
+
+ Database.Execute(sqlDelete);
+ }
+
+ ///
+ /// Clear any scheduled publishing associated with content for a content type
+ ///
+ private void ClearScheduledPublishing(IContentTypeComposition contentType)
+ {
+ //TODO: Fill this in when scheduled publishing is enabled for variants
+ }
+
+ ///
+ /// Moves variant data for property type changes
+ ///
+ ///
+ private void MoveVariantData(IDictionary propertyTypeChanges)
+ {
+ var defaultLangId = Database.First(Sql().Select(x => x.Id).From().Where(x => x.IsDefault));
+
+ //Group by the "To" variation so we can bulk update in the correct batches
+ foreach(var g in propertyTypeChanges.GroupBy(x => x.Value.Item2))
+ {
+ var propertyTypeIds = g.Select(s => s.Key).ToList();
+
+ //the ContentVariation that the data is moving "To"
+ var toVariantType = g.Key;
+
+ switch(toVariantType)
+ {
+ case ContentVariation.Culture:
+ MovePropertyDataToVariantCulture(defaultLangId, propertyTypeIds: propertyTypeIds);
+ break;
+ case ContentVariation.Nothing:
+ MovePropertyDataToVariantNothing(defaultLangId, propertyTypeIds: propertyTypeIds);
+ break;
+ case ContentVariation.CultureAndSegment:
+ case ContentVariation.Segment:
+ default:
+ throw new NotSupportedException(); //TODO: Support this
+ }
+ }
+ }
+
+ ///
+ /// Moves variant data for a content type variation change
+ ///
+ ///
+ ///
+ ///
+ private void MoveVariantData(IContentTypeComposition contentType, ContentVariation from, ContentVariation to)
+ {
+ var defaultLangId = Database.First(Sql().Select(x => x.Id).From().Where(x => x.IsDefault));
+
+ var sqlPropertyTypeIds = Sql().Select(x => x.Id).From().Where(x => x.ContentTypeId == contentType.Id);
+ switch (to)
+ {
+ case ContentVariation.Culture:
+ //move the property data
+
+ MovePropertyDataToVariantCulture(defaultLangId, sqlPropertyTypeIds: sqlPropertyTypeIds);
+
+ //now we need to move the names
+ //first clear out any existing names that might already exists under the default lang
+ //there's 2x tables to update
+
+ //clear out the versionCultureVariation table
+ var sqlSelect = Sql().Select(x => x.Id)
+ .From()
+ .InnerJoin().On(x => x.Id, x => x.VersionId)
+ .InnerJoin().On(x => x.NodeId, x => x.NodeId)
+ .Where(x => x.ContentTypeId == contentType.Id)
+ .Where(x => x.LanguageId == defaultLangId);
+ var sqlDelete = Sql()
+ .Delete()
+ .WhereIn(x => x.Id, sqlSelect);
+ Database.Execute(sqlDelete);
+
+ //clear out the documentCultureVariation table
+ sqlSelect = Sql().Select(x => x.Id)
+ .From()
+ .InnerJoin().On(x => x.NodeId, x => x.NodeId)
+ .Where(x => x.ContentTypeId == contentType.Id)
+ .Where(x => x.LanguageId == defaultLangId);
+ sqlDelete = Sql()
+ .Delete()
+ .WhereIn(x => x.Id, sqlSelect);
+ Database.Execute(sqlDelete);
+
+ //now we need to insert names into these 2 tables based on the invariant data
+
+ //insert rows into the versionCultureVariationDto table based on the data from contentVersionDto for the default lang
+ var cols = Sql().Columns(x => x.VersionId, x => x.Name, x => x.UpdateUserId, x => x.UpdateDate, x => x.LanguageId);
+ sqlSelect = Sql().Select(x => x.Id, x => x.Text, x => x.UserId, x => x.VersionDate)
+ .Append($", {defaultLangId}") //default language ID
+ .From()
+ .InnerJoin().On(x => x.NodeId, x => x.NodeId)
+ .Where(x => x.ContentTypeId == contentType.Id);
+ var sqlInsert = Sql($"INSERT INTO {ContentVersionCultureVariationDto.TableName} ({cols})").Append(sqlSelect);
+ Database.Execute(sqlInsert);
+
+ //insert rows into the documentCultureVariation table
+ cols = Sql().Columns(x => x.NodeId, x => x.Edited, x => x.Published, x => x.Name, x => x.Available, x => x.LanguageId);
+ sqlSelect = Sql().Select(x => x.NodeId, x => x.Edited, x => x.Published)
+ .AndSelect(x => x.Text)
+ .Append($", 1, {defaultLangId}") //make Available + default language ID
+ .From()
+ .InnerJoin().On(x => x.NodeId, x => x.NodeId)
+ .InnerJoin().On(x => x.NodeId, x => x.NodeId)
+ .Where(x => x.ContentTypeId == contentType.Id);
+ sqlInsert = Sql($"INSERT INTO {DocumentCultureVariationDto.TableName} ({cols})").Append(sqlSelect);
+ Database.Execute(sqlInsert);
+
+ break;
+ case ContentVariation.Nothing:
+ //move the property data
+
+ MovePropertyDataToVariantNothing(defaultLangId, sqlPropertyTypeIds: sqlPropertyTypeIds);
+
+ //we dont 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
+
+ break;
+ case ContentVariation.CultureAndSegment:
+ case ContentVariation.Segment:
+ default:
+ throw new NotSupportedException(); //TODO: Support this
+ }
+ }
+
+ ///
+ /// This will move all property data from variant to invariant
+ ///
+ ///
+ /// Optional list of property type ids of the properties to be updated
+ /// Optional SQL statement used for the sub-query to select the properties type ids for the properties to be updated
+ private void MovePropertyDataToVariantNothing(int defaultLangId, IReadOnlyCollection propertyTypeIds = null, Sql sqlPropertyTypeIds = null)
+ {
+ //first clear out any existing property data that might already exists under the default lang
+ var sqlDelete = Sql()
+ .Delete()
+ .Where(x => x.LanguageId == null);
+ if (sqlPropertyTypeIds != null)
+ sqlDelete.WhereIn(x => x.PropertyTypeId, sqlPropertyTypeIds);
+ if (propertyTypeIds != null)
+ sqlDelete.WhereIn(x => x.PropertyTypeId, propertyTypeIds);
+
+ Database.Execute(sqlDelete);
+
+ //now insert all property data into the default language that exists under the invariant lang
+ var cols = Sql().Columns(x => x.VersionId, x => x.PropertyTypeId, x => x.Segment, x => x.IntegerValue, x => x.DecimalValue, x => x.DateValue, x => x.VarcharValue, x => x.TextValue, x => x.LanguageId);
+ var sqlSelectData = Sql().Select(x => x.VersionId, x => x.PropertyTypeId, x => x.Segment, x => x.IntegerValue, x => x.DecimalValue, x => x.DateValue, x => x.VarcharValue, x => x.TextValue)
+ .Append(", NULL") //null language ID
+ .From()
+ .Where(x => x.LanguageId == defaultLangId);
+ if (sqlPropertyTypeIds != null)
+ sqlSelectData.WhereIn(x => x.PropertyTypeId, sqlPropertyTypeIds);
+ if (propertyTypeIds != null)
+ sqlSelectData.WhereIn(x => x.PropertyTypeId, propertyTypeIds);
+
+ var sqlInsert = Sql($"INSERT INTO {PropertyDataDto.TableName} ({cols})").Append(sqlSelectData);
+
+ Database.Execute(sqlInsert);
+ }
+
+ ///
+ /// This will move all property data from invariant to variant
+ ///
+ ///
+ /// Optional list of property type ids of the properties to be updated
+ /// Optional SQL statement used for the sub-query to select the properties type ids for the properties to be updated
+ private void MovePropertyDataToVariantCulture(int defaultLangId, IReadOnlyCollection propertyTypeIds = null, Sql sqlPropertyTypeIds = null)
+ {
+ //first clear out any existing property data that might already exists under the default lang
+ var sqlDelete = Sql()
+ .Delete()
+ .Where(x => x.LanguageId == defaultLangId);
+ if (sqlPropertyTypeIds != null)
+ sqlDelete.WhereIn(x => x.PropertyTypeId, sqlPropertyTypeIds);
+ if (propertyTypeIds != null)
+ sqlDelete.WhereIn(x => x.PropertyTypeId, propertyTypeIds);
+
+ Database.Execute(sqlDelete);
+
+ //now insert all property data into the default language that exists under the invariant lang
+ var cols = Sql().Columns(x => x.VersionId, x => x.PropertyTypeId, x => x.Segment, x => x.IntegerValue, x => x.DecimalValue, x => x.DateValue, x => x.VarcharValue, x => x.TextValue, x => x.LanguageId);
+ var sqlSelectData = Sql().Select(x => x.VersionId, x => x.PropertyTypeId, x => x.Segment, x => x.IntegerValue, x => x.DecimalValue, x => x.DateValue, x => x.VarcharValue, x => x.TextValue)
+ .Append($", {defaultLangId}") //default language ID
+ .From()
+ .Where(x => x.LanguageId == null);
+ if (sqlPropertyTypeIds != null)
+ sqlSelectData.WhereIn(x => x.PropertyTypeId, sqlPropertyTypeIds);
+ if (propertyTypeIds != null)
+ sqlSelectData.WhereIn(x => x.PropertyTypeId, propertyTypeIds);
+
+ var sqlInsert = Sql($"INSERT INTO {PropertyDataDto.TableName} ({cols})").Append(sqlSelectData);
+
+ Database.Execute(sqlInsert);
+ }
+
private void DeletePropertyType(int contentTypeId, int propertyTypeId)
{
// first clear dependencies
@@ -571,8 +848,11 @@ AND umbracoNode.id <> @id",
}
}
+ ///
public IEnumerable GetTypesDirectlyComposedOf(int id)
{
+ //fixme - this will probably be more efficient to simply load all content types and do the calculation, see GetWhereCompositionIsUsedInContentTypes
+
var sql = Sql()
.SelectAll()
.From()
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs
index bf41cd1ad1..8aa99e8ce0 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs
@@ -95,6 +95,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
return GetBaseQuery(queryType, true);
}
+ // gets the COALESCE expression for variant/invariant name
+ private string VariantNameSqlExpression
+ => SqlContext.VisitDto((ccv, node) => ccv.Name ?? node.Text, "ccv").Sql;
+
protected virtual Sql GetBaseQuery(QueryType queryType, bool current)
{
var sql = SqlContext.Sql();
@@ -110,12 +114,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
case QueryType.Single:
case QueryType.Many:
sql = sql.Select(r =>
- r.Select(documentDto => documentDto.ContentDto, r1 =>
- r1.Select(contentDto => contentDto.NodeDto))
- .Select(documentDto => documentDto.DocumentVersionDto, r1 =>
- r1.Select(documentVersionDto => documentVersionDto.ContentVersionDto))
- .Select(documentDto => documentDto.PublishedVersionDto, "pdv", r1 =>
- r1.Select(documentVersionDto => documentVersionDto.ContentVersionDto, "pcv")));
+ r.Select(documentDto => documentDto.ContentDto, r1 =>
+ r1.Select(contentDto => contentDto.NodeDto))
+ .Select(documentDto => documentDto.DocumentVersionDto, r1 =>
+ r1.Select(documentVersionDto => documentVersionDto.ContentVersionDto))
+ .Select(documentDto => documentDto.PublishedVersionDto, "pdv", r1 =>
+ r1.Select(documentVersionDto => documentVersionDto.ContentVersionDto, "pcv")))
+
+ // select the variant name, coalesce to the invariant name, as "variantName"
+ .AndSelect(VariantNameSqlExpression + " AS variantName");
break;
}
@@ -125,13 +132,22 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
.InnerJoin().On(left => left.NodeId, right => right.NodeId)
// inner join on mandatory edited version
- .InnerJoin().On((left, right) => left.NodeId == right.NodeId)
- .InnerJoin().On((left, right) => left.Id == right.Id)
+ .InnerJoin()
+ .On((left, right) => left.NodeId == right.NodeId)
+ .InnerJoin()
+ .On((left, right) => left.Id == right.Id)
// left join on optional published version
.LeftJoin(nested =>
- nested.InnerJoin("pdv").On((left, right) => left.Id == right.Id && right.Published, "pcv", "pdv"), "pcv")
- .On((left, right) => left.NodeId == right.NodeId, aliasRight: "pcv");
+ nested.InnerJoin("pdv")
+ .On((left, right) => left.Id == right.Id && right.Published, "pcv", "pdv"), "pcv")
+ .On((left, right) => left.NodeId == right.NodeId, aliasRight: "pcv")
+
+ // left join on optional culture variation
+ //the magic "[[[ISOCODE]]]" parameter value will be replaced in ContentRepositoryBase.GetPage() by the actual ISO code
+ .LeftJoin(nested =>
+ nested.InnerJoin("lang").On((ccv, lang) => ccv.LanguageId == lang.Id && lang.IsoCode == "[[[ISOCODE]]]", "ccv", "lang"), "ccv")
+ .On((version, ccv) => version.Id == ccv.VersionId, aliasRight: "ccv");
sql
.Where(x => x.NodeObjectType == NodeObjectTypeId);
@@ -204,6 +220,16 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
return MapDtosToContent(Database.Fetch(sql), true);
}
+ public override IEnumerable GetAllVersionsSlim(int nodeId, int skip, int take)
+ {
+ var sql = GetBaseQuery(QueryType.Many, false)
+ .Where(x => x.NodeId == nodeId)
+ .OrderByDescending(x => x.Current)
+ .AndByDescending(x => x.VersionDate);
+
+ return MapDtosToContent(Database.Fetch(sql), true, true);
+ }
+
public override IContent GetVersion(int versionId)
{
var sql = GetBaseQuery(QueryType.Single, false)
@@ -341,6 +367,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// persist the variations
if (content.ContentType.VariesByCulture())
{
+ // bump dates to align cultures to version
+ if (publishing)
+ content.AdjustDates(contentVersionDto.VersionDate);
+
// names also impact 'edited'
foreach (var (culture, name) in content.CultureNames)
if (name != content.GetPublishName(culture))
@@ -413,7 +443,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// whatever we do, we must check that we are saving the current version
// fixme maybe we can just fetch Current (bool)
var version = Database.Fetch(SqlContext.Sql().Select().From().Where(x => x.Id == content.VersionId)).FirstOrDefault();
- if (version == null || !version.Current )
+ if (version == null || !version.Current)
throw new InvalidOperationException("Cannot save a non-current version.");
// update
@@ -499,6 +529,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (content.ContentType.VariesByCulture())
{
+ // bump dates to align cultures to version
+ if (publishing)
+ content.AdjustDates(contentVersionDto.VersionDate);
+
// names also impact 'edited'
foreach (var (culture, name) in content.CultureNames)
if (name != content.GetPublishName(culture))
@@ -682,16 +716,33 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
///
public override IEnumerable GetPage(IQuery query,
- long pageIndex, int pageSize, out long totalRecords,
+ long pageIndex, int pageSize, out long totalRecords,
IQuery filter, Ordering ordering)
{
Sql filterSql = null;
+ // if we have a filter, map its clauses to an Sql statement
if (filter != null)
{
+ // if the clause works on "name", we need to swap the field and use the variantName instead,
+ // so that querying also works on variant content (for instance when searching a listview).
+
+ // figure out how the "name" field is going to look like - so we can look for it
+ var nameField = SqlContext.VisitModelField(x => x.Name);
+
filterSql = Sql();
foreach (var filterClause in filter.GetWhereClauses())
- filterSql.Append($"AND ({filterClause.Item1})", filterClause.Item2);
+ {
+ var clauseSql = filterClause.Item1;
+ var clauseArgs = filterClause.Item2;
+
+ // replace the name field
+ // we cannot reference an aliased field in a WHERE clause, so have to repeat the expression here
+ clauseSql = clauseSql.Replace(nameField, VariantNameSqlExpression);
+
+ // append the clause
+ filterSql.Append($"AND ({clauseSql})", clauseArgs);
+ }
}
return GetPage(query, pageIndex, pageSize, out totalRecords,
@@ -828,7 +879,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
.InnerJoin("updaterUser").On((version, user) => version.UserId == user.Id, aliasRight: "updaterUser");
// see notes in ApplyOrdering: the field MUST be selected + aliased
- sql = Sql(InsertBefore(sql, "FROM", SqlSyntax.GetFieldName(x => x.UserName, "updaterUser") + " AS ordering"), sql.Arguments);
+ sql = Sql(InsertBefore(sql, "FROM", ", " + SqlSyntax.GetFieldName(x => x.UserName, "updaterUser") + " AS ordering "), sql.Arguments);
sql = InsertJoins(sql, joins);
@@ -850,10 +901,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// variant: left join may yield NULL or something, and that determines published
var joins = Sql()
- .InnerJoin("ctype").On((content, contentType) => content.ContentTypeId == contentType.NodeId, aliasRight: "ctype")
- .LeftJoin(nested =>
- nested.InnerJoin("lang").On((ccv, lang) => ccv.LanguageId == lang.Id && lang.IsoCode == ordering.Culture, "ccv", "lang"), "ccv")
- .On((pcv, ccv) => pcv.Id == ccv.VersionId, "pcv", "ccv"); // join on *published* content version
+ .InnerJoin("ctype").On((content, contentType) => content.ContentTypeId == contentType.NodeId, aliasRight: "ctype");
sql = InsertJoins(sql, joins);
@@ -873,7 +921,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
return base.ApplySystemOrdering(ref sql, ordering);
}
- private IEnumerable MapDtosToContent(List dtos, bool withCache = false)
+ private IEnumerable MapDtosToContent(List dtos, bool withCache = false, bool slim = false)
{
var temps = new List>();
var contentTypes = new Dictionary();
@@ -906,18 +954,21 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
var c = content[i] = ContentBaseFactory.BuildEntity(dto, contentType);
- // need templates
- var templateId = dto.DocumentVersionDto.TemplateId;
- if (templateId.HasValue && templateId.Value > 0)
- templateIds.Add(templateId.Value);
- if (dto.Published)
+ if (!slim)
{
- templateId = dto.PublishedVersionDto.TemplateId;
+ // need templates
+ var templateId = dto.DocumentVersionDto.TemplateId;
if (templateId.HasValue && templateId.Value > 0)
templateIds.Add(templateId.Value);
+ if (dto.Published)
+ {
+ templateId = dto.PublishedVersionDto.TemplateId;
+ if (templateId.HasValue && templateId.Value > 0)
+ templateIds.Add(templateId.Value);
+ }
}
- // need properties
+ // need temps, for properties, templates and variations
var versionId = dto.DocumentVersionDto.Id;
var publishedVersionId = dto.Published ? dto.PublishedVersionDto.Id : 0;
var temp = new TempContent(dto.NodeId, versionId, publishedVersionId, contentType, c)
@@ -928,25 +979,32 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
temps.Add(temp);
}
- // load all required templates in 1 query, and index
- var templates = _templateRepository.GetMany(templateIds.ToArray())
- .ToDictionary(x => x.Id, x => x);
-
- // load all properties for all documents from database in 1 query - indexed by version id
- var properties = GetPropertyCollections(temps);
-
- // assign templates and properties
- foreach (var temp in temps)
+ if (!slim)
{
- // complete the item
- if (temp.Template1Id.HasValue && templates.TryGetValue(temp.Template1Id.Value, out var template))
- temp.Content.Template = template;
- if (temp.Template2Id.HasValue && templates.TryGetValue(temp.Template2Id.Value, out template))
- temp.Content.PublishTemplate = template;
- temp.Content.Properties = properties[temp.VersionId];
+ // load all required templates in 1 query, and index
+ var templates = _templateRepository.GetMany(templateIds.ToArray())
+ .ToDictionary(x => x.Id, x => x);
- // reset dirty initial properties (U4-1946)
- temp.Content.ResetDirtyProperties(false);
+ // load all properties for all documents from database in 1 query - indexed by version id
+ var properties = GetPropertyCollections(temps);
+
+ // assign templates and properties
+ foreach (var temp in temps)
+ {
+ // complete the item
+ if (temp.Template1Id.HasValue && templates.TryGetValue(temp.Template1Id.Value, out var template))
+ temp.Content.Template = template;
+ if (temp.Template2Id.HasValue && templates.TryGetValue(temp.Template2Id.Value, out template))
+ temp.Content.PublishTemplate = template;
+
+ if (properties.ContainsKey(temp.VersionId))
+ temp.Content.Properties = properties[temp.VersionId];
+ else
+ throw new InvalidOperationException($"No property data found for version: '{temp.VersionId}'.");
+
+ // reset dirty initial properties (U4-1946)
+ temp.Content.ResetDirtyProperties(false);
+ }
}
// set variations, if varying
@@ -1220,7 +1278,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// of whether the name has changed (ie the culture has been updated) - some saving culture
// fr-FR could cause culture en-UK name to change - not sure that is clean
- foreach(var (culture, name) in content.CultureNames)
+ foreach (var (culture, name) in content.CultureNames)
{
var langId = LanguageRepository.GetIdByIsoCode(culture);
if (!langId.HasValue) continue;
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs
index 2b3674700b..e316d1d04b 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs
@@ -30,7 +30,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
return new FullDataSetRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, GetEntityId, /*expires:*/ false);
}
- private FullDataSetRepositoryCachePolicy TypedCachePolicy => (FullDataSetRepositoryCachePolicy) CachePolicy;
+ private FullDataSetRepositoryCachePolicy TypedCachePolicy => CachePolicy as FullDataSetRepositoryCachePolicy;
#region Overrides of RepositoryBase
@@ -225,7 +225,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
public ILanguage GetByIsoCode(string isoCode)
{
- TypedCachePolicy.GetAllCached(PerformGetAll); // ensure cache is populated, in a non-expensive way
+ // ensure cache is populated, in a non-expensive way
+ if (TypedCachePolicy != null)
+ TypedCachePolicy.GetAllCached(PerformGetAll);
+
+
var id = GetIdByIsoCode(isoCode, throwOnNotFound: false);
return id.HasValue ? Get(id.Value) : null;
}
@@ -238,7 +242,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
if (isoCode == null) return null;
- TypedCachePolicy.GetAllCached(PerformGetAll); // ensure cache is populated, in a non-expensive way
+ // ensure cache is populated, in a non-expensive way
+ if (TypedCachePolicy != null)
+ TypedCachePolicy.GetAllCached(PerformGetAll);
+ else
+ PerformGetAll(); //we don't have a typed cache (i.e. unit tests) but need to populate the _codeIdMap
+
lock (_codeIdMap)
{
if (_codeIdMap.TryGetValue(isoCode, out var id)) return id;
@@ -256,7 +265,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
if (id == null) return null;
- TypedCachePolicy.GetAllCached(PerformGetAll); // ensure cache is populated, in a non-expensive way
+ // ensure cache is populated, in a non-expensive way
+ if (TypedCachePolicy != null)
+ TypedCachePolicy.GetAllCached(PerformGetAll);
+ else
+ PerformGetAll();
+
lock (_codeIdMap) // yes, we want to lock _codeIdMap
{
if (_idCodeMap.TryGetValue(id.Value, out var isoCode)) return isoCode;
@@ -279,8 +293,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// do NOT leak that language, it's not deep-cloned!
private ILanguage GetDefault()
{
- // get all cached, non-cloned
- var languages = TypedCachePolicy.GetAllCached(PerformGetAll).ToList();
+ // get all cached
+ var languages = (TypedCachePolicy?.GetAllCached(PerformGetAll) //try to get all cached non-cloned if using the correct cache policy (not the case in unit tests)
+ ?? CachePolicy.GetAll(Array.Empty(), PerformGetAll)).ToList();
+
var language = languages.FirstOrDefault(x => x.IsDefault);
if (language != null) return language;
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs
index 2390ce9a7b..dbfdc8e980 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs
@@ -8,12 +8,12 @@ using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
-using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Dtos;
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
{
@@ -100,7 +100,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
case QueryType.Many:
sql = sql.Select(r =>
r.Select(x => x.NodeDto)
- .Select(x => x.ContentVersionDto));
+ .Select(x => x.ContentVersionDto))
+
+ // ContentRepositoryBase expects a variantName field to order by name
+ // for now, just return the plain invariant node name
+ // fixme media should support variants !!
+ .AndSelect(x => Alias(x.Text, "variantName"));
break;
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberGroupRepository.cs
index e6783eaf6d..e80faaa44a 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberGroupRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberGroupRepository.cs
@@ -22,7 +22,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override IMemberGroup PerformGet(int id)
{
var sql = GetBaseQuery(false);
- sql.Where(GetBaseWhereClause(), new { Id = id });
+ sql.Where(GetBaseWhereClause(), new { id = id });
var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault();
@@ -262,7 +262,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
var nonAssignedRoles = roleNames.Except(assignedRoles, StringComparer.CurrentCultureIgnoreCase);
foreach (var toAssign in nonAssignedRoles)
{
- var groupId = rolesForNames.First(x => x.Text == toAssign).NodeId;
+ var groupId = rolesForNames.First(x => x.Text.InvariantEquals(toAssign)).NodeId;
Database.Insert(new Member2MemberGroupDto { Member = mId, MemberGroup = groupId });
}
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs
index 84ef154ae8..fd79b231de 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs
@@ -6,12 +6,12 @@ using NPoco;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
-using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Dtos;
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
{
@@ -114,9 +114,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
case QueryType.Single:
case QueryType.Many:
sql = sql.Select(r =>
- r.Select(x => x.ContentVersionDto)
- .Select(x => x.ContentDto, r1 =>
- r1.Select(x => x.NodeDto)));
+ r.Select(x => x.ContentVersionDto)
+ .Select(x => x.ContentDto, r1 =>
+ r1.Select(x => x.NodeDto)))
+
+ // ContentRepositoryBase expects a variantName field to order by name
+ // so get it here, though for members it's just the plain node name
+ .AndSelect(x => Alias(x.Text, "variantName"));
break;
}
diff --git a/src/Umbraco.Core/Persistence/SqlContextExtensions.cs b/src/Umbraco.Core/Persistence/SqlContextExtensions.cs
index e28816b6a4..249e2cafd0 100644
--- a/src/Umbraco.Core/Persistence/SqlContextExtensions.cs
+++ b/src/Umbraco.Core/Persistence/SqlContextExtensions.cs
@@ -17,11 +17,11 @@ namespace Umbraco.Core.Persistence
/// An expression to visit.
/// An optional table alias.
/// A SQL statement, and arguments, corresponding to the expression.
- public static (string Sql, object[] Args) Visit(this ISqlContext sqlContext, Expression> expression, string alias = null)
+ public static (string Sql, object[] Args) VisitDto(this ISqlContext sqlContext, Expression> expression, string alias = null)
{
- var expresionist = new PocoToSqlExpressionVisitor(sqlContext, alias);
- var visited = expresionist.Visit(expression);
- return (visited, expresionist.GetSqlParameters());
+ var visitor = new PocoToSqlExpressionVisitor(sqlContext, alias);
+ var visited = visitor.Visit(expression);
+ return (visited, visitor.GetSqlParameters());
}
///
@@ -33,11 +33,11 @@ namespace Umbraco.Core.Persistence
/// An expression to visit.
/// An optional table alias.
/// A SQL statement, and arguments, corresponding to the expression.
- public static (string Sql, object[] Args) Visit(this ISqlContext sqlContext, Expression> expression, string alias = null)
+ public static (string Sql, object[] Args) VisitDto(this ISqlContext sqlContext, Expression> expression, string alias = null)
{
- var expresionist = new PocoToSqlExpressionVisitor(sqlContext, alias);
- var visited = expresionist.Visit(expression);
- return (visited, expresionist.GetSqlParameters());
+ var visitor = new PocoToSqlExpressionVisitor(sqlContext, alias);
+ var visited = visitor.Visit(expression);
+ return (visited, visitor.GetSqlParameters());
}
///
@@ -50,11 +50,11 @@ namespace Umbraco.Core.Persistence
/// An optional table alias for the first DTO.
/// An optional table alias for the second DTO.
/// A SQL statement, and arguments, corresponding to the expression.
- public static (string Sql, object[] Args) Visit(this ISqlContext sqlContext, Expression> expression, string alias1 = null, string alias2 = null)
+ public static (string Sql, object[] Args) VisitDto(this ISqlContext sqlContext, Expression> expression, string alias1 = null, string alias2 = null)
{
- var expresionist = new PocoToSqlExpressionVisitor(sqlContext, alias1, alias2);
- var visited = expresionist.Visit(expression);
- return (visited, expresionist.GetSqlParameters());
+ var visitor = new PocoToSqlExpressionVisitor(sqlContext, alias1, alias2);
+ var visited = visitor.Visit(expression);
+ return (visited, visitor.GetSqlParameters());
}
///
@@ -68,11 +68,42 @@ namespace Umbraco.Core.Persistence
/// An optional table alias for the first DTO.
/// An optional table alias for the second DTO.
/// A SQL statement, and arguments, corresponding to the expression.
- public static (string Sql, object[] Args) Visit(this ISqlContext sqlContext, Expression> expression, string alias1 = null, string alias2 = null)
+ public static (string Sql, object[] Args) VisitDto(this ISqlContext sqlContext, Expression> expression, string alias1 = null, string alias2 = null)
{
- var expresionist = new PocoToSqlExpressionVisitor(sqlContext, alias1, alias2);
- var visited = expresionist.Visit(expression);
- return (visited, expresionist.GetSqlParameters());
+ var visitor = new PocoToSqlExpressionVisitor(sqlContext, alias1, alias2);
+ var visited = visitor.Visit(expression);
+ return (visited, visitor.GetSqlParameters());
+ }
+
+ ///
+ /// Visit a model expression.
+ ///
+ /// The type of the model.
+ /// An .
+ /// An expression to visit.
+ /// A SQL statement, and arguments, corresponding to the expression.
+ public static (string Sql, object[] Args) VisitModel(this ISqlContext sqlContext, Expression> expression)
+ {
+ var visitor = new ModelToSqlExpressionVisitor(sqlContext.SqlSyntax, sqlContext.Mappers);
+ var visited = visitor.Visit(expression);
+ return (visited, visitor.GetSqlParameters());
+ }
+
+ ///
+ /// Visit a model expression representing a field.
+ ///
+ /// The type of the model.
+ /// An .
+ /// An expression to visit, representing a field.
+ /// The name of the field.
+ public static string VisitModelField(this ISqlContext sqlContext, Expression> field)
+ {
+ var (sql, _) = sqlContext.VisitModel(field);
+
+ // going to return " = @0"
+ // take the first part only
+ var pos = sql.IndexOf(' ');
+ return sql.Substring(0, pos);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Umbraco.Core/PropertyEditors/Validators/EmailValidator.cs b/src/Umbraco.Core/PropertyEditors/Validators/EmailValidator.cs
index 762b6dd7dd..4df11e4f60 100644
--- a/src/Umbraco.Core/PropertyEditors/Validators/EmailValidator.cs
+++ b/src/Umbraco.Core/PropertyEditors/Validators/EmailValidator.cs
@@ -14,7 +14,7 @@ namespace Umbraco.Core.PropertyEditors.Validators
///
public IEnumerable Validate(object value, string valueType, object dataTypeConfiguration)
{
- var asString = value.ToString();
+ var asString = value == null ? "" : value.ToString();
var emailVal = new EmailAddressAttribute();
diff --git a/src/Umbraco.Core/ReflectionUtilities.cs b/src/Umbraco.Core/ReflectionUtilities.cs
index a5acfe78e3..870cb9ec13 100644
--- a/src/Umbraco.Core/ReflectionUtilities.cs
+++ b/src/Umbraco.Core/ReflectionUtilities.cs
@@ -295,7 +295,7 @@ namespace Umbraco.Core
/// Occurs when the constructor does not exist and is true.
/// Occurs when is not a Func or when
/// is specified and does not match the function's returned type.
- public static TLambda EmitCtor(bool mustExist = true, Type declaring = null)
+ public static TLambda EmitConstuctor(bool mustExist = true, Type declaring = null)
{
var (_, lambdaParameters, lambdaReturned) = AnalyzeLambda(true, true);
@@ -313,7 +313,7 @@ namespace Umbraco.Core
}
// emit
- return EmitCtorSafe(lambdaParameters, lambdaReturned, ctor);
+ return EmitConstructorSafe(lambdaParameters, lambdaReturned, ctor);
}
///
@@ -325,16 +325,16 @@ namespace Umbraco.Core
/// Occurs when is not a Func or when its generic
/// arguments do not match those of .
/// Occurs when is null.
- public static TLambda EmitCtor(ConstructorInfo ctor)
+ public static TLambda EmitConstructor(ConstructorInfo ctor)
{
if (ctor == null) throw new ArgumentNullException(nameof(ctor));
var (_, lambdaParameters, lambdaReturned) = AnalyzeLambda(true, true);
- return EmitCtorSafe(lambdaParameters, lambdaReturned, ctor);
+ return EmitConstructorSafe(lambdaParameters, lambdaReturned, ctor);
}
- private static TLambda EmitCtorSafe(Type[] lambdaParameters, Type returned, ConstructorInfo ctor)
+ private static TLambda EmitConstructorSafe(Type[] lambdaParameters, Type returned, ConstructorInfo ctor)
{
// get type and args
var ctorDeclaring = ctor.DeclaringType;
@@ -350,7 +350,7 @@ namespace Umbraco.Core
ThrowInvalidLambda("ctor", ctorDeclaring, ctorParameters);
// emit
- return EmitCtor(ctorDeclaring, ctorParameters, ctor);
+ return EmitConstructor(ctorDeclaring, ctorParameters, ctor);
}
///
@@ -367,17 +367,17 @@ namespace Umbraco.Core
/// Occurs when is not a Func or when its generic
/// arguments do not match those of .
/// Occurs when is null.
- public static TLambda EmitCtorUnsafe(ConstructorInfo ctor)
+ public static TLambda EmitConstructorUnsafe(ConstructorInfo ctor)
{
if (ctor == null) throw new ArgumentNullException(nameof(ctor));
var (_, lambdaParameters, lambdaReturned) = AnalyzeLambda(true, true);
// emit - unsafe - use lambda's args and assume they are correct
- return EmitCtor(lambdaReturned, lambdaParameters, ctor);
+ return EmitConstructor