Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 258e221930 | |||
| 5a07e34fb4 | |||
| 1b5d614f5a | |||
| abb08260eb | |||
| 57c567c570 | |||
| 424f4601ae | |||
| a2a247b413 | |||
| 0b6a369047 | |||
| 522fdefb8c | |||
| 4d8d10f07a | |||
| 6f17129364 | |||
| 6f44e4fd09 | |||
| 02ddf80014 | |||
| 723d9eddd1 | |||
| fa655b812c | |||
| 576c10cd50 | |||
| 179afe7e0d | |||
| be7f42c454 | |||
| 7e1ee26dee | |||
| 1c9b449c46 | |||
| e38095b727 | |||
| b5796ad237 | |||
| f985c437b5 | |||
| 63fb8f5933 | |||
| eb529783c4 | |||
| a8265db5f0 | |||
| 7dcf2b1abb | |||
| 514452ddb9 | |||
| 1eb55a83c0 | |||
| dfe6f2029a | |||
| 851c844c8b | |||
| 7e413ed406 | |||
| c56b8863ee | |||
| 13a8548d3a | |||
| a70cb51559 | |||
| 8a56b3db16 | |||
| 4fcf2ce7bd | |||
| 080ace90b2 | |||
| 2bfa81c707 |
@@ -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! 🎉👍
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
@@ -1,9 +1,7 @@
|
||||
[](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=75) [](https://pullreminders.com?ref=badge)
|
||||
|
||||
|
||||
_You are browsing the Umbraco v8 branch. Umbraco 8 is currently under development._
|
||||
[](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)._
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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")]
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 });
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,8 @@ HAVING COUNT(v2.id) <> 1").Any())
|
||||
// transform column versionId from guid to integer (contentVersion.id)
|
||||
if (ColumnType(PreTables.PropertyData, "versionId") == "uniqueidentifier")
|
||||
{
|
||||
Database.Execute($"ALTER TABLE {PreTables.PropertyData} ADD COLUMN versionId2 INT NULL;");
|
||||
Alter.Table(PreTables.PropertyData).AddColumn("versionId2").AsInt32().Nullable().Do();
|
||||
|
||||
// SQLCE does not support UPDATE...FROM
|
||||
var temp = Database.Fetch<dynamic>($"SELECT id, versionId FROM {PreTables.ContentVersion}");
|
||||
foreach (var t in temp)
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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.
|
||||
+2
-29
@@ -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();
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@
|
||||
item: '=',
|
||||
onOpen: '&',
|
||||
onOpenAnchor: '&',
|
||||
hotkey: '<'
|
||||
index: '@'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
-72
@@ -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);
|
||||
|
||||
})();
|
||||
-57
@@ -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);
|
||||
|
||||
})();
|
||||
+9
-22
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+1
-3
@@ -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) {
|
||||
|
||||
+7
-4
@@ -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 {
|
||||
|
||||
+2
-2
@@ -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;
|
||||
|
||||
+4
-19
@@ -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);
|
||||
|
||||
})();
|
||||
})();
|
||||
+9
-9
@@ -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>
|
||||
|
||||
+6
-6
@@ -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);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user