Compare commits

..

38 Commits

Author SHA1 Message Date
Sebastiaan Janssen 5a07e34fb4 Bump version to 8.0.2 2019-04-26 15:15:17 +02:00
Stephan 1b5d614f5a Fix recycle bin moves
(cherry picked from commit 173df80e3d)

# Conflicts:
#	src/Umbraco.Core/Services/Implement/MediaService.cs
2019-04-26 15:03:09 +02:00
Sebastiaan Janssen abb08260eb v8: Imagepicker prevalue and grid setting/styles as infinite editor (#4971)
(cherry picked from commit f82dff597b)

# Conflicts:
#	src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js
2019-04-25 13:43:03 +02:00
Stephan 57c567c570 Fix paging error in NuCache
(cherry picked from commit 5d0010a993)
2019-04-23 14:08:19 +02:00
Stephan 424f4601ae Merge commit 'a2a247b413' into v8/8.0 2019-04-23 13:51:29 +02:00
Shannon a2a247b413 removes the enforcement of non duplicate property type aliases for now 2019-04-17 21:00:15 +10:00
Shannon 0b6a369047 When removing a property group, we need to first remove the group and then re-assign the property types so that duplicates are not added 2019-04-17 17:12:34 +10:00
Shannon 522fdefb8c Ensures that ALL membership properties are created by the installer, removes the logic that updates a membertype while fetching it, fixes the logic of mapping membership properties when fetching and they don't exist. 2019-04-17 16:42:17 +10:00
Shannon 4d8d10f07a adds comments, ignores test for now 2019-04-17 16:03:36 +10:00
Shannon 6f17129364 Make DropDownFlexibleConfiguration class public to allow configuration to be set when creating a new data type 2019-04-17 15:23:35 +10:00
Shannon 6f44e4fd09 Fixes INotifyCollectionChanged collections to raise the correct events, ensures that no duplicate property type aliases can be added/updated to 2019-04-16 17:37:42 +10:00
Shannon 02ddf80014 Adds a unit test 2019-04-16 15:39:28 +10:00
Shannon 723d9eddd1 unbreaks a ctor change preventing 8.0.2 from being dropped into the bin 2019-04-15 18:15:52 +10:00
Shannon fa655b812c Fix composers ordering 2019-04-15 17:55:22 +10:00
Shannon 576c10cd50 NuCache: fix vanishing content when refreshing content types 2019-04-15 12:56:45 +10:00
Kenn Jacobsen 179afe7e0d Add IsVisible extension on IPublishedElement 2019-04-11 17:44:16 +02:00
Shannon be7f42c454 Don't show the recycle bin in tree pickers/dialogs 2019-04-10 09:52:06 +10:00
Shannon 7e1ee26dee Fix spelling mistake 2019-04-09 10:34:59 +10:00
Stephan 1c9b449c46 Log more details about boot environment 2019-04-07 16:25:06 +02:00
Shannon e38095b727 NuCache: fix loading the media cache from local files, was missing media 2019-04-05 10:49:19 +11:00
Shannon b5796ad237 Enable configuration of NuCache BTree block size #5114 2019-04-05 10:44:36 +11:00
Stephan f985c437b5 Add content.SetValue overload for posted files 2019-04-04 09:28:34 +02:00
Andrew Deans 63fb8f5933 Fix case of query parameter 2019-04-04 09:12:40 +02:00
Shannon eb529783c4 Don't allow move in members listview 2019-04-03 16:22:13 +11:00
Shannon a8265db5f0 Reload listview after moving items 2019-04-03 10:45:04 +11:00
Shannon 7dcf2b1abb infinity editing dim-layer for umb-modelcolumn 2019-04-01 17:05:13 +11:00
Stephan 514452ddb9 Better ServerMessenger configuration 2019-04-01 16:30:37 +11:00
Stephan 1eb55a83c0 Merge branch 'patch-8.0.1' into v8/8.0 2019-03-30 12:13:12 +01:00
Stephan dfe6f2029a NuCache: better fixing, cleanup 2019-03-26 10:25:05 +01:00
Stephan 851c844c8b NuCache: fix panic exception 2019-03-26 10:25:05 +01:00
Stephan 7e413ed406 NuCache: troubleshooting 2019-03-26 10:25:05 +01:00
Stephan c56b8863ee NuCache: reorg some classes 2019-03-26 10:25:05 +01:00
Stephan 13a8548d3a NuCache: rename things 2019-03-26 10:25:05 +01:00
Stephan a70cb51559 NuCache: cleanup scoped objects 2019-03-26 10:25:05 +01:00
Kenn Jacobsen 8a56b3db16 Fix the "Language" dropdown in the "Culture and Hostnames" dialog 2019-03-26 20:10:50 +11:00
Bjarke Berg 4fcf2ce7bd Fixed issue when creating variant content templates, but the properties is invariant. Before this change an exception was thrown 2019-03-26 20:10:03 +11:00
Shannon 080ace90b2 Bugfix: https://github.com/umbraco/Umbraco-CMS/issues/4827 - Fixed MediaPickerValueConverter when config is Only Images 2019-03-26 20:09:12 +11:00
Shannon 2bfa81c707 fixes 4627 + makes front-end validations appear as soon as they are invalid again. 2019-03-26 20:07:42 +11:00
159 changed files with 1665 additions and 1890 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
_Looking for Umbraco version 8? [Click here](https://github.com/umbraco/Umbraco-CMS/blob/dev-v8/.github/V8_GETTING_STARTED.md) to go to the v8 branch_
_Looking for Umbraco version 8? [Click here](https://github.com/umbraco/Umbraco-CMS/blob/temp8/.github/V8_GETTING_STARTED.md) to go to the v8 branch_
# Contributing to Umbraco CMS
👍🎉 First off, thanks for taking the time to contribute! 🎉👍
+4 -2
View File
@@ -103,9 +103,11 @@ There's two big areas that you should know about:
1. The Umbraco backoffice is a extensible AngularJS app and requires you to run a `gulp dev` command while you're working with it, so changes are copied over to the appropriate directories and you can refresh your browser to view the results of your changes.
You may need to run the following commands to set up gulp properly:
```
npm cache clean --force
npm cache clean
npm install -g gulp
npm install -g gulp-cli
npm install
npm run build
gulp build
```
2. "The rest" is a C# based codebase, with some traces of our WebForms past but mostly ASP.NET MVC based these days. You can make changes, build them in Visual Studio, and hit `F5` to see the result.
+1 -1
View File
@@ -2,7 +2,7 @@
- [ ] I have added steps to test this contribution in the description below
If there's an existing issue for this PR then this fixes <!-- link to the issue here! -->
If there's an existing issue for this PR then this fixes: <!-- link to the issue here! -->
### Description
<!-- A description of the changes proposed in the pull-request and how to test these changes -->
+2 -4
View File
@@ -1,9 +1,7 @@
[![Build status](https://umbraco.visualstudio.com/Umbraco%20Cms/_apis/build/status/Cms%208%20Continuous?branchName=dev-v8)](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=75) [![pullreminders](https://pullreminders.com/badge.svg)](https://pullreminders.com?ref=badge)
_You are browsing the Umbraco v8 branch. Umbraco 8 is currently under development._
[![pullreminders](https://pullreminders.com/badge.svg)](https://pullreminders.com?ref=badge)
_Looking for Umbraco version 7? [Click here](https://github.com/umbraco/Umbraco-CMS/tree/dev-v7) to go to the v7 branch._
_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)._
+1 -1
View File
@@ -5,7 +5,7 @@
* [Visual Studio 2017 Community (Free)](https://www.visualstudio.com/vs/community/), or Professional, Enterprise, etc... _(**Minimum of Version 15.7** or higher, this is important, you WILL get issues with lesser versions)_
* .NET Framework 4.7.2 installed, get it here: https://www.microsoft.com/net/download/thank-you/net472?survey=false
* .NET Framework 4.7.2 developer pack, get it here: https://www.microsoft.com/net/download/thank-you/net472-developer-pack _(be sure this is the ENU file which will be named `NDP472-DevPack-ENU.exe`)_
* Clone the Umbraco repository using the `dev-v8` branch. If your git client doesn't support specifying the branch as you clone then use the command `git clone --single-branch -b dev-v8 <your fork url>`. _(If you clone the repo using the default v7 branch and then checkout the `dev-v8` branch you **might** get problems)_
* Clone the Umbraco repository using the `temp8` branch. If your git client doesn't support specifying the branch as you clone then use the command `git clone --single-branch -b temp8 <your fork url>`. _(If you clone the repo using the default v7 branch and then checkout the `temp8` branch you **might** get problems)_
### Start the solution
+2 -11
View File
@@ -15,12 +15,7 @@
[Parameter(Mandatory=$false)]
[Alias("c")]
[Alias("cont")]
[switch] $continue = $false,
# execute a command
[Parameter(Mandatory=$false, ValueFromRemainingArguments=$true)]
[String[]]
$command
[switch] $continue = $false
)
# ################################################################
@@ -480,11 +475,7 @@
# run
if (-not $get)
{
if ($command.Length -eq 0)
{
$command = @( "Build" )
}
$ubuild.RunMethod($command);
$ubuild.Build()
if ($ubuild.OnError()) { return }
}
if ($get) { return $ubuild }
+2 -2
View File
@@ -18,5 +18,5 @@ using System.Resources;
[assembly: AssemblyVersion("8.0.0")]
// these are FYI and changed automatically
[assembly: AssemblyFileVersion("8.1.0")]
[assembly: AssemblyInformationalVersion("8.1.0")]
[assembly: AssemblyFileVersion("8.0.2")]
[assembly: AssemblyInformationalVersion("8.0.2")]
+95 -38
View File
@@ -70,7 +70,23 @@ namespace Umbraco.Core.Composing
}
}
private IEnumerable<Type> PrepareComposerTypes()
internal IEnumerable<Type> PrepareComposerTypes()
{
var requirements = GetRequirements();
// only for debugging, this is verbose
//_logger.Debug<Composers>(GetComposersReport(requirements));
var sortedComposerTypes = SortComposers(requirements);
// bit verbose but should help for troubleshooting
//var text = "Ordered Composers: " + Environment.NewLine + string.Join(Environment.NewLine, sortedComposerTypes) + Environment.NewLine;
_logger.Debug<Composers>("Ordered Composers: {SortedComposerTypes}", sortedComposerTypes);
return sortedComposerTypes;
}
internal Dictionary<Type, List<Type>> GetRequirements(bool throwOnMissing = true)
{
// create a list, remove those that cannot be enabled due to runtime level
var composerTypeList = _composerTypes
@@ -89,25 +105,69 @@ namespace Umbraco.Core.Composing
// enable or disable composers
EnableDisableComposers(composerTypeList);
// sort the composers according to their dependencies
var requirements = new Dictionary<Type, List<Type>>();
foreach (var type in composerTypeList) requirements[type] = null;
foreach (var type in composerTypeList)
void GatherInterfaces<TAttribute>(Type type, Func<TAttribute, Type> getTypeInAttribute, HashSet<Type> iset, List<Type> set2)
where TAttribute : Attribute
{
GatherRequirementsFromRequireAttribute(type, composerTypeList, requirements);
GatherRequirementsFromRequiredByAttribute(type, composerTypeList, requirements);
foreach (var attribute in type.GetCustomAttributes<TAttribute>())
{
var typeInAttribute = getTypeInAttribute(attribute);
if (typeInAttribute != null && // if the attribute references a type ...
typeInAttribute.IsInterface && // ... which is an interface ...
typeof(IComposer).IsAssignableFrom(typeInAttribute) && // ... which implements IComposer ...
!iset.Contains(typeInAttribute)) // ... which is not already in the list
{
// add it to the new list
iset.Add(typeInAttribute);
set2.Add(typeInAttribute);
// add all its interfaces implementing IComposer
foreach (var i in typeInAttribute.GetInterfaces().Where(x => typeof(IComposer).IsAssignableFrom(x)))
{
iset.Add(i);
set2.Add(i);
}
}
}
}
// only for debugging, this is verbose
//_logger.Debug<Composers>(GetComposersReport(requirements));
// gather interfaces too
var interfaces = new HashSet<Type>(composerTypeList.SelectMany(x => x.GetInterfaces().Where(y => typeof(IComposer).IsAssignableFrom(y))));
composerTypeList.AddRange(interfaces);
var list1 = composerTypeList;
while (list1.Count > 0)
{
var list2 = new List<Type>();
foreach (var t in list1)
{
GatherInterfaces<ComposeAfterAttribute>(t, a => a.RequiredType, interfaces, list2);
GatherInterfaces<ComposeBeforeAttribute>(t, a => a.RequiringType, interfaces, list2);
}
composerTypeList.AddRange(list2);
list1 = list2;
}
// sort the composers according to their dependencies
var requirements = new Dictionary<Type, List<Type>>();
foreach (var type in composerTypeList)
requirements[type] = null;
foreach (var type in composerTypeList)
{
GatherRequirementsFromAfterAttribute(type, composerTypeList, requirements, throwOnMissing);
GatherRequirementsFromBeforeAttribute(type, composerTypeList, requirements);
}
return requirements;
}
internal IEnumerable<Type> SortComposers(Dictionary<Type, List<Type>> requirements)
{
// sort composers
var graph = new TopoGraph<Type, KeyValuePair<Type, List<Type>>>(kvp => kvp.Key, kvp => kvp.Value);
graph.AddItems(requirements);
List<Type> sortedComposerTypes;
try
{
sortedComposerTypes = graph.GetSortedItems().Select(x => x.Key).ToList();
sortedComposerTypes = graph.GetSortedItems().Select(x => x.Key).Where(x => !x.IsInterface).ToList();
}
catch (Exception e)
{
@@ -117,40 +177,37 @@ namespace Umbraco.Core.Composing
throw;
}
// bit verbose but should help for troubleshooting
//var text = "Ordered Composers: " + Environment.NewLine + string.Join(Environment.NewLine, sortedComposerTypes) + Environment.NewLine;
_logger.Debug<Composers>("Ordered Composers: {SortedComposerTypes}", sortedComposerTypes);
return sortedComposerTypes;
}
private static string GetComposersReport(Dictionary<Type, List<Type>> requirements)
internal static string GetComposersReport(Dictionary<Type, List<Type>> requirements)
{
var text = new StringBuilder();
text.AppendLine("Composers & Dependencies:");
text.AppendLine(" < compose before");
text.AppendLine(" > compose after");
text.AppendLine(" : implements");
text.AppendLine(" = depends");
text.AppendLine();
bool HasReq(IEnumerable<Type> types, Type type)
=> types.Any(x => type.IsAssignableFrom(x) && !x.IsInterface);
foreach (var kvp in requirements)
{
var type = kvp.Key;
text.AppendLine(type.FullName);
foreach (var attribute in type.GetCustomAttributes<ComposeAfterAttribute>())
text.AppendLine(" -> " + attribute.RequiredType + (attribute.Weak.HasValue
? (attribute.Weak.Value ? " (weak)" : (" (strong" + (requirements.ContainsKey(attribute.RequiredType) ? ", missing" : "") + ")"))
: ""));
foreach (var attribute in type.GetCustomAttributes<ComposeBeforeAttribute>())
text.AppendLine(" -< " + attribute.RequiringType);
foreach (var i in type.GetInterfaces())
{
text.AppendLine(" : " + i.FullName);
foreach (var attribute in i.GetCustomAttributes<ComposeAfterAttribute>())
text.AppendLine(" -> " + attribute.RequiredType + (attribute.Weak.HasValue
? (attribute.Weak.Value ? " (weak)" : (" (strong" + (requirements.ContainsKey(attribute.RequiredType) ? ", missing" : "") + ")"))
: ""));
foreach (var attribute in i.GetCustomAttributes<ComposeBeforeAttribute>())
text.AppendLine(" -< " + attribute.RequiringType);
var weak = !(attribute.RequiredType.IsInterface ? attribute.Weak == false : attribute.Weak != true);
text.AppendLine(" > " + attribute.RequiredType +
(weak ? " (weak" : " (strong") + (HasReq(requirements.Keys, attribute.RequiredType) ? ", found" : ", missing") + ")");
}
foreach (var attribute in type.GetCustomAttributes<ComposeBeforeAttribute>())
text.AppendLine(" < " + attribute.RequiringType);
foreach (var i in type.GetInterfaces())
text.AppendLine(" : " + i.FullName);
if (kvp.Value != null)
foreach (var t in kvp.Value)
text.AppendLine(" = " + t);
@@ -221,16 +278,16 @@ namespace Umbraco.Core.Composing
types.Remove(kvp.Key);
}
private static void GatherRequirementsFromRequireAttribute(Type type, ICollection<Type> types, IDictionary<Type, List<Type>> requirements)
private static void GatherRequirementsFromAfterAttribute(Type type, ICollection<Type> types, IDictionary<Type, List<Type>> requirements, bool throwOnMissing = true)
{
// get 'require' attributes
// these attributes are *not* inherited because we want to "custom-inherit" for interfaces only
var requireAttributes = type
var afterAttributes = type
.GetInterfaces().SelectMany(x => x.GetCustomAttributes<ComposeAfterAttribute>()) // those marking interfaces
.Concat(type.GetCustomAttributes<ComposeAfterAttribute>()); // those marking the composer
// what happens in case of conflicting attributes (different strong/weak for same type) is not specified.
foreach (var attr in requireAttributes)
foreach (var attr in afterAttributes)
{
if (attr.RequiredType == type) continue; // ignore self-requirements (+ exclude in implems, below)
@@ -238,13 +295,13 @@ namespace Umbraco.Core.Composing
// unless strong, and then require at least one enabled composer implementing that interface
if (attr.RequiredType.IsInterface)
{
var implems = types.Where(x => x != type && attr.RequiredType.IsAssignableFrom(x)).ToList();
var implems = types.Where(x => x != type && attr.RequiredType.IsAssignableFrom(x) && !x.IsInterface).ToList();
if (implems.Count > 0)
{
if (requirements[type] == null) requirements[type] = new List<Type>();
requirements[type].AddRange(implems);
}
else if (attr.Weak == false) // if explicitly set to !weak, is strong, else is weak
else if (attr.Weak == false && throwOnMissing) // if explicitly set to !weak, is strong, else is weak
throw new Exception($"Broken composer dependency: {type.FullName} -> {attr.RequiredType.FullName}.");
}
// requiring a class = require that the composer is enabled
@@ -256,28 +313,28 @@ namespace Umbraco.Core.Composing
if (requirements[type] == null) requirements[type] = new List<Type>();
requirements[type].Add(attr.RequiredType);
}
else if (attr.Weak != true) // if not explicitly set to weak, is strong
else if (attr.Weak != true && throwOnMissing) // if not explicitly set to weak, is strong
throw new Exception($"Broken composer dependency: {type.FullName} -> {attr.RequiredType.FullName}.");
}
}
}
private static void GatherRequirementsFromRequiredByAttribute(Type type, ICollection<Type> types, IDictionary<Type, List<Type>> requirements)
private static void GatherRequirementsFromBeforeAttribute(Type type, ICollection<Type> types, IDictionary<Type, List<Type>> requirements)
{
// get 'required' attributes
// these attributes are *not* inherited because we want to "custom-inherit" for interfaces only
var requiredAttributes = type
var beforeAttributes = type
.GetInterfaces().SelectMany(x => x.GetCustomAttributes<ComposeBeforeAttribute>()) // those marking interfaces
.Concat(type.GetCustomAttributes<ComposeBeforeAttribute>()); // those marking the composer
foreach (var attr in requiredAttributes)
foreach (var attr in beforeAttributes)
{
if (attr.RequiringType == type) continue; // ignore self-requirements (+ exclude in implems, below)
// required by an interface = by any enabled composer implementing this that interface
if (attr.RequiringType.IsInterface)
{
var implems = types.Where(x => x != type && attr.RequiringType.IsAssignableFrom(x)).ToList();
var implems = types.Where(x => x != type && attr.RequiringType.IsAssignableFrom(x) && !x.IsInterface).ToList();
foreach (var implem in implems)
{
if (requirements[implem] == null) requirements[implem] = new List<Type>();
+1 -5
View File
@@ -133,11 +133,7 @@ namespace Umbraco.Core.Composing
Configs.RegisterWith(_register);
IFactory factory = null;
// ReSharper disable once AccessToModifiedClosure -- on purpose
_register.Register(_ => factory, Lifetime.Singleton);
factory = _register.CreateFactory();
return factory;
return _register.CreateFactory();
}
/// <summary>
+22
View File
@@ -203,6 +203,28 @@ namespace Umbraco.Core
composition.RegisterUnique(_ => registrar);
}
/// <summary>
/// Sets the database server messenger options.
/// </summary>
/// <param name="composition">The composition.</param>
/// <param name="factory">A function creating the options.</param>
/// <remarks>Use DatabaseServerRegistrarAndMessengerComposer.GetDefaultOptions to get the options that Umbraco would use by default.</remarks>
public static void SetDatabaseServerMessengerOptions(this Composition composition, Func<IFactory, DatabaseServerMessengerOptions> factory)
{
composition.RegisterUnique(factory);
}
/// <summary>
/// Sets the database server messenger options.
/// </summary>
/// <param name="composition">The composition.</param>
/// <param name="options">Options.</param>
/// <remarks>Use DatabaseServerRegistrarAndMessengerComposer.GetDefaultOptions to get the options that Umbraco would use by default.</remarks>
public static void SetDatabaseServerMessengerOptions(this Composition composition, DatabaseServerMessengerOptions options)
{
composition.RegisterUnique(_ => options);
}
/// <summary>
/// Sets the short string helper.
/// </summary>
@@ -4,7 +4,6 @@ using System.Linq;
using System.Net.Configuration;
using System.Web;
using System.Web.Configuration;
using System.Web.Hosting;
using System.Xml.Linq;
using Umbraco.Core.IO;
@@ -18,15 +17,16 @@ namespace Umbraco.Core.Configuration
/// </summary>
public class GlobalSettings : IGlobalSettings
{
private string _localTempPath;
// TODO these should not be static
#region Private static fields
private static string _reservedPaths;
private static string _reservedUrls;
//ensure the built on (non-changeable) reserved paths are there at all times
internal const string StaticReservedPaths = "~/app_plugins/,~/install/,~/mini-profiler-resources/,"; //must end with a comma!
internal const string StaticReservedUrls = "~/config/splashes/noNodes.aspx,~/.well-known,"; //must end with a comma!
#endregion
/// <summary>
/// Used in unit testing to reset all config items that were set with property setters (i.e. did not come from config)
@@ -131,7 +131,7 @@ namespace Umbraco.Core.Configuration
: "~/App_Data/umbraco.config";
}
}
/// <summary>
/// Gets the path to umbraco's root directory (/umbraco by default).
/// </summary>
@@ -163,7 +163,7 @@ namespace Umbraco.Core.Configuration
SaveSetting(Constants.AppSettings.ConfigurationStatus, value);
}
}
/// <summary>
/// Saves a setting into the configuration file.
/// </summary>
@@ -206,7 +206,7 @@ namespace Umbraco.Core.Configuration
ConfigurationManager.RefreshSection("appSettings");
}
}
/// <summary>
/// Gets a value indicating whether umbraco is running in [debug mode].
/// </summary>
@@ -250,7 +250,7 @@ namespace Umbraco.Core.Configuration
}
}
}
/// <summary>
/// Returns the number of days that should take place between version checks.
/// </summary>
@@ -269,7 +269,7 @@ namespace Umbraco.Core.Configuration
}
}
}
/// <inheritdoc />
public LocalTempStorage LocalTempStorageLocation
{
@@ -288,43 +288,25 @@ namespace Umbraco.Core.Configuration
{
get
{
if (_localTempPath != null)
return _localTempPath;
switch (LocalTempStorageLocation)
{
case LocalTempStorage.AspNetTemp:
return _localTempPath = System.IO.Path.Combine(HttpRuntime.CodegenDir, "UmbracoData");
return System.IO.Path.Combine(HttpRuntime.CodegenDir, "UmbracoData");
case LocalTempStorage.EnvironmentTemp:
// environment temp is unique, we need a folder per site
// use a hash
// combine site name and application id
// site name is a Guid on Cloud
// application id is eg /LM/W3SVC/123456/ROOT
// the combination is unique on one server
// and, if a site moves from worker A to B and then back to A...
// hopefully it gets a new Guid or new application id?
var siteName = HostingEnvironment.SiteName;
var applicationId = HostingEnvironment.ApplicationID; // ie HttpRuntime.AppDomainAppId
var hashString = siteName + "::" + applicationId;
var hash = hashString.GenerateHash();
var siteTemp = System.IO.Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData", hash);
return _localTempPath = System.IO.Path.Combine(siteTemp, "umbraco.config");
// include the appdomain hash is just a safety check, for example if a website is moved from worker A to worker B and then back
// to worker A again, in theory the %temp% folder should already be empty but we really want to make sure that its not
// utilizing an old path - assuming we cannot have SHA1 collisions on AppDomainAppId
var appDomainHash = HttpRuntime.AppDomainAppId.GenerateHash();
return System.IO.Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData", appDomainHash);
//case LocalTempStorage.Default:
//case LocalTempStorage.Unknown:
default:
return _localTempPath = IOHelper.MapPath("~/App_Data/TEMP");
return IOHelper.MapPath("~/App_Data/TEMP");
}
}
}
/// <summary>
/// Gets the default UI language.
/// </summary>
+11 -5
View File
@@ -7,6 +7,7 @@ using System.Web;
using System.Xml.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NPoco.Expressions;
using Umbraco.Core.Composing;
using Umbraco.Core.IO;
using Umbraco.Core.Models;
@@ -52,8 +53,8 @@ namespace Umbraco.Core
return ContentStatus.Unpublished;
}
#endregion
/// <summary>
@@ -134,9 +135,14 @@ namespace Umbraco.Core
/// <summary>
/// Sets the posted file value of a property.
/// </summary>
/// <remarks>This really is for FileUpload fields only, and should be obsoleted. For anything else,
/// you need to store the file by yourself using Store and then figure out
/// how to deal with auto-fill properties (if any) and thumbnails (if any) by yourself.</remarks>
public static void SetValue(this IContentBase content, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, string propertyTypeAlias, string filename, HttpPostedFileBase postedFile, string culture = null, string segment = null)
{
content.SetValue(contentTypeBaseServiceProvider, propertyTypeAlias, postedFile.FileName, postedFile.InputStream, culture, segment);
}
/// <summary>
/// Sets the posted file value of a property.
/// </summary>
public static void SetValue(this IContentBase content, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, string propertyTypeAlias, string filename, Stream filestream, string culture = null, string segment = null)
{
if (filename == null || filestream == null) return;
+17
View File
@@ -10,6 +10,23 @@ namespace Umbraco.Core
///</summary>
public static class EnumerableExtensions
{
internal static bool HasDuplicates<T>(this IEnumerable<T> items, bool includeNull)
{
var hs = new HashSet<T>();
foreach (var item in items)
{
if (item != null || includeNull)
{
if (!hs.Add(item))
{
return true;
}
}
}
return false;
}
/// <summary>
/// Wraps this object instance into an IEnumerable{T} consisting of a single item.
/// </summary>
@@ -8,9 +8,12 @@ namespace Umbraco.Core.IO.MediaPathSchemes
/// </summary>
/// <remarks>
/// <para>Path is "{combinedGuid}/{filename}" where combinedGuid is a combination of itemGuid and propertyGuid.</para>
/// <para>This scheme is dangerous, as it does not prevent potential collisions.</para>
/// </remarks>
public class CombinedGuidsMediaPathScheme : IMediaPathScheme
{
private const int DirectoryLength = 8;
/// <inheritdoc />
public string GetFilePath(IMediaFileSystem fileSystem, Guid itemGuid, Guid propertyGuid, string filename, string previous = null)
{
@@ -18,7 +21,7 @@ namespace Umbraco.Core.IO.MediaPathSchemes
// for a single content cannot store two different files with the same name
var combinedGuid = GuidUtils.Combine(itemGuid, propertyGuid);
var directory = HexEncoder.Encode(combinedGuid.ToByteArray()/*'/', 2, 4*/); // could use ext to fragment path eg 12/e4/f2/...
var directory = GuidUtils.ToBase32String(combinedGuid, DirectoryLength); // see also HexEncoder, we may want to fragment path eg 12/e4/f3...
return Path.Combine(directory, filename).Replace('\\', '/');
}
@@ -7,7 +7,7 @@ namespace Umbraco.Core.IO.MediaPathSchemes
/// Implements a unique directory media path scheme.
/// </summary>
/// <remarks>
/// <para>This scheme provides deterministic short paths, with potential collisions.</para>
/// <para>This scheme provides short paths, yet handle potential collisions.</para>
/// </remarks>
public class UniqueMediaPathScheme : IMediaPathScheme
{
@@ -16,22 +16,23 @@ namespace Umbraco.Core.IO.MediaPathSchemes
/// <inheritdoc />
public string GetFilePath(IMediaFileSystem fileSystem, Guid itemGuid, Guid propertyGuid, string filename, string previous = null)
{
var combinedGuid = GuidUtils.Combine(itemGuid, propertyGuid);
var directory = GuidUtils.ToBase32String(combinedGuid, DirectoryLength);
string directory;
// no point "combining" guids if all we want is some random guid - just get a new one
// and then, because we don't want collisions, ensure that the directory does not already exist
// (should be quite rare, but eh...)
do
{
var combinedGuid = Guid.NewGuid();
directory = GuidUtils.ToBase32String(combinedGuid, DirectoryLength); // see also HexEncoder, we may want to fragment path eg 12/e4/f3...
} while (fileSystem.DirectoryExists(directory));
return Path.Combine(directory, filename).Replace('\\', '/');
}
/// <inheritdoc />
/// <remarks>
/// <para>Returning null so that <see cref="MediaFileSystem.DeleteMediaFiles"/> does *not*
/// delete any directory. This is because the above shortening of the Guid to 8 chars
/// means we're increasing the risk of collision, and we don't want to delete files
/// belonging to other media items.</para>
/// <para>And, at the moment, we cannot delete directory "only if it is empty" because of
/// race conditions. We'd need to implement locks in <see cref="MediaFileSystem"/> for
/// this.</para>
/// </remarks>
public string GetDeleteDirectory(IMediaFileSystem fileSystem, string filepath) => null;
public string GetDeleteDirectory(IMediaFileSystem fileSystem, string filepath) => Path.GetDirectoryName(filepath);
}
}
}
@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using Serilog.Events;
using Serilog.Formatting.Compact.Reader;
@@ -11,15 +10,13 @@ namespace Umbraco.Core.Logging.Viewer
internal class JsonLogViewer : LogViewerSourceBase
{
private readonly string _logsPath;
private readonly ILogger _logger;
public JsonLogViewer(ILogger logger, string logsPath = "", string searchPath = "") : base(searchPath)
public JsonLogViewer(string logsPath = "", string searchPath = "") : base(searchPath)
{
if (string.IsNullOrEmpty(logsPath))
logsPath = $@"{AppDomain.CurrentDomain.BaseDirectory}\App_Data\Logs\";
_logsPath = logsPath;
_logger = logger;
}
private const int FileSizeCap = 100;
@@ -80,14 +77,8 @@ namespace Umbraco.Core.Logging.Viewer
using (var stream = new StreamReader(fs))
{
var reader = new LogEventReader(stream);
while (TryRead(reader, out var evt))
while (reader.TryRead(out var evt))
{
//We may get a null if log line is malformed
if (evt == null)
{
continue;
}
if (count > skip + take)
{
break;
@@ -114,21 +105,5 @@ namespace Umbraco.Core.Logging.Viewer
return logs;
}
private bool TryRead(LogEventReader reader, out LogEvent evt)
{
try
{
return reader.TryRead(out evt);
}
catch (JsonReaderException ex)
{
// As we are reading/streaming one line at a time in the JSON file
// Thus we can not report the line number, as it will always be 1
_logger.Error<JsonLogViewer>(ex, "Unable to parse a line in the JSON log file");
evt = null;
return true;
}
}
}
}
@@ -9,7 +9,7 @@ namespace Umbraco.Core.Logging.Viewer
{
public void Compose(Composition composition)
{
composition.SetLogViewer(_ => new JsonLogViewer(composition.Logger));
composition.SetLogViewer(_ => new JsonLogViewer());
}
}
}
@@ -226,6 +226,8 @@ namespace Umbraco.Core.Migrations.Install
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 32, UniqueId = 32.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLockoutDate, Name = Constants.Conventions.Member.LastLockoutDateLabel, SortOrder = 4, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 33, UniqueId = 33.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLoginDate, Name = Constants.Conventions.Member.LastLoginDateLabel, SortOrder = 5, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 34, UniqueId = 34.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastPasswordChangeDate, Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, SortOrder = 6, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 35, UniqueId = 35.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = null, Alias = Constants.Conventions.Member.PasswordQuestion, Name = Constants.Conventions.Member.PasswordQuestionLabel, SortOrder = 7, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing });
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 36, UniqueId = 36.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = null, Alias = Constants.Conventions.Member.PasswordAnswer, Name = Constants.Conventions.Member.PasswordAnswerLabel, SortOrder = 8, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing });
}
@@ -1,16 +0,0 @@
using Umbraco.Core.Migrations.PostMigrations;
namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0
{
public class ChangeNuCacheJsonFormat : MigrationBase
{
public ChangeNuCacheJsonFormat(IMigrationContext context) : base(context)
{ }
public override void Migrate()
{
// nothing - just adding the post-migration
Context.AddPostMigration<RebuildPublishedSnapshot>();
}
}
}
+19 -3
View File
@@ -95,6 +95,21 @@ namespace Umbraco.Core.Models
protected void PropertyTypesChanged(object sender, NotifyCollectionChangedEventArgs e)
{
//enable this to detect duplicate property aliases. We do want this, however making this change in a
//patch release might be a little dangerous
////detect if there are any duplicate aliases - this cannot be allowed
//if (e.Action == NotifyCollectionChangedAction.Add
// || e.Action == NotifyCollectionChangedAction.Replace)
//{
// var allAliases = _noGroupPropertyTypes.Concat(PropertyGroups.SelectMany(x => x.PropertyTypes)).Select(x => x.Alias);
// if (allAliases.HasDuplicates(false))
// {
// var newAliases = string.Join(", ", e.NewItems.Cast<PropertyType>().Select(x => x.Alias));
// throw new InvalidOperationException($"Other property types already exist with the aliases: {newAliases}");
// }
//}
OnPropertyChanged(nameof(PropertyTypes));
}
@@ -397,15 +412,16 @@ namespace Umbraco.Core.Models
var group = PropertyGroups[propertyGroupName];
if (group == null) return;
// re-assign the group's properties to no group
// first remove the group
PropertyGroups.RemoveItem(propertyGroupName);
// Then re-assign the group's properties to no group
foreach (var property in group.PropertyTypes)
{
property.PropertyGroupId = null;
_noGroupPropertyTypes.Add(property);
}
// actually remove the group
PropertyGroups.RemoveItem(propertyGroupName);
OnPropertyChanged(nameof(PropertyGroups));
}
@@ -15,7 +15,7 @@ namespace Umbraco.Core.Models
public class PropertyCollection : KeyedCollection<string, Property>, INotifyCollectionChanged, IDeepCloneable
{
private readonly object _addLocker = new object();
internal Action OnAdd;
internal Func<Property, bool> AdditionValidator { get; set; }
/// <summary>
@@ -49,10 +49,12 @@ namespace Umbraco.Core.Models
/// </summary>
internal void Reset(IEnumerable<Property> properties)
{
//collection events will be raised in each of these calls
Clear();
//collection events will be raised in each of these calls
foreach (var property in properties)
Add(property);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
/// <summary>
@@ -60,8 +62,9 @@ namespace Umbraco.Core.Models
/// </summary>
protected override void SetItem(int index, Property property)
{
var oldItem = index >= 0 ? this[index] : property;
base.SetItem(index, property);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, property, index));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, property, oldItem));
}
/// <summary>
@@ -120,10 +123,8 @@ namespace Umbraco.Core.Models
}
}
//collection events will be raised in InsertItem with Add
base.Add(property);
OnAdd?.Invoke();
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, property));
}
}
@@ -19,9 +19,6 @@ namespace Umbraco.Core.Models
{
private readonly ReaderWriterLockSlim _addLocker = new ReaderWriterLockSlim();
// TODO: this doesn't seem to be used anywhere
internal Action OnAdd;
internal PropertyGroupCollection()
{ }
@@ -37,16 +34,19 @@ namespace Umbraco.Core.Models
/// <remarks></remarks>
internal void Reset(IEnumerable<PropertyGroup> groups)
{
//collection events will be raised in each of these calls
Clear();
//collection events will be raised in each of these calls
foreach (var group in groups)
Add(group);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
protected override void SetItem(int index, PropertyGroup item)
{
var oldItem = index >= 0 ? this[index] : item;
base.SetItem(index, item);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, item, oldItem));
}
protected override void RemoveItem(int index)
@@ -84,6 +84,7 @@ namespace Umbraco.Core.Models
if (keyExists)
throw new Exception($"Naming conflict: Changing the name of PropertyGroup '{item.Name}' would result in duplicates");
//collection events will be raised in SetItem
SetItem(IndexOfKey(item.Id), item);
return;
}
@@ -96,16 +97,14 @@ namespace Umbraco.Core.Models
var exists = Contains(key);
if (exists)
{
//collection events will be raised in SetItem
SetItem(IndexOfKey(key), item);
return;
}
}
}
//collection events will be raised in InsertItem
base.Add(item);
OnAdd?.Invoke();
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
}
finally
{
@@ -19,9 +19,6 @@ namespace Umbraco.Core.Models
[IgnoreDataMember]
private readonly ReaderWriterLockSlim _addLocker = new ReaderWriterLockSlim();
// TODO: This doesn't seem to be used
[IgnoreDataMember]
internal Action OnAdd;
internal PropertyTypeCollection(bool supportsPublishing)
{
@@ -43,36 +40,44 @@ namespace Umbraco.Core.Models
/// <remarks></remarks>
internal void Reset(IEnumerable<PropertyType> properties)
{
//collection events will be raised in each of these calls
Clear();
//collection events will be raised in each of these calls
foreach (var property in properties)
Add(property);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
Add(property);
}
protected override void SetItem(int index, PropertyType item)
{
item.SupportsPublishing = SupportsPublishing;
base.SetItem(index, item);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
var oldItem = index >= 0 ? this[index] : item;
base.SetItem(index, item);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, item, oldItem));
item.PropertyChanged += Item_PropertyChanged;
}
protected override void RemoveItem(int index)
{
var removed = this[index];
base.RemoveItem(index);
removed.PropertyChanged -= Item_PropertyChanged;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed));
}
protected override void InsertItem(int index, PropertyType item)
{
item.SupportsPublishing = SupportsPublishing;
base.InsertItem(index, item);
base.InsertItem(index, item);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
item.PropertyChanged += Item_PropertyChanged;
}
protected override void ClearItems()
{
base.ClearItems();
foreach (var item in this)
item.PropertyChanged -= Item_PropertyChanged;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
@@ -91,6 +96,7 @@ namespace Umbraco.Core.Models
var exists = Contains(key);
if (exists)
{
//collection events will be raised in SetItem
SetItem(IndexOfKey(key), item);
return;
}
@@ -103,10 +109,8 @@ namespace Umbraco.Core.Models
item.SortOrder = this.Max(x => x.SortOrder) + 1;
}
//collection events will be raised in InsertItem
base.Add(item);
OnAdd?.Invoke();
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
}
finally
{
@@ -115,6 +119,17 @@ namespace Umbraco.Core.Models
}
}
/// <summary>
/// Occurs when a property changes on a PropertyType that exists in this collection
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Item_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
var propType = (PropertyType)sender;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, propType, propType));
}
/// <summary>
/// Determines whether this collection contains a <see cref="Property"/> whose alias matches the specified PropertyType.
/// </summary>
@@ -9,11 +9,10 @@ namespace Umbraco.Core.Persistence.Factories
{
internal static class MemberTypeReadOnlyFactory
{
public static IMemberType BuildEntity(MemberTypeReadOnlyDto dto, out bool needsSaving)
public static IMemberType BuildEntity(MemberTypeReadOnlyDto dto)
{
var standardPropertyTypes = Constants.Conventions.Member.GetStandardPropertyTypeStubs();
needsSaving = false;
var memberType = new MemberType(dto.ParentId);
try
@@ -41,27 +40,29 @@ namespace Umbraco.Core.Persistence.Factories
var propertyTypeGroupCollection = GetPropertyTypeGroupCollection(dto, memberType, standardPropertyTypes);
memberType.PropertyGroups = propertyTypeGroupCollection;
var propertyTypes = GetPropertyTypes(dto, memberType, standardPropertyTypes);
memberType.NoGroupPropertyTypes = GetNoGroupPropertyTypes(dto, memberType, standardPropertyTypes);
//By Convention we add 9 standard PropertyTypes - This is only here to support loading of types that didn't have these conventions before.
// By Convention we add 9 standard PropertyTypes - This is only here to support loading of types that didn't have these conventions before.
// In theory this should not happen! The only reason this did happen was because:
// A) we didn't install all of the default membership properties by default
// B) the existing data is super old when we didn't store membership properties at all (very very old)
// So what to do? We absolutely do not want to update the database when only a read was requested, this will cause problems
// so we should just add these virtual properties, they will have no ids but they will have aliases and this should be perfectly
// fine for any membership provider logic to work since neither membership providers or asp.net identity care about whether a property
// has an ID or not.
// When the member type is saved, all the properties will correctly be added.
//This will add this group if it doesn't exist, no need to error check here
memberType.AddPropertyGroup(Constants.Conventions.Member.StandardPropertiesGroupName);
foreach (var standardPropertyType in standardPropertyTypes)
{
if (dto.PropertyTypes.Any(x => x.Alias.Equals(standardPropertyType.Key))) continue;
//This will add the property if it doesn't exist, no need to error check here
memberType.AddPropertyType(standardPropertyType.Value, Constants.Conventions.Member.StandardPropertiesGroupName);
// beware!
// means that we can return a memberType "from database" that has some property types
// that do *not* come from the database and therefore are incomplete eg have no key,
// no id, no dataTypeDefinitionId - ouch! - better notify caller of the situation
needsSaving = true;
//Add the standard PropertyType to the current list
propertyTypes.Add(standardPropertyType.Value);
//Internal dictionary for adding "MemberCanEdit", "VisibleOnProfile", "IsSensitive" properties to each PropertyType
memberType.MemberTypePropertyTypes.Add(standardPropertyType.Key,
new MemberTypePropertyProfileAccess(false, false, false));
if (!memberType.MemberTypePropertyTypes.TryGetValue(standardPropertyType.Key, out var memberTypePropertyProfile))
memberType.MemberTypePropertyTypes[standardPropertyType.Key] = new MemberTypePropertyProfileAccess(false, false, false);
}
memberType.NoGroupPropertyTypes = propertyTypes;
return memberType;
}
@@ -147,13 +148,14 @@ namespace Umbraco.Core.Persistence.Factories
return propertyGroups;
}
private static List<PropertyType> GetPropertyTypes(MemberTypeReadOnlyDto dto, MemberType memberType, Dictionary<string, PropertyType> standardProps)
private static List<PropertyType> GetNoGroupPropertyTypes(MemberTypeReadOnlyDto dto, MemberType memberType, Dictionary<string, PropertyType> standardProps)
{
//Find PropertyTypes that does not belong to a PropertyTypeGroup
var propertyTypes = new List<PropertyType>();
foreach (var typeDto in dto.PropertyTypes.Where(x => (x.PropertyTypeGroupId.HasValue == false || x.PropertyTypeGroupId.Value == 0) && x.Id.HasValue))
{
//Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType
memberType.MemberTypePropertyTypes.Add(typeDto.Alias,
new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit, typeDto.IsSensitive));
@@ -292,6 +292,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (psql.Arguments[i] is string s && s == "[[[ISOCODE]]]")
{
psql.Arguments[i] = ordering.Culture;
break;
}
}
}
@@ -982,14 +982,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// invariant: left join will yield NULL and we must use pcv to determine published
// variant: left join may yield NULL or something, and that determines published
var joins = Sql()
.InnerJoin<ContentTypeDto>("ctype").On<ContentDto, ContentTypeDto>((content, contentType) => content.ContentTypeId == contentType.NodeId, aliasRight: "ctype")
// left join on optional culture variation
//the magic "[[[ISOCODE]]]" parameter value will be replaced in ContentRepositoryBase.GetPage() by the actual ISO code
.LeftJoin<ContentVersionCultureVariationDto>(nested =>
nested.InnerJoin<LanguageDto>("langp").On<ContentVersionCultureVariationDto, LanguageDto>((ccv, lang) => ccv.LanguageId == lang.Id && lang.IsoCode == "[[[ISOCODE]]]", "ccvp", "langp"), "ccvp")
.On<ContentVersionDto, ContentVersionCultureVariationDto>((version, ccv) => version.Id == ccv.VersionId, aliasLeft: "pcv", aliasRight: "ccvp");
.InnerJoin<ContentTypeDto>("ctype").On<ContentDto, ContentTypeDto>((content, contentType) => content.ContentTypeId == contentType.NodeId, aliasRight: "ctype");
sql = InsertJoins(sql, joins);
@@ -999,7 +993,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// when invariant, ie 'variations' does not have the culture flag (value 1), use the global 'published' flag on pcv.id,
// otherwise check if there's a version culture variation for the lang, via ccv.id
", (CASE WHEN (ctype.variations & 1) = 0 THEN (CASE WHEN pcv.id IS NULL THEN 0 ELSE 1 END) ELSE (CASE WHEN ccvp.id IS NULL THEN 0 ELSE 1 END) END) AS ordering "); // trailing space is important!
", (CASE WHEN (ctype.variations & 1) = 0 THEN (CASE WHEN pcv.id IS NULL THEN 0 ELSE 1 END) ELSE (CASE WHEN ccv.id IS NULL THEN 0 ELSE 1 END) END) AS ordering "); // trailing space is important!
sql = Sql(sqlText, sql.Arguments);
@@ -46,7 +46,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override IIdentityUserLogin PerformGet(int id)
{
var sql = GetBaseQuery(false);
sql.Where(GetBaseWhereClause(), new { Id = id });
sql.Where(GetBaseWhereClause(), new { id = id });
var dto = Database.Fetch<ExternalLoginDto>(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault();
if (dto == null)
@@ -308,13 +308,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (dtos == null || dtos.Any() == false)
return Enumerable.Empty<IMemberType>();
return dtos.Select(x =>
{
bool needsSaving;
var memberType = MemberTypeReadOnlyFactory.BuildEntity(x, out needsSaving);
if (needsSaving) PersistUpdatedItem(memberType);
return memberType;
}).ToList();
return dtos.Select(MemberTypeReadOnlyFactory.BuildEntity).ToList();
}
/// <summary>
@@ -1,8 +1,8 @@
namespace Umbraco.Core.PropertyEditors
{
internal class DropDownFlexibleConfiguration : ValueListConfiguration
public class DropDownFlexibleConfiguration : ValueListConfiguration
{
[ConfigurationField("multiple", "Enable multiple choice", "boolean", Description = "When checked, the dropdown will be a select multiple / combo box style dropdown.")]
public bool Multiple { get; set; }
}
}
}
+7 -1
View File
@@ -5,6 +5,7 @@ using System.Linq;
using System.Reflection;
using System.Threading;
using System.Web;
using System.Web.Hosting;
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration;
@@ -70,10 +71,15 @@ namespace Umbraco.Core.Runtime
// objects.
using (var timer = profilingLogger.TraceDuration<CoreRuntime>(
$"Booting Umbraco {UmbracoVersion.SemanticVersion.ToSemanticString()} on {NetworkHelper.MachineName}.",
$"Booting Umbraco {UmbracoVersion.SemanticVersion.ToSemanticString()}.",
"Booted.",
"Boot failed."))
{
logger.Info<CoreRuntime>("Booting site '{HostingSiteName}', app '{HostingApplicationID}', path '{HostingPhysicalPath}', server '{MachineName}'.",
HostingEnvironment.SiteName,
HostingEnvironment.ApplicationID,
HostingEnvironment.ApplicationPhysicalPath,
NetworkHelper.MachineName);
logger.Debug<CoreRuntime>("Runtime: {Runtime}", GetType().FullName);
// application environment
@@ -2,39 +2,61 @@
namespace Umbraco.Core.Scoping
{
// base class for an object that will be enlisted in scope context, if any. it
// must be used in a 'using' block, and if not scoped, released when disposed,
// else when scope context runs enlisted actions
/// <summary>
/// Provides a base class for scope contextual objects.
/// </summary>
/// <remarks>
/// <para>A scope contextual object is enlisted in the current scope context,
/// if any, and released when the context exists. It must be used in a 'using'
/// block, and will be released when disposed, if not part of a scope.</para>
/// </remarks>
public abstract class ScopeContextualBase : IDisposable
{
private bool _using, _scoped;
private bool _scoped;
/// <summary>
/// Gets a contextual object.
/// </summary>
/// <typeparam name="T">The type of the object.</typeparam>
/// <param name="scopeProvider">A scope provider.</param>
/// <param name="key">A context key for the object.</param>
/// <param name="ctor">A function producing the contextual object.</param>
/// <returns>The contextual object.</returns>
/// <remarks>
/// <para></para>
/// </remarks>
public static T Get<T>(IScopeProvider scopeProvider, string key, Func<bool, T> ctor)
where T : ScopeContextualBase
{
// no scope context = create a non-scoped object
var scopeContext = scopeProvider.Context;
if (scopeContext == null)
return ctor(false);
// create & enlist the scoped object
var w = scopeContext.Enlist("ScopeContextualBase_" + key,
() => ctor(true),
(completed, item) => { item.Release(completed); });
if (w._using) throw new InvalidOperationException("panic: used.");
w._using = true;
w._scoped = true;
return w;
}
/// <inheritdoc />
/// <remarks>
/// <para>If not scoped, then this releases the contextual object.</para>
/// </remarks>
public void Dispose()
{
_using = false;
if (_scoped == false)
Release(true);
}
/// <summary>
/// Releases the contextual object.
/// </summary>
/// <param name="completed">A value indicating whether the scoped operation completed.</param>
public abstract void Release(bool completed);
}
}
@@ -1902,9 +1902,8 @@ namespace Umbraco.Core.Services.Implement
content.ParentId = parentId;
// get the level delta (old pos to new pos)
var levelDelta = parent == null
? 1 - content.Level + (parentId == Constants.System.RecycleBinContent ? 1 : 0)
: parent.Level + 1 - content.Level;
// note that recycle bin (id:-20) level is 0!
var levelDelta = 1 - content.Level + (parent?.Level ?? 0);
var paths = new Dictionary<int, string>();
@@ -2862,7 +2861,7 @@ namespace Umbraco.Core.Services.Implement
{
if (blueprint == null) throw new ArgumentNullException(nameof(blueprint));
var contentType = GetContentType(blueprint.ContentType.Alias);
var contentType = _contentTypeRepository.Get(blueprint.ContentType.Id);
var content = new Content(name, -1, contentType);
content.Path = string.Concat(content.ParentId.ToString(), ",", content.Id);
@@ -289,7 +289,7 @@ namespace Umbraco.Core.Services.Implement
scope.Events.Dispatch(Saved, this, saveEventArgs);
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IMedia>(media, TreeChangeTypes.RefreshNode).ToEventArgs());
}
if (withIdentity == false)
return;
@@ -716,7 +716,7 @@ namespace Umbraco.Core.Services.Implement
#endregion
#region Delete
/// <summary>
/// Permanently deletes an <see cref="IMedia"/> object
/// </summary>
@@ -975,9 +975,8 @@ namespace Umbraco.Core.Services.Implement
media.ParentId = parentId;
// get the level delta (old pos to new pos)
var levelDelta = parent == null
? 1 - media.Level + (parentId == Constants.System.RecycleBinMedia ? 1 : 0)
: parent.Level + 1 - media.Level;
// note that recycle bin (id:-20) level is 0!
var levelDelta = 1 - media.Level + (parent?.Level ?? 0);
var paths = new Dictionary<int, string>();
@@ -1024,6 +1023,7 @@ namespace Umbraco.Core.Services.Implement
/// <summary>
/// Empties the Recycle Bin by deleting all <see cref="IMedia"/> that resides in the bin
/// </summary>
/// <param name="userId">Optional Id of the User emptying the Recycle Bin</param>
public OperationResult EmptyRecycleBin()
{
var nodeObjectType = Constants.ObjectTypes.Media;
@@ -17,7 +17,7 @@ namespace Umbraco.Core.Strings
/// Converts an Utf8 string into an Ascii string.
/// </summary>
/// <param name="text">The text to convert.</param>
/// <param name="fail">The character to use to replace characters that cannot properly be converted.</param>
/// <param name="fail">The character to used to replace characters that cannot properly be converted.</param>
/// <returns>The converted text.</returns>
public static string ToAsciiString(string text, char fail = '?')
{
@@ -39,7 +39,7 @@ namespace Umbraco.Core.Strings
/// Converts an Utf8 string into an array of Ascii characters.
/// </summary>
/// <param name="text">The text to convert.</param>
/// <param name="fail">The character to use to replace characters that cannot properly be converted.</param>
/// <param name="fail">The character to used to replace characters that cannot properly be converted.</param>
/// <returns>The converted text.</returns>
public static char[] ToAsciiCharArray(string text, char fail = '?')
{
@@ -66,7 +66,7 @@ namespace Umbraco.Core.Strings
/// </summary>
/// <param name="input">The input array.</param>
/// <param name="output">The output array.</param>
/// <param name="fail">The character to use to replace characters that cannot properly be converted.</param>
/// <param name="fail">The character to used to replace characters that cannot properly be converted.</param>
/// <returns>The number of characters in the output array.</returns>
/// <remarks>The caller must ensure that the output array is big enough.</remarks>
/// <exception cref="OverflowException">The output array is not big enough.</exception>
@@ -112,7 +112,7 @@ namespace Umbraco.Core.Strings
/// <param name="ipos">The input position.</param>
/// <param name="output">The output array.</param>
/// <param name="opos">The output position.</param>
/// <param name="fail">The character to use to replace characters that cannot properly be converted.</param>
/// <param name="fail">The character to used to replace characters that cannot properly be converted.</param>
/// <remarks>
/// <para>Adapted from various sources on the 'net including <c>Lucene.Net.Analysis.ASCIIFoldingFilter</c>.</para>
/// <para>Input should contain Utf8 characters exclusively and NOT Unicode.</para>
-2
View File
@@ -28,8 +28,6 @@ namespace Umbraco.Examine
/// </remarks>
internal static readonly Regex CultureIsoCodeFieldNameMatchExpression = new Regex("^([_\\w]+)_([a-z]{2}-[a-z0-9]{2,4})$", RegexOptions.Compiled);
//TODO: We need a public method here to just match a field name against CultureIsoCodeFieldNameMatchExpression
/// <summary>
/// Returns all index fields that are culture specific (suffixed)
/// </summary>
+214 -28
View File
@@ -5,6 +5,7 @@ using Moq;
using NUnit.Framework;
using Umbraco.Core.Scoping;
using Umbraco.Web.PublishedCache.NuCache;
using Umbraco.Web.PublishedCache.NuCache.Snap;
namespace Umbraco.Tests.Cache
{
@@ -388,8 +389,7 @@ namespace Umbraco.Tests.Cache
// collect liveGen
GC.Collect();
SnapDictionary<int, string>.GenerationObject genObj;
Assert.IsTrue(d.Test.GenerationObjects.TryPeek(out genObj));
Assert.IsTrue(d.Test.GenObjs.TryPeek(out var genObj));
genObj = null;
// in Release mode, it works, but in Debug mode, the weak reference is still alive
@@ -399,14 +399,14 @@ namespace Umbraco.Tests.Cache
GC.Collect();
#endif
Assert.IsTrue(d.Test.GenerationObjects.TryPeek(out genObj));
Assert.IsFalse(genObj.WeakReference.IsAlive); // snapshot is gone, along with its reference
Assert.IsTrue(d.Test.GenObjs.TryPeek(out genObj));
Assert.IsFalse(genObj.WeakGenRef.IsAlive); // snapshot is gone, along with its reference
await d.CollectAsync();
Assert.AreEqual(0, d.Test.GetValues(1).Length); // null value is gone
Assert.AreEqual(0, d.Count); // item is gone
Assert.AreEqual(0, d.Test.GenerationObjects.Count);
Assert.AreEqual(0, d.Test.GenObjs.Count);
Assert.AreEqual(0, d.SnapCount); // snapshot is gone
Assert.AreEqual(0, d.GenCount); // and generation has been dequeued
}
@@ -632,7 +632,7 @@ namespace Umbraco.Tests.Cache
Assert.AreEqual(1, d.Test.LiveGen);
Assert.IsTrue(d.Test.NextGen);
using (d.GetWriter(GetScopeProvider()))
using (d.GetScopedWriteLock(GetScopeProvider()))
{
var s1 = d.CreateSnapshot();
@@ -685,7 +685,7 @@ namespace Umbraco.Tests.Cache
Assert.IsFalse(d.Test.NextGen);
Assert.AreEqual("uno", s2.Get(1));
using (d.GetWriter(GetScopeProvider()))
using (d.GetScopedWriteLock(GetScopeProvider()))
{
// gen 3
Assert.AreEqual(2, d.Test.GetValues(1).Length);
@@ -712,16 +712,102 @@ namespace Umbraco.Tests.Cache
}
[Test]
public void NestedWriteLocking()
public void NestedWriteLocking1()
{
var d = new SnapDictionary<int, string>();
var t = d.Test;
t.CollectAuto = false;
Assert.AreEqual(0, d.CreateSnapshot().Gen);
// no scope context: writers nest, last one to be disposed commits
var scopeProvider = GetScopeProvider();
using (var w1 = d.GetScopedWriteLock(scopeProvider))
{
Assert.AreEqual(1, t.LiveGen);
Assert.AreEqual(1, t.WLocked);
Assert.IsTrue(t.NextGen);
using (var w2 = d.GetScopedWriteLock(scopeProvider))
{
Assert.AreEqual(1, t.LiveGen);
Assert.AreEqual(2, t.WLocked);
Assert.IsTrue(t.NextGen);
Assert.AreNotSame(w1, w2); // get a new writer each time
d.Set(1, "one");
Assert.AreEqual(0, d.CreateSnapshot().Gen);
}
Assert.AreEqual(1, t.LiveGen);
Assert.AreEqual(1, t.WLocked);
Assert.IsTrue(t.NextGen);
Assert.AreEqual(0, d.CreateSnapshot().Gen);
}
Assert.AreEqual(1, t.LiveGen);
Assert.AreEqual(0, t.WLocked);
Assert.IsTrue(t.NextGen);
Assert.AreEqual(1, d.CreateSnapshot().Gen);
}
[Test]
public void NestedWriteLocking2()
{
var d = new SnapDictionary<int, string>();
d.Test.CollectAuto = false;
var scopeProvider = GetScopeProvider();
using (d.GetWriter(scopeProvider))
Assert.AreEqual(0, d.CreateSnapshot().Gen);
// scope context: writers enlist
var scopeContext = new ScopeContext();
var scopeProvider = GetScopeProvider(scopeContext);
using (var w1 = d.GetScopedWriteLock(scopeProvider))
{
using (d.GetWriter(scopeProvider))
using (var w2 = d.GetScopedWriteLock(scopeProvider))
{
Assert.AreSame(w1, w2);
d.Set(1, "one");
}
}
}
[Test]
public void NestedWriteLocking3()
{
var d = new SnapDictionary<int, string>();
var t = d.Test;
t.CollectAuto = false;
Assert.AreEqual(0, d.CreateSnapshot().Gen);
var scopeContext = new ScopeContext();
var scopeProvider1 = GetScopeProvider();
var scopeProvider2 = GetScopeProvider(scopeContext);
using (var w1 = d.GetScopedWriteLock(scopeProvider1))
{
Assert.AreEqual(1, t.LiveGen);
Assert.AreEqual(1, t.WLocked);
Assert.IsTrue(t.NextGen);
using (var w2 = d.GetScopedWriteLock(scopeProvider2))
{
Assert.AreEqual(1, t.LiveGen);
Assert.AreEqual(2, t.WLocked);
Assert.IsTrue(t.NextGen);
Assert.AreNotSame(w1, w2);
d.Set(1, "one");
}
}
@@ -764,7 +850,7 @@ namespace Umbraco.Tests.Cache
var scopeProvider = GetScopeProvider();
using (d.GetWriter(scopeProvider))
using (d.GetScopedWriteLock(scopeProvider))
{
// gen 3
Assert.AreEqual(2, d.Test.GetValues(1).Length);
@@ -809,7 +895,7 @@ namespace Umbraco.Tests.Cache
var scopeProvider = GetScopeProvider();
using (d.GetWriter(scopeProvider))
using (d.GetScopedWriteLock(scopeProvider))
{
// creating a snapshot in a write-lock does NOT return the "current" content
// it uses the previous snapshot, so new snapshot created only on release
@@ -846,9 +932,10 @@ namespace Umbraco.Tests.Cache
Assert.AreEqual(2, s2.Gen);
Assert.AreEqual("uno", s2.Get(1));
var scopeProvider = GetScopeProvider(true);
var scopeContext = new ScopeContext();
var scopeProvider = GetScopeProvider(scopeContext);
using (d.GetWriter(scopeProvider))
using (d.GetScopedWriteLock(scopeProvider))
{
// creating a snapshot in a write-lock does NOT return the "current" content
// it uses the previous snapshot, so new snapshot created only on release
@@ -867,7 +954,7 @@ namespace Umbraco.Tests.Cache
Assert.AreEqual(2, s4.Gen);
Assert.AreEqual("uno", s4.Get(1));
((ScopeContext) scopeProvider.Context).ScopeExit(true);
scopeContext.ScopeExit(true);
var s5 = d.CreateSnapshot();
Assert.AreEqual(3, s5.Gen);
@@ -878,7 +965,8 @@ namespace Umbraco.Tests.Cache
public void ScopeLocking2()
{
var d = new SnapDictionary<int, string>();
d.Test.CollectAuto = false;
var t = d.Test;
t.CollectAuto = false;
// gen 1
d.Set(1, "one");
@@ -891,12 +979,13 @@ namespace Umbraco.Tests.Cache
Assert.AreEqual(2, s2.Gen);
Assert.AreEqual("uno", s2.Get(1));
var scopeProviderMock = new Mock<IScopeProvider>();
var scopeContext = new ScopeContext();
scopeProviderMock.Setup(x => x.Context).Returns(scopeContext);
var scopeProvider = scopeProviderMock.Object;
Assert.AreEqual(2, t.LiveGen);
Assert.IsFalse(t.NextGen);
using (d.GetWriter(scopeProvider))
var scopeContext = new ScopeContext();
var scopeProvider = GetScopeProvider(scopeContext);
using (d.GetScopedWriteLock(scopeProvider))
{
// creating a snapshot in a write-lock does NOT return the "current" content
// it uses the previous snapshot, so new snapshot created only on release
@@ -905,18 +994,35 @@ namespace Umbraco.Tests.Cache
Assert.AreEqual(2, s3.Gen);
Assert.AreEqual("uno", s3.Get(1));
// we made some changes, so a next gen is required
Assert.AreEqual(3, t.LiveGen);
Assert.IsTrue(t.NextGen);
Assert.AreEqual(1, t.WLocked);
// but live snapshot contains changes
var ls = d.Test.LiveSnapshot;
var ls = t.LiveSnapshot;
Assert.AreEqual("ein", ls.Get(1));
Assert.AreEqual(3, ls.Gen);
}
// nothing is committed until scope exits
Assert.AreEqual(3, t.LiveGen);
Assert.IsTrue(t.NextGen);
Assert.AreEqual(1, t.WLocked);
// no changes until exit
var s4 = d.CreateSnapshot();
Assert.AreEqual(2, s4.Gen);
Assert.AreEqual("uno", s4.Get(1));
scopeContext.ScopeExit(false);
// now things have changed
Assert.AreEqual(2, t.LiveGen);
Assert.IsFalse(t.NextGen);
Assert.AreEqual(0, t.WLocked);
// no changes since not completed
var s5 = d.CreateSnapshot();
Assert.AreEqual(2, s5.Gen);
Assert.AreEqual("uno", s5.Get(1));
@@ -955,12 +1061,92 @@ namespace Umbraco.Tests.Cache
Assert.AreEqual("four", all[3]);
}
private IScopeProvider GetScopeProvider(bool withContext = false)
[Test]
public void DontPanic()
{
var scopeProviderMock = new Mock<IScopeProvider>();
var scopeContext = withContext ? new ScopeContext() : null;
scopeProviderMock.Setup(x => x.Context).Returns(scopeContext);
var scopeProvider = scopeProviderMock.Object;
var d = new SnapDictionary<int, string>();
d.Test.CollectAuto = false;
Assert.IsNull(d.Test.GenObj);
// gen 1
d.Set(1, "one");
Assert.IsTrue(d.Test.NextGen);
Assert.AreEqual(1, d.Test.LiveGen);
Assert.IsNull(d.Test.GenObj);
var s1 = d.CreateSnapshot();
Assert.IsFalse(d.Test.NextGen);
Assert.AreEqual(1, d.Test.LiveGen);
Assert.IsNotNull(d.Test.GenObj);
Assert.AreEqual(1, d.Test.GenObj.Gen);
Assert.AreEqual(1, s1.Gen);
Assert.AreEqual("one", s1.Get(1));
d.Set(1, "uno");
Assert.IsTrue(d.Test.NextGen);
Assert.AreEqual(2, d.Test.LiveGen);
Assert.IsNotNull(d.Test.GenObj);
Assert.AreEqual(1, d.Test.GenObj.Gen);
var scopeContext = new ScopeContext();
var scopeProvider = GetScopeProvider(scopeContext);
// scopeProvider.Context == scopeContext -> writer is scoped
// writer is scope contextual and scoped
// when disposed, nothing happens
// when the context exists, the writer is released
using (d.GetScopedWriteLock(scopeProvider))
{
d.Set(1, "ein");
Assert.IsTrue(d.Test.NextGen);
Assert.AreEqual(3, d.Test.LiveGen);
Assert.IsNotNull(d.Test.GenObj);
Assert.AreEqual(2, d.Test.GenObj.Gen);
}
// writer has not released
Assert.AreEqual(1, d.Test.WLocked);
Assert.IsNotNull(d.Test.GenObj);
Assert.AreEqual(2, d.Test.GenObj.Gen);
// nothing changed
Assert.IsTrue(d.Test.NextGen);
Assert.AreEqual(3, d.Test.LiveGen);
// panic!
var s2 = d.CreateSnapshot();
Assert.AreEqual(1, d.Test.WLocked);
Assert.IsNotNull(d.Test.GenObj);
Assert.AreEqual(2, d.Test.GenObj.Gen);
Assert.AreEqual(3, d.Test.LiveGen);
Assert.IsTrue(d.Test.NextGen);
// release writer
scopeContext.ScopeExit(true);
Assert.AreEqual(0, d.Test.WLocked);
Assert.IsNotNull(d.Test.GenObj);
Assert.AreEqual(2, d.Test.GenObj.Gen);
Assert.AreEqual(3, d.Test.LiveGen);
Assert.IsTrue(d.Test.NextGen);
var s3 = d.CreateSnapshot();
Assert.AreEqual(0, d.Test.WLocked);
Assert.IsNotNull(d.Test.GenObj);
Assert.AreEqual(3, d.Test.GenObj.Gen);
Assert.AreEqual(3, d.Test.LiveGen);
Assert.IsFalse(d.Test.NextGen);
}
private IScopeProvider GetScopeProvider(ScopeContext scopeContext = null)
{
var scopeProvider = Mock.Of<IScopeProvider>();
Mock.Get(scopeProvider)
.Setup(x => x.Context).Returns(scopeContext);
return scopeProvider;
}
}
@@ -4,6 +4,7 @@ using System.Linq;
using Moq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Compose;
using Umbraco.Core.Composing;
using Umbraco.Core.IO;
@@ -299,11 +300,19 @@ namespace Umbraco.Tests.Components
composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
Composed.Clear();
Assert.Throws<Exception>(() => composers.Compose());
Console.WriteLine("throws:");
composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
var requirements = composers.GetRequirements(false);
Console.WriteLine(Composers.GetComposersReport(requirements));
types = new[] { typeof(Composer2) };
composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
Composed.Clear();
Assert.Throws<Exception>(() => composers.Compose());
Console.WriteLine("throws:");
composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
requirements = composers.GetRequirements(false);
Console.WriteLine(Composers.GetComposersReport(requirements));
types = new[] { typeof(Composer12) };
composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
@@ -349,6 +358,25 @@ namespace Umbraco.Tests.Components
Assert.AreEqual(typeof(Composer27), Composed[1]);
}
[Test]
public void AllComposers()
{
var typeLoader = new TypeLoader(AppCaches.Disabled.RuntimeCache, IOHelper.MapPath("~/App_Data/TEMP"), Mock.Of<IProfilingLogger>());
var register = MockRegister();
var composition = new Composition(register, typeLoader, Mock.Of<IProfilingLogger>(), MockRuntimeState(RuntimeLevel.Run));
var types = typeLoader.GetTypes<IComposer>().Where(x => x.FullName.StartsWith("Umbraco.Core.") || x.FullName.StartsWith("Umbraco.Web"));
var composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
var requirements = composers.GetRequirements();
var report = Composers.GetComposersReport(requirements);
Console.WriteLine(report);
var composerTypes = composers.SortComposers(requirements);
foreach (var type in composerTypes)
Console.WriteLine(type);
}
#region Compothings
public class TestComposerBase : IComposer
@@ -1,53 +0,0 @@
using System;
using Moq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
using Umbraco.Core.Logging;
namespace Umbraco.Tests.Composing
{
[TestFixture]
public class CompositionTests
{
[Test]
public void FactoryIsResolvable()
{
Func<IFactory, IFactory> factoryFactory = null;
var mockedRegister = Mock.Of<IRegister>();
var mockedFactory = Mock.Of<IFactory>();
// the mocked register creates the mocked factory
Mock.Get(mockedRegister)
.Setup(x => x.CreateFactory())
.Returns(mockedFactory);
// the mocked register can register a factory factory
Mock.Get(mockedRegister)
.Setup(x => x.Register(It.IsAny<Func<IFactory, IFactory>>(), Lifetime.Singleton))
.Callback<Func<IFactory, IFactory>, Lifetime>((ff, lt) => factoryFactory = ff);
// the mocked factory can invoke the factory factory
Mock.Get(mockedFactory)
.Setup(x => x.GetInstance(typeof(IFactory)))
.Returns(() => factoryFactory?.Invoke(mockedFactory));
var logger = new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>());
var typeLoader = new TypeLoader(Mock.Of<IAppPolicyCache>(), "", logger);
var composition = new Composition(mockedRegister, typeLoader, logger, Mock.Of<IRuntimeState>());
// create the factory, ensure it is the mocked factory
var factory = composition.CreateFactory();
Assert.AreSame(mockedFactory, factory);
// ensure we can get an IFactory instance,
// meaning that it has been properly registered
var resolved = factory.GetInstance<IFactory>();
Assert.IsNotNull(resolved);
Assert.AreSame(factory, resolved);
}
}
}
+3 -13
View File
@@ -112,19 +112,9 @@ namespace Umbraco.Tests.IO
fs.DeleteMediaFiles(new[] { virtPath });
Assert.IsFalse(File.Exists(physPath));
var scheme = Current.Factory.GetInstance<IMediaPathScheme>();
if (scheme is UniqueMediaPathScheme)
{
// ~/media/1234 is *not* gone
physPath = Path.GetDirectoryName(physPath);
Assert.IsTrue(Directory.Exists(physPath));
}
else
{
// ~/media/1234 is gone
physPath = Path.GetDirectoryName(physPath);
Assert.IsFalse(Directory.Exists(physPath));
}
// ~/media/1234 is gone
physPath = Path.GetDirectoryName(physPath);
Assert.IsFalse(Directory.Exists(physPath));
// ~/media exists
physPath = Path.GetDirectoryName(physPath);
+2 -4
View File
@@ -1,5 +1,4 @@
using Moq;
using NUnit.Framework;
using NUnit.Framework;
using System;
using System.IO;
using System.Linq;
@@ -48,8 +47,7 @@ namespace Umbraco.Tests.Logging
File.Copy(exampleLogfilePath, _newLogfilePath, true);
File.Copy(exampleSearchfilePath, _newSearchfilePath, true);
var logger = Mock.Of<Core.Logging.ILogger>();
_logViewer = new JsonLogViewer(logger, logsPath: _newLogfileDirPath, searchPath: _newSearchfilePath);
_logViewer = new JsonLogViewer(logsPath: _newLogfileDirPath, searchPath: _newSearchfilePath);
}
[OneTimeTearDown]
+43 -9
View File
@@ -15,13 +15,47 @@ namespace Umbraco.Tests.Models
[TestFixture]
public class ContentTypeTests : UmbracoTestBase
{
[Test]
[Ignore("Ignoring this test until we actually enforce this, see comments in ContentTypeBase.PropertyTypesChanged")]
public void Cannot_Add_Duplicate_Property_Aliases()
{
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.PropertyGroups.Add(new PropertyGroup(new PropertyTypeCollection(false, new[]
{
new PropertyType("testPropertyEditor", ValueStorageType.Nvarchar){ Alias = "myPropertyType" }
})));
Assert.Throws<InvalidOperationException>(() =>
contentType.PropertyTypeCollection.Add(
new PropertyType("testPropertyEditor", ValueStorageType.Nvarchar) { Alias = "myPropertyType" }));
}
[Test]
[Ignore("Ignoring this test until we actually enforce this, see comments in ContentTypeBase.PropertyTypesChanged")]
public void Cannot_Update_Duplicate_Property_Aliases()
{
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.PropertyGroups.Add(new PropertyGroup(new PropertyTypeCollection(false, new[]
{
new PropertyType("testPropertyEditor", ValueStorageType.Nvarchar){ Alias = "myPropertyType" }
})));
contentType.PropertyTypeCollection.Add(new PropertyType("testPropertyEditor", ValueStorageType.Nvarchar) { Alias = "myPropertyType2" });
var toUpdate = contentType.PropertyTypeCollection["myPropertyType2"];
Assert.Throws<InvalidOperationException>(() => toUpdate.Alias = "myPropertyType");
}
[Test]
public void Can_Deep_Clone_Content_Type_Sort()
{
var contentType = new ContentTypeSort(new Lazy<int>(() => 3), 4, "test");
var clone = (ContentTypeSort) contentType.DeepClone();
var clone = (ContentTypeSort)contentType.DeepClone();
Assert.AreNotSame(clone, contentType);
Assert.AreEqual(clone, contentType);
Assert.AreEqual(clone.Id.Value, contentType.Id.Value);
@@ -54,7 +88,7 @@ namespace Umbraco.Tests.Models
contentType.Id = 10;
contentType.CreateDate = DateTime.Now;
contentType.CreatorId = 22;
contentType.SetDefaultTemplate(new Template((string) "Test Template", (string) "testTemplate")
contentType.SetDefaultTemplate(new Template((string)"Test Template", (string)"testTemplate")
{
Id = 88
});
@@ -117,12 +151,12 @@ namespace Umbraco.Tests.Models
{
group.Id = ++i;
}
contentType.AllowedTemplates = new[] { new Template((string) "Name", (string) "name") { Id = 200 }, new Template((string) "Name2", (string) "name2") { Id = 201 } };
contentType.AllowedTemplates = new[] { new Template((string)"Name", (string)"name") { Id = 200 }, new Template((string)"Name2", (string)"name2") { Id = 201 } };
contentType.AllowedContentTypes = new[] { new ContentTypeSort(new Lazy<int>(() => 888), 8, "sub"), new ContentTypeSort(new Lazy<int>(() => 889), 9, "sub2") };
contentType.Id = 10;
contentType.CreateDate = DateTime.Now;
contentType.CreatorId = 22;
contentType.SetDefaultTemplate(new Template((string) "Test Template", (string) "testTemplate")
contentType.SetDefaultTemplate(new Template((string)"Test Template", (string)"testTemplate")
{
Id = 88
});
@@ -167,12 +201,12 @@ namespace Umbraco.Tests.Models
{
group.Id = ++i;
}
contentType.AllowedTemplates = new[] { new Template((string) "Name", (string) "name") { Id = 200 }, new Template((string) "Name2", (string) "name2") { Id = 201 } };
contentType.AllowedContentTypes = new[] {new ContentTypeSort(new Lazy<int>(() => 888), 8, "sub"), new ContentTypeSort(new Lazy<int>(() => 889), 9, "sub2")};
contentType.AllowedTemplates = new[] { new Template((string)"Name", (string)"name") { Id = 200 }, new Template((string)"Name2", (string)"name2") { Id = 201 } };
contentType.AllowedContentTypes = new[] { new ContentTypeSort(new Lazy<int>(() => 888), 8, "sub"), new ContentTypeSort(new Lazy<int>(() => 889), 9, "sub2") };
contentType.Id = 10;
contentType.CreateDate = DateTime.Now;
contentType.CreatorId = 22;
contentType.SetDefaultTemplate(new Template((string) "Test Template", (string) "testTemplate")
contentType.SetDefaultTemplate(new Template((string)"Test Template", (string)"testTemplate")
{
Id = 88
});
@@ -264,12 +298,12 @@ namespace Umbraco.Tests.Models
{
propertyType.Id = ++i;
}
contentType.AllowedTemplates = new[] { new Template((string) "Name", (string) "name") { Id = 200 }, new Template((string) "Name2", (string) "name2") { Id = 201 } };
contentType.AllowedTemplates = new[] { new Template((string)"Name", (string)"name") { Id = 200 }, new Template((string)"Name2", (string)"name2") { Id = 201 } };
contentType.AllowedContentTypes = new[] { new ContentTypeSort(new Lazy<int>(() => 888), 8, "sub"), new ContentTypeSort(new Lazy<int>(() => 889), 9, "sub2") };
contentType.Id = 10;
contentType.CreateDate = DateTime.Now;
contentType.CreatorId = 22;
contentType.SetDefaultTemplate(new Template((string) "Test Template", (string) "testTemplate")
contentType.SetDefaultTemplate(new Template((string)"Test Template", (string)"testTemplate")
{
Id = 88
});
@@ -232,6 +232,35 @@ namespace Umbraco.Tests.Persistence.Repositories
}
}
/// <summary>
/// This demonstates an issue found: https://github.com/umbraco/Umbraco-CMS/issues/4963#issuecomment-483516698
/// </summary>
[Test]
[Ignore("Still testing")]
public void Bug_Changing_Built_In_Member_Type_Property_Type_Aliases_Results_In_Exception()
{
//TODO: Fix this bug and then change this test
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider);
IMemberType memberType = MockedContentTypes.CreateSimpleMemberType();
repository.Save(memberType);
foreach (var stub in Constants.Conventions.Member.GetStandardPropertyTypeStubs())
{
var prop = memberType.PropertyTypes.First(x => x.Alias == stub.Key);
prop.Alias = prop.Alias + "__0000";
}
repository.Save(memberType);
//Assert.Throws<ArgumentException>(() => );
}
}
[Test]
public void Built_In_Member_Type_Properties_Are_Automatically_Added_When_Creating()
{
-1
View File
@@ -118,7 +118,6 @@
<Compile Include="Cache\SnapDictionaryTests.cs" />
<Compile Include="Clr\ReflectionUtilitiesTests.cs" />
<Compile Include="Collections\OrderedHashSetTests.cs" />
<Compile Include="Composing\CompositionTests.cs" />
<Compile Include="Composing\LightInjectValidation.cs" />
<Compile Include="Composing\ContainerConformingTests.cs" />
<Compile Include="CoreThings\CallContextTests.cs" />
+22
View File
@@ -0,0 +1,22 @@
@ECHO OFF
ECHO.
ECHO.
ECHO This will only work when you have NPM available on the command line
ECHO Works great with NodeJS Portable - https://gareth.flowers/nodejs-portable/
ECHO.
ECHO.
set /P c=Are you sure you want to continue [Y/N]?
if /I "%c%" EQU "Y" goto :setupgulp
if /I "%c%" EQU "N" goto :eof
:setupgulp
call npm install
call npm -g install gulp
call npm -g install gulp-cli
ECHO.
ECHO.
ECHO You should now be able to run: gulp build or gulp dev
ECHO.
ECHO.
@@ -331,35 +331,8 @@
// This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish
function performSave(args) {
// Check that all variants for publishing have a name.
if (args.action === "publish" || args.action === "sendToPublish") {
if ($scope.content.variants) {
var iVariant;
for (var i = 0; i < $scope.content.variants.length; i++) {
iVariant = $scope.content.variants[i];
iVariant.notifications = [];// maybe not needed, need to investigate.
if(iVariant.publish === true) {
if (iVariant.name == null) {
var tokens = [iVariant.language.name];
localizationService.localize("publish_contentPublishedFailedByMissingName", tokens).then(function (value) {
iVariant.notifications.push({"message": value, "type": 1});
});
return $q.reject();
}
}
}
}
}
//Used to check validility of nested form - coming from Content Apps mostly
//Set them all to be invalid
var fieldsToRollback = checkValidility();
@@ -42,7 +42,7 @@
item: '=',
onOpen: '&',
onOpenAnchor: '&',
hotkey: '<'
index: '@'
}
});
@@ -1,72 +0,0 @@
/**
@ngdoc directive
@name umbraco.directives.directive:umbCheckbox
@restrict E
@scope
@description
<b>Added in Umbraco version 7.14.0</b> Use this directive to render an umbraco checkbox.
<h3>Markup example</h3>
<pre>
<div ng-controller="My.Controller as vm">
<umb-checkbox
name="checkboxlist"
value="{{key}}"
model="true"
text="{{text}}">
</umb-checkbox>
</div>
</pre>
@param {boolean} model Set to <code>true</code> or <code>false</code> to set the checkbox to checked or unchecked.
@param {string} input-id Set the <code>id</code> of the checkbox.
@param {string} value Set the value of the checkbox.
@param {string} name Set the name of the checkbox.
@param {string} text Set the text for the checkbox label.
@param {string} server-validation-field Set the <code>val-server-field</code> of the checkbox.
@param {boolean} disabled Set the checkbox to be disabled.
@param {boolean} required Set the checkbox to be required.
@param {string} on-change Callback when the value of the checkbox changed by interaction.
**/
(function () {
'use strict';
function UmbCheckboxController($timeout) {
var vm = this;
vm.callOnChange = function() {
$timeout(function() {
vm.onChange({model:vm.model, value:vm.value});
}, 0);
}
}
var component = {
templateUrl: 'views/components/forms/umb-checkbox.html',
controller: UmbCheckboxController,
controllerAs: 'vm',
bindings: {
model: "=",
inputId: "@",
value: "@",
name: "@",
text: "@",
serverValidationField: "@",
disabled: "<",
required: "<",
onChange: "&"
}
};
angular.module('umbraco.directives').component('umbCheckbox', component);
})();
@@ -1,57 +0,0 @@
/**
@ngdoc directive
@name umbraco.directives.directive:umbRadiobutton
@restrict E
@scope
@description
<b>Added in Umbraco version 7.14.0</b> Use this directive to render an umbraco radio button.
<h3>Markup example</h3>
<pre>
<div ng-controller="My.Controller as vm">
<umb-radiobutton
name="radiobuttonlist"
value="{{key}}"
model="true"
text="{{text}}">
</umb-radiobutton>
</div>
</pre>
@param {boolean} model Set to <code>true</code> or <code>false</code> to set the radiobutton to checked or unchecked.
@param {string} value Set the value of the radiobutton.
@param {string} name Set the name of the radiobutton.
@param {string} text Set the text for the radiobutton label.
@param {boolean} disabled Set the radiobutton to be disabled.
@param {boolean} required Set the radiobutton to be required.
**/
(function () {
'use strict';
function RadiobuttonDirective() {
var directive = {
restrict: 'E',
replace: true,
templateUrl: 'views/components/forms/umb-radiobutton.html',
scope: {
model: "=",
value: "@",
name: "@",
text: "@",
disabled: "=",
required: "="
}
};
return directive;
}
angular.module('umbraco.directives').directive('umbRadiobutton', RadiobuttonDirective);
})();
@@ -19,15 +19,9 @@ angular.module("umbraco.directives")
promises.push(assetsService.loadJs("lib/tinymce/tinymce.min.js", scope));
}
var editorConfig = scope.configuration ? scope.configuration : null;
if (!editorConfig || angular.isString(editorConfig)) {
editorConfig = tinyMceService.defaultPrevalues();
//for the grid by default, we don't want to include the macro toolbar
editorConfig.toolbar = _.without(editorConfig, "umbmacro");
}
//make sure there's a max image size
if (!scope.configuration.maxImageSize && scope.configuration.maxImageSize !== 0) {
editorConfig.maxImageSize = tinyMceService.defaultPrevalues().maxImageSize;
var toolbar = ["code", "styleselect", "bold", "italic", "alignleft", "aligncenter", "alignright", "bullist", "numlist", "link", "umbmediapicker", "umbembeddialog"];
if (scope.configuration && scope.configuration.toolbar) {
toolbar = scope.configuration.toolbar;
}
//stores a reference to the editor
@@ -35,9 +29,9 @@ angular.module("umbraco.directives")
promises.push(tinyMceService.getTinyMceEditorConfig({
htmlId: scope.uniqueId,
stylesheets: editorConfig.stylesheets,
toolbar: editorConfig.toolbar,
mode: editorConfig.mode
stylesheets: scope.configuration ? scope.configuration.stylesheets : null,
toolbar: toolbar,
mode: scope.configuration.mode
}));
// pin toolbar to top of screen if we have focus and it scrolls off the screen
@@ -52,16 +46,9 @@ angular.module("umbraco.directives")
$q.all(promises).then(function (result) {
var standardConfig = result[promises.length - 1];
var tinyMceEditorConfig = result[promises.length - 1];
//create a baseline Config to extend upon
var baseLineConfigObj = {
maxImageSize: editorConfig.maxImageSize
};
angular.extend(baseLineConfigObj, standardConfig);
baseLineConfigObj.setup = function (editor) {
tinyMceEditorConfig.setup = function (editor) {
//set the reference
tinyMceEditor = editor;
@@ -124,7 +111,7 @@ angular.module("umbraco.directives")
//the elements needed
$timeout(function () {
tinymce.DOM.events.domLoaded = true;
tinymce.init(baseLineConfigObj);
tinymce.init(tinyMceEditorConfig);
}, 150, false);
}
@@ -117,11 +117,9 @@ Use this directive to generate a list of content items presented as a flexbox gr
};
scope.clickItemName = function(item, $event, $index) {
if(scope.onClickName && !($event.metaKey || $event.ctrlKey)) {
if(scope.onClickName) {
scope.onClickName(item, $event, $index);
$event.preventDefault();
}
$event.stopPropagation();
};
}
@@ -117,11 +117,10 @@
var vm = this;
vm.clickItem = function (item, $event) {
if (vm.onClick && !($event.metaKey || $event.ctrlKey)) {
if (vm.onClick) {
vm.onClick({ item: item});
$event.preventDefault();
$event.stopPropagation();
}
$event.stopPropagation();
};
vm.selectItem = function (item, $index, $event) {
@@ -62,8 +62,8 @@ function valPropertyMsg(serverValidationManager) {
if (!watcher) {
watcher = scope.$watch("currentProperty.value",
function (newValue, oldValue) {
if (!newValue || angular.equals(newValue, oldValue)) {
if (angular.equals(newValue, oldValue)) {
return;
}
@@ -78,10 +78,12 @@ function valPropertyMsg(serverValidationManager) {
// based on other errors. We'll also check if there's no other validation errors apart from valPropertyMsg, if valPropertyMsg
// is the only one, then we'll clear.
if ((errCount === 1 && angular.isArray(formCtrl.$error.valPropertyMsg)) || (formCtrl.$invalid && angular.isArray(formCtrl.$error.valServer))) {
if (errCount === 0 || (errCount === 1 && angular.isArray(formCtrl.$error.valPropertyMsg)) || (formCtrl.$invalid && angular.isArray(formCtrl.$error.valServer))) {
scope.errorMsg = "";
formCtrl.$setValidity('valPropertyMsg', true);
stopWatch();
} else if (showValidation && scope.errorMsg === "") {
formCtrl.$setValidity('valPropertyMsg', false);
scope.errorMsg = getErrorMsg();
}
}, true);
}
@@ -152,6 +154,7 @@ function valPropertyMsg(serverValidationManager) {
showValidation = true;
if (hasError && scope.errorMsg === "") {
scope.errorMsg = getErrorMsg();
startWatch();
}
else if (!hasError) {
scope.errorMsg = "";
@@ -301,19 +301,6 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
return allProps;
},
/**
* @ngdoc method
* @name umbraco.services.contentEditingHelper#buildCompositeVariantId
* @methodOf umbraco.services.contentEditingHelper
* @function
*
* @description
* Returns a id for the variant that is unique between all variants on the content
*/
buildCompositeVariantId: function (variant) {
return (variant.language ? variant.language.culture : "invariant") + "_" + (variant.segment ? variant.segment : "");
},
/**
* @ngdoc method
@@ -112,6 +112,7 @@ When building a custom infinite editor view you can use the same components as a
type="button"
button-style="link"
label-key="general_close"
shortcut="esc"
action="vm.close()">
</umb-button>
<umb-button
@@ -26,8 +26,6 @@ function navigationService($routeParams, $location, $q, $timeout, $injector, eve
navReadyPromise.resolve(mainTreeApi);
});
//A list of query strings defined that when changed will not cause a reload of the route
var nonRoutingQueryStrings = ["mculture", "cculture", "lq"];
var retainedQueryStrings = ["mculture"];
@@ -427,8 +427,8 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
data["rel"] = img.id;
data["data-id"] = img.id;
}
editor.selection.setContent(editor.dom.createHTML('img', data));
editor.insertContent(editor.dom.createHTML('img', data));
$timeout(function () {
var imgElm = editor.dom.get('__mcenew');
@@ -350,9 +350,6 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS
parent.children.splice(parent.children.indexOf(treeNode), 1);
parent.hasChildren = parent.children.length !== 0;
//Notify that the node has been removed
eventsService.emit("treeService.removeNode", { node: treeNode });
},
/**
@@ -9,7 +9,7 @@
*
* @param {navigationService} navigationService A reference to the navigationService
*/
function NavigationController($scope, $rootScope, $location, $log, $q, $routeParams, $timeout, $cookies, treeService, appState, navigationService, keyboardService, historyService, eventsService, angularHelper, languageResource, contentResource, editorState) {
function NavigationController($scope, $rootScope, $location, $log, $q, $routeParams, $timeout, $cookies, treeService, appState, navigationService, keyboardService, historyService, eventsService, angularHelper, languageResource, contentResource) {
//this is used to trigger the tree to start loading once everything is ready
var treeInitPromise = $q.defer();
@@ -249,20 +249,6 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar
$scope.infiniteMode = args && args.editors.length > 0 ? true : false;
}));
evts.push(eventsService.on("treeService.removeNode", function (e, args) {
//check to see if the current page has been removed
var currentEditorState = editorState.getCurrent()
if (currentEditorState && currentEditorState.id.toString() === args.node.id.toString()) {
//current page is loaded, so navigate to root
var section = appState.getSectionState("currentSection");
$location.path("/" + section);
}
}));
/**
* Based on the current state of the application, this configures the scope variables that control the main tree and language drop down
*/
@@ -117,7 +117,6 @@
@import "components/umb-confirm-action.less";
@import "components/umb-keyboard-shortcuts-overview.less";
@import "components/umb-checkbox-list.less";
@import "components/umb-form-check.less";
@import "components/umb-locked-field.less";
@import "components/umb-tabs.less";
@import "components/umb-load-indicator.less";
@@ -167,7 +166,6 @@
@import "components/umb-property-file-upload.less";
@import "components/users/umb-user-cards.less";
@import "components/users/umb-user-table.less";
@import "components/users/umb-user-details.less";
@import "components/users/umb-user-group-picker-list.less";
@import "components/users/umb-user-group-preview.less";
@@ -195,9 +195,12 @@ input[type="button"] {
}
// Made for Umbraco, 2019
.btn-action {
.buttonBackground(@blueExtraDark, lighten(@blueExtraDark, 10), @white, @u-white);
transition: background-color 240ms;
font-weight: 700;
.buttonBackground(@blueExtraDark, @blueDark, @white, @u-white);
}
// Made for Umbraco, 2019
.btn-selection {
@btnSelectionBackgroundHover: darken(@pinkLight, 10%);
.buttonBackground(@pinkLight, @btnSelectionBackgroundHover, @blueExtraDark, @blueDark);
}
// Made for Umbraco, 2019, used for buttons that has to stand back.
.btn-white {
@@ -1,7 +1,7 @@
.umb-editor-sub-header {
padding: 10px 0;
margin-bottom: 10px;
background: @brownGrayLight;
background: @gray-10;
display: flex;
justify-content: space-between;
margin-top: -10px;
@@ -12,7 +12,7 @@
&.nested {
margin-top: 0;
margin-bottom: 0;
background: @brownGrayLight;
background: @gray-10;
}
}
@@ -250,11 +250,3 @@
.form-horizontal .umb-overlay .controls {
margin-left: 0 !important;
}
.umb-overlay .text-error {
color: @formErrorText;
}
.umb-overlay .text-success {
color: @formSuccessText;
}
@@ -1,6 +1,3 @@
@checkboxWidth: 15px;
@checkboxHeight: 15px;
.umb-checkbox-list {
list-style: none;
margin-left: 0;
@@ -13,6 +13,7 @@
user-select: none;
box-shadow: 0 1px 1px 0 rgba(0,0,0,0.16);
border-radius: 3px;
}
.umb-content-grid__item.-selected {
@@ -58,8 +59,7 @@
display: inline-flex;
color: @ui-option-type;
&:hover, &:focus {
text-decoration: none;
&:hover {
color:@ui-option-type-hover;
}
}
@@ -61,9 +61,6 @@
.show-validation .umb-sub-views-nav-item > a.-has-error {
color: @red;
&::after {
background-color: @red;
}
}
.umb-sub-views-nav-item .icon {
@@ -1,136 +0,0 @@
@checkboxWidth: 16px;
@checkboxHeight: 16px;
.umb-form-check {
display: flex;
flex-wrap: wrap;
align-items: center;
position: relative;
padding: 0;
margin: 0;
min-height: 22px;
line-height: 22px;
cursor: pointer !important;
&__text {
margin: 0 0 0 26px;
position: relative;
top: 0;
}
&__input {
position: absolute;
top: 0;
left: 0;
opacity: 0;
&:checked ~ .umb-form-check__state .umb-form-check__check {
border-color: @ui-option-type;
}
&:focus:checked ~ .umb-form-check .umb-form-check__check,
&:focus ~ .umb-form-check__state .umb-form-check__check {
border-color: @inputBorderFocus;
}
&:checked ~ .umb-form-check__state {
.umb-form-check__check {
// This only happens if the state has a radiobutton modifier
.umb-form-check--radiobutton & {
&:before {
opacity: 1;
transform: scale(1);
}
}
// This only happens if state has the checkbox modifier
.umb-form-check--checkbox & {
&:before {
width: @checkboxWidth;
height: @checkboxHeight;
}
}
}
// This only happens if state has the checkbox modifier
.umb-form-check--checkbox & {
.umb-form-check__icon {
opacity: 1;
}
}
}
}
&__state {
display: flex;
height: 18px;
margin-top: 2px;
position: absolute;
top: 0;
left: 0;
}
&__check {
display: flex;
position: relative;
background: @white;
border: 1px solid @inputBorder;
width: @checkboxWidth;
height: @checkboxHeight;
&:before {
content: "";
background: @ui-option-type;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
}
// This only happens if state has the radiobutton modifier
.umb-form-check--radiobutton & {
border-radius: 100%;
&:before {
width: 10px;
height: 10px;
border-radius: 100%;
opacity: 0;
transform: scale(0);
transition: .15s ease-out;
}
}
// This only happens if state has the checkbox modifier
.umb-form-check--checkbox & {
&:before {
width: 0;
height: 0;
transition: .05s ease-out;
}
}
}
&__icon {
color: @white;
text-align: center;
font-size: 12px;
opacity: 0;
transition: .2s ease-out;
&:before {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
margin: auto;
}
}
&.umb-form-check--disabled {
cursor: not-allowed !important;
opacity: 0.5;
}
}
@@ -22,10 +22,6 @@ a.umb-list-item:focus {
opacity: 0.6;
}
.umb-list-item--error {
color: @red;
}
.umb-list-item:hover .umb-list-checkbox,
.umb-list-item--selected .umb-list-checkbox {
opacity: 1;
@@ -38,4 +34,4 @@ a.umb-list-item:focus {
.umb-list-checkbox--visible {
opacity: 1;
}
}
@@ -261,7 +261,6 @@
.umb-package-details {
display: flex;
flex-flow: row wrap;
}
a.umb-package-details__back-link {
@@ -281,7 +280,6 @@ a.umb-package-details__back-link {
flex: 1 1 auto;
margin-right: 20px;
width: calc(~'100%' - ~'@{sidebarwidth}' - ~'20px'); // Make sure that the main content area doesn't gets affected by inline styling
min-width: 500px;
}
.umb-package-details__sidebar {
@@ -17,10 +17,6 @@
outline: none;
text-decoration: none !important;
}
.umb-user-card.-selectable {
cursor: pointer;
}
.umb-user-card.-selected {
&::before {
content: "";
@@ -35,6 +31,7 @@
box-shadow: 0 0 4px 0 darken(@ui-selected-border, 20), inset 0 0 2px 0 darken(@ui-selected-border, 20);
pointer-events: none;
}
}
.umb-user-card__content {
@@ -47,18 +44,14 @@
box-sizing: border-box;
display: flex;
flex-direction: column;
cursor: pointer;
max-width: 100%;
}
.umb-user-card__goToUser {
&:hover, &:focus {
text-decoration: none;
&:hover {
.umb-user-card__name {
text-decoration: underline;
color: @ui-option-type-hover;
}
.umb-avatar {
border: 1px solid @ui-option-type-hover;
text-decoration: underline;
}
}
}
@@ -1,81 +0,0 @@
.umb-user-table {
.umb-user-table-col-avatar {
flex: 0 0 32px;
padding: 15px 0;
}
.umb-table-cell a {
&:hover, &:focus {
.umb-avatar {
border: 1px solid @ui-option-type-hover;
}
}
}
.umb-table-body .umb-table-cell.umb-table__name {
margin: 0;
padding: 0;
a {
display: flex;
padding: 6px 2px;
height: 42px;
span {
margin: auto 14px;
}
}
}
.umb-table-cell.umb-table__name a {
&:hover, &:focus {
text-decoration: underline;
}
}
.umb-user-table-row {
cursor: default;
.umb-checkmark {
visibility: hidden;
}
}
.umb-user-table-row.-selectable {
cursor: pointer;
}
&.-has-selection {
.umb-user-table-row.-selectable {
.umb-checkmark {
visibility: visible;
}
}
}
.umb-user-table-row.-selectable:hover {
.umb-checkmark {
visibility: visible;
}
}
.umb-user-table-row.-selected {
.umb-checkmark {
visibility: visible;
}
&::before {
content: "";
position: absolute;
z-index:1;
top: 1px;
left: 1px;
right: 1px;
bottom: 1px;
border: 2px solid @ui-selected-border;
box-shadow: 0 0 2px 0 fade(@ui-selected-border, 80%);
pointer-events: none;
}
}
}
@@ -30,7 +30,7 @@
position: absolute;
padding: 5px 8px;
pointer-events: none;
top: 2px;
top: 0;
}
input[type="text"] {
@@ -55,6 +55,16 @@
position: absolute;;
}
.--notInFront .umb-modalcolumn::after {
content: '';
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
background: rgba(0,0,0,.4);
}
/* re-align loader */
.umb-modal .umb-loader-wrapper, .umb-modalcolumn .umb-loader-wrapper, .umb-dialog .umb-loader-wrapper{
position:relative;
@@ -13,25 +13,23 @@
border: none;
border-radius: 0;
overflow-y: auto;
background-color: @blueNight;
background-color: @purple-d2;
}
.login-overlay__background-image {
background-position: center center;
background-repeat: no-repeat;
background-size: cover;
background-image: url('../img/login.jpg');
width: 100%;
height: 100%;
position: absolute;
opacity: 0.05;
}
.login-overlay__logo {
position: absolute;
top: 22px;
left: 25px;
width: 30px;
height: 30px;
z-index: 1;
}
@@ -18,16 +18,7 @@
&-push {
float:right;
}
&--list{
float: left;
}
&__item{
line-height: 1;
margin: 0 0 5px;
}
}
}
.umb-property-editor-tiny {
@@ -78,11 +69,6 @@
}
}
.umb-property .alert {
border-radius: 3px;
}
//
// Content picker
@@ -227,7 +213,7 @@
margin: 24px 0 0;
display: flex;
}
&__input {
width: 100%;
&-wrap{
@@ -37,8 +37,7 @@ ul.sections>li>a::after {
height: 4px;
width: 100%;
background-color: @pinkLight;
position: absolute;
left: 0;
position: absolute;
bottom: -4px;
border-radius: 3px 3px 0 0;
opacity: 0;
@@ -41,8 +41,8 @@
@purple-washed: #F6F3FD;
// UI Colors
@red-d1: #F02E28;
@red: #D42054;// updated 2019
@red-d1: #F02E28;// currently used for validation, and is hard coded inline in various html places :/
@red: #D42054;// updated 2019 - should be used as validation! and is already in some cases like the .umb-validation-label
@red-l1: #e22c60;// updated 2019
@red-l2: #FE8B88;
@red-l3: #FFB2B0;
@@ -508,7 +508,7 @@
@warningBorder: transparent;
@errorText: @white;
@errorBackground: @red;
@errorBackground: @red-d1;
@errorBorder: transparent;
@successText: @white;
@@ -10,7 +10,7 @@
(function() {
"use strict";
function DataTypePicker($scope, $filter, dataTypeResource, dataTypeHelper, contentTypeResource, localizationService, editorService) {
function DataTypePicker($scope, dataTypeResource, dataTypeHelper, contentTypeResource, localizationService, editorService) {
var vm = this;
@@ -119,28 +119,13 @@
$scope.model.itemDetails = null;
if (vm.searchTerm) {
vm.showFilterResult = true;
vm.showTabs = false;
var regex = new RegExp(vm.searchTerm, "i");
vm.filterResult = {
userConfigured: filterCollection(vm.userConfigured, regex),
typesAndEditors: filterCollection(vm.typesAndEditors, regex)
};
} else {
vm.filterResult = null;
vm.showFilterResult = false;
vm.showTabs = true;
}
}
function filterCollection(collection, regex) {
return _.map(_.keys(collection), function (key) {
return {
group: key,
dataTypes: $filter('filter')(collection[key], function (dataType) {
return regex.test(dataType.name) || regex.test(dataType.alias);
})
}
});
}
function showDetailsOverlay(property) {
@@ -216,4 +201,4 @@
angular.module("umbraco").controller("Umbraco.Editors.DataTypePickerController", DataTypePicker);
})();
})();
@@ -79,13 +79,13 @@
</umb-tab-content>
</div>
<!-- FILTER RESULTS -->
<div ng-if="vm.filterResult">
<div ng-if="vm.showFilterResult">
<h5 class="-border-bottom -black"><localize key="contentTypeEditor_reuse"></localize></h5>
<div ng-repeat="result in vm.filterResult.userConfigured">
<div ng-if="result.dataTypes.length > 0">
<h5>{{result.group}}</h5>
<div ng-repeat="(key,value) in vm.userConfigured">
<div ng-if="(value | filter:vm.searchTerm).length > 0">
<h5>{{key}}</h5>
<ul class="umb-card-grid" ng-mouseleave="vm.hideDetailsOverlay()">
<li ng-repeat="dataType in result.dataTypes | orderBy:'name'"
<li ng-repeat="dataType in value | orderBy:'name' | filter: vm.searchTerm"
ng-mouseover="vm.showDetailsOverlay(dataType)"
ng-click="vm.pickDataType(dataType)"
class="-four-in-row">
@@ -101,11 +101,11 @@
</div>
</div>
<h5 class="-border-bottom -black"><localize key="contentTypeEditor_availableEditors"></localize></h5>
<div ng-repeat="result in vm.filterResult.typesAndEditors">
<div ng-if="result.dataTypes.length > 0">
<h5>{{result.group}}</h5>
<div ng-repeat="(key,value) in vm.typesAndEditors">
<div ng-if="(value | filter:vm.searchTerm).length > 0">
<h5>{{key}}</h5>
<ul class="umb-card-grid" ng-mouseleave="vm.hideDetailsOverlay()">
<li ng-repeat="systemDataType in result.dataTypes | orderBy:'name'"
<li ng-repeat="systemDataType in value | orderBy:'name' | filter: vm.searchTerm"
ng-mouseover="vm.showDetailsOverlay(systemDataType)"
ng-click="vm.pickEditor(systemDataType)"
class="-four-in-row">
@@ -1,4 +1,5 @@
<div ng-controller="Umbraco.Overlays.UserController">
<div class="umb-control-group" ng-if="!showPasswordFields">
<h5><localize key="user_yourProfile" /></h5>
@@ -78,7 +79,7 @@
</div>
<div class="umb-control-group" ng-if="!showPasswordFields && history.length">
<div class="umb-control-group" ng-if="!showPasswordFields">
<h5><localize key="user_yourHistory" /></h5>
<ul class="umb-tree">
<li ng-repeat="item in history | orderBy:'time':true">
@@ -126,7 +127,7 @@
</div>
<div class="umb-control-group" ng-if="tab.length">
<div class="umb-control-group">
<div ng-repeat="tab in dashboard">
<div ng-repeat="property in tab.properties">
<div>
@@ -2,10 +2,10 @@
<div id="login" class="umb-modalcolumn umb-dialog" ng-class="{'show-validation': vm.loginForm.$invalid}" ng-cloak>
<div class="login-overlay__background-image" ng-style="{'background-image': 'url('+vm.backgroundImage+')'}"></div>
<div class="login-overlay__background-image" ng-if="vm.backgroundImage" ng-style="{'background-image':'url(' + vm.backgroundImage + ')'}"></div>
<div class="login-overlay__logo">
<img src="assets/img/application/umbraco_logo_white.svg">
<img ng-src="assets/img/application/logo.png" ng-srcset="assets/img/application/logo@2x.png 2x, assets/img/application/logo@3x.png 3x">
</div>
<div ng-show="vm.invitedUser != null" class="umb-login-container">
@@ -37,11 +37,11 @@
<span class="help-inline" ng-message="required"><localize key="general_required">Required</localize></span>
<span class="help-inline" ng-message="valCompare"><localize key="user_passwordMismatch">The confirmed password doesn't match the new password!</localize></span>
</span>
</div>
<div class="flex justify-between items-center">
<umb-button
<umb-button
type="submit"
button-style="success"
state="vm.invitedUserPasswordModel.buttonState"
@@ -58,7 +58,7 @@
<ng-form name="vm.avatarForm">
<umb-progress-bar
<umb-progress-bar
style="max-width: 100px; margin-bottom: 5px;"
ng-show="vm.avatarFile.uploadStatus === 'uploading'"
progress="{{ vm.avatarFile.uploadProgress }}"
@@ -77,7 +77,7 @@
ngf-pattern="{{vm.avatarFile.acceptedFileTypes}}"
ngf-max-size="{{ vm.avatarFile.maxFileSize }}">
<umb-avatar
<umb-avatar
color="gray"
size="xl"
unknown-char="+"
@@ -94,7 +94,7 @@
<localize key="user_userinviteAvatarMessage"></localize>
</p>
<div class="flex justify-center items-center">
<umb-button
<umb-button
type="button"
button-style="success"
label="Done"
@@ -102,7 +102,7 @@
</umb-button>
</div>
</div>
</div>
<div ng-show="vm.invitedUser == null && vm.inviteStep === 3" ng-if="vm.inviteStep === 3" class="umb-login-container">
<div class="form">
@@ -169,7 +169,7 @@
</div>
<div class="flex justify-between items-center">
<umb-button
<umb-button
button-style="success"
size="m"
label-key="general_login"
@@ -261,4 +261,4 @@
</div>
</div>
</div>
</div>
</div>
@@ -1,10 +1,10 @@
<a data-element="sub-view-{{vm.item.alias}}"
tabindex="-1"
ng-href=""
ng-click="vm.clicked()"
hotkey="{{::vm.hotkey}}"
hotkey-when-hidden="true"
ng-class="{'is-active': vm.item.active, '-has-error': vm.item.hasError}">
tabindex="-1"
ng-href=""
ng-click="vm.clicked()"
hotkey="{{vm.index+1}}"
hotkey-when-hidden="true"
ng-class="{'is-active': vm.item.active, '-has-error': vm.item.hasError}">
<i class="icon {{ vm.item.icon }}"></i>
<span class="umb-sub-views-nav-item-text">{{ vm.item.name }}</span>
<div ng-show="vm.item.badge" class="badge -type-{{vm.item.badge.type}}">{{vm.item.badge.count}}</div>
@@ -7,7 +7,7 @@
item="navItem"
on-open="openNavigationItem(item)"
on-open-anchor="openAnchorItem(item, anchor)"
hotkey="$index + 1">
index="{{$index}}">
</umb-editor-navigation-item>
</div>
</li>
@@ -1,19 +0,0 @@
<label class="checkbox umb-form-check umb-form-check--checkbox" ng-class="{ 'umb-form-check--disabled': disabled }">
<input type="checkbox"
id="{{vm.inputId}}"
name="{{vm.name}}"
value="{{vm.value}}"
class="umb-form-check__input"
val-server-field="{{vm.serverValidationField}}"
ng-model="vm.model"
ng-disabled="vm.disabled"
ng-required="vm.required"
ng-change="vm.callOnChange()"/>
<span class="umb-form-check__state" aria-hidden="true">
<span class="umb-form-check__check">
<i class="umb-form-check__icon icon-check"></i>
</span>
</span>
<span class="umb-form-check__text">{{vm.text}}</span>
</label>
@@ -1,13 +0,0 @@
<label class="radio umb-form-check umb-form-check--radiobutton" ng-class="{ 'umb-form-check--disabled': disabled }">
<input type="radio" name="{{name}}"
value="{{value}}"
class="umb-form-check__input"
ng-model="model"
ng-disabled="disabled"
ng-required="required" />
<span class="umb-form-check__state" aria-hidden="true">
<span class="umb-form-check__check"></span>
</span>
<span class="umb-form-check__text">{{text}}</span>
</label>
@@ -10,13 +10,10 @@
<div class="umb-content-grid__content">
<a class="umb-content-grid__item-name"
ng-href="{{'#' + item.editPath}}"
ng-click="clickItemName(item, $event, $index)"
ng-class="{'-light': !item.published && item.updater != null}">
<div class="umb-content-grid__item-name" ng-click="clickItemName(item, $event, $index)" ng-class="{'-light': !item.published && item.updater != null}">
<i class="umb-content-grid__icon {{ item.icon }}"></i>
<span>{{ item.name }}</span>
</a>
</div>
<ul class="umb-content-grid__details-list" ng-class="{'-light': !item.published && item.updater != null}">
<li class="umb-content-grid__details-item" ng-if="item.state">
@@ -40,8 +40,7 @@
<i class="umb-table-body__icon umb-table-body__checkicon icon-check"></i>
</div>
<div class="umb-table-cell umb-table__name">
<a title="{{ item.name }}" class="umb-table-body__link"
ng-href="{{'#' + item.editPath}}"
<a title="{{ item.name }}" class="umb-table-body__link" href=""
ng-click="vm.clickItem(item, $event)"
ng-bind="item.name">
</a>
@@ -115,7 +115,6 @@
if(response.valid) {
vm.submitButtonState = "success";
closeDialog();
// show validation messages for each domain
} else {
@@ -1,7 +1,7 @@
(function () {
"use strict";
function PublishController($scope, localizationService, contentEditingHelper) {
function PublishController($scope, localizationService) {
var vm = this;
vm.loading = true;
@@ -14,51 +14,34 @@
/** Returns true if publishing is possible based on if there are un-published mandatory languages */
function canPublish() {
var possible = false;
var selected = [];
for (var i = 0; i < vm.variants.length; i++) {
var variant = vm.variants[i];
var state = canVariantPublish(variant);
if (state === true) {
possible = true;
}
if (state === false) {
//if this variant will show up in the publish-able list
var publishable = dirtyVariantFilter(variant);
var published = !(variant.state === "NotCreated" || variant.state === "Draft");
if ((variant.language.isMandatory && !published) && (!publishable || !variant.publish)) {
//if a mandatory variant isn't published
//and it's not publishable or not selected to be published
//then we cannot continue
// TODO: Show a message when this occurs
return false;
}
if (variant.publish) {
selected.push(variant.publish);
}
}
return possible;
}
/** Returns true if publishing is possible based on if the variant is a un-published mandatory language */
function canVariantPublish(variant) {
//if this variant will show up in the publish-able list
var publishable = dirtyVariantFilter(variant);
var published = !(variant.state === "NotCreated" || variant.state === "Draft");
// is this variant mandatory:
if (variant.language.isMandatory && !published && !variant.publish) {
//if a mandatory variant isn't published or set to be published
//then we cannot continue
return false;
}
// is this variant selected for publish:
if (variant.publish === true) {
return publishable;
}
return null;
return selected.length > 0;
}
function changeSelection(variant) {
$scope.model.disableSubmitButton = !canPublish();
//need to set the Save state to true if publish is true
variant.save = variant.publish;
variant.willPublish = canVariantPublish(variant);
}
function dirtyVariantFilter(variant) {
@@ -116,49 +99,32 @@
_.each(vm.variants,
function (variant) {
if(variant.state === "NotCreated") {
vm.isNew = true;
if(variant.state !== "NotCreated"){
vm.isNew = false;
}
}
);
});
_.each(vm.variants,
function (variant) {
variant.compositeId = contentEditingHelper.buildCompositeVariantId(variant);
variant.compositeId = variant.language.culture + "_" + (variant.segment ? variant.segment : "");
variant.htmlId = "_content_variant_" + variant.compositeId;
// reset to not be published
variant.publish = false;
variant.save = false;
//check for pristine variants
if (!vm.hasPristineVariants) {
vm.hasPristineVariants = pristineVariantFilter(variant);
}
// If the variant havent been created jet.
if(variant.state === "NotCreated") {
// If the variant is mandatory, then set the variant to be published.
if (variant.language.isMandatory === true) {
variant.publish = true;
variant.save = true;
}
}
variant.canPublish = dirtyVariantFilter(variant);
// if we have data on this variant.
if(variant.canPublish && hasAnyData(variant)) {
// and if some varaints havent been saved before, or they dont have a publishing date set, then we set it for publishing.
if(hasAnyData(variant)){
if(vm.isNew || variant.publishDate == null){
variant.publish = true;
variant.save = true;
}
}else{
variant.publish = false;
variant.save = false;
variant.canSave = false;
}
variant.willPublish = canVariantPublish(variant);
}
);
});
if (vm.variants.length !== 0) {
//now sort it so that the current one is at the top
@@ -187,6 +153,7 @@
localizationService.localize(labelKey).then(function (value) {
vm.headline = value;
vm.loading = false;
});
@@ -10,7 +10,7 @@
<div class="umb-list umb-list--condensed" ng-if="!vm.loading">
<div class="umb-list-item" ng-repeat="variant in vm.variants | filter:vm.dirtyVariantFilter track by variant.compositeId" ng-class="{'umb-list-item--error': publishVariantSelectorForm.publishVariantSelector.$invalid || (variant.notifications | filter:{type:1}).length > 0}">
<div class="umb-list-item" ng-repeat="variant in vm.variants | filter:vm.dirtyVariantFilter track by variant.compositeId">
<ng-form name="publishVariantSelectorForm">
<div class="flex">
<input id="{{variant.htmlId}}"
@@ -18,28 +18,27 @@
type="checkbox"
ng-model="variant.publish"
ng-change="vm.changeSelection(variant)"
ng-disabled="(variant.canPublish === false)"
ng-disabled="(vm.isNew && variant.language.isMandatory) || (variant.canSave === false)"
style="margin-right: 8px;"
val-server-field="{{variant.htmlId}}" />
<div>
<label for="{{variant.htmlId}}" style="margin-bottom: 2px;">
<span>{{ variant.language.name }}</span>
<strong ng-if="variant.language.isMandatory" class="umb-control-required">*</strong>
<span class="db umb-permission__description" ng-if="!publishVariantSelectorForm.publishVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state variant="variant"></umb-variant-state>
<span ng-if="variant.language.isMandatory"> - </span>
<span ng-if="variant.language.isMandatory" ng-class="{'text-error': (variant.language.isMandatory && variant.willPublish === false) }"><localize key="languages_mandatoryLanguage"></localize></span>
</span>
<span class="db" ng-messages="publishVariantSelectorForm.publishVariantSelector.$error" show-validation-on-submit>
<span class="db umb-permission__description text-error" ng-message="valServerField">{{publishVariantSelectorForm.publishVariantSelector.errorMsg}}</span>
</span>
<span class="db" ng-repeat="notification in variant.notifications">
<span class="db umb-permission__description" ng-class="{'text-success': notification.type === 3, 'text-error': notification.type !== 3}">{{notification.message}}</span>
</span>
</label>
<div class="umb-permission__description" ng-if="!publishVariantSelectorForm.publishVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state variant="variant"></umb-variant-state>
<span ng-if="variant.language.isMandatory"> - <localize key="languages_mandatoryLanguage"></localize></span>
</div>
<div ng-messages="publishVariantSelectorForm.publishVariantSelector.$error" show-validation-on-submit>
<div class="umb-permission__description" style="color: #F02E28;" ng-message="valServerField">{{publishVariantSelectorForm.publishVariantSelector.errorMsg}}</div>
</div>
<div ng-repeat="notification in variant.notifications">
<div class="umb-permission__description" style="color: #1FB572;">{{notification.message}}</div>
</div>
</div>
</div>
@@ -65,7 +64,7 @@
</div>
<div ng-repeat="notification in variant.notifications">
<div class="umb-permission__description text-success">{{notification.message}}</div>
<div class="umb-permission__description" style="color: #1FB572;">{{notification.message}}</div>
</div>
</div>
@@ -22,13 +22,11 @@
<p><localize key="content_publishDescendantsWithVariantsHelp"></localize></p>
</div>
<div style="margin-bottom: 15px;" class="flex">
<umb-checkbox
input-id="includeUnpublishedSelector"
model="model.includeUnpublished" />
<label for="includeUnpublishedSelector">
<div style="margin-bottom: 15px;">
<label>
<input type="checkbox"
ng-model="model.includeUnpublished"
style="margin-right: 8px;" />
<localize key="content_includeUnpublished"></localize>
</label>
</div>
@@ -42,31 +40,31 @@
<div class="umb-list-item umb-list--condensed" ng-repeat="variant in vm.variants">
<ng-form name="publishVariantSelectorForm">
<div class="flex">
<umb-checkbox
input-id="{{variant.htmlId}}"
name="publishVariantSelector"
model="variant.publish"
on-change="vm.changeSelection(variant)"
server-validation-field="{{variant.htmlId}}"/>
<input id="{{variant.htmlId}}"
name="publishVariantSelector"
type="checkbox"
ng-model="variant.publish"
ng-change="vm.changeSelection(variant)"
style="margin-right: 8px;"
val-server-field="{{variant.htmlId}}" />
<div>
<label for="{{variant.htmlId}}" style="margin-bottom: 0;">
<span>{{ variant.language.name }}</span>
<span class="db umb-permission__description" ng-if="!publishVariantSelectorForm.publishVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state variant="variant"></umb-variant-state>
<span ng-if="variant.language.isMandatory"> - <localize key="languages_mandatoryLanguage"></localize></span>
</span>
<span class="db" ng-messages="publishVariantSelectorForm.publishVariantSelector.$error" show-validation-on-submit>
<span class="db umb-permission__description text-error" ng-message="valServerField">{{publishVariantSelectorForm.publishVariantSelector.errorMsg}}</span>
</span>
<span class="db" ng-repeat="notification in variant.notifications">
<span class="db umb-permission__description text-success">{{notification.message}}</span>
</span>
</label>
<div class="umb-permission__description" ng-if="!publishVariantSelectorForm.publishVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state variant="variant"></umb-variant-state>
<span ng-if="variant.language.isMandatory"> - <localize key="languages_mandatoryLanguage"></localize></span>
</div>
<div ng-messages="publishVariantSelectorForm.publishVariantSelector.$error" show-validation-on-submit>
<div class="umb-permission__description" style="color: #F02E28;" ng-message="valServerField">{{publishVariantSelectorForm.publishVariantSelector.errorMsg}}</div>
</div>
<div ng-repeat="notification in variant.notifications">
<div class="umb-permission__description" style="color: #1FB572;">{{notification.message}}</div>
</div>
</div>
</div>
@@ -1,7 +1,7 @@
(function () {
"use strict";
function SaveContentController($scope, localizationService, contentEditingHelper) {
function SaveContentController($scope, localizationService) {
var vm = this;
vm.loading = true;
@@ -75,7 +75,7 @@
_.each(vm.variants,
function (variant) {
variant.compositeId = contentEditingHelper.buildCompositeVariantId(variant);
variant.compositeId = variant.language.culture + "_" + (variant.segment ? variant.segment : "");
variant.htmlId = "_content_variant_" + variant.compositeId;
//check for pristine variants
@@ -6,12 +6,54 @@
<umb-load-indicator></umb-load-indicator>
</div>
<div ng-if="!vm.loading">
<div ng-if="vm.isNew && !vm.loading">
<div style="margin-bottom: 15px;">
<p>
<localize ng-if="!vm.isNew" key="content_languagesToSave"></localize>
<localize ng-if="vm.isNew" key="content_languagesToSaveForFirstTime"></localize>
</p>
<p><localize key="content_languagesToSaveForFirstTime"></localize></p>
</div>
<div class="umb-list umb-list--condensed">
<div class="umb-list-item" ng-repeat="variant in vm.variants">
<ng-form name="saveVariantSelectorForm">
<div class="flex">
<input id="{{variant.htmlId}}"
name="saveVariantSelector"
type="checkbox"
ng-model="variant.save"
ng-disabled="vm.isNew"
style="margin-right: 8px;"
val-server-field="{{variant.htmlId}}" />
<div>
<label for="{{variant.htmlId}}" style="margin-bottom: 2px;">
<span>{{ variant.language.name }}</span>
<strong ng-if="variant.language.isMandatory" class="umb-control-required">*</strong>
</label>
<div ng-if="!saveVariantSelectorForm.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state class="umb-permission__description" variant="variant"></umb-variant-state>
</div>
<div ng-messages="saveVariantSelectorForm.saveVariantSelector.$error" show-validation-on-submit>
<div class="umb-permission__description" style="color: #F02E28;" ng-message="valServerField">{{saveVariantSelectorForm.saveVariantSelector.errorMsg}}</div>
</div>
<div ng-repeat="notification in variant.notifications">
<div class="umb-permission__description" style="color: #1FB572;">{{notification.message}}</div>
</div>
</div>
</div>
</ng-form>
</div>
<br/>
</div>
</div>
<div ng-if="!vm.isNew && !vm.loading">
<div style="margin-bottom: 15px;">
<p><localize key="content_languagesToSave"></localize></p>
</div>
<div class="umb-list umb-list--condensed">
@@ -19,31 +61,31 @@
<div class="umb-list-item" ng-repeat="variant in vm.variants | filter:vm.dirtyVariantFilter track by variant.compositeId">
<ng-form name="saveVariantSelectorForm">
<div class="flex">
<umb-checkbox
input-id="{{variant.htmlId}}"
name="saveVariantSelector"
model="variant.save"
on-change="vm.changeSelection(variant)"
server-validation-field="{{variant.htmlId}}"/>
<input id="{{variant.htmlId}}"
name="saveVariantSelector"
type="checkbox"
ng-model="variant.save"
ng-change="vm.changeSelection(variant)"
style="margin-right: 8px;"
val-server-field="{{variant.htmlId}}" />
<div>
<label for="{{variant.htmlId}}" style="margin-bottom: 2px;">
<span>{{ variant.language.name }}</span>
<strong ng-if="variant.language.isMandatory" class="umb-control-required">*</strong>
<span class="db" ng-if="!saveVariantSelectorForm.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state class="umb-permission__description" variant="variant"></umb-variant-state>
</span>
<span class="db" ng-messages="saveVariantSelectorForm.saveVariantSelector.$error" show-validation-on-submit>
<span class="db umb-permission__description text-error" ng-message="valServerField">{{saveVariantSelectorForm.saveVariantSelector.errorMsg}}</span>
</span>
<span class="db" ng-repeat="notification in variant.notifications">
<span class="db umb-permission__description text-success">{{notification.message}}</span>
</span>
</label>
<div ng-if="!saveVariantSelectorForm.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state class="umb-permission__description" variant="variant"></umb-variant-state>
</div>
<div ng-messages="saveVariantSelectorForm.saveVariantSelector.$error" show-validation-on-submit>
<div class="umb-permission__description" style="color: #F02E28;" ng-message="valServerField">{{saveVariantSelectorForm.saveVariantSelector.errorMsg}}</div>
</div>
<div ng-repeat="notification in variant.notifications">
<div class="umb-permission__description" style="color: #1FB572;">{{notification.message}}</div>
</div>
</div>
</div>
@@ -54,10 +96,7 @@
<div class="umb-list umb-list--condensed" ng-if="vm.hasPristineVariants">
<div style="margin-bottom: 15px; font-weight: bold;">
<p>
<localize ng-if="!vm.isNew" key="content_unmodifiedLanguages"></localize>
<localize ng-if="vm.isNew" key="content_untouchedLanguagesForFirstTime"></localize>
</p>
<p><localize key="content_unmodifiedLanguages"></localize></p>
</div>
<div class="umb-list-item" ng-repeat="variant in vm.variants | filter:vm.pristineVariantFilter">
@@ -72,7 +111,7 @@
</div>
<div ng-repeat="notification in variant.notifications">
<div class="umb-permission__description text-success">{{notification.message}}</div>
<div class="umb-permission__description" style="color: #1FB572;">{{notification.message}}</div>
</div>
</div>
</div>
@@ -1,7 +1,7 @@
(function () {
"use strict";
function ScheduleContentController($scope, $timeout, localizationService, dateHelper, userService, contentEditingHelper) {
function ScheduleContentController($scope, $timeout, localizationService, dateHelper, userService) {
var vm = this;
@@ -45,7 +45,7 @@
_.each(vm.variants,
function (variant) {
variant.compositeId = contentEditingHelper.buildCompositeVariantId(variant);
variant.compositeId = variant.language.culture + "_" + (variant.segment ? variant.segment : "");
variant.htmlId = "_content_variant_" + variant.compositeId;
//check for pristine variants
@@ -87,25 +87,24 @@
<div class="umb-list-item" ng-repeat="variant in vm.variants | filter:vm.dirtyVariantFilter">
<ng-form name="scheduleSelectorForm">
<div class="flex">
<umb-checkbox
input-id="{{'saveVariantSelector_' + variant.language.culture}}"
name="saveVariantSelector"
model="variant.save"
on-change="vm.changeSelection(variant)"/>
<input id="{{'saveVariantSelector_' + variant.language.culture}}"
name="saveVariantSelector"
type="checkbox"
ng-model="variant.save"
ng-change="vm.changeSelection(variant)"
style="margin-right: 8px;" />
<div>
<label for="{{'saveVariantSelector_' + variant.language.culture}}" style="margin-bottom: 2px;">
<span>{{ variant.language.name }}</span>
<span class="db umb-permission__description"
ng-if="!scheduleSelectorForm.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state variant="variant"></umb-variant-state>
<span ng-show="variant.language.isMandatory"> - <localize key="languages_mandatoryLanguage"></localize></span>
</span>
</label>
<div class="umb-permission__description"
ng-if="!scheduleSelectorForm.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state variant="variant"></umb-variant-state>
<span ng-show="variant.language.isMandatory"> - <localize key="languages_mandatoryLanguage"></localize></span>
</div>
<div class="flex items-center" style="margin-top: 10px; margin-bottom: 10px;">
<div class="flex items-center" ng-if="variant.releaseDate || variant.save">
@@ -167,11 +166,11 @@
val-server-field="{{variant.htmlId}}" />
<div ng-messages="scheduleSelectorForm.saveVariantReleaseDate.$error" show-validation-on-submit>
<div class="umb-permission__description text-error" ng-message="valServerField">{{scheduleSelectorForm.saveVariantReleaseDate.errorMsg}}</div>
<div class="umb-permission__description" style="color: #F02E28;" ng-message="valServerField">{{scheduleSelectorForm.saveVariantReleaseDate.errorMsg}}</div>
</div>
<div ng-repeat="notification in variant.notifications">
<div class="umb-permission__description text-success">{{notification.message}}</div>
<div class="umb-permission__description" style="color: #1FB572;">{{notification.message}}</div>
</div>
</div>
@@ -1,7 +1,7 @@
(function () {
"use strict";
function SendToPublishController($scope, localizationService, contentEditingHelper) {
function SendToPublishController($scope, localizationService) {
var vm = this;
vm.loading = true;
@@ -23,12 +23,6 @@
if (vm.variants.length !== 0) {
_.each(vm.variants,
function (variant) {
variant.compositeId = contentEditingHelper.buildCompositeVariantId(variant);
variant.htmlId = "_content_variant_" + variant.compositeId;
});
//now sort it so that the current one is at the top
vm.variants = _.sortBy(vm.variants, function (v) {
return v.active ? 0 : 1;
@@ -13,31 +13,31 @@
<div class="umb-list-item" ng-repeat="variant in vm.variants | filter:vm.modifiedVariantFilter">
<ng-form name="publishVariantSelectorForm">
<div class="flex">
<umb-checkbox
input-id="{{variant.htmlId}}"
name="publishVariantSelector"
model="variant.save"
on-change="vm.changeSelection(variant)"
server-validation-field="{{variant.htmlId}}"/>
<input id="{{variant.htmlId}}"
name="publishVariantSelector"
type="checkbox"
ng-model="variant.save"
ng-change="vm.changeSelection(variant)"
style="margin-right: 8px;"
val-server-field="{{variant.htmlId}}" />
<div>
<label for="{{variant.htmlId}}" style="margin-bottom: 2px;">
<span>{{ variant.language.name }}</span>
<strong ng-if="variant.language.isMandatory" class="umb-control-required">*</strong>
<span class="db" ng-if="!publishVariantSelectorForm.publishVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state class="umb-permission__description" variant="variant"></umb-variant-state>
</span>
<span class="db" ng-messages="publishVariantSelectorForm.publishVariantSelector.$error" show-validation-on-submit>
<span class="db umb-permission__description text-error" ng-message="valServerField">{{publishVariantSelectorForm.publishVariantSelector.errorMsg}}</span>
</span>
<span class="db" ng-repeat="notification in variant.notifications">
<span class="db umb-permission__description text-success">{{notification.message}}</span>
</span>
</label>
<div ng-if="!publishVariantSelectorForm.publishVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state class="umb-permission__description" variant="variant"></umb-variant-state>
</div>
<div ng-messages="publishVariantSelectorForm.publishVariantSelector.$error" show-validation-on-submit>
<div class="umb-permission__description" style="color: #F02E28;" ng-message="valServerField">{{publishVariantSelectorForm.publishVariantSelector.errorMsg}}</div>
</div>
<div ng-repeat="notification in variant.notifications">
<div class="umb-permission__description" style="color: #1FB572;">{{notification.message}}</div>
</div>
</div>
</div>
@@ -1,7 +1,7 @@
(function () {
"use strict";
function UnpublishController($scope, localizationService, contentEditingHelper) {
function UnpublishController($scope, localizationService) {
var vm = this;
var autoSelectedVariants = [];
@@ -21,12 +21,6 @@
});
}
_.each(vm.variants,
function (variant) {
variant.compositeId = contentEditingHelper.buildCompositeVariantId(variant);
variant.htmlId = "_content_variant_" + variant.compositeId;
});
// node has variants
if (vm.variants.length !== 1) {
//now sort it so that the current one is at the top
@@ -57,7 +51,7 @@
});
$scope.model.disableSubmitButton = !firstSelected; //disable submit button if there is none selected
// if a mandatory variant is selected we want to select all other variants
// if a mandatory variant is selected we want to selet all other variants
// and disable selection for the others
if(selectedVariant.save && selectedVariant.language.isMandatory) {
@@ -16,24 +16,24 @@
<div class="umb-list-item" ng-repeat="variant in vm.variants | filter:vm.publishedVariantFilter">
<ng-form name="unpublishVariantSelectorForm">
<div class="flex">
<umb-checkbox
input-id="{{variant.htmlId}}"
name="unpublishVariantSelector"
model="variant.save"
on-change="vm.changeSelection(variant)"
disabled="variant.disabled"
server-validation-field="{{variant.htmlId}}"/>
<input id="{{variant.htmlId}}"
name="unpublishVariantSelector"
type="checkbox"
ng-model="variant.save"
ng-change="vm.changeSelection(variant)"
ng-disabled="variant.disabled"
style="margin-right: 8px;"
val-server-field="{{variant.htmlId}}" />
<div>
<label for="{{variant.htmlId}}" style="margin-bottom: 2px;">
<span>{{ variant.language.name }}</span>
<span class="db umb-permission__description">
<umb-variant-state variant="variant"></umb-variant-state>
<span ng-if="variant.language.isMandatory"> - <localize key="languages_mandatoryLanguage"></localize></span>
</span>
</label>
</label>
<div class="umb-permission__description">
<umb-variant-state variant="variant"></umb-variant-state>
<span ng-if="variant.language.isMandatory"> - <localize key="languages_mandatoryLanguage"></localize></span>
</div>
</div>
</div>
@@ -22,7 +22,7 @@ function startUpVideosDashboardController($scope, dashboardResource) {
angular.module("umbraco").controller("Umbraco.Dashboard.StartupVideosController", startUpVideosDashboardController);
function startUpDynamicContentController($q, $timeout, $scope, dashboardResource, assetsService, tourService, eventsService) {
function startUpDynamicContentController($timeout, $scope, dashboardResource, assetsService, tourService, eventsService) {
var vm = this;
var evts = [];
@@ -167,7 +167,7 @@ function FormsController($scope, $cookies, packageResource, localizationService)
return packageResource.installData(pack);
}, vm.error)
.then(function(pack) {
vm.state = labels.installStateComplete;
vm.state = installStateComplete;
return packageResource.cleanUp(pack);
}, vm.error)
.then(vm.complete, vm.error);
@@ -22,6 +22,7 @@ function DataTypeDeleteController($scope, dataTypeResource, treeService, navigat
treeService.removeNode($scope.currentNode);
navigationService.hideMenu();
});
};
$scope.performContainerDelete = function () {

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