diff --git a/.github/CONTRIBUTING_DETAILED.md b/.github/CONTRIBUTING_DETAILED.md index b3e34ef55d..8c2bfffd87 100644 --- a/.github/CONTRIBUTING_DETAILED.md +++ b/.github/CONTRIBUTING_DETAILED.md @@ -19,7 +19,7 @@ When contributing code to Umbraco there's plenty of things you'll want to know, * [What branch should I target for my contributions?](#what-branch-should-i-target-for-my-contributions) * [Building Umbraco from source code](#building-umbraco-from-source-code) * [Keeping your Umbraco fork in sync with the main repository](#keeping-your-umbraco-fork-in-sync-with-the-main-repository) - + ## How Can I Contribute? ### Reporting Bugs @@ -52,7 +52,7 @@ Provide more context by answering these questions: Include details about your configuration and environment: - * **Which version of Umbraco are you using?** + * **Which version of Umbraco are you using?** * **What is the environment you're using Umbraco in?** Is this a problem on your local machine or on a server. Tell us about your configuration: Windows version, IIS/IISExpress, database type, etc. * **Which packages do you have installed?** @@ -80,7 +80,7 @@ The most successful pull requests usually look a like this: * Unit tests, while optional are awesome, thank you! * New code is commented with documentation from which [the reference documentation](https://our.umbraco.com/documentation/Reference/) is generated -Again, these are guidelines, not strict requirements. +Again, these are guidelines, not strict requirements. ## Making changes after the PR was opened @@ -90,7 +90,7 @@ If you make the corrections we ask for in the same branch and push them to your To be honest, we don't like rules very much. We trust you have the best of intentions and we encourage you to create working code. If it doesn't look perfect then we'll happily help clean it up. -That said, the Umbraco development team likes to follow the hints that ReSharper gives us (no problem if you don't have this installed) and we've added a `.editorconfig` file so that Visual Studio knows what to do with whitespace, line endings, etc. +That said, the Umbraco development team likes to follow the hints that ReSharper gives us (no problem if you don't have this installed) and we've added a `.editorconfig` file so that Visual Studio knows what to do with whitespace, line endings, etc. ## What should I know before I get started? @@ -125,6 +125,12 @@ We like to use [Gitflow as much as possible](https://jeffkreeftmeijer.com/git-fl ### Building Umbraco from source code +In order to build the Umbraco source code locally, first make sure you have the following installed. + + * Visual Studio 2017 v15.3+ + * Node v10+ (Installed via `build.bat` script. If you already have it installed, make sure you're running at least v10) + * npm v6.4.1+ (Installed via `build.bat` script. If you already have it installed, make sure you're running at least v6.4.1) + The easiest way to get started is to run `build.bat` which will build both the backoffice (also known as "Belle") and the Umbraco core. You can then easily start debugging from Visual Studio, or if you need to debug Belle you can run `gulp dev` in `src\Umbraco.Web.UI.Client`. See [this page](BUILD.md) for more details. Alternatively, you can open `src\umbraco.sln` in Visual Studio 2017 (version 15.3 or higher, [the community edition is free](https://www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15) for you to use to contribute to Open Source projects). In Visual Studio, find the Task Runner Explorer (in the View menu under Other Windows) and run the build task under the gulpfile. diff --git a/build/NuSpecs/tools/Dashboard.config.install.xdt b/build/NuSpecs/tools/Dashboard.config.install.xdt index 036beeba29..a81af8c365 100644 --- a/build/NuSpecs/tools/Dashboard.config.install.xdt +++ b/build/NuSpecs/tools/Dashboard.config.install.xdt @@ -3,7 +3,7 @@
- + views/dashboard/settings/settingsdashboardintro.html @@ -14,7 +14,7 @@ forms - + views/dashboard/forms/formsdashboardintro.html @@ -28,7 +28,7 @@
- + views/dashboard/developer/developerdashboardvideos.html @@ -47,7 +47,7 @@ - + views/dashboard/media/mediafolderbrowser.html @@ -56,7 +56,7 @@
- + views/dashboard/members/membersdashboardvideos.html @@ -92,4 +92,4 @@
- \ No newline at end of file + diff --git a/src/Umbraco.Core/Composing/CompositionRoots/ConfigurationCompositionRoot.cs b/src/Umbraco.Core/Composing/CompositionRoots/ConfigurationCompositionRoot.cs index 82912163b6..80de6d1c35 100644 --- a/src/Umbraco.Core/Composing/CompositionRoots/ConfigurationCompositionRoot.cs +++ b/src/Umbraco.Core/Composing/CompositionRoots/ConfigurationCompositionRoot.cs @@ -16,6 +16,7 @@ namespace Umbraco.Core.Composing.CompositionRoots container.Register(factory => factory.GetInstance().Templates); container.Register(factory => factory.GetInstance().RequestHandler); container.Register(factory => UmbracoConfig.For.GlobalSettings()); + container.Register(factory => UmbracoConfig.For.DashboardSettings()); // fixme - other sections we need to add? } diff --git a/src/Umbraco.Core/Configuration/Dashboard/AccessElement.cs b/src/Umbraco.Core/Configuration/Dashboard/AccessElement.cs index 1642f23fc5..01538c8e0b 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/AccessElement.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/AccessElement.cs @@ -7,26 +7,22 @@ namespace Umbraco.Core.Configuration.Dashboard internal class AccessElement : RawXmlConfigurationElement, IAccess { public AccessElement() - { - - } + { } public AccessElement(XElement rawXml) - :base(rawXml) - { - } + : base(rawXml) + { } - public IEnumerable Rules + public IEnumerable Rules { get { - var result = new List(); - if (RawXml != null) - { - result.AddRange(RawXml.Elements("deny").Select(x => new AccessItem {Action = AccessType.Deny, Value = x.Value })); - result.AddRange(RawXml.Elements("grant").Select(x => new AccessItem { Action = AccessType.Grant, Value = x.Value })); - result.AddRange(RawXml.Elements("grantBySection").Select(x => new AccessItem { Action = AccessType.GrantBySection, Value = x.Value })); - } + var result = new List(); + if (RawXml == null) return result; + + result.AddRange(RawXml.Elements("deny").Select(x => new AccessRule {Type = AccessRuleType.Deny, Value = x.Value })); + result.AddRange(RawXml.Elements("grant").Select(x => new AccessRule { Type = AccessRuleType.Grant, Value = x.Value })); + result.AddRange(RawXml.Elements("grantBySection").Select(x => new AccessRule { Type = AccessRuleType.GrantBySection, Value = x.Value })); return result; } } diff --git a/src/Umbraco.Core/Configuration/Dashboard/AccessItem.cs b/src/Umbraco.Core/Configuration/Dashboard/AccessItem.cs deleted file mode 100644 index 37cf491536..0000000000 --- a/src/Umbraco.Core/Configuration/Dashboard/AccessItem.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Umbraco.Core.Configuration.Dashboard -{ - internal class AccessItem : IAccessItem - { - /// - /// This can be grant, deny or grantBySection - /// - public AccessType Action { get; set; } - - /// - /// The value of the action - /// - public string Value { get; set; } - } -} diff --git a/src/Umbraco.Core/Configuration/Dashboard/AccessRule.cs b/src/Umbraco.Core/Configuration/Dashboard/AccessRule.cs new file mode 100644 index 0000000000..fe6840ff64 --- /dev/null +++ b/src/Umbraco.Core/Configuration/Dashboard/AccessRule.cs @@ -0,0 +1,14 @@ +namespace Umbraco.Core.Configuration.Dashboard +{ + /// + /// Implements . + /// + internal class AccessRule : IAccessRule + { + /// + public AccessRuleType Type { get; set; } + + /// + public string Value { get; set; } + } +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/AccessRuleType.cs b/src/Umbraco.Core/Configuration/Dashboard/AccessRuleType.cs new file mode 100644 index 0000000000..cb9ce983fe --- /dev/null +++ b/src/Umbraco.Core/Configuration/Dashboard/AccessRuleType.cs @@ -0,0 +1,28 @@ +namespace Umbraco.Core.Configuration.Dashboard +{ + /// + /// Defines dashboard access rules type. + /// + public enum AccessRuleType + { + /// + /// Unknown (default value). + /// + Unknown = 0, + + /// + /// Grant access to the dashboard if user belongs to the specified user group. + /// + Grant, + + /// + /// Deny access to the dashboard if user belongs to the specified user group. + /// + Deny, + + /// + /// Grant access to the dashboard if user has access to the specified section. + /// + GrantBySection + } +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/AccessType.cs b/src/Umbraco.Core/Configuration/Dashboard/AccessType.cs deleted file mode 100644 index d72cac15d0..0000000000 --- a/src/Umbraco.Core/Configuration/Dashboard/AccessType.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Umbraco.Core.Configuration.Dashboard -{ - public enum AccessType - { - Grant, - Deny, - GrantBySection - } -} diff --git a/src/Umbraco.Core/Configuration/Dashboard/ControlElement.cs b/src/Umbraco.Core/Configuration/Dashboard/ControlElement.cs index 0434eea47e..20dac7460e 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/ControlElement.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/ControlElement.cs @@ -1,5 +1,4 @@ -using System; -using System.Configuration; +using System.Configuration; using System.Linq; using System.Xml.Linq; @@ -8,33 +7,12 @@ namespace Umbraco.Core.Configuration.Dashboard internal class ControlElement : RawXmlConfigurationElement, IDashboardControl { - public bool ShowOnce - { - get - { - return RawXml.Attribute("showOnce") == null - ? false - : bool.Parse(RawXml.Attribute("showOnce").Value); - } - } - - public bool AddPanel - { - get - { - return RawXml.Attribute("addPanel") == null - ? true - : bool.Parse(RawXml.Attribute("addPanel").Value); - } - } - public string PanelCaption { get { - return RawXml.Attribute("panelCaption") == null - ? "" - : RawXml.Attribute("panelCaption").Value; + var panelCaption = RawXml.Attribute("panelCaption"); + return panelCaption == null ? "" : panelCaption.Value; } } @@ -43,11 +21,7 @@ namespace Umbraco.Core.Configuration.Dashboard get { var access = RawXml.Element("access"); - if (access == null) - { - return new AccessElement(); - } - return new AccessElement(access); + return access == null ? new AccessElement() : new AccessElement(access); } } @@ -65,10 +39,6 @@ namespace Umbraco.Core.Configuration.Dashboard } } - - IAccess IDashboardControl.AccessRights - { - get { return Access; } - } + IAccess IDashboardControl.AccessRights => Access; } } diff --git a/src/Umbraco.Core/Configuration/Dashboard/IAccess.cs b/src/Umbraco.Core/Configuration/Dashboard/IAccess.cs index b7d8540a79..8ac1b18cca 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/IAccess.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/IAccess.cs @@ -4,6 +4,6 @@ namespace Umbraco.Core.Configuration.Dashboard { public interface IAccess { - IEnumerable Rules { get; } + IEnumerable Rules { get; } } } diff --git a/src/Umbraco.Core/Configuration/Dashboard/IAccessItem.cs b/src/Umbraco.Core/Configuration/Dashboard/IAccessItem.cs deleted file mode 100644 index 8b18d50bb3..0000000000 --- a/src/Umbraco.Core/Configuration/Dashboard/IAccessItem.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Umbraco.Core.Configuration.Dashboard -{ - public interface IAccessItem - { - /// - /// This can be grant, deny or grantBySection - /// - AccessType Action { get; set; } - - /// - /// The value of the action - /// - string Value { get; set; } - } -} diff --git a/src/Umbraco.Core/Configuration/Dashboard/IAccessRule.cs b/src/Umbraco.Core/Configuration/Dashboard/IAccessRule.cs new file mode 100644 index 0000000000..8b51b1b73a --- /dev/null +++ b/src/Umbraco.Core/Configuration/Dashboard/IAccessRule.cs @@ -0,0 +1,18 @@ +namespace Umbraco.Core.Configuration.Dashboard +{ + /// + /// Represents an access rule. + /// + public interface IAccessRule + { + /// + /// Gets or sets the rule type. + /// + AccessRuleType Type { get; set; } + + /// + /// Gets or sets the value for the rule. + /// + string Value { get; set; } + } +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/IDashboardControl.cs b/src/Umbraco.Core/Configuration/Dashboard/IDashboardControl.cs index 7dab542258..cdf05af1ec 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/IDashboardControl.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/IDashboardControl.cs @@ -2,10 +2,6 @@ { public interface IDashboardControl { - bool ShowOnce { get; } - - bool AddPanel { get; } - string PanelCaption { get; } string ControlPath { get; } diff --git a/src/Umbraco.Core/Manifest/DashboardAccessRuleConverter.cs b/src/Umbraco.Core/Manifest/DashboardAccessRuleConverter.cs new file mode 100644 index 0000000000..c627728a32 --- /dev/null +++ b/src/Umbraco.Core/Manifest/DashboardAccessRuleConverter.cs @@ -0,0 +1,45 @@ +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Umbraco.Core.Configuration.Dashboard; +using Umbraco.Core.Serialization; + +namespace Umbraco.Core.Manifest +{ + /// + /// Implements a json read converter for . + /// + internal class DashboardAccessRuleConverter : JsonReadConverter + { + /// + protected override IAccessRule Create(Type objectType, string path, JObject jObject) + { + return new AccessRule(); + } + + /// + protected override void Deserialize(JObject jobject, IAccessRule target, JsonSerializer serializer) + { + // see Create above, target is either DataEditor (parameter) or ConfiguredDataEditor (property) + + if (!(target is AccessRule accessRule)) + throw new Exception("panic."); + + GetRule(accessRule, jobject, "grant", AccessRuleType.Grant); + GetRule(accessRule, jobject, "deny", AccessRuleType.Deny); + GetRule(accessRule, jobject, "grantBySection", AccessRuleType.GrantBySection); + + if (accessRule.Type == AccessRuleType.Unknown) throw new InvalidOperationException("Rule is not defined."); + } + + private void GetRule(AccessRule rule, JObject jobject, string name, AccessRuleType type) + { + var token = jobject[name]; + if (token == null) return; + if (rule.Type != AccessRuleType.Unknown) throw new InvalidOperationException("Multiple definition of a rule."); + if (token.Type != JTokenType.String) throw new InvalidOperationException("Rule value is not a string."); + rule.Type = type; + rule.Value = token.Value(); + } + } +} diff --git a/src/Umbraco.Core/Manifest/ManifestDashboardDefinition.cs b/src/Umbraco.Core/Manifest/ManifestDashboardDefinition.cs new file mode 100644 index 0000000000..83f047b264 --- /dev/null +++ b/src/Umbraco.Core/Manifest/ManifestDashboardDefinition.cs @@ -0,0 +1,36 @@ +using System; +using System.ComponentModel; +using Newtonsoft.Json; +using Umbraco.Core.Configuration.Dashboard; +using Umbraco.Core.IO; + +namespace Umbraco.Core.Manifest +{ + public class ManifestDashboardDefinition + { + private string _view; + + [JsonProperty("name", Required = Required.Always)] + public string Name { get; set; } + + [JsonProperty("alias", Required = Required.Always)] + public string Alias { get; set; } + + [JsonProperty("weight", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] + [DefaultValue(100)] + public int Weight { get; set; } + + [JsonProperty("view", Required = Required.Always)] + public string View + { + get => _view; + set => _view = IOHelper.ResolveVirtualUrl(value); + } + + [JsonProperty("sections")] + public string[] Sections { get; set; } = Array.Empty(); + + [JsonProperty("access")] + public IAccessRule[] AccessRules { get; set; } = Array.Empty(); + } +} diff --git a/src/Umbraco.Core/Manifest/ManifestParser.cs b/src/Umbraco.Core/Manifest/ManifestParser.cs index 125dee5c05..fe021fae5b 100644 --- a/src/Umbraco.Core/Manifest/ManifestParser.cs +++ b/src/Umbraco.Core/Manifest/ManifestParser.cs @@ -1,177 +1,180 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using Newtonsoft.Json; -using Umbraco.Core.Cache; -using Umbraco.Core.Exceptions; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.ContentEditing; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Core.Manifest -{ - /// - /// Parses the Main.js file and replaces all tokens accordingly. - /// - public class ManifestParser - { - private static readonly string Utf8Preamble = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble()); - - private readonly IRuntimeCacheProvider _cache; - private readonly ILogger _logger; - private readonly ManifestValueValidatorCollection _validators; - - private string _path; - - /// - /// Initializes a new instance of the class. - /// - public ManifestParser(IRuntimeCacheProvider cache, ManifestValueValidatorCollection validators, ILogger logger) - : this(cache, validators, "~/App_Plugins", logger) - { } - - /// - /// Initializes a new instance of the class. - /// - private ManifestParser(IRuntimeCacheProvider cache, ManifestValueValidatorCollection validators, string path, ILogger logger) - { - _cache = cache ?? throw new ArgumentNullException(nameof(cache)); - _validators = validators ?? throw new ArgumentNullException(nameof(validators)); - if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullOrEmptyException(nameof(path)); - Path = path; - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public string Path - { - get => _path; - set => _path = value.StartsWith("~/") ? IOHelper.MapPath(value) : value; - } - - /// - /// Gets all manifests, merged into a single manifest object. - /// - /// - public PackageManifest Manifest - => _cache.GetCacheItem("Umbraco.Core.Manifest.ManifestParser::Manifests", () => - { - var manifests = GetManifests(); - return MergeManifests(manifests); - }, new TimeSpan(0, 4, 0)); - - /// - /// Gets all manifests. - /// - private IEnumerable GetManifests() - { - var manifests = new List(); - - foreach (var path in GetManifestFiles()) - { - try - { - var text = File.ReadAllText(path); - text = TrimPreamble(text); - if (string.IsNullOrWhiteSpace(text)) - continue; - var manifest = ParseManifest(text); - manifests.Add(manifest); - } - catch (Exception e) - { - _logger.Error(e, "Failed to parse manifest at '{Path}', ignoring.", path); - } - } - - return manifests; - } - - /// - /// Merges all manifests into one. - /// - private static PackageManifest MergeManifests(IEnumerable manifests) - { - var scripts = new HashSet(); - var stylesheets = new HashSet(); - var propertyEditors = new List(); - var parameterEditors = new List(); - var gridEditors = new List(); - var contentApps = new List(); - - foreach (var manifest in manifests) - { - if (manifest.Scripts != null) foreach (var script in manifest.Scripts) scripts.Add(script); - if (manifest.Stylesheets != null) foreach (var stylesheet in manifest.Stylesheets) stylesheets.Add(stylesheet); - if (manifest.PropertyEditors != null) propertyEditors.AddRange(manifest.PropertyEditors); - if (manifest.ParameterEditors != null) parameterEditors.AddRange(manifest.ParameterEditors); - if (manifest.GridEditors != null) gridEditors.AddRange(manifest.GridEditors); - if (manifest.ContentApps != null) contentApps.AddRange(manifest.ContentApps); - } - - return new PackageManifest - { - Scripts = scripts.ToArray(), - Stylesheets = stylesheets.ToArray(), - PropertyEditors = propertyEditors.ToArray(), - ParameterEditors = parameterEditors.ToArray(), - GridEditors = gridEditors.ToArray(), - ContentApps = contentApps.ToArray() - }; - } - - // gets all manifest files (recursively) - private IEnumerable GetManifestFiles() - { - if (Directory.Exists(_path) == false) - return new string[0]; - return Directory.GetFiles(_path, "package.manifest", SearchOption.AllDirectories); - } - - - private static string TrimPreamble(string text) - { - // strangely StartsWith(preamble) would always return true - if (text.Substring(0, 1) == Utf8Preamble) - text = text.Remove(0, Utf8Preamble.Length); - - return text; - } - - /// - /// Parses a manifest. - /// - internal PackageManifest ParseManifest(string text) - { - if (string.IsNullOrWhiteSpace(text)) - throw new ArgumentNullOrEmptyException(nameof(text)); - - var manifest = JsonConvert.DeserializeObject(text, - new DataEditorConverter(_logger), - new ValueValidatorConverter(_validators), - new ContentAppDefinitionConverter()); - - // scripts and stylesheets are raw string, must process here - for (var i = 0; i < manifest.Scripts.Length; i++) - manifest.Scripts[i] = IOHelper.ResolveVirtualUrl(manifest.Scripts[i]); - for (var i = 0; i < manifest.Stylesheets.Length; i++) - manifest.Stylesheets[i] = IOHelper.ResolveVirtualUrl(manifest.Stylesheets[i]); - - // add property editors that are also parameter editors, to the parameter editors list - // (the manifest format is kinda legacy) - var ppEditors = manifest.PropertyEditors.Where(x => (x.Type & EditorType.MacroParameter) > 0).ToList(); - if (ppEditors.Count > 0) - manifest.ParameterEditors = manifest.ParameterEditors.Union(ppEditors).ToArray(); - - return manifest; - } - - // purely for tests - internal IEnumerable ParseGridEditors(string text) - { - return JsonConvert.DeserializeObject>(text); - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using Newtonsoft.Json; +using Umbraco.Core.Cache; +using Umbraco.Core.Exceptions; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.ContentEditing; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Manifest +{ + /// + /// Parses the Main.js file and replaces all tokens accordingly. + /// + public class ManifestParser + { + private static readonly string Utf8Preamble = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble()); + + private readonly IRuntimeCacheProvider _cache; + private readonly ILogger _logger; + private readonly ManifestValueValidatorCollection _validators; + + private string _path; + + /// + /// Initializes a new instance of the class. + /// + public ManifestParser(IRuntimeCacheProvider cache, ManifestValueValidatorCollection validators, ILogger logger) + : this(cache, validators, "~/App_Plugins", logger) + { } + + /// + /// Initializes a new instance of the class. + /// + private ManifestParser(IRuntimeCacheProvider cache, ManifestValueValidatorCollection validators, string path, ILogger logger) + { + _cache = cache ?? throw new ArgumentNullException(nameof(cache)); + _validators = validators ?? throw new ArgumentNullException(nameof(validators)); + if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullOrEmptyException(nameof(path)); + Path = path; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public string Path + { + get => _path; + set => _path = value.StartsWith("~/") ? IOHelper.MapPath(value) : value; + } + + /// + /// Gets all manifests, merged into a single manifest object. + /// + /// + public PackageManifest Manifest + => _cache.GetCacheItem("Umbraco.Core.Manifest.ManifestParser::Manifests", () => + { + var manifests = GetManifests(); + return MergeManifests(manifests); + }, new TimeSpan(0, 4, 0)); + + /// + /// Gets all manifests. + /// + private IEnumerable GetManifests() + { + var manifests = new List(); + + foreach (var path in GetManifestFiles()) + { + try + { + var text = File.ReadAllText(path); + text = TrimPreamble(text); + if (string.IsNullOrWhiteSpace(text)) + continue; + var manifest = ParseManifest(text); + manifests.Add(manifest); + } + catch (Exception e) + { + _logger.Error(e, "Failed to parse manifest at '{Path}', ignoring.", path); + } + } + + return manifests; + } + + /// + /// Merges all manifests into one. + /// + private static PackageManifest MergeManifests(IEnumerable manifests) + { + var scripts = new HashSet(); + var stylesheets = new HashSet(); + var propertyEditors = new List(); + var parameterEditors = new List(); + var gridEditors = new List(); + var contentApps = new List(); + var dashboards = new List(); + + foreach (var manifest in manifests) + { + if (manifest.Scripts != null) foreach (var script in manifest.Scripts) scripts.Add(script); + if (manifest.Stylesheets != null) foreach (var stylesheet in manifest.Stylesheets) stylesheets.Add(stylesheet); + if (manifest.PropertyEditors != null) propertyEditors.AddRange(manifest.PropertyEditors); + if (manifest.ParameterEditors != null) parameterEditors.AddRange(manifest.ParameterEditors); + if (manifest.GridEditors != null) gridEditors.AddRange(manifest.GridEditors); + if (manifest.ContentApps != null) contentApps.AddRange(manifest.ContentApps); + if (manifest.Dashboards != null) dashboards.AddRange(manifest.Dashboards); + } + + return new PackageManifest + { + Scripts = scripts.ToArray(), + Stylesheets = stylesheets.ToArray(), + PropertyEditors = propertyEditors.ToArray(), + ParameterEditors = parameterEditors.ToArray(), + GridEditors = gridEditors.ToArray(), + ContentApps = contentApps.ToArray(), + Dashboards = dashboards.ToArray() + }; + } + + // gets all manifest files (recursively) + private IEnumerable GetManifestFiles() + { + if (Directory.Exists(_path) == false) + return new string[0]; + return Directory.GetFiles(_path, "package.manifest", SearchOption.AllDirectories); + } + + private static string TrimPreamble(string text) + { + // strangely StartsWith(preamble) would always return true + if (text.Substring(0, 1) == Utf8Preamble) + text = text.Remove(0, Utf8Preamble.Length); + + return text; + } + + /// + /// Parses a manifest. + /// + internal PackageManifest ParseManifest(string text) + { + if (string.IsNullOrWhiteSpace(text)) + throw new ArgumentNullOrEmptyException(nameof(text)); + + var manifest = JsonConvert.DeserializeObject(text, + new DataEditorConverter(_logger), + new ValueValidatorConverter(_validators), + new ContentAppDefinitionConverter(), + new DashboardAccessRuleConverter()); + + // scripts and stylesheets are raw string, must process here + for (var i = 0; i < manifest.Scripts.Length; i++) + manifest.Scripts[i] = IOHelper.ResolveVirtualUrl(manifest.Scripts[i]); + for (var i = 0; i < manifest.Stylesheets.Length; i++) + manifest.Stylesheets[i] = IOHelper.ResolveVirtualUrl(manifest.Stylesheets[i]); + + // add property editors that are also parameter editors, to the parameter editors list + // (the manifest format is kinda legacy) + var ppEditors = manifest.PropertyEditors.Where(x => (x.Type & EditorType.MacroParameter) > 0).ToList(); + if (ppEditors.Count > 0) + manifest.ParameterEditors = manifest.ParameterEditors.Union(ppEditors).ToArray(); + + return manifest; + } + + // purely for tests + internal IEnumerable ParseGridEditors(string text) + { + return JsonConvert.DeserializeObject>(text); + } + } +} diff --git a/src/Umbraco.Core/Manifest/PackageManifest.cs b/src/Umbraco.Core/Manifest/PackageManifest.cs index 32dae46a9a..95a5c01b6a 100644 --- a/src/Umbraco.Core/Manifest/PackageManifest.cs +++ b/src/Umbraco.Core/Manifest/PackageManifest.cs @@ -1,31 +1,34 @@ -using System; -using Newtonsoft.Json; -using Umbraco.Core.Models.ContentEditing; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Core.Manifest -{ - /// - /// Represents the content of a package manifest. - /// - public class PackageManifest - { - [JsonProperty("javascript")] - public string[] Scripts { get; set; } = Array.Empty(); - - [JsonProperty("css")] - public string[] Stylesheets { get; set; }= Array.Empty(); - - [JsonProperty("propertyEditors")] - public IDataEditor[] PropertyEditors { get; set; } = Array.Empty(); - - [JsonProperty("parameterEditors")] - public IDataEditor[] ParameterEditors { get; set; } = Array.Empty(); - - [JsonProperty("gridEditors")] - public GridEditor[] GridEditors { get; set; } = Array.Empty(); - - [JsonProperty("contentApps")] - public IContentAppDefinition[] ContentApps { get; set; } = Array.Empty(); - } -} +using System; +using Newtonsoft.Json; +using Umbraco.Core.Models.ContentEditing; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Manifest +{ + /// + /// Represents the content of a package manifest. + /// + public class PackageManifest + { + [JsonProperty("javascript")] + public string[] Scripts { get; set; } = Array.Empty(); + + [JsonProperty("css")] + public string[] Stylesheets { get; set; }= Array.Empty(); + + [JsonProperty("propertyEditors")] + public IDataEditor[] PropertyEditors { get; set; } = Array.Empty(); + + [JsonProperty("parameterEditors")] + public IDataEditor[] ParameterEditors { get; set; } = Array.Empty(); + + [JsonProperty("gridEditors")] + public GridEditor[] GridEditors { get; set; } = Array.Empty(); + + [JsonProperty("contentApps")] + public IContentAppDefinition[] ContentApps { get; set; } = Array.Empty(); + + [JsonProperty("dashboards")] + public ManifestDashboardDefinition[] Dashboards { get; set; } = Array.Empty(); + } +} diff --git a/src/Umbraco.Core/Models/ContentEditing/IContentAppDefinition.cs b/src/Umbraco.Core/Models/ContentEditing/IContentAppDefinition.cs index 2d30fc6ba9..af83c5a2f5 100644 --- a/src/Umbraco.Core/Models/ContentEditing/IContentAppDefinition.cs +++ b/src/Umbraco.Core/Models/ContentEditing/IContentAppDefinition.cs @@ -3,6 +3,7 @@ using Umbraco.Core.Models.Membership; namespace Umbraco.Core.Models.ContentEditing { + /// /// Represents a content app definition. /// diff --git a/src/Umbraco.Core/Models/ContentScheduleCollection.cs b/src/Umbraco.Core/Models/ContentScheduleCollection.cs index 46813bdb45..4c06f8927d 100644 --- a/src/Umbraco.Core/Models/ContentScheduleCollection.cs +++ b/src/Umbraco.Core/Models/ContentScheduleCollection.cs @@ -169,6 +169,7 @@ namespace Umbraco.Core.Models /// Gets the schedule for a culture /// /// + /// /// public IEnumerable GetSchedule(string culture, ContentScheduleAction? action = null) { diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index ea61228864..6733eea500 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; +using System.Security.Cryptography; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Composing; @@ -55,8 +56,11 @@ namespace Umbraco.Core.Models /// internal static string[] GetUserAvatarUrls(this IUser user, ICacheProvider staticCache) { - //check if the user has explicitly removed all avatars including a gravatar, this will be possible and the value will be "none" - if (user.Avatar == "none") + // If FIPS is required, never check the Gravatar service as it only supports MD5 hashing. + // Unfortunately, if the FIPS setting is enabled on Windows, using MD5 will throw an exception + // and the website will not run. + // Also, check if the user has explicitly removed all avatars including a gravatar, this will be possible and the value will be "none" + if (user.Avatar == "none" || CryptoConfig.AllowOnlyFipsAlgorithms) { return new string[0]; } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs index 64489bb059..84c76dbb53 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs @@ -211,12 +211,20 @@ namespace Umbraco.Core.Persistence.Repositories.Implement //.Where(x => Equals(x, default(TId)) == false) .ToArray(); - if (ids.Length > 2000) + // can't query more than 2000 ids at a time... but if someone is really querying 2000+ entities, + // the additional overhead of fetching them in groups is minimal compared to the lookup time of each group + const int maxParams = 2000; + if (ids.Length <= maxParams) { - throw new InvalidOperationException("Cannot perform a query with more than 2000 parameters"); + return CachePolicy.GetAll(ids, PerformGetAll); } - return CachePolicy.GetAll(ids, PerformGetAll); + var entities = new List(); + foreach (var groupOfIds in ids.InGroupsOf(maxParams)) + { + entities.AddRange(CachePolicy.GetAll(groupOfIds.ToArray(), PerformGetAll)); + } + return entities; } /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 31014753af..7da0189345 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -194,8 +194,8 @@ - - + + @@ -203,7 +203,7 @@ - + @@ -334,7 +334,9 @@ + + diff --git a/src/Umbraco.Tests/Configurations/DashboardSettings/Dashboard.config b/src/Umbraco.Tests/Configurations/DashboardSettings/Dashboard.config index 4040412603..4c86355a1b 100644 --- a/src/Umbraco.Tests/Configurations/DashboardSettings/Dashboard.config +++ b/src/Umbraco.Tests/Configurations/DashboardSettings/Dashboard.config @@ -6,10 +6,10 @@ settings - + views/dashboard/settings/settingsdashboardintro.html - + views/dashboard/settings/settingsdashboardvideos.html @@ -23,10 +23,10 @@ developer - + views/dashboard/developer/developerdashboardintro.html - + views/dashboard/developer/developerdashboardvideos.html @@ -37,7 +37,7 @@ media - + views/dashboard/media/mediafolderbrowser.html @@ -45,13 +45,13 @@ admin - + views/dashboard/media/mediadashboardintro.html - + views/dashboard/media/desktopmediauploader.html - + views/dashboard/media/mediadashboardvideos.html @@ -70,25 +70,25 @@ admin - + views/dashboard/default/startupdashboardintro.html - + views/dashboard/default/startupdashboardkits.html editor writer - + views/dashboard/default/startupdashboardvideos.html - dashboard/latestEdits.ascx + dashboard/latestEdits.ascx - + views/dashboard/changepassword.html @@ -100,13 +100,13 @@ member - + views/dashboard/members/membersdashboardintro.html - + members/membersearch.ascx - + views/dashboard/members/membersdashboardvideos.html diff --git a/src/Umbraco.Tests/Configurations/DashboardSettings/DashboardSettingsTests.cs b/src/Umbraco.Tests/Configurations/DashboardSettings/DashboardSettingsTests.cs index 862dfb3dc2..920de683b4 100644 --- a/src/Umbraco.Tests/Configurations/DashboardSettings/DashboardSettingsTests.cs +++ b/src/Umbraco.Tests/Configurations/DashboardSettings/DashboardSettingsTests.cs @@ -56,11 +56,11 @@ namespace Umbraco.Tests.Configurations.DashboardSettings Assert.AreEqual(3, SettingsSection.Sections.ElementAt(3).AccessRights.Rules.Count()); Assert.AreEqual("translator", SettingsSection.Sections.ElementAt(3).AccessRights.Rules.ElementAt(0).Value); - Assert.AreEqual(AccessType.Deny, SettingsSection.Sections.ElementAt(3).AccessRights.Rules.ElementAt(0).Action); + Assert.AreEqual(AccessRuleType.Deny, SettingsSection.Sections.ElementAt(3).AccessRights.Rules.ElementAt(0).Type); Assert.AreEqual("hello", SettingsSection.Sections.ElementAt(3).AccessRights.Rules.ElementAt(1).Value); - Assert.AreEqual(AccessType.Grant, SettingsSection.Sections.ElementAt(3).AccessRights.Rules.ElementAt(1).Action); + Assert.AreEqual(AccessRuleType.Grant, SettingsSection.Sections.ElementAt(3).AccessRights.Rules.ElementAt(1).Type); Assert.AreEqual("world", SettingsSection.Sections.ElementAt(3).AccessRights.Rules.ElementAt(2).Value); - Assert.AreEqual(AccessType.GrantBySection, SettingsSection.Sections.ElementAt(3).AccessRights.Rules.ElementAt(2).Action); + Assert.AreEqual(AccessRuleType.GrantBySection, SettingsSection.Sections.ElementAt(3).AccessRights.Rules.ElementAt(2).Type); } [Test] @@ -94,21 +94,17 @@ namespace Umbraco.Tests.Configurations.DashboardSettings public void Test_Tab_Access() { Assert.AreEqual(1, SettingsSection.Sections.ElementAt(2).Tabs.ElementAt(1).AccessRights.Rules.Count()); - Assert.AreEqual(AccessType.Grant, SettingsSection.Sections.ElementAt(2).Tabs.ElementAt(1).AccessRights.Rules.ElementAt(0).Action); + Assert.AreEqual(AccessRuleType.Grant, SettingsSection.Sections.ElementAt(2).Tabs.ElementAt(1).AccessRights.Rules.ElementAt(0).Type); Assert.AreEqual("admin", SettingsSection.Sections.ElementAt(2).Tabs.ElementAt(1).AccessRights.Rules.ElementAt(0).Value); } [Test] public void Test_Control() { - Assert.AreEqual(true, SettingsSection.Sections.ElementAt(0).Tabs.ElementAt(0).Controls.ElementAt(0).ShowOnce); - Assert.AreEqual(true, SettingsSection.Sections.ElementAt(0).Tabs.ElementAt(0).Controls.ElementAt(0).AddPanel); Assert.AreEqual("hello", SettingsSection.Sections.ElementAt(0).Tabs.ElementAt(0).Controls.ElementAt(0).PanelCaption); Assert.AreEqual("views/dashboard/settings/settingsdashboardintro.html", SettingsSection.Sections.ElementAt(0).Tabs.ElementAt(0).Controls.ElementAt(0).ControlPath); - Assert.AreEqual(false, SettingsSection.Sections.ElementAt(0).Tabs.ElementAt(0).Controls.ElementAt(1).ShowOnce); - Assert.AreEqual(false, SettingsSection.Sections.ElementAt(0).Tabs.ElementAt(0).Controls.ElementAt(1).AddPanel); Assert.AreEqual("", SettingsSection.Sections.ElementAt(0).Tabs.ElementAt(0).Controls.ElementAt(1).PanelCaption); Assert.AreEqual("views/dashboard/settings/settingsdashboardvideos.html", SettingsSection.Sections.ElementAt(0).Tabs.ElementAt(0).Controls.ElementAt(1).ControlPath); @@ -118,9 +114,9 @@ namespace Umbraco.Tests.Configurations.DashboardSettings public void Test_Control_Access() { Assert.AreEqual(2, SettingsSection.Sections.ElementAt(3).Tabs.ElementAt(0).Controls.ElementAt(1).AccessRights.Rules.Count()); - Assert.AreEqual(AccessType.Deny, SettingsSection.Sections.ElementAt(3).Tabs.ElementAt(0).Controls.ElementAt(1).AccessRights.Rules.ElementAt(0).Action); + Assert.AreEqual(AccessRuleType.Deny, SettingsSection.Sections.ElementAt(3).Tabs.ElementAt(0).Controls.ElementAt(1).AccessRights.Rules.ElementAt(0).Type); Assert.AreEqual("editor", SettingsSection.Sections.ElementAt(3).Tabs.ElementAt(0).Controls.ElementAt(1).AccessRights.Rules.ElementAt(0).Value); - Assert.AreEqual(AccessType.Deny, SettingsSection.Sections.ElementAt(3).Tabs.ElementAt(0).Controls.ElementAt(1).AccessRights.Rules.ElementAt(1).Action); + Assert.AreEqual(AccessRuleType.Deny, SettingsSection.Sections.ElementAt(3).Tabs.ElementAt(0).Controls.ElementAt(1).AccessRights.Rules.ElementAt(1).Type); Assert.AreEqual("writer", SettingsSection.Sections.ElementAt(3).Tabs.ElementAt(0).Controls.ElementAt(1).AccessRights.Rules.ElementAt(1).Value); } } diff --git a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs index 5145b848ed..19aaf79581 100644 --- a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs +++ b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs @@ -8,6 +8,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Umbraco.Core.Cache; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration.Dashboard; using Umbraco.Core.Logging; using Umbraco.Core.Manifest; using Umbraco.Core.PropertyEditors; @@ -383,5 +384,53 @@ javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2 Assert.AreEqual("icon-bar", app1.Icon); Assert.AreEqual("/App_Plugins/MyPackage/ContentApps/MyApp2.html", app1.View); } + + [Test] + public void CanParseManifest_Dashboards() + { + const string json = @"{'dashboards': [ + { + 'name': 'First One', + 'alias': 'something', + 'view': '~/App_Plugins/MyPackage/Dashboards/one.html', + 'sections': [ 'content' ], + 'access': [ {'grant':'user'}, {'deny':'foo'} ] + + }, + { + 'name': 'Second-One', + 'alias': 'something.else', + 'weight': -1, + 'view': '~/App_Plugins/MyPackage/Dashboards/two.html', + 'sections': [ 'forms' ], + } +]}"; + + var manifest = _parser.ParseManifest(json); + Assert.AreEqual(2, manifest.Dashboards.Length); + + Assert.IsInstanceOf(manifest.Dashboards[0]); + var db0 = manifest.Dashboards[0]; + Assert.AreEqual("something", db0.Alias); + Assert.AreEqual("First One", db0.Name); + Assert.AreEqual(100, db0.Weight); + Assert.AreEqual("/App_Plugins/MyPackage/Dashboards/one.html", db0.View); + Assert.AreEqual(1, db0.Sections.Length); + Assert.AreEqual("content", db0.Sections[0]); + Assert.AreEqual(2, db0.AccessRules.Length); + Assert.AreEqual(AccessRuleType.Grant, db0.AccessRules[0].Type); + Assert.AreEqual("user", db0.AccessRules[0].Value); + Assert.AreEqual(AccessRuleType.Deny, db0.AccessRules[1].Type); + Assert.AreEqual("foo", db0.AccessRules[1].Value); + + Assert.IsInstanceOf(manifest.Dashboards[1]); + var db1 = manifest.Dashboards[1]; + Assert.AreEqual("something.else", db1.Alias); + Assert.AreEqual("Second-One", db1.Name); + Assert.AreEqual(-1, db1.Weight); + Assert.AreEqual("/App_Plugins/MyPackage/Dashboards/two.html", db1.View); + Assert.AreEqual(1, db1.Sections.Length); + Assert.AreEqual("forms", db1.Sections[0]); + } } } diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js index 580acae6ca..0af327d148 100644 --- a/src/Umbraco.Web.UI.Client/gulpfile.js +++ b/src/Umbraco.Web.UI.Client/gulpfile.js @@ -282,48 +282,29 @@ gulp.task('dependencies', function () { ], "base": "./node_modules/jquery/dist" }, - { - "name": "jquery-migrate", - "src": ["./node_modules/jquery-migrate/dist/jquery-migrate.min.js"], - "base": "./node_modules/jquery-migrate/dist" - }, { "name": "jquery-ui", "src": ["./node_modules/jquery-ui-dist/jquery-ui.min.js"], "base": "./node_modules/jquery-ui-dist" }, { - "name": "jquery-validate", - "src": ["./node_modules/jquery-validation/dist/jquery.validate.min.js"], - "base": "./node_modules/jquery-validation/dist" - }, - { - "name": "jquery-validation-unobtrusive", - "src": ["./node_modules/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js"], - "base": "./node_modules/jquery-validation-unobtrusive/dist" + "name": "jquery-ui-touch-punch", + "src": ["./node_modules/jquery-ui-touch-punch/jquery.ui.touch-punch.min.js"], + "base": "./node_modules/jquery-ui-touch-punch" }, { "name": "lazyload-js", "src": ["./node_modules/lazyload-js/lazyload.min.js"], "base": "./node_modules/lazyload-js" }, - // TODO: We can optimize here: - // we don't have to ship with the moment-with-locales libraries - // we lazyload the user locale { "name": "moment", - "src": [ - "./node_modules/moment/min/moment.min.js", - "./node_modules/moment/min/moment-with-locales.js", - "./node_modules/moment/min/moment-with-locales.min.js" - ], + "src": ["./node_modules/moment/min/moment.min.js"], "base": "./node_modules/moment/min" }, { "name": "moment", - "src": [ - "./node_modules/moment/locale/*.js" - ], + "src": ["./node_modules/moment/locale/*.js"], "base": "./node_modules/moment/locale" }, { @@ -344,6 +325,14 @@ gulp.task('dependencies', function () { "src": ["./node_modules/signalr/jquery.signalR.js"], "base": "./node_modules/signalr" }, + { + "name": "spectrum", + "src": [ + "./node_modules/spectrum-colorpicker/spectrum.js", + "./node_modules/spectrum-colorpicker/spectrum.css" + ], + "base": "./node_modules/spectrum-colorpicker" + }, { "name": "tinymce", "src": [ diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/js/bootstrap.2.3.2.js b/src/Umbraco.Web.UI.Client/lib/bootstrap/js/bootstrap.2.3.2.js deleted file mode 100644 index 31701ad5df..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/js/bootstrap.2.3.2.js +++ /dev/null @@ -1,2284 +0,0 @@ -/* =================================================== - * bootstrap-transition.js v2.3.2 - * http://getbootstrap.com/2.3.2/javascript.html#transitions - * =================================================== - * Copyright 2013 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ========================================================== */ - - -!function ($) { - - "use strict"; // jshint ;_; - - - /* CSS TRANSITION SUPPORT (http://www.modernizr.com/) - * ======================================================= */ - - $(function () { - - $.support.transition = (function () { - - var transitionEnd = (function () { - - var el = document.createElement('bootstrap') - , transEndEventNames = { - 'WebkitTransition' : 'webkitTransitionEnd' - , 'MozTransition' : 'transitionend' - , 'OTransition' : 'oTransitionEnd otransitionend' - , 'transition' : 'transitionend' - } - , name - - for (name in transEndEventNames){ - if (el.style[name] !== undefined) { - return transEndEventNames[name] - } - } - - }()) - - return transitionEnd && { - end: transitionEnd - } - - })() - - }) - -}(window.jQuery);/* ========================================================== - * bootstrap-alert.js v2.3.2 - * http://getbootstrap.com/2.3.2/javascript.html#alerts - * ========================================================== - * Copyright 2013 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ========================================================== */ - - -!function ($) { - - "use strict"; // jshint ;_; - - - /* ALERT CLASS DEFINITION - * ====================== */ - - var dismiss = '[data-dismiss="alert"]' - , Alert = function (el) { - $(el).on('click', dismiss, this.close) - } - - Alert.prototype.close = function (e) { - var $this = $(this) - , selector = $this.attr('data-target') - , $parent - - if (!selector) { - selector = $this.attr('href') - selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 - } - - $parent = $(selector) - - e && e.preventDefault() - - $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent()) - - $parent.trigger(e = $.Event('close')) - - if (e.isDefaultPrevented()) return - - $parent.removeClass('in') - - function removeElement() { - $parent - .trigger('closed') - .remove() - } - - $.support.transition && $parent.hasClass('fade') ? - $parent.on($.support.transition.end, removeElement) : - removeElement() - } - - - /* ALERT PLUGIN DEFINITION - * ======================= */ - - var old = $.fn.alert - - $.fn.alert = function (option) { - return this.each(function () { - var $this = $(this) - , data = $this.data('alert') - if (!data) $this.data('alert', (data = new Alert(this))) - if (typeof option == 'string') data[option].call($this) - }) - } - - $.fn.alert.Constructor = Alert - - - /* ALERT NO CONFLICT - * ================= */ - - $.fn.alert.noConflict = function () { - $.fn.alert = old - return this - } - - - /* ALERT DATA-API - * ============== */ - - $(document).on('click.alert.data-api', dismiss, Alert.prototype.close) - -}(window.jQuery);/* ============================================================ - * bootstrap-button.js v2.3.2 - * http://getbootstrap.com/2.3.2/javascript.html#buttons - * ============================================================ - * Copyright 2013 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ============================================================ */ - - -!function ($) { - - "use strict"; // jshint ;_; - - - /* BUTTON PUBLIC CLASS DEFINITION - * ============================== */ - - var Button = function (element, options) { - this.$element = $(element) - this.options = $.extend({}, $.fn.button.defaults, options) - } - - Button.prototype.setState = function (state) { - var d = 'disabled' - , $el = this.$element - , data = $el.data() - , val = $el.is('input') ? 'val' : 'html' - - state = state + 'Text' - data.resetText || $el.data('resetText', $el[val]()) - - $el[val](data[state] || this.options[state]) - - // push to event loop to allow forms to submit - setTimeout(function () { - state == 'loadingText' ? - $el.addClass(d).attr(d, d) : - $el.removeClass(d).removeAttr(d) - }, 0) - } - - Button.prototype.toggle = function () { - var $parent = this.$element.closest('[data-toggle="buttons-radio"]') - - $parent && $parent - .find('.active') - .removeClass('active') - - this.$element.toggleClass('active') - } - - - /* BUTTON PLUGIN DEFINITION - * ======================== */ - - var old = $.fn.button - - $.fn.button = function (option) { - return this.each(function () { - var $this = $(this) - , data = $this.data('button') - , options = typeof option == 'object' && option - if (!data) $this.data('button', (data = new Button(this, options))) - if (option == 'toggle') data.toggle() - else if (option) data.setState(option) - }) - } - - $.fn.button.defaults = { - loadingText: 'loading...' - } - - $.fn.button.Constructor = Button - - - /* BUTTON NO CONFLICT - * ================== */ - - $.fn.button.noConflict = function () { - $.fn.button = old - return this - } - - - /* BUTTON DATA-API - * =============== */ - - $(document).on('click.button.data-api', '[data-toggle^=button]', function (e) { - var $btn = $(e.target) - if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') - $btn.button('toggle') - }) - -}(window.jQuery);/* ========================================================== - * bootstrap-carousel.js v2.3.2 - * http://getbootstrap.com/2.3.2/javascript.html#carousel - * ========================================================== - * Copyright 2013 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ========================================================== */ - - -!function ($) { - - "use strict"; // jshint ;_; - - - /* CAROUSEL CLASS DEFINITION - * ========================= */ - - var Carousel = function (element, options) { - this.$element = $(element) - this.$indicators = this.$element.find('.carousel-indicators') - this.options = options - this.options.pause == 'hover' && this.$element - .on('mouseenter', $.proxy(this.pause, this)) - .on('mouseleave', $.proxy(this.cycle, this)) - } - - Carousel.prototype = { - - cycle: function (e) { - if (!e) this.paused = false - if (this.interval) clearInterval(this.interval); - this.options.interval - && !this.paused - && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) - return this - } - - , getActiveIndex: function () { - this.$active = this.$element.find('.item.active') - this.$items = this.$active.parent().children() - return this.$items.index(this.$active) - } - - , to: function (pos) { - var activeIndex = this.getActiveIndex() - , that = this - - if (pos > (this.$items.length - 1) || pos < 0) return - - if (this.sliding) { - return this.$element.one('slid', function () { - that.to(pos) - }) - } - - if (activeIndex == pos) { - return this.pause().cycle() - } - - return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos])) - } - - , pause: function (e) { - if (!e) this.paused = true - if (this.$element.find('.next, .prev').length && $.support.transition.end) { - this.$element.trigger($.support.transition.end) - this.cycle(true) - } - clearInterval(this.interval) - this.interval = null - return this - } - - , next: function () { - if (this.sliding) return - return this.slide('next') - } - - , prev: function () { - if (this.sliding) return - return this.slide('prev') - } - - , slide: function (type, next) { - var $active = this.$element.find('.item.active') - , $next = next || $active[type]() - , isCycling = this.interval - , direction = type == 'next' ? 'left' : 'right' - , fallback = type == 'next' ? 'first' : 'last' - , that = this - , e - - this.sliding = true - - isCycling && this.pause() - - $next = $next.length ? $next : this.$element.find('.item')[fallback]() - - e = $.Event('slide', { - relatedTarget: $next[0] - , direction: direction - }) - - if ($next.hasClass('active')) return - - if (this.$indicators.length) { - this.$indicators.find('.active').removeClass('active') - this.$element.one('slid', function () { - var $nextIndicator = $(that.$indicators.children()[that.getActiveIndex()]) - $nextIndicator && $nextIndicator.addClass('active') - }) - } - - if ($.support.transition && this.$element.hasClass('slide')) { - this.$element.trigger(e) - if (e.isDefaultPrevented()) return - $next.addClass(type) - $next[0].offsetWidth // force reflow - $active.addClass(direction) - $next.addClass(direction) - this.$element.one($.support.transition.end, function () { - $next.removeClass([type, direction].join(' ')).addClass('active') - $active.removeClass(['active', direction].join(' ')) - that.sliding = false - setTimeout(function () { that.$element.trigger('slid') }, 0) - }) - } else { - this.$element.trigger(e) - if (e.isDefaultPrevented()) return - $active.removeClass('active') - $next.addClass('active') - this.sliding = false - this.$element.trigger('slid') - } - - isCycling && this.cycle() - - return this - } - - } - - - /* CAROUSEL PLUGIN DEFINITION - * ========================== */ - - var old = $.fn.carousel - - $.fn.carousel = function (option) { - return this.each(function () { - var $this = $(this) - , data = $this.data('carousel') - , options = $.extend({}, $.fn.carousel.defaults, typeof option == 'object' && option) - , action = typeof option == 'string' ? option : options.slide - if (!data) $this.data('carousel', (data = new Carousel(this, options))) - if (typeof option == 'number') data.to(option) - else if (action) data[action]() - else if (options.interval) data.pause().cycle() - }) - } - - $.fn.carousel.defaults = { - interval: 5000 - , pause: 'hover' - } - - $.fn.carousel.Constructor = Carousel - - - /* CAROUSEL NO CONFLICT - * ==================== */ - - $.fn.carousel.noConflict = function () { - $.fn.carousel = old - return this - } - - /* CAROUSEL DATA-API - * ================= */ - - $(document).on('click.carousel.data-api', '[data-slide], [data-slide-to]', function (e) { - var $this = $(this), href - , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 - , options = $.extend({}, $target.data(), $this.data()) - , slideIndex - - $target.carousel(options) - - if (slideIndex = $this.attr('data-slide-to')) { - $target.data('carousel').pause().to(slideIndex).cycle() - } - - e.preventDefault() - }) - -}(window.jQuery);/* ============================================================= - * bootstrap-collapse.js v2.3.2 - * http://getbootstrap.com/2.3.2/javascript.html#collapse - * ============================================================= - * Copyright 2013 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ============================================================ */ - - -!function ($) { - - "use strict"; // jshint ;_; - - - /* COLLAPSE PUBLIC CLASS DEFINITION - * ================================ */ - - var Collapse = function (element, options) { - this.$element = $(element) - this.options = $.extend({}, $.fn.collapse.defaults, options) - - if (this.options.parent) { - this.$parent = $(this.options.parent) - } - - this.options.toggle && this.toggle() - } - - Collapse.prototype = { - - constructor: Collapse - - , dimension: function () { - var hasWidth = this.$element.hasClass('width') - return hasWidth ? 'width' : 'height' - } - - , show: function () { - var dimension - , scroll - , actives - , hasData - - if (this.transitioning || this.$element.hasClass('in')) return - - dimension = this.dimension() - scroll = $.camelCase(['scroll', dimension].join('-')) - actives = this.$parent && this.$parent.find('> .accordion-group > .in') - - if (actives && actives.length) { - hasData = actives.data('collapse') - if (hasData && hasData.transitioning) return - actives.collapse('hide') - hasData || actives.data('collapse', null) - } - - this.$element[dimension](0) - this.transition('addClass', $.Event('show'), 'shown') - $.support.transition && this.$element[dimension](this.$element[0][scroll]) - } - - , hide: function () { - var dimension - if (this.transitioning || !this.$element.hasClass('in')) return - dimension = this.dimension() - this.reset(this.$element[dimension]()) - this.transition('removeClass', $.Event('hide'), 'hidden') - this.$element[dimension](0) - } - - , reset: function (size) { - var dimension = this.dimension() - - this.$element - .removeClass('collapse') - [dimension](size || 'auto') - [0].offsetWidth - - this.$element[size !== null ? 'addClass' : 'removeClass']('collapse') - - return this - } - - , transition: function (method, startEvent, completeEvent) { - var that = this - , complete = function () { - if (startEvent.type == 'show') that.reset() - that.transitioning = 0 - that.$element.trigger(completeEvent) - } - - this.$element.trigger(startEvent) - - if (startEvent.isDefaultPrevented()) return - - this.transitioning = 1 - - this.$element[method]('in') - - $.support.transition && this.$element.hasClass('collapse') ? - this.$element.one($.support.transition.end, complete) : - complete() - } - - , toggle: function () { - this[this.$element.hasClass('in') ? 'hide' : 'show']() - } - - } - - - /* COLLAPSE PLUGIN DEFINITION - * ========================== */ - - var old = $.fn.collapse - - $.fn.collapse = function (option) { - return this.each(function () { - var $this = $(this) - , data = $this.data('collapse') - , options = $.extend({}, $.fn.collapse.defaults, $this.data(), typeof option == 'object' && option) - if (!data) $this.data('collapse', (data = new Collapse(this, options))) - if (typeof option == 'string') data[option]() - }) - } - - $.fn.collapse.defaults = { - toggle: true - } - - $.fn.collapse.Constructor = Collapse - - - /* COLLAPSE NO CONFLICT - * ==================== */ - - $.fn.collapse.noConflict = function () { - $.fn.collapse = old - return this - } - - - /* COLLAPSE DATA-API - * ================= */ - - $(document).on('click.collapse.data-api', '[data-toggle=collapse]', function (e) { - var $this = $(this), href - , target = $this.attr('data-target') - || e.preventDefault() - || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7 - , option = $(target).data('collapse') ? 'toggle' : $this.data() - $this[$(target).hasClass('in') ? 'addClass' : 'removeClass']('collapsed') - $(target).collapse(option) - }) - -}(window.jQuery);/* ============================================================ - * bootstrap-dropdown.js v2.3.2 - * http://getbootstrap.com/2.3.2/javascript.html#dropdowns - * ============================================================ - * Copyright 2013 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ============================================================ */ - - -!function ($) { - - "use strict"; // jshint ;_; - - - /* DROPDOWN CLASS DEFINITION - * ========================= */ - - var toggle = '[data-toggle=dropdown]' - , Dropdown = function (element) { - var $el = $(element).on('click.dropdown.data-api', this.toggle) - $('html').on('click.dropdown.data-api', function () { - $el.parent().removeClass('open') - }) - } - - Dropdown.prototype = { - - constructor: Dropdown - - , toggle: function (e) { - var $this = $(this) - , $parent - , isActive - - if ($this.is('.disabled, :disabled')) return - - $parent = getParent($this) - - isActive = $parent.hasClass('open') - - clearMenus() - - if (!isActive) { - if ('ontouchstart' in document.documentElement) { - // if mobile we we use a backdrop because click events don't delegate - $(' diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/valuetype.html b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/valuetype.html index 59b7c55c19..4146ba0763 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/valuetype.html +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/valuetype.html @@ -1,7 +1,7 @@ - - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.html index b287b0b58e..29760e3f5b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.html @@ -6,7 +6,8 @@ + ng-change="changed(item)" + ng-required="model.validation.mandatory && !model.value.length" /> {{item.val}} diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.html index c86e7f44f7..ef45e99638 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.html @@ -5,10 +5,11 @@ You haven't defined any colors - + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js index f8e02a240a..3b341f7ac0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js @@ -77,8 +77,16 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.DropdownFlexibleCo // if we run in single mode we'll store the value in a local variable // so we can pass an array as the model as our PropertyValueEditor expects that $scope.model.singleDropdownValue = ""; - if (!Object.toBoolean($scope.model.config.multiple)) { + if (!Object.toBoolean($scope.model.config.multiple) && $scope.model.value) { $scope.model.singleDropdownValue = Array.isArray($scope.model.value) ? $scope.model.value[0] : $scope.model.value; } + // if we run in multiple mode, make sure the model is an array (in case the property was previously saved in single mode) + // also explicitly set the model to null if it's an empty array, so mandatory validation works on the client + if ($scope.model.config.multiple === "1" && $scope.model.value) { + $scope.model.value = !Array.isArray($scope.model.value) ? [$scope.model.value] : $scope.model.value; + if ($scope.model.value.length === 0) { + $scope.model.value = null; + } + } }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.html index 3239e64acc..5f873e9e43 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.html @@ -5,7 +5,8 @@ ng-switch-default ng-change="updateSingleDropdownValue()" ng-model="model.singleDropdownValue" - ng-options="item.value as item.value for item in model.config.items"> + ng-options="item.value as item.value for item in model.config.items" + ng-required="model.validation.mandatory"> @@ -15,5 +16,6 @@ ng-switch-when="true" multiple ng-model="model.value" - ng-options="item.value as item.value for item in model.config.items"> + ng-options="item.value as item.value for item in model.config.items" + ng-required="model.validation.mandatory"> diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.prevalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.prevalues.controller.js index 5a0b43d105..de56442239 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.prevalues.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.prevalues.controller.js @@ -69,4 +69,8 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.CropSizesControlle //there was an error, do the highlight (will be set back by the directive) $scope.hasError = true; }; + + $scope.sortableOptions = { + axis: 'y' + } }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.prevalues.html index 86804fddb0..d11e176c2b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.prevalues.html @@ -58,7 +58,7 @@ -
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.html index 0117bac92d..1ff6666907 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.html @@ -37,7 +37,7 @@
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/radiobuttons/radiobuttons.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/radiobuttons/radiobuttons.html index a5fc2bfb39..7b9805d664 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/radiobuttons/radiobuttons.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/radiobuttons/radiobuttons.html @@ -1,17 +1,12 @@ 
  • -
-
+
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js index af5f4db1e6..8ac9dc78e8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js @@ -191,7 +191,7 @@ $timeout(function() { var nameField = angular.element(document.querySelector('[data-element="editor-name-field"]')); if (nameField) { - nameField.bind('blur', function(event) { + nameField.on('blur', function(event) { if (event.target.value) { vm.save(true); } diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js index d78095a90f..2b531b7ef2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js @@ -531,26 +531,24 @@ // copy to clip board success function copySuccess() { - - if (vm.page.copyPasswordButtonState != "success") { - - vm.page.copyPasswordButtonState = "success"; - + if (vm.page.copyPasswordButtonState !== "success") { + $timeout(function(){ + vm.page.copyPasswordButtonState = "success"; + }); $timeout(function () { - resetClipboardButtonState() + resetClipboardButtonState(); }, 1000); } - } // copy to clip board error function copyError() { - if (vm.page.copyPasswordButtonState != "error") { - - vm.page.copyPasswordButtonState = "error"; - + if (vm.page.copyPasswordButtonState !== "error") { + $timeout(function() { + vm.page.copyPasswordButtonState = "error"; + }); $timeout(function () { - resetClipboardButtonState() + resetClipboardButtonState(); }, 1000); } } diff --git a/src/Umbraco.Web.UI.Client/test/e2e/app/admin/users/users-edit.scenario.js b/src/Umbraco.Web.UI.Client/test/e2e/app/admin/users/users-edit.scenario.js index b078ad0d9b..ea17450a08 100644 --- a/src/Umbraco.Web.UI.Client/test/e2e/app/admin/users/users-edit.scenario.js +++ b/src/Umbraco.Web.UI.Client/test/e2e/app/admin/users/users-edit.scenario.js @@ -4,7 +4,7 @@ describe('admin edit user', function() { browser().navigateTo('/admin/users/new'); input('user.email').enter('admin@abc.com'); input('user.password').enter('changeme'); - element('button.login').click(); + element('button.login').trigger('click'); }); it('enables the save button when the user info is filled in correctly', function() { diff --git a/src/Umbraco.Web.UI/.eslintignore b/src/Umbraco.Web.UI/.eslintignore new file mode 100644 index 0000000000..6cac59fac0 --- /dev/null +++ b/src/Umbraco.Web.UI/.eslintignore @@ -0,0 +1,3 @@ + +/Umbraco/** +/Umbraco_Client/** diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index c108cffcf4..48a8b9208d 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -151,13 +151,6 @@ create.aspx - - UserControlProxy.aspx - ASPXCodeBehind - - - UserControlProxy.aspx - editMacro.aspx ASPXCodeBehind @@ -236,9 +229,9 @@ - + 404handlers.config @@ -460,7 +453,10 @@ $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v11.0\Web\Microsoft.Web.Publishing.Tasks.dll $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v12.0\Web\Microsoft.Web.Publishing.Tasks.dll $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v14.0\Web\Microsoft.Web.Publishing.Tasks.dll - $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v15.0\Web\Microsoft.Web.Publishing.Tasks.dll + $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v15.0\Web\Microsoft.Web.Publishing.Tasks.dll + $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v16.0\Web\Microsoft.Web.Publishing.Tasks.dll + + $(ProgramFiles32)\Microsoft Visual Studio\2019\Preview\MSBuild\Microsoft\VisualStudio\v16.0\Web\Microsoft.Web.Publishing.Tasks.dll diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Default.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Default.cshtml index 0d0c39aba7..4659674c59 100644 --- a/src/Umbraco.Web.UI/Umbraco/Views/Default.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Views/Default.cshtml @@ -67,7 +67,7 @@
-
+
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 7c25782442..b2e02b1f9c 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -1863,9 +1863,6 @@ To manage your website, simply open the Umbraco back office and start adding con File does not exist: '%0%'. '%0%' in config file '%1%'.]]> There was an error, check log for full error: %0%. - Members - Total XML: %0%, Total: %1%, Total invalid: %2% - Media - Total XML: %0%, Total: %1%, Total invalid: %2% - Content - Total XML: %0%, Total published: %1%, Total invalid: %2% Database - The database schema is correct for this version of Umbraco %0% problems were detected with your database schema (Check the log for details) Some errors were detected while validating the database schema against the current version of Umbraco. @@ -1934,7 +1931,7 @@ To manage your website, simply open the Umbraco back office and start adding con %0%.]]> %0%.]]>

Results of the scheduled Umbraco Health Checks run on %0% at %1% are as follows:

%2%]]>
- Umbraco Health Check Status + Umbraco Health Check Status: %0% Disable URL tracker diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index 5b39f3d25f..e4050d3a7b 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -340,6 +340,7 @@ Publishing will make the selected items visible on the site. Unpublishing will remove the selected items and all their descendants from the site. Unpublishing will remove this page and all its descendants from the site. + You have unsaved changes. Making changes to the Document Type will discard the changes. Done @@ -1913,9 +1914,6 @@ To manage your website, simply open the Umbraco back office and start adding con File does not exist: '%0%'. '%0%' in config file '%1%'.]]> There was an error, check log for full error: %0%. - Members - Total XML: %0%, Total: %1%, Total invalid: %2% - Media - Total XML: %0%, Total: %1%, Total invalid: %2% - Content - Total XML: %0%, Total published: %1%, Total invalid: %2% Database - The database schema is correct for this version of Umbraco %0% problems were detected with your database schema (Check the log for details) Some errors were detected while validating the database schema against the current version of Umbraco. @@ -1984,7 +1982,7 @@ To manage your website, simply open the Umbraco back office and start adding con %0%.]]> %0%.]]>

Results of the scheduled Umbraco Health Checks run on %0% at %1% are as follows:

%2%]]>
- Umbraco Health Check Status + Umbraco Health Check Status: %0% Disable URL tracker diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml index 01e88d22cb..db94121405 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml @@ -5,8 +5,8 @@ https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - Administrar hostnames - Auditoría + Administrar dominios + Historial Nodo de Exploración Cambiar tipo de documento Copiar @@ -20,26 +20,26 @@ Exportar Documento (tipo) Importar Documento (tipo) Importar Paquete - Editar en lienzo - Salir + Editar en vivo + Cerrar sesión Mover Notificaciones Acceso Público Publicar - Unpublish + Retirar publicación Recargar Nodos Republicar sitio completo Renombrar Restaurar Establecer permisos para la página %0% - Elije dónde mover + Elige dónde mover En el árbol de contenido Permisos Deshacer Enviar a Publicar Enviar a Traducir Establecer grupo - Ordernar + Ordenar Traducir Actualizar Establecer permisos @@ -54,7 +54,7 @@ Permitir acceso para asignar cultura y dominios - Permitir acceso para ver informes de nodos + Permitir acceso para ver el historial de un nodo Permitir acceso para ver un nodo Permitir acceso para cambiar el tipo de documento de un nodo Permitir acceso para copiar un nodo @@ -79,7 +79,7 @@ Nodo no válido. Formato de dominio no válido. Este dominio ya ha sido asignado. - Language + Idioma Dominio El nuevo dominio %0% ha sido creado El dominio %0% ha sido borrado @@ -89,9 +89,12 @@
Los dominios de un nivel están soportados, por ej. "example.com/en". De todas formas deberían de evitarse. Mejor usar la configuración cultural especificada arriba.]]>
Heredar - Cultura - o hereda la cultura de los nodos padres. También se aplicará
- para el nodo actual, a menos que un dominio por debajo lo aplique también.]]>
+ Idioma + + o hereda el idioma de los nodos padres. También se aplicará
+ para el nodo actual, a menos que un dominio por debajo lo aplique también.]]> +
+ Dominios @@ -112,7 +115,7 @@ Alinear a la Izquierda Alinear a la Derecha Insertar Link - Insertar link local (anchor) + Insertar link local (ancla) Lista en Viñetas Lista Numérica Insertar macro @@ -134,7 +137,7 @@ Para cambiar el tipo de documento al contenido seleccionado, primero selecciona uno de la lista de tipos válidos. - Entonces confirma el mapeo de propiedades del tipo actual al nuevo y haz click en Guardar. + Entonces confirma el mapeo de propiedades del tipo actual al nuevo y haz clic en Guardar. El contenido se ha vuelto a publicar. Propiedad actual Tipo actual @@ -157,8 +160,8 @@ Acerca de Link alternativo (como describe la imagen sobre el teléfono) - Vinculos Alternativos - Click para editar esta entrada + Vínculos Alternativos + Clic para editar esta entrada Creado por Autor original Actualizado por @@ -183,16 +186,16 @@ Título de la página Propiedades Este documento ha sido publicado pero no es visible porque el padre '%0%' no esta publicado - Upss: este documento está publicado pero no está en la caché (error interno) + Ups: este documento está publicado pero no está en la caché (error interno) No se pudo obtener la url - Este documento está publicado pero su url colisionará con contenido %0% + Este documento está publicado pero tu url colisionará con contenido %0% Publicar Estado de la Publicación Publicar el - Despublicar el + Retirar publicación el Fecha de Eliminación El Orden esta actualizado - Para organizar los nodos, simplemente arrastre los nodos o realice un clic en uno de los encabezados de columna. Puede seleccionar multiple nodos manteniendo presionados "Shift" o "Control" mientras selecciona + Para organizar los nodos, simplemente arrastra los nodos o realice un clic en uno de los encabezados de columna. Puedes seleccionar múltiple nodos manteniendo presionados "Shift" o "Control" mientras seleccionas Estadísticas Título (opcional) Texto alternativo (opcional) @@ -203,16 +206,16 @@ Eliminar archivo Vínculo al documento Miembro de grupo(s) - No es miembreo de grupo(s) + No es miembro de grupo(s) Nodos hijo - Target + Destino Esto se traduce en la siguiente hora en el servidor: ¿Esto qué significa?]]> ¿Estás seguro que quieres eliminar este elemento? Propiedad %0% utiliza editor %1% que no está soportado por Nested Content. Añadir otra caja de texto Eliminar caja de texto - Raiz de contenido + Raíz de contenido Crear nueva Plantilla de Contenido desde '%0%' @@ -224,7 +227,7 @@ Una Plantilla de Contenido es contenido predefinido que un editor puede usar como base para crear nuevo contenido - Haz click para subir archivos + Haz clic para subir archivos Crear nuevo miembro @@ -234,7 +237,7 @@ ¿Dónde quieres crear el nuevo %0% Crear debajo de Selecciona el Tipo de Documento para el que quieres crear una plantilla de contenido - Elije un tipo y un título + Elige un tipo y un título "Tipos de documentos".]]> "Tipos de medios".]]> Tipo de Documento sin plantilla @@ -291,16 +294,16 @@ Nombre Administrar dominios Cerrar esta ventana - Esta usted seguro que desea borrar - Esta usted seguro que desea deshabilitar - Por favor seleccione esta casilla para confirmar la eliminación de %0% entrada(s) - Esta usted seguro? - Esta usted Seguro? + Estás seguro que quieres borrar + Estás seguro que quieres deshabilitar + Por favor selecciona esta casilla para confirmar la eliminación de %0% entrada(s) + ¿Estás seguro? + ¿Estás seguro? Cortar Editar entrada del Diccionario Editar idioma Agregar enlace interno - Insertar caracter + Insertar carácter Insertar titular gráfico Insertar imagen Insertar enlace @@ -318,9 +321,9 @@ Establecer permisos para Establecer permisos para %0% para grupo %1% Selecciona el grupo de usuarios para el cual quieres establecer permisos - Se está vaciando la papelera. No cierre esta ventana mientras se ejecuta este proceso + Se está vaciando la papelera. No cierres esta ventana mientras se ejecuta este proceso La papelera está vacía - No podrá recuperar los items una vez sean borrados de la papelera + No podrás recuperar los elementos una vez sean borrados de la papelera regexlib.com está experimentando algunos problemas en estos momentos, de los cuales no somos responsables. Pedimos disculpas por las molestias.]]> Buscar una expresión regular para agregar validación a un campo de formulario. Ejemplo: 'correo electrónico', código postal "," url " Eliminar macro @@ -331,15 +334,15 @@ Número de columnas Número de filas Coloca un 'placeholder' id al colocar un ID en tu 'placeholder' puedes insertar contenido en esta plantilla desde una plantilla hija, referenciando este ID usando un elemento<asp:content />.]]> - Seleccione una tecla de la lista abajo indicada. Sólo puede elegir a partir de la ID de la plantilla actual del dominio. - Haga clic sobre la imagen para verla a tamaño completo. - Seleccionar item - Ver item en la caché + Selecciona una tecla de la lista abajo indicada. Sólo puedes elegir a partir de la ID de la plantilla actual del dominio. + Haz clic sobre la imagen para verla a tamaño completo. + Seleccionar elemento + Ver elemento en la caché Relacionar con original Incluir descendientes La amigable comunidad Enlazar a página - Abre el documento enlazado en una nueva ventana o Opens the linked document in a new window o pestaña + Abre el documento enlazado en una nueva ventana o pestaña Enlazar a medio Selecciona nodo de inicio de contenido Selecciona medio @@ -355,8 +358,8 @@ Selecciona secciones Selecciona usuarios No se encontraron iconos - No hay parametros para esta macro - No hay maros disponibles para insertar + No hay parámetros para esta macro + No hay macros disponibles para insertar Proveedores de login externo Detalles de la Excepción Stacktrace @@ -368,7 +371,7 @@ Selecciona snippet - Editar las diferentes versiones lingüísticas para la entrada en el diccionario '% 0%' debajo añadir otros idiomas en el menu de 'idiomas' en el menú de la izquierda + Editar las diferentes versiones lingüísticas para la entrada en el diccionario '% 0%' debajo añadir otros idiomas en el menú de 'idiomas' en el menú de la izquierda Renombrado - Introduce un nuevo nombre para la carpeta aqui + Introduce un nuevo nombre para la carpeta aquí %0% fue renombrada a %1% - añadir prevalor + añadir valor preestablecido Tipo de datos GUID - Tipo de datos GUIDprestar control + Renderizar control Botones Habilitar la configuración avanzada para Habilitar menú contextual @@ -437,31 +440,34 @@ %0% ya existe Se han encontrado los siguientes errores: Se han encontrado los siguientes errores: - La clave debe tener como mínimo %0% caracteres y %1% caracter(es) no alfanuméricos + La clave debe tener como mínimo %0% caracteres y %1% carácter(es) no alfanuméricos %0% debe ser un número entero - Debe llenar los campos del %0% al %1% - Debe llenar el campo %0% - Debe poner el formato correcto del %0% al %1% - Debe poner un formato correcto en %0% + Debes llenar los campos del %0% al %1% + Debes llenar el campo %0% + Debes poner el formato correcto del %0% al %1% + Debes poner un formato correcto en %0% Se recibió un error desde el servidor El tipo de archivo especificado ha sido deshabilitado por el administrador - NOTA: Aunque CodeMirror esté activado en los ajustes de configuracion, no se muestra en Internet Explorer debido a que no es lo suficientemente estable.' - Debe llenar el alias y el nombre en el propertytype + NOTA: Aunque CodeMirror esté activado en los ajustes de configuración, no se muestra en Internet Explorer debido a que no es lo suficientemente estable.' + Debes rellenar el alias y el nombre en el tipo de propiedad Hay un problema de lectura y escritura al acceder a un archivo o carpeta Error cargando Vista Parcial (archivo: %0%) Error cargando userControl '%0%' - Por favor, elija un tipo - Usted está a punto de hacer la foto más grande que el tamaño original. ¿Está seguro de que desea continuar? + Por favor, elige un tipo + Estás a punto de hacer la foto más grande que el tamaño original. ¿Estás seguro de que desea continuar? - Por favor, marque el contenido antes de cambiar de estilo - No active estilos disponibles - + Por favor, marca el contenido antes de cambiar de estilo + No actives estilos disponibles + + + + @@ -471,7 +477,7 @@ Acciones Añadir Alias - ¿Está seguro? + ¿Estás seguro? Borde o Cancelar @@ -492,7 +498,7 @@ Borrado Borrando... Diseño - Dictionario + Diccionario Dimensiones Abajo Descargar @@ -512,11 +518,11 @@ Margen interno Insertar Instalar - Invalido + Inválido Justificar Etiqueta Idioma - Ultimo + Último Diseño Cargando Bloqueado @@ -528,7 +534,7 @@ Mensaje Mover Nombre - New + Nuevo Próximo No de @@ -590,7 +596,7 @@ Guardando... actual Insertar - selecionado + seleccionado Azul @@ -607,9 +613,9 @@ Atajos mostrar atajos Activar/Desactivar vista de lista - Activar/Desactivar permitir como raiz - Act/Desact Comentar líneas - Elimiar línea + Activar/Desactivar permitir como raíz + Comentar/Descomentar líneas + Eliminar línea Copiar líneas arriba Copiar líneas abajo Mover líneas arriba @@ -629,18 +635,18 @@ El instalador no puede conectar con la base de datos. - No se ha podido guardar el archivo Web.config. Por favor, modifique la cadena de conexión manualmente. - Su base de datos ha sido encontrada y ha sido identificada como + No se ha podido guardar el archivo Web.config. Por favor, modifica la cadena de conexión manualmente. + Tu base de datos ha sido encontrada y ha sido identificada como Configuración de la base de datos - instalar para instalar %0% la base de datos de Umbraco]]> - Próximo para continuar]]> - ¡No se ha encontrado ninguna base de datos! Mira si la información en la "connection string" del “web.config” es correcta.

Para continuar, edite el "web.config" (bien sea usando Visual Studio o su editor de texto preferido), vaya al final del archivo y añada la cadena de conexión para la base de datos con el nombre (key) "umbracoDbDSN" y guarde el archivo.

Pinche en reintentar cuando haya terminado.
Pinche aquí para mayor información de como editar el web.config (en inglés)

]]>
+ instalar para instalar %0% la base de datos de Umbraco]]> + Próximo para continuar]]> + ¡No se ha encontrado ninguna base de datos! Mira si la información en la cadena de conexión del “web.config” es correcta.

Para continuar, edita el "web.config" (bien sea usando Visual Studio o tu editor de texto preferido), ve al final del archivo y añade la cadena de conexión para la base de datos con el nombre (key) "umbracoDbDSN" y guarda el archivo.

Pincha en reintentar cuando hayas terminado.
Pincha aquí para mayor información de como editar el web.config (en inglés)

]]>
Por favor, contacta con tu ISP si es necesario. Si estás realizando la instalación en una máquina o servidor local, quizás necesites información de tu administrador de sistemas.]]> - Pinche en actualizar para actualizar la base de datos a Umbraco %0%

Ningún contenido será borrado de la base de datos y seguirá funcionando después de la actualización

]]>
- Pinche en Próximo para continuar. ]]> - próximo para continuar con el asistente de configuración]]> + Pincha en actualizar para actualizar la base de datos a Umbraco %0%

Ningún contenido será borrado de la base de datos y seguirá funcionando después de la actualización

]]>
+ Pincha en Próximo para continuar. ]]> + próximo para continuar con el asistente de configuración]]> La contraseña del usuario por defecto debe ser cambiada]]> - El usuario por defecto ha sido desabilitado o ha perdido el acceso a Umbraco!

Pinche en Próximo para continuar.]]> + El usuario por defecto ha sido deshabilitado o ha perdido el acceso a Umbraco!

Pincha en Próximo para continuar.]]> ¡La contraseña del usuario por defecto ha sido cambiada desde que se instaló!

No hay que realizar ninguna tarea más. Pulsa Siguiente para proseguir.]]> ¡La contraseña se ha cambiado! Ten un buen comienzo, visita nuestros videos de introducción @@ -659,15 +665,15 @@ Resolviendo problemas con directorios Sigue este enlace para más información sobre problemas con ASP.NET y creación de directorios Configurando los permisos de directorios - Umbraco necesita permisos de lectura/escritura en algunos directorios para poder almacenar archivos tales como imagenes y PDFs. También almacena datos en la caché para mejorar el rendimiento de su sitio web + Umbraco necesita permisos de lectura/escritura en algunos directorios para poder almacenar archivos tales como imágenes y PDFs. También almacena datos en la caché para mejorar el rendimiento de tu sitio web Quiero empezar de cero - learn how). Todavía podrás elegir instalar Runway más adelante. Por favor ve a la sección del Desarrollador y elije Paquetes.]]> + aprende cómo). Todavía podrás elegir instalar Runway más adelante. Por favor ve a la sección del Desarrollador y elige Paquetes.]]> Acabas de configurar una nueva plataforma Umbraco. ¿Qué deseas hacer ahora? Se ha instalado Runway Esta es nuestra lista de módulos recomendados, selecciona los que desees instalar, o mira la lista completa de módulos ]]> Sólo recomendado para usuarios expertos Quiero empezar con un sitio web sencillo - "Runway" es un sitio web sencillo que contiene unos tipos de documentos y plantillas básicos. El instalador puede configurar Runway por ti de forma automática, pero fácilmente puedes editarlo, extenderlo o eliminarlo. No es necesario y puedes usar Umbrao perfectamente sin él. Sin embargo, Runway ofrece unos cimientos sencillos basados en buenas prácticas para iniciarte más rápido que nunca. Si eliges instalar Runway, puedes seleccionar bloques de construcción básicos llamados Módulos de Runway de forma opcional para realzar tus páginas de Runway. Incluido con Runway: Página de inicio, página de Cómo empezar, página de Instalación de módulos.
Módulos opcionales: Navegación superior, Mapa del sitio, Contacto, Galería.
]]>
+ "Runway" es un sitio web sencillo que contiene unos tipos de documentos y plantillas básicos. El instalador puede configurar Runway por ti de forma automática, pero fácilmente puedes editarlo, extenderlo o eliminarlo. No es necesario y puedes usar Umbraco perfectamente sin él. Sin embargo, Runway ofrece unos cimientos sencillos basados en buenas prácticas para iniciarte más rápido que nunca. Si eliges instalar Runway, puedes seleccionar bloques de construcción básicos llamados Módulos de Runway de forma opcional para realzar tus páginas de Runway. Incluido con Runway: Página de inicio, página de Cómo empezar, página de Instalación de módulos.
Módulos opcionales: Navegación superior, Mapa del sitio, Contacto, Galería.
]]>
¿Qué es Runway? Paso 1 de 5. Aceptar los términos de la licencia Paso 2 de 5. Configuración de la base de datos @@ -685,15 +691,15 @@ Umbraco versión 3 Umbraco versión 4 Mirar - Umbraco %0% o actualizar la versión 3.0 a Umbraco %0%.

Pinche en "próximo" para empezar con el asistente de configuración.]]>
+ Umbraco %0% o actualizar la versión 3.0 a Umbraco %0%.

Pincha en "próximo" para empezar con el asistente de configuración.]]>
Código de cultura Nombre de cultura - No ha habido ninguna actividad y su sessión se cerrará en - Renovar su sesión para guardar sus cambios + No ha habido ninguna actividad y tu sesión se cerrará en + Renovar tu sesión para guardar sus cambios Feliz super domingo @@ -708,10 +714,10 @@ © 2001 - %0%
umbraco.com

]]>
¿Olvidaste tu contraseña? Enviaremos un email a la dirección especificada con un enlace para restaurar tu contraseña - Un email con instrucciones para restaurar tu contraseña será enviado a la dirección especificada si esta está registrada. - Volver a formularion de acceso + Un email con instrucciones para restaurar tu contraseña será enviado a la dirección especificada si ésta está registrada. + Volver al formulario de acceso Por favor, introduce una nueva contraseña - Tu contraseña has sido actualizada + Tu contraseña ha sido actualizada El enlace pulsado es inválido o ha caducado Umbraco: Restaurar contraseña Contenido - Elija una página arriba... + Elige una página arriba... %0% ha sido copiado al %1% - Seleccione donde el documento %0% debe ser copiado abajo + Selecciona donde el documento %0% debe ser copiado abajo %0% ha sido movido a %1% - Seleccione debajo donde mover el documento %0% - ha sido seleccionado como raíz de su nuevo contenido, haga click sobre 'ok' debajo. - No ha seleccionado ningún nodo. Seleccione un nodo en la lista mostrada arriba antes the pinchar en 'continuar' (continue) - No se puede colgar el nodo actual bajo el nodo elegido debido a su tipo + Selecciona debajo donde mover el documento %0% + ha sido seleccionado como raíz de tu nuevo contenido, haga clic sobre 'ok' debajo. + No ha seleccionado ningún nodo. Selecciona un nodo en la lista mostrada arriba antes de pinchar en 'continuar' + No se puede colgar el nodo actual bajo el nodo elegido debido a tu tipo El nodo actual no puede moverse a ninguna de sus subpáginas - El nodo actual no puede existir en la raiz - Acción no permitida. No tiene permisos suficientes para uno o más subnodos.' + El nodo actual no puede existir en la raíz + Acción no permitida. No tienes permisos suficientes para uno o más subnodos.' Relacionar elemento copiado al original - Edite su notificación para %0% - Hola %0% Esto es un e-mail automático para informarle que la tarea '%1%' ha sido realizada sobre la página '%2%' por el usuario '%3%' Vaya a http://%4%/#/content/content/edit/%5% para editarla. ¡Espero que tenga un buen día! Saludos del robot de Umbraco + Edita tu notificación para %0% + Hola %0% Esto es un e-mail automático para informarte que la tarea '%1%' ha sido realizada sobre la página '%2%' por el usuario '%3%' Vaya a http://%4%/#/content/content/edit/%5% para editarla. ¡Espero que tenga un buen día! Saludos del robot de Umbraco Hola %0%

Esto es un e-mail generado automáticamente para informarle que la tarea '%1%' ha sido realizada sobre la página '%2%' por el usuario '%3%'

Resumen de actualización:

%6%

¡Espero que tenga un buen día!

Saludos del robot Umbraco.

]]>
[%0%] Notificación acerca de %1% realizado en %2% Notificaciones y localizando el paquete. Los paquetes de Umbraco normalmente tienen la extensión ".umb" o ".zip".]]> - Suelte para subir archivo - o pulse aquí para elegir paquete + Suelta para subir archivo + o Pulsa aquí para elegir paquete Subir paquete - Instala un paquete local seleccionándolo desde tu ordenador. Sólo instala paquetes de fuentes que conoces y en las que confías + Instala un paquete local seleccionándolo desde tu ordenador. instala sólo paquetes de fuentes que conoces y en las que confías Subir otro paquete Cancelar y subir otro paquete Licencia - Aceptop + Aceptar términos de uso Instalar paquete Terminar @@ -847,13 +853,13 @@ tiene puntos de karma Información - Propetarioa + Propietario Contribuidores Creado Versión actual Versión .NET Descargas - Gustas + Me Gusta Compatibilidad Este paquete es compatible con las siguientes versiones de Umbraco, declaradas según miembros de la comunidad. No se puede garantizar compatibilidad completa para versiones declaradas debajo del 100% Fuentes externas @@ -867,13 +873,13 @@
Puedes eliminarlo del sistema de forma segura seleccionando la opción "desinstalar paquete" de abajo.]]>
No hay actualizaciones disponibles Opciones del paquete - Leeme del paquete + Léeme del paquete Repositorio de paquetes Confirma la desinstalación El paquete ha sido desinstalado El paquete se ha desinstalado correctamente Desinstalar paquete - Nota: cualquier documento, archivo etc dependiente de los elementos eliminados, dejará de funcionar, y puede conllevar inestabilidad en el sistema, por lo que lleva cuidado al desinstalar elementos. En caso de duda, contacta con el autor del paquete.]]> + Nota: cualquier documento, archivo etc dependiente de los elementos eliminados, dejará de funcionar, y puede conllevar inestabilidad en el sistema, por lo que lleva cuidado al desinstalar elementos. En caso de duda, contacta con el autor del paquete.]]> Descargar actualización del repositorio Actualizar paquete Instrucciones de actualización @@ -893,27 +899,27 @@ Pegar con formato completo (No recomendado) - El texto que estás intentando pegar contiene caractéres o formato especial. El problema puede ser debido al copiar texto desde Microsoft Word. Umbraco puede eliminar estos caractéres o formato especial automáticamente, de esa manera el contenido será más adecuado para la web. + El texto que estás intentando pegar contiene caracteres o formato especial. El problema puede ser debido al copiar texto desde Microsoft Word. Umbraco puede eliminar estos caracteres o formato especial automáticamente, de esa manera el contenido será más adecuado para la web. Pegar como texto sin formato Pegar, pero quitando el formato (Recomendado) - Proteccion basada en roles - usando los grupos de miembros de Umbraco.]]> - Necesita crear un grupo de miembros antes de poder usar autenticación basada en roles + Protección basada en roles + usando los grupos de miembros de Umbraco.]]> + Necesitas crear un grupo de miembros antes de poder usar autenticación basada en roles Página de error Usada cuando alguien hace login, pero no tiene acceso - Elija cómo restringir el acceso a esta página + Elige cómo restringir el acceso a esta página %0% está protegido Protección borrada de %0% Página de login - Elija la página que contenga el formulario de login + Elige la página que contenga el formulario de login Borrar protección - Elija las páginas que contendrán el formulario de login y mensajes de error - Elija los roles que tendrán acceso a esta página - Elija el login y password para esta página + Elige las páginas que contendrán el formulario de login y mensajes de error + Elige los roles que tendrán acceso a esta página + Elige el login y contraseña para esta página Protección de usuario único - Si sólo necesita configurar una protección simple usando un único login y password + Si sólo necesita configurar una protección simple usando un único login y contraseña %0% se ha publicado %0% y sus subpáginas se han publicado Publicar %0% y todas sus subpáginas - aceptar para publicar %0% y por lo tanto, hacer que su contenido esté disponible al público.

Puedes publicar esta página y todas sus subpáginas marcando publicar todos los hijos debajo. ]]>
+ aceptar para publicar %0% y por lo tanto, hacer que tu contenido esté disponible al público.

Puedes publicar esta página y todas sus subpáginas marcando publicar todos los hijos debajo. ]]>
No has configurado ningún color @@ -956,7 +962,7 @@ Enlace Abrir en una nueva ventana Introduce texto - Introduzce el enlace + Introduce el enlace Reiniciar @@ -965,11 +971,11 @@ Versión actual - Red el texto de la versión seleccionada no se mostrará. , green means added]]> + Red el texto de la versión seleccionada no se mostrará. , el verde significa añadido]]> Se ha recuperado la última versión del documento. - Esto muestra la versión seleccionada como html, si desea ver la diferencia entre 2 versiones al mismo tiempo, por favor use la vista diff + Esto muestra la versión seleccionada como html, si deseas ver la diferencia entre 2 versiones al mismo tiempo, por favor usa la vista diff Volver a - Elija versión + Elige versión Ver @@ -996,7 +1002,7 @@ Plantilla por defecto Clave de diccionario - Para importar un tipo de documento encuentre el fichero ".udt" en su ordenador haciendo click sobre el botón "Navegar" y pulsando "Importar" (se le solicitará confirmación en la siguiente pantalla) + Para importar un tipo de documento encuentra el fichero ".udt" en tu ordenador haciendo clic sobre el botón "Navegar" y pulsando "Importar" (se te solicitará confirmación en la siguiente pantalla) Nuevo nombre de la pestaña Tipo de nodo Tipo @@ -1009,19 +1015,19 @@ Tipo de Contenido Maestro activado Este Tipo de Contenido usa como Tipo de Contenido Maestro. Las pestañas para los Tipos de Contenido Maestros no se muestran y solo se pueden modificar desde el Tipo de Contenido Maestro - No existen propiedades para esta pestaña. Haga clic en el enlace "añadir nueva propiedad" para crear una nueva propiedad. + No existen propiedades para esta pestaña. Haz clic en el enlace "añadir nueva propiedad" para crear una nueva propiedad. Añadir icono Ordenar Fecha Creado Ordenación completa - Arrastra las diferentes páginas debajo para colocarlas como deberían estar. O haz click en las cabeceras de las columnas para ordenar todas las páginas - + Arrastra las diferentes páginas debajo para colocarlas como deberían estar o haz clic en las cabeceras de las columnas para ordenar todas las páginas + Validación - Los errors de validación deben ser arreglados antes de que el elemento pueda ser guardado + Los errores de validación deben ser arreglados antes de que el elemento pueda ser guardado Fallo Guardado Insuficientes permisos de usuario, no se pudo completar la operación @@ -1072,7 +1078,7 @@ Contenido oculto Vista parcial guardada Vista parcial guardada sin errores - ista parcial no guardada + Vista parcial no guardada Error guardando el archivo. Permisos guardados para Borrados %0% grupos de usuario @@ -1082,7 +1088,7 @@ %0% usuario activado %0% desactivado Grupos de usuario establecidos - %0% usuarios desbloquedaos + %0% usuarios desbloqueados %0% está desbloqueado @@ -1099,21 +1105,21 @@ Insertar área de contenido Insertar marcador de posición de área de contenido Insertar - Elije que insertar en tu plantilla + Elige que insertar en tu plantilla Insertar objeto del diccionario - A dictionary item is a placeholder for a translatable piece of text, which makes it easy to create designs for multilingual websites. + Un objeto de diccionario es una variable para un texto traducible, lo que facilita crear sitios multi idioma. Insertar macro - Una Macor es un componente configurable que es genial como partes reutilizables de tu diseño, + Una Macro es un componente configurable que es genial como partes reutilizables de tu diseño, donde necesites una forma de proporcionar parámetros, como galerías, formularios y listas. Insertar campo de página de Umbraco - Muestra el valor de una propiedad de la página actual, con opciones para modificar el valor or usar valores alternativos. + Muestra el valor de una propiedad de la página actual, con opciones para modificar el valor o usar valores alternativos. Vista parcial Una vista parcial es una platilla separada que puede ser mostrada dentro de otra plantilla. - Es útil para reutilizar código or para distribuir plantillas complejas en archivos separados. + Es útil para reutilizar código o para distribuir plantillas complejas en archivos separados. Plantilla principal Sin principal @@ -1129,12 +1135,12 @@ Muestra una sección nombrada @RenderSection(name) placeholder. - Esto muestra un area de una plantilla hija rodeada de la corresponsiente definición @section [name]{ ... }. + Esto muestra un area de una plantilla hija rodeada de la correspondiente definición @section [name]{ ... }. ]]> Nombre de sección Sección es obligatoria - Si obligatoria, la plantilla hija debe contener una definición de @section o se mostrará un error. + Si está marcada como obligatoria, la plantilla hija debe contener una definición de @section o se mostrará un error. Constructor de consultas elementos devueltos, en @@ -1170,20 +1176,20 @@ Plantilla - Image + Imagen Macro Insertar control - Elije configuración + Elige configuración Añade más filas Añadir contenido Soltar contenido - Settings applied + Configuración aplicada Contenido no permitido aquí Contenido permitido aquí - Pulse para insertar - Pulse para insertar imagen + Pulsa para insertar + Pulsa para insertar imagen Leyenda de imagen... - Escribe aqui... + Escribe aquí... Plantillas de Grid Las plantillas son el área de trabajo para el editor de grids, normalmente sólo necesitas una o dos plantillas diferentes Añadir plantilla de grid @@ -1209,16 +1215,16 @@ Composiciones - No has añadido nunguna pestaña + No has añadido ninguna pestaña Heredado de Añadir propiedad Etiqueta requerida Activar vista de lista Configura la página para mostrar una lista de sus hijas que puedes ordenar y buscar, los hijas no se mostrarán en el árbol de contenido Platillas permitidas - Elije que plantillas se permite a los editores utilizar en contenido de este tipo - Permitir como raiz - Permite a los editores crear contenido de este tipo en la raiz del árbol de contenido + Elige que plantillas se permite a los editores utilizar en contenido de este tipo + Permitir como raíz + Permite a los editores crear contenido de este tipo en la raíz del árbol de contenido Tipos de nodos hijos permitidos Permite contenido de los tipos permitidos ser creados debajo de este tipo de contenido Elegir nodo hijo @@ -1265,15 +1271,15 @@ MAYÚSCULA/minúscula Elegir campo Convertir a salto de línea - Sí, convertir salto de linea + Sí, convertir salto de línea Reemplaza los saltos de línea con la etiqueta HTML &lt;br&gt; Campos personalizados Si, solamente la fecha Formato y codificación Cambiar formato a fecha - Formatear el valor como una fecha o una fecha con hora , de acuerdo con la cultura activa + Formatear el valor como una fecha o una fecha con hora, de acuerdo con el idioma activa Codificar HTML - Se reemplazarán los caracteres especiales por su código HTML equivalente. + Se reemplazarán los caracteres especiales por tu código HTML equivalente. Será insertado después del valor del campo Será insertado antes del valor del campo Minúscula @@ -1289,8 +1295,8 @@ Mayúscula Codificar URL Formateará los caracteres especiales de las URLs - Sólo será usado cuando el campo superior esté vacio - Este campo será usado unicamente si el campo primario está vacío + Sólo será usado cuando el campo superior esté vacío + Este campo será usado únicamente si el campo primario está vacío Si, con el tiempo. Separador: @@ -1301,15 +1307,15 @@ - No se encontraron usuarios traductores. Por favor, crea un usuario traductor antes de empezar a mandar contenido para su traducción + No se encontraron usuarios traductores. Por favor, crea un usuario traductor antes de empezar a mandar contenido para tu traducción La página '%0%' se ha mandado a traducción Manda la página '%0%' a traducción Total de palabras @@ -1366,17 +1372,17 @@ Basado en los grupos asignados y los nodos iniciales, el usuario tiene acceso a los siguientes nodos. Asignar acceso Administrador - Campo de categoria + Campo de categoría Cambiar contraseña Cambiar foto Nueva contraseña no ha sido bloqueado La contraseña no se ha cambiado Confirma nueva contraseña - Puede cambiar su contraseña para acceder al 'back office' de Umbraco rellenando el siguiente formulario y haciendo clic en el botón 'Cambiar contraseña' + Puedes cambiar tu contraseña para acceder al 'back office' de Umbraco rellenando el siguiente formulario y haciendo clic en el botón 'Cambiar contraseña' Canal de contenido Crear otro usuario - Crear nuevos usuarios para darles acceso a Umbraco. Cuando un nuevo usuario es creado, una nueva contrasela será generada y la podrás compartir con el usuario. + Crear nuevos usuarios para darles acceso a Umbraco. Cuando un nuevo usuario es creado, una nueva contraseña será generada y la podrás compartir con el usuario. Campo descriptivo Deshabilitar usuario Tipo de documento @@ -1388,39 +1394,39 @@ Invitar otro usuario Invita nuevos usuarios para darles acceso a Umbraco. Un email de invitación será enviado al usuario con información sobre cómo acceder a Umbraco. Idioma - Establecer el idioma que verás en menús y dialogos + Establecer el idioma que verás en menús y diálogos Última fecha bloqueado Último acceso Última contraseña cambiada Acceso - Nodo de comienzo en la libreria de medios - Limitar la librería de medios al siguiente nodo de inicio + Nodo de comienzo en la biblioteca de medios + Limitar la biblioteca de medios al siguiente nodo de inicio Nodos de inicio para Medios - Limitar la librería de medios a los siguientes nodos de inicio + Limitar la biblioteca de medios a los siguientes nodos de inicio Secciones Deshabilitar acceso a Umbraco no se ha conectado aún - Contraseña antigüa + Contraseña antigua Contraseña Reiniciar contraseña - Su contraseña ha sido cambiada - Por favor confirme su nueva contraseña - Introduzca su nueva contraseña + Tu contraseña ha sido cambiada + Por favor confirma tu nueva contraseña + Introduce tu nueva contraseña La nueva contraseña no puede estar vacía Contraseña actual Contraseña actual inválida - La nueva contraseña no coincide con la contraseña de confirmación. Por favor, vuela a intentarlo!' - La contraseña de confirmación no coincide con la nueva contraseña!' + La nueva contraseña no coincide con la contraseña de confirmación. Por favor, vuele a intentarlo! + La contraseña de confirmación no coincide con la nueva contraseña! Reemplazar los permisos de los nodos hijo - Estas modificando los permisos para las páginas: + Estás modificando los permisos para las páginas: Selecciona las páginas para modificar sus permisos - Eliminar photo + Eliminar imagen Permisos por defecto Permisos granulares - Establecer permisos para nodos especificos + Establecer permisos para nodos específicos Perfil Buscar en todos los hijos - Añadir secciones para dar aceso a usuarios + Añadir secciones para dar acceso a usuarios Seleccionar grupos de usuarios Nodo de inicio no seleccionado Nodos de inicio no seleccionado @@ -1446,7 +1452,7 @@ La sesión caduca en Invitar usuario Crear usuario - Enviar invitatión + Enviar invitación Volver a usuarios Umbraco: Invitación Validar como email Validar como número Validar como Url - ...or introduce tu propia validación + ...o introduce tu propia validación Campo obligatorio Introduce una expresión regular Necesitas añadir al menos @@ -1590,17 +1596,14 @@ Archivo no existe: '%0%'. '%0%' en archivo de configuración '%1%'.]]> Hubo un error, revisa los logs para ver el error completo: %0%. - Miembros - Total XML: %0%, Total: %1%, Total invalidos: %2% - Media - Total XML: %0%, Total: %1%, Total invalidos: %2% - Contenido - Total XML: %0%, Total publicados: %1%, Total invalidos: %2% El certificado de tu sitio es válido. Error validando certificado: '%0%' - El ceriticado SSL de tu sitio ha caducado. - El ceriticado SSL de tu sitio caducará en %0% días. - Error pinging la URL %0% - '%1%' + El certificado SSL de tu sitio ha caducado. + El certificado SSL de tu sitio caducará en %0% días. + Error haciendo ping a la URL %0% - '%1%' Actualmente estás %0% viendo el sitio usando el esquema HTTPS. - El appSetting 'umbracoUseSSL' está configurado como 'false' en tu archivo web.config. Una vez que accedes al sitio usando HTTPS, debería ser configuraio como 'true'. - Ele appSetting 'umbracoUseSSL' está configurado como '%0%' en tu archivo your web.config, tus cookies son %1% marcadas como seguras. + El appSetting 'umbracoUseSSL' está configurado como 'false' en tu archivo web.config. Una vez que accedes al sitio usando HTTPS, debería ser configurado como 'true'. + Ele appSetting 'umbracoUseSSL' está configurado como '%0%' en tu archivo web.config, tus cookies son %1% marcadas como seguras. No se pudo actualizar 'umbracoUseSSL' en tu archivo web.config. Error: %0% Activar HTTPS @@ -1610,8 +1613,8 @@ No se pudo arreglar chequeo con un valor de comparación 'ShouldNotEqual'. No se pudo arreglar chequeo con un valor de comparación 'ShouldEqual' con el valor introducido. Valor para arreglar chequeo no introducido. - Modo Debug en compilacion está desactivado. - Modo Debug en compilacion está activado. Se recomienda desactivarlo antes de publicar el sitio. + Modo Debug en compilación está desactivado. + Modo Debug en compilación está activado. Se recomienda desactivarlo antes de publicar el sitio. Modo Debug en compilación se ha desactivado correctamente. Modo Trace está desactivado. Modo Trace está activado. Se recomienda desactivarlo antes de publicar el sitio. @@ -1630,23 +1633,23 @@ %0%. Opcional.]]> X-Frame-Options usado para controlar si un sitio puede ser IFRAMEd por otra fue encontrado.]]> X-Frame-Options usado para controlar si un sitio puede ser IFRAMEd por otra no se ha encontrado.]]> - Establecer Header en Config - Adds a value to the httpProtocol/customHeaders section of web.config to prevent the site being IFRAMEd by other websites. - A setting to create a header preventing IFRAMEing of the site by other websites has been added to your web.config file. - Could not update web.config file. Error: %0% + Establecer Cabecera en Config + Añade una entrada a la sección httpProtocol/customHeaders del archivo web.config para prevenir que el sitio sea incrustado en un iframe por otros sitios web. + Una entrada ha sido añadida a la sección httpProtocol/customHeaders del archivo web.config para prevenir que el sitio sea incrustado en un iframe por otros sitios web. + No se ha podido actualizar el archivo web.config. Error: %0% %0%.]]> - No se ha encontrado ninguna cabecerá que revele información sobre la tecnología del sitio. + No se ha encontrado ninguna cabecera que revele información sobre la tecnología del sitio. No se encontró system.net/mailsettings en Web.config. En la sección system.net/mailsettings section de web.config, el host no está configurado. Los valores SMTP están configurados correctamente y el servicio opera con normalidad. El servidor SMTP configurado con host '%0%' y puerto '%1%' no se pudo alcanzar. Por favor revisa que la configuración en la sección system.net/mailsettings del archivo Web.config es correcta. %0%.]]> - %0%.]]> + %0%.]]>

Los resultados de los Chequeos de Salud de Umbraco programados para ejecutarse el %0% a las %1% son:

%2%]]>
- Status de los Chequeos de Salud de Umbraco + Status de los Chequeos de Salud de Umbraco: %0% Desactivar URL tracker @@ -1658,7 +1661,7 @@ Eliminar ¿Estás seguro que quieres eliminar la redirección de '%0%' a '%1%'? Redirección URL eliminada. - Error removing redirect URL. + Error borrando la redirección URL. ¿Seguro que quieres desactivar URL tracker? URL tracker ha sido desactivado. Error desactivando URL tracker, más información se puede encontrar en los logs. diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml index 73f213b8bc..b44da9ba04 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml @@ -1790,9 +1790,6 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Le fichier n'existe pas : '%0%'. '%0%' dans le fichier config '%1%'.]]> Une erreur est survenue, consultez le log pour voir l'erreur complète : %0%. - Total XML : %0%, Total : %1% - Total XML : %0%, Total : %1% - Total XML : %0%, Total publié : %1% Base de données - Le schéma de la base de données est correct pour cette version de Umbraco %0% problèmes ont été détectés avec le schéma de votre base de données (Voyez le fichier log pour les détails) Des erreurs ont été détectées lors de la validation du schéma de la base de données par rapport à la version actuelle de Umbraco. @@ -1861,7 +1858,7 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à %0%.]]> %0%.]]>

Les résultats de l'exécution du Umbraco Health Checks planifiée le %0% à %1% sont les suivants :

%2%]]>
- Statut du Umbraco Health Check + Statut du Umbraco Health Check: %0% Désactiver URL tracker @@ -1886,4 +1883,4 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à caractères restant - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml index d42511abdb..395fc1a869 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml @@ -1356,9 +1356,6 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Het volgende bestand bestaat niet: '%0%'. '%0%' kon niet gevonden worden in configuratie bestand '%1%'.]]> Er is een fout opgetreden. Bekijk de log file voor de volledige fout: %0%. - Members - Totaal XML: %0%, Totaal: %1%, Total incorrect: %2% - Media - Totaal XML: %0%, Totaal: %1%, Total incorrect: %2% - Content - Totaal XML: %0%, Totaal gepubliceerd: %1%, Total incorrect: %2% Het cerficaat van de website is ongeldig. Cerficaat validatie foutmelding: '%0%' Fout bij pingen van URL %0% - '%1%' diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml index 918e1e31c0..d0f18119ef 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml @@ -1434,9 +1434,6 @@ Naciśnij przycisk instaluj, aby zainstalować bazę danych Umb Plik nie istnieje: '%0%'. '%0%' w pliku konfiguracyjnym '%1%'.]]> Wystąpił błąd, sprawdź logi, aby wyświetlić pełen opis błędu: %0%. - Członkowie - Suma XML: %0%, Suma: %1%, Suma niepoprawnych: %2% - Media - Suma XML: %0%, Suma: %1%, Suma niepoprawnych: %2% - Zawartość - Suma XML: %0%, Suma opublikowanych: %1%, Suma niepoprawnych: %2% Certifikat Twojej strony jest poprawny. Błąd walidacji certyfikatu: '%0%' Certyfikat SSL Twojej strony wygasł. @@ -1513,4 +1510,4 @@ Naciśnij przycisk instaluj, aby zainstalować bazę danych Umb pozostało znaków - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml index 3280d99997..61e4ea2d06 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml @@ -715,9 +715,6 @@ Файл не существует: '%0%'. '%0%' в файле конфигурации '%1%'.]]> Обнаружена ошибка, для получения полной информации обратитесь к журналу: %0%. - Участники - всего в XML: %0%, всего: %1%, с ошибками: %2% - Медиа - всего в XML: %0%, всего: %1%Б с ошибками: %2% - Содержимое - всего в XML: %0%, всего опубликовано: %1%, с ошибками: %2% Ошибка проверки адреса URL %0% - '%1%' Сертификат Вашего веб-сайта отмечен как проверенный. Ошибка проверки сертификата: '%0%' @@ -781,7 +778,7 @@ %0%.]]> %0%.]]>

Зафиксированы следующие результаты автоматической проверки состояния Umbraco по расписанию, запущенной на %0% в %1%:

%2%]]>
- Результат проверки состояния Umbraco + Результат проверки состояния Umbraco: %0% Лучшие обучающие видео-курсы по Umbraco @@ -1873,4 +1870,4 @@ Не является числом неверный формат email-адреса - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml index 1dbadcff4c..0064653a69 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml @@ -1221,9 +1221,6 @@ File does not exist: '%0%'. '%0%' in config file '%1%'.]]> There was an error, check log for full error: %0%. - Members - Total XML: %0%, Total: %1%, Total invalid: %2% - Media - Total XML: %0%, Total: %1%, Total invalid: %2% - Content - Total XML: %0%, Total published: %1%, Total invalid: %2% Your site certificate was marked as valid. Certificate validation error: '%0%' Error pinging the URL %0% - '%1%' @@ -1292,4 +1289,4 @@ 现在已启用 url 跟踪程序。 启用 url 跟踪程序时出错, 可以在日志文件中找到更多信息。 - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml index 9a94020275..2579592cfe 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml @@ -1199,9 +1199,6 @@ 檔案不存在:%0%。 '%1%'中無法找到'%0%'。]]> 有錯誤產生,請參閱下列錯誤的紀錄:%0%。 - 成員 - 所有XML:%0%,總共:%1%,不合格:%2% - 媒體 - 所有XML:%0%,總共發佈:%1%,不合格:%2% - 內容 - 所有XML:%0%,總共發佈:%1%,不合格:%2% 憑證驗證錯誤:%0% 網址探查錯誤:%0% - '%1%' 您目前使用HTTPS瀏覽本站:%0% @@ -1270,4 +1267,4 @@ 轉址追蹤器已開啟。 啟動轉址追蹤器錯誤,更多資訊請參閱您的紀錄檔。 - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI/Umbraco/dashboard/UserControlProxy.aspx b/src/Umbraco.Web.UI/Umbraco/dashboard/UserControlProxy.aspx deleted file mode 100644 index ac59074de3..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/dashboard/UserControlProxy.aspx +++ /dev/null @@ -1,25 +0,0 @@ -<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="UserControlProxy.aspx.cs" Inherits="Umbraco.Web.UI.Umbraco.Dashboard.UserControlProxy" %> -<%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> -<%@ Register TagPrefix="cc1" Namespace="Umbraco.Web.UI.JavaScript" Assembly="umbraco" %> - - - - - - - - - - - - - - - - -
- -
- - - diff --git a/src/Umbraco.Web.UI/Umbraco/dashboard/UserControlProxy.aspx.cs b/src/Umbraco.Web.UI/Umbraco/dashboard/UserControlProxy.aspx.cs deleted file mode 100644 index 40caff36c8..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/dashboard/UserControlProxy.aspx.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Web.UI; -using Umbraco.Core.IO; - - -namespace Umbraco.Web.UI.Umbraco.Dashboard -{ - public partial class UserControlProxy : Pages.UmbracoEnsuredPage - { - protected void Page_Load(object sender, EventArgs e) - { - - } - - protected override void OnInit(EventArgs e) - { - base.OnInit(e); - - var path = Request.QueryString["ctrl"]; - if (string.IsNullOrEmpty(path) == false) - { - path = IOHelper.FindFile(path); - - try - { - var c = LoadControl(path); - container.Controls.Add(c); - } - catch (Exception ee) - { - container.Controls.Add( - new LiteralControl( - "

Could not load control: '" + path + - "'.
Error message: " + - ee.ToString() + "

")); - } - } - } - } -} diff --git a/src/Umbraco.Web.UI/Umbraco/dashboard/UserControlProxy.aspx.designer.cs b/src/Umbraco.Web.UI/Umbraco/dashboard/UserControlProxy.aspx.designer.cs deleted file mode 100644 index 5bfa6983d1..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/dashboard/UserControlProxy.aspx.designer.cs +++ /dev/null @@ -1,69 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Umbraco.Web.UI.Umbraco.Dashboard { - - - public partial class UserControlProxy { - - /// - /// ClientLoader control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web.UI.JavaScript.UmbracoClientDependencyLoader ClientLoader; - - /// - /// CssInclude1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.CssInclude CssInclude1; - - /// - /// JsInclude1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude1; - - /// - /// JsInclude4 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude4; - - /// - /// form1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.HtmlControls.HtmlForm form1; - - /// - /// container control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.PlaceHolder container; - } -} diff --git a/src/Umbraco.Web.UI/Umbraco/developer/RelationTypes/EditRelationType.aspx b/src/Umbraco.Web.UI/Umbraco/developer/RelationTypes/EditRelationType.aspx index da2aed25cb..f93f4c0920 100644 --- a/src/Umbraco.Web.UI/Umbraco/developer/RelationTypes/EditRelationType.aspx +++ b/src/Umbraco.Web.UI/Umbraco/developer/RelationTypes/EditRelationType.aspx @@ -11,6 +11,8 @@ table.relations td { background: transparent none no-repeat scroll center center } + table.relations td a { text-decoration: underline; } + /* objectType icons */ table.relations td.ContentItemType {} table.relations td.ROOT {} @@ -122,10 +124,10 @@   - <%# DataBinder.Eval(Container.DataItem, "ParentText") %> + " target="_blank"><%# DataBinder.Eval(Container.DataItem, "ParentText") %>     - <%# DataBinder.Eval(Container.DataItem, "ChildText") %> + " target="_blank"><%# DataBinder.Eval(Container.DataItem, "ChildText") %> <%# DataBinder.Eval(Container.DataItem, "DateTime") %> <%# DataBinder.Eval(Container.DataItem, "Comment") %> diff --git a/src/Umbraco.Web.UI/config/Dashboard.Release.config b/src/Umbraco.Web.UI/config/Dashboard.Release.config index 0a39395213..fec6ab34ae 100644 --- a/src/Umbraco.Web.UI/config/Dashboard.Release.config +++ b/src/Umbraco.Web.UI/config/Dashboard.Release.config @@ -1,115 +1,107 @@  -
- - settings - - - - views/dashboard/settings/settingsdashboardintro.html - - - - +
+ + settings + + + + views/dashboard/settings/settingsdashboardintro.html + + + + views/dashboard/settings/examinemanagement.html - - - + + + views/dashboard/settings/publishedstatus.html - -
+
+
-
- - forms - - - - views/dashboard/forms/formsdashboardintro.html - - -
+
+ + forms + + + + views/dashboard/forms/formsdashboardintro.html + + +
-
- - developer - -
+
+ + media + + + + views/dashboard/media/mediafolderbrowser.html + + +
-
- - media - - - - views/dashboard/media/mediafolderbrowser.html - - +
+ + translator + + + content + + + + admin + -
+ + views/dashboard/default/startupdashboardintro.html + + +
-
- - translator - - - content - - - - admin - +
+ + member + + + + views/dashboard/members/membersdashboardvideos.html + + +
- - views/dashboard/default/startupdashboardintro.html - +
+ + settings + + + + /App_Plugins/ModelsBuilder/modelsbuilder.htm + + +
-
-
- -
- - member - - - - views/dashboard/members/membersdashboardvideos.html - - -
- -
- - developer - - - - /App_Plugins/ModelsBuilder/modelsbuilder.htm - - -
- -
- - settings - - - - views/dashboard/settings/healthcheck.html - - -
-
- - content - - - - views/dashboard/content/redirecturls.html - - -
+
+ + settings + + + + views/dashboard/settings/healthcheck.html + + +
+
+ + content + + + + views/dashboard/content/redirecturls.html + + +
diff --git a/src/Umbraco.Web.UI/config/Dashboard.config b/src/Umbraco.Web.UI/config/Dashboard.config index fab6fa6ea3..fec6ab34ae 100644 --- a/src/Umbraco.Web.UI/config/Dashboard.config +++ b/src/Umbraco.Web.UI/config/Dashboard.config @@ -1,55 +1,49 @@  +
settings - + views/dashboard/settings/settingsdashboardintro.html - views/dashboard/settings/examinemanagement.html - + views/dashboard/settings/examinemanagement.html + - views/dashboard/settings/publishedstatus.html - + views/dashboard/settings/publishedstatus.html +
+
forms - + views/dashboard/forms/formsdashboardintro.html -
+
+
media - + views/dashboard/media/mediafolderbrowser.html
-
- - forms - - - - views/dashboard/forms/formsdashboardintro.html - - -
+
translator @@ -61,29 +55,35 @@ admin - + + views/dashboard/default/startupdashboardintro.html
+
member - + views/dashboard/members/membersdashboardvideos.html
-
+ +
- contour + settings - - plugins/umbracocontour/formsdashboard.ascx + + + /App_Plugins/ModelsBuilder/modelsbuilder.htm +
+
settings @@ -104,14 +104,4 @@
-
- - settings - - - - /App_Plugins/ModelsBuilder/modelsbuilder.htm - - -
diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml b/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml index 9a94020275..2579592cfe 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml @@ -1199,9 +1199,6 @@ 檔案不存在:%0%。 '%1%'中無法找到'%0%'。]]> 有錯誤產生,請參閱下列錯誤的紀錄:%0%。 - 成員 - 所有XML:%0%,總共:%1%,不合格:%2% - 媒體 - 所有XML:%0%,總共發佈:%1%,不合格:%2% - 內容 - 所有XML:%0%,總共發佈:%1%,不合格:%2% 憑證驗證錯誤:%0% 網址探查錯誤:%0% - '%1%' 您目前使用HTTPS瀏覽本站:%0% @@ -1270,4 +1267,4 @@ 轉址追蹤器已開啟。 啟動轉址追蹤器錯誤,更多資訊請參閱您的紀錄檔。 - \ No newline at end of file + diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 5f33a31872..7c9124c04f 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1909,6 +1909,10 @@ namespace Umbraco.Web.Editors { case PublishResultType.SuccessPublishAlready: { + //TODO: Here we should have messaging for when there are release dates specified like https://github.com/umbraco/Umbraco-CMS/pull/3507 + // but this will take a bit of effort because we need to deal with variants, different messaging, etc... A quick attempt was made here: + // http://github.com/umbraco/Umbraco-CMS/commit/9b3de7b655e07c612c824699b48a533c0448131a + //special case, we will only show messages for this if: // * it's not a bulk publish operation // * it's a bulk publish operation and all successful statuses are this one @@ -1936,6 +1940,10 @@ namespace Umbraco.Web.Editors break; case PublishResultType.SuccessPublish: { + //TODO: Here we should have messaging for when there are release dates specified like https://github.com/umbraco/Umbraco-CMS/pull/3507 + // but this will take a bit of effort because we need to deal with variants, different messaging, etc... A quick attempt was made here: + // http://github.com/umbraco/Umbraco-CMS/commit/9b3de7b655e07c612c824699b48a533c0448131a + var itemCount = status.Count(); if (successfulCultures == null) { diff --git a/src/Umbraco.Web/Editors/DashboardController.cs b/src/Umbraco.Web/Editors/DashboardController.cs index e88b9975e7..72b7acc9e7 100644 --- a/src/Umbraco.Web/Editors/DashboardController.cs +++ b/src/Umbraco.Web/Editors/DashboardController.cs @@ -1,126 +1,126 @@ -using System.Collections.Generic; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Mvc; -using System.Linq; -using Umbraco.Core.IO; -using Newtonsoft.Json.Linq; -using System.Threading.Tasks; -using System.Net.Http; -using System.Web.Http; -using System; -using System.Net; -using System.Text; -using Umbraco.Core.Cache; -using Umbraco.Web.WebApi; -using Umbraco.Web.WebApi.Filters; -using Umbraco.Core.Logging; - -namespace Umbraco.Web.Editors -{ - //we need to fire up the controller like this to enable loading of remote css directly from this controller - [PluginController("UmbracoApi")] - [ValidationFilter] - [AngularJsonOnlyConfiguration] - [IsBackOffice] - [WebApi.UmbracoAuthorize] - public class DashboardController : UmbracoApiController - { - //we have just one instance of HttpClient shared for the entire application - private static readonly HttpClient HttpClient = new HttpClient(); - //we have baseurl as a param to make previewing easier, so we can test with a dev domain from client side - [ValidateAngularAntiForgeryToken] - public async Task GetRemoteDashboardContent(string section, string baseUrl = "https://dashboard.umbraco.org/") - { - var context = UmbracoContext.Current; - if (context == null) - throw new HttpResponseException(HttpStatusCode.InternalServerError); - - var user = Security.CurrentUser; - var allowedSections = string.Join(",", user.AllowedSections); - var language = user.Language; - var version = UmbracoVersion.SemanticVersion.ToSemanticString(); - - var url = string.Format(baseUrl + "{0}?section={0}&allowed={1}&lang={2}&version={3}", section, allowedSections, language, version); - var key = "umbraco-dynamic-dashboard-" + language + allowedSections.Replace(",", "-") + section; - - var content = ApplicationCache.RuntimeCache.GetCacheItem(key); - var result = new JObject(); - if (content != null) - { - result = content; - } - else - { - //content is null, go get it - try - { - //fetch dashboard json and parse to JObject - var json = await HttpClient.GetStringAsync(url); - content = JObject.Parse(json); - result = content; - - ApplicationCache.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 30, 0)); - } - catch (HttpRequestException ex) - { - Logger.Error(ex.InnerException ?? ex, "Error getting dashboard content from '{Url}'", url); - - //it's still new JObject() - we return it like this to avoid error codes which triggers UI warnings - ApplicationCache.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 5, 0)); - } - } - - return result; - } - - public async Task GetRemoteDashboardCss(string section, string baseUrl = "https://dashboard.umbraco.org/") - { - var url = string.Format(baseUrl + "css/dashboard.css?section={0}", section); - var key = "umbraco-dynamic-dashboard-css-" + section; - - var content = ApplicationCache.RuntimeCache.GetCacheItem(key); - var result = string.Empty; - - if (content != null) - { - result = content; - } - else - { - //content is null, go get it - try - { - //fetch remote css - content = await HttpClient.GetStringAsync(url); - - //can't use content directly, modified closure problem - result = content; - - //save server content for 30 mins - ApplicationCache.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 30, 0)); - } - catch (HttpRequestException ex) - { - Logger.Error(ex.InnerException ?? ex, "Error getting dashboard CSS from '{Url}'", url); - - //it's still string.Empty - we return it like this to avoid error codes which triggers UI warnings - ApplicationCache.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 5, 0)); - } - } - - return new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new StringContent(result, Encoding.UTF8, "text/css") - }; - } - - [ValidateAngularAntiForgeryToken] - public IEnumerable> GetDashboard(string section) - { - var dashboardHelper = new DashboardHelper(Services.SectionService); - return dashboardHelper.GetDashboard(section, Security.CurrentUser); - } - } -} +using System.Collections.Generic; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Mvc; +using Newtonsoft.Json.Linq; +using System.Threading.Tasks; +using System.Net.Http; +using System.Web.Http; +using System; +using System.Net; +using System.Text; +using Umbraco.Core.Cache; +using Umbraco.Web.WebApi; +using Umbraco.Web.WebApi.Filters; +using Umbraco.Core.Logging; + +namespace Umbraco.Web.Editors +{ + //we need to fire up the controller like this to enable loading of remote css directly from this controller + [PluginController("UmbracoApi")] + [ValidationFilter] + [AngularJsonOnlyConfiguration] + [IsBackOffice] + [WebApi.UmbracoAuthorize] + public class DashboardController : UmbracoApiController + { + private readonly Dashboards _dashboards; + + public DashboardController(Dashboards dashboards) + { + _dashboards = dashboards; + } + + //we have just one instance of HttpClient shared for the entire application + private static readonly HttpClient HttpClient = new HttpClient(); + //we have baseurl as a param to make previewing easier, so we can test with a dev domain from client side + [ValidateAngularAntiForgeryToken] + public async Task GetRemoteDashboardContent(string section, string baseUrl = "https://dashboard.umbraco.org/") + { + var user = Security.CurrentUser; + var allowedSections = string.Join(",", user.AllowedSections); + var language = user.Language; + var version = UmbracoVersion.SemanticVersion.ToSemanticString(); + + var url = string.Format(baseUrl + "{0}?section={0}&allowed={1}&lang={2}&version={3}", section, allowedSections, language, version); + var key = "umbraco-dynamic-dashboard-" + language + allowedSections.Replace(",", "-") + section; + + var content = ApplicationCache.RuntimeCache.GetCacheItem(key); + var result = new JObject(); + if (content != null) + { + result = content; + } + else + { + //content is null, go get it + try + { + //fetch dashboard json and parse to JObject + var json = await HttpClient.GetStringAsync(url); + content = JObject.Parse(json); + result = content; + + ApplicationCache.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 30, 0)); + } + catch (HttpRequestException ex) + { + Logger.Error(ex.InnerException ?? ex, "Error getting dashboard content from '{Url}'", url); + + //it's still new JObject() - we return it like this to avoid error codes which triggers UI warnings + ApplicationCache.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 5, 0)); + } + } + + return result; + } + + public async Task GetRemoteDashboardCss(string section, string baseUrl = "https://dashboard.umbraco.org/") + { + var url = string.Format(baseUrl + "css/dashboard.css?section={0}", section); + var key = "umbraco-dynamic-dashboard-css-" + section; + + var content = ApplicationCache.RuntimeCache.GetCacheItem(key); + var result = string.Empty; + + if (content != null) + { + result = content; + } + else + { + //content is null, go get it + try + { + //fetch remote css + content = await HttpClient.GetStringAsync(url); + + //can't use content directly, modified closure problem + result = content; + + //save server content for 30 mins + ApplicationCache.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 30, 0)); + } + catch (HttpRequestException ex) + { + Logger.Error(ex.InnerException ?? ex, "Error getting dashboard CSS from '{Url}'", url); + + //it's still string.Empty - we return it like this to avoid error codes which triggers UI warnings + ApplicationCache.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 5, 0)); + } + } + + return new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(result, Encoding.UTF8, "text/css") + }; + } + + [ValidateAngularAntiForgeryToken] + public IEnumerable> GetDashboard(string section) + { + return _dashboards.GetDashboards(section, Security.CurrentUser); + } + } +} diff --git a/src/Umbraco.Web/Editors/DashboardHelper.cs b/src/Umbraco.Web/Editors/DashboardHelper.cs deleted file mode 100644 index 75ccda1a9b..0000000000 --- a/src/Umbraco.Web/Editors/DashboardHelper.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.IO; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Services; -using Umbraco.Web.Models.ContentEditing; - -namespace Umbraco.Web.Editors -{ - internal class DashboardHelper - { - private readonly ISectionService _sectionService; - - public DashboardHelper(ISectionService sectionService) - { - if (sectionService == null) throw new ArgumentNullException("sectionService"); - _sectionService = sectionService; - } - - /// - /// Returns the dashboard models per section for the current user and it's access - /// - /// - /// - public IDictionary>> GetDashboards(IUser currentUser) - { - var result = new Dictionary>>(); - foreach (var section in _sectionService.GetSections()) - { - result[section.Alias] = GetDashboard(section.Alias, currentUser); - } - return result; - } - - /// - /// Returns the dashboard model for the given section based on the current user and it's access - /// - /// - /// - /// - public IEnumerable> GetDashboard(string section, IUser currentUser) - { - var tabs = new List>(); - var i = 1; - - //disable packages section dashboard - if (section == "packages") return tabs; - - foreach (var dashboardSection in UmbracoConfig.For.DashboardSettings().Sections.Where(x => x.Areas.Contains(section))) - { - //we need to validate access to this section - if (DashboardSecurity.AuthorizeAccess(dashboardSection, currentUser, _sectionService) == false) - continue; - - //User is authorized - foreach (var tab in dashboardSection.Tabs) - { - //we need to validate access to this tab - if (DashboardSecurity.AuthorizeAccess(tab, currentUser, _sectionService) == false) - continue; - - var dashboardControls = new List(); - - foreach (var control in tab.Controls) - { - if (DashboardSecurity.AuthorizeAccess(control, currentUser, _sectionService) == false) - continue; - - var dashboardControl = new DashboardControl(); - var controlPath = control.ControlPath.Trim(); - dashboardControl.Caption = control.PanelCaption; - dashboardControl.Path = IOHelper.FindFile(controlPath); - if (controlPath.ToLowerInvariant().EndsWith(".ascx".ToLowerInvariant())) - dashboardControl.ServerSide = true; - - dashboardControls.Add(dashboardControl); - } - - tabs.Add(new Tab - { - Id = i, - Alias = tab.Caption.ToSafeAlias(), - IsActive = i == 1, - Label = tab.Caption, - Properties = dashboardControls - }); - - i++; - } - } - - //In case there are no tabs or a user doesn't have access the empty tabs list is returned - return tabs; - } - } -} diff --git a/src/Umbraco.Web/Editors/DashboardSecurity.cs b/src/Umbraco.Web/Editors/DashboardSecurity.cs index 87ed5af196..1481606c7e 100644 --- a/src/Umbraco.Web/Editors/DashboardSecurity.cs +++ b/src/Umbraco.Web/Editors/DashboardSecurity.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using Umbraco.Core; @@ -17,105 +18,97 @@ namespace Umbraco.Web.Editors public static bool AuthorizeAccess(ISection dashboardSection, IUser user, ISectionService sectionService) { - if (user.Id.ToString(CultureInfo.InvariantCulture) == 0.ToInvariantString()) - { - return true; - } - - var denyTypes = dashboardSection.AccessRights.Rules.Where(x => x.Action == AccessType.Deny).ToArray(); - var grantedTypes = dashboardSection.AccessRights.Rules.Where(x => x.Action == AccessType.Grant).ToArray(); - var grantedBySectionTypes = dashboardSection.AccessRights.Rules.Where(x => x.Action == AccessType.GrantBySection).ToArray(); - - return CheckUserAccessByRules(user, sectionService, denyTypes, grantedTypes, grantedBySectionTypes); + return CheckUserAccessByRules(user, sectionService, dashboardSection.AccessRights.Rules); } public static bool AuthorizeAccess(IDashboardTab dashboardTab, IUser user, ISectionService sectionService) { - if (user.Id.ToString(CultureInfo.InvariantCulture) == Constants.System.Root.ToInvariantString()) - { - return true; - } - - var denyTypes = dashboardTab.AccessRights.Rules.Where(x => x.Action == AccessType.Deny).ToArray(); - var grantedTypes = dashboardTab.AccessRights.Rules.Where(x => x.Action == AccessType.Grant).ToArray(); - var grantedBySectionTypes = dashboardTab.AccessRights.Rules.Where(x => x.Action == AccessType.GrantBySection).ToArray(); - - return CheckUserAccessByRules(user, sectionService, denyTypes, grantedTypes, grantedBySectionTypes); + return CheckUserAccessByRules(user, sectionService, dashboardTab.AccessRights.Rules); } - public static bool AuthorizeAccess(IDashboardControl dashboardTab, IUser user, ISectionService sectionService) + public static bool AuthorizeAccess(IDashboardControl dashboardControl, IUser user, ISectionService sectionService) { - if (user.Id.ToString(CultureInfo.InvariantCulture) == Constants.System.Root.ToInvariantString()) - { - return true; - } - - var denyTypes = dashboardTab.AccessRights.Rules.Where(x => x.Action == AccessType.Deny).ToArray(); - var grantedTypes = dashboardTab.AccessRights.Rules.Where(x => x.Action == AccessType.Grant).ToArray(); - var grantedBySectionTypes = dashboardTab.AccessRights.Rules.Where(x => x.Action == AccessType.GrantBySection).ToArray(); - - return CheckUserAccessByRules(user, sectionService, denyTypes, grantedTypes, grantedBySectionTypes); + return CheckUserAccessByRules(user, sectionService, dashboardControl.AccessRights.Rules); } - public static bool CheckUserAccessByRules(IUser user, ISectionService sectionService, IAccessItem[] denyTypes, IAccessItem[] grantedTypes, IAccessItem[] grantedBySectionTypes) + private static (IAccessRule[], IAccessRule[], IAccessRule[]) GroupRules(IEnumerable rules) { - var allowedSoFar = false; + IAccessRule[] denyRules = null, grantRules = null, grantBySectionRules = null; - // if there's no grantBySection or grant rules defined - we allow access so far and skip to checking deny rules - if (grantedBySectionTypes.Any() == false && grantedTypes.Any() == false) + var groupedRules = rules.GroupBy(x => x.Type); + foreach (var group in groupedRules) { - allowedSoFar = true; + var a = group.ToArray(); + switch (group.Key) + { + case AccessRuleType.Deny: + denyRules = a; + break; + case AccessRuleType.Grant: + grantRules = a; + break; + case AccessRuleType.GrantBySection: + grantBySectionRules = a; + break; + default: + throw new Exception("panic"); + } } - // else we check the rules and only allow if one matches - else + + return (denyRules ?? Array.Empty(), grantRules ?? Array.Empty(), grantBySectionRules ?? Array.Empty()); + } + + public static bool CheckUserAccessByRules(IUser user, ISectionService sectionService, IEnumerable rules) + { + if (user.Id == Constants.Security.SuperUserId) + return true; + + var (denyRules, grantRules, grantBySectionRules) = GroupRules(rules); + + var hasAccess = true; + string[] assignedUserGroups = null; + + // if there are no grant rules, then access is granted by default, unless denied + // otherwise, grant rules determine if access can be granted at all + if (grantBySectionRules.Length > 0 || grantRules.Length > 0) { + hasAccess = false; + // check if this item has any grant-by-section arguments. // if so check if the user has access to any of the sections approved, if so they will be allowed to see it (so far) - if (grantedBySectionTypes.Any()) + if (grantBySectionRules.Length > 0) { - var allowedApps = sectionService.GetAllowedSections(Convert.ToInt32(user.Id)) - .Select(x => x.Alias) - .ToArray(); + var allowedSections = sectionService.GetAllowedSections(user.Id).Select(x => x.Alias).ToArray(); + var wantedSections = grantBySectionRules.SelectMany(g => g.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)).ToArray(); - var allApprovedSections = grantedBySectionTypes.SelectMany(g => g.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)).ToArray(); - if (allApprovedSections.Any(allowedApps.Contains)) - { - allowedSoFar = true; - } + if (wantedSections.Intersect(allowedSections).Any()) + hasAccess = true; } // if not already granted access, check if this item as any grant arguments. // if so check if the user is in one of the user groups approved, if so they will be allowed to see it (so far) - if (allowedSoFar == false && grantedTypes.Any()) + if (hasAccess == false && grantRules.Any()) { - var allApprovedUserTypes = grantedTypes.SelectMany(g => g.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)).ToArray(); - foreach (var userGroup in user.Groups) - { - if (allApprovedUserTypes.InvariantContains(userGroup.Alias)) - { - allowedSoFar = true; - break; - } - } + assignedUserGroups = user.Groups.Select(x => x.Alias).ToArray(); + var wantedUserGroups = grantRules.SelectMany(g => g.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)).ToArray(); + + if (wantedUserGroups.Intersect(assignedUserGroups).Any()) + hasAccess = true; } } + if (!hasAccess || denyRules.Length == 0) + return false; + // check if this item has any deny arguments, if so check if the user is in one of the denied user groups, if so they will // be denied to see it no matter what - if (denyTypes.Any()) - { - var allDeniedUserTypes = denyTypes.SelectMany(g => g.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)).ToArray(); - foreach (var userGroup in user.Groups) - { - if (allDeniedUserTypes.InvariantContains(userGroup.Alias)) - { - allowedSoFar = false; - break; - } - } - } + assignedUserGroups = assignedUserGroups ?? user.Groups.Select(x => x.Alias).ToArray(); + var deniedUserGroups = denyRules.SelectMany(g => g.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)).ToArray(); - return allowedSoFar; + if (deniedUserGroups.Intersect(assignedUserGroups).Any()) + hasAccess = false; + + return hasAccess; } } } diff --git a/src/Umbraco.Web/Editors/Dashboards.cs b/src/Umbraco.Web/Editors/Dashboards.cs new file mode 100644 index 0000000000..4bf2d81045 --- /dev/null +++ b/src/Umbraco.Web/Editors/Dashboards.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Configuration.Dashboard; +using Umbraco.Core.IO; +using Umbraco.Core.Manifest; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Editors +{ + public class Dashboards + { + private readonly ISectionService _sectionService; + private readonly IDashboardSection _dashboardSection; + private readonly ManifestParser _manifestParser; + + public Dashboards(ISectionService sectionService, IDashboardSection dashboardSection, ManifestParser manifestParser) + { + _sectionService = sectionService ?? throw new ArgumentNullException(nameof(sectionService)); + _dashboardSection = dashboardSection; + _manifestParser = manifestParser; + } + + /// + /// Gets all dashboards, organized by section, for a user. + /// + public IDictionary>> GetDashboards(IUser currentUser) + { + return _sectionService.GetSections().ToDictionary(x => x.Alias, x => GetDashboards(x.Alias, currentUser)); + } + + /// + /// Returns dashboards for a specific section, for a user. + /// + public IEnumerable> GetDashboards(string section, IUser currentUser) + { + var tabId = 1; + var configDashboards = GetDashboardsFromConfig(ref tabId, section, currentUser); + var pluginDashboards = GetDashboardsFromPlugins(ref tabId, section, currentUser); + + // merge dashboards + // both collections contain tab.alias -> controls + var dashboards = configDashboards; + + // until now, it was fine to have duplicate tab.aliases in configDashboard + // so... the rule should be - just merge whatever we get, don't be clever + dashboards.AddRange(pluginDashboards); + + // re-sort by id + dashboards.Sort((tab1, tab2) => tab1.Id > tab2.Id ? 1 : 0); + + // re-assign ids (why?) + var i = 1; + foreach (var tab in dashboards) + { + tab.Id = i++; + tab.IsActive = tab.Id == 1; + } + + return configDashboards; + } + + // note: + // in dashboard.config we have 'sections' which define 'tabs' for 'areas' + // and 'areas' are the true UI sections - and each tab can have more than + // one control + // in a manifest, we directly have 'dashboards' which map to a unique + // control in a tab + + // gets all tabs & controls from the config file + private List> GetDashboardsFromConfig(ref int tabId, string section, IUser currentUser) + { + var tabs = new List>(); + + // disable packages section dashboard + if (section == "packages") return tabs; + + foreach (var dashboardSection in _dashboardSection.Sections.Where(x => x.Areas.InvariantContains(section))) + { + // validate access to this section + if (!DashboardSecurity.AuthorizeAccess(dashboardSection, currentUser, _sectionService)) + continue; + + foreach (var tab in dashboardSection.Tabs) + { + // validate access to this tab + if (!DashboardSecurity.AuthorizeAccess(tab, currentUser, _sectionService)) + continue; + + var dashboardControls = new List(); + + foreach (var control in tab.Controls) + { + // validate access to this control + if (!DashboardSecurity.AuthorizeAccess(control, currentUser, _sectionService)) + continue; + + // create and add control + var dashboardControl = new DashboardControl + { + Caption = control.PanelCaption, + Path = IOHelper.FindFile(control.ControlPath.Trim()) + }; + + if (dashboardControl.Path.InvariantEndsWith(".ascx")) + throw new NotSupportedException("Legacy UserControl (.ascx) dashboards are no longer supported."); + + dashboardControls.Add(dashboardControl); + } + + // create and add tab + tabs.Add(new Tab + { + Id = tabId++, + Alias = tab.Caption.ToSafeAlias(), + Label = tab.Caption, + Properties = dashboardControls + }); + } + } + + return tabs; + } + + private List> GetDashboardsFromPlugins(ref int tabId, string section, IUser currentUser) + { + var tabs = new List>(); + + foreach (var dashboard in _manifestParser.Manifest.Dashboards.Where(x => x.Sections.InvariantContains(section)).OrderBy(x => x.Weight)) + { + // validate access + if (!DashboardSecurity.CheckUserAccessByRules(currentUser, _sectionService, dashboard.AccessRules)) + continue; + + var dashboardControl = new DashboardControl + { + Caption = "", + Path = IOHelper.FindFile(dashboard.View.Trim()) + }; + + if (dashboardControl.Path.InvariantEndsWith(".ascx")) + throw new NotSupportedException("Legacy UserControl (.ascx) dashboards are no longer supported."); + + tabs.Add(new Tab + { + Id = tabId++, + Alias = dashboard.Alias.ToSafeAlias(), + Label = dashboard.Name, + Properties = new[] { dashboardControl } + }); + } + + return tabs; + } + } +} diff --git a/src/Umbraco.Web/Editors/SectionController.cs b/src/Umbraco.Web/Editors/SectionController.cs index dc7ddb7201..bcf451a5bb 100644 --- a/src/Umbraco.Web/Editors/SectionController.cs +++ b/src/Umbraco.Web/Editors/SectionController.cs @@ -1,98 +1,94 @@ -using System.Collections.Generic; -using AutoMapper; -using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Mvc; -using System.Linq; -using Umbraco.Core.Composing; -using Umbraco.Core.Models; -using Umbraco.Web.Trees; -using Section = Umbraco.Web.Models.ContentEditing.Section; -using Umbraco.Web.Models.Trees; - -namespace Umbraco.Web.Editors -{ - /// - /// The API controller used for using the list of sections - /// - [PluginController("UmbracoApi")] - public class SectionController : UmbracoAuthorizedJsonController - { - public IEnumerable
GetSections() - { - - var sections = Services.SectionService.GetAllowedSections(Security.GetUserId().ResultOr(0)); - - var sectionModels = sections.Select(Mapper.Map).ToArray(); - - //Check if there are empty dashboards or dashboards that will end up empty based on the current user's access - //and add the meta data about them - var dashboardHelper = new DashboardHelper(Services.SectionService); - - // this is a bit nasty since we'll be proxying via the app tree controller but we sort of have to do that - // since tree's by nature are controllers and require request contextual data - and then we have to - // remember to inject properties - nasty indeed - // fixme - this controller could/should be able to be created from the container and/or from webapi's IHttpControllerTypeResolver - var appTreeController = new ApplicationTreeController(); - Current.Container.InjectProperties(appTreeController); - appTreeController.ControllerContext = ControllerContext; - - var dashboards = dashboardHelper.GetDashboards(Security.CurrentUser); - //now we can add metadata for each section so that the UI knows if there's actually anything at all to render for - //a dashboard for a given section, then the UI can deal with it accordingly (i.e. redirect to the first tree) - foreach (var section in sectionModels) - { - var hasDashboards = false; - if (dashboards.TryGetValue(section.Alias, out var dashboardsForSection)) - { - if (dashboardsForSection.Any()) - hasDashboards = true; - } - - if (hasDashboards == false) - { - //get the first tree in the section and get it's root node route path - var sectionRoot = appTreeController.GetApplicationTrees(section.Alias, null, null).Result; - section.RoutePath = GetRoutePathForFirstTree(sectionRoot); - } - } - - return sectionModels; - } - - /// - /// Returns the first non root/group node's route path - /// - /// - /// - private string GetRoutePathForFirstTree(TreeRootNode rootNode) - { - if (!rootNode.IsContainer || !rootNode.ContainsTrees) - return rootNode.RoutePath; - - foreach(var node in rootNode.Children) - { - if (node is TreeRootNode groupRoot) - return GetRoutePathForFirstTree(groupRoot);//recurse to get the first tree in the group - else - return node.RoutePath; - } - - return string.Empty; - } - - /// - /// Returns all the sections that the user has access to - /// - /// - public IEnumerable
GetAllSections() - { - var sections = Services.SectionService.GetSections(); - var mapped = sections.Select(Mapper.Map); - if (Security.CurrentUser.IsAdmin()) - return mapped; - - return mapped.Where(x => Security.CurrentUser.AllowedSections.Contains(x.Alias)).ToArray(); - } - - } -} +using System.Collections.Generic; +using AutoMapper; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Mvc; +using System.Linq; +using Umbraco.Core.Composing; +using Umbraco.Core.Models; +using Umbraco.Web.Trees; +using Section = Umbraco.Web.Models.ContentEditing.Section; +using Umbraco.Web.Models.Trees; + +namespace Umbraco.Web.Editors +{ + /// + /// The API controller used for using the list of sections + /// + [PluginController("UmbracoApi")] + public class SectionController : UmbracoAuthorizedJsonController + { + private readonly Dashboards _dashboards; + + public SectionController(Dashboards dashboards) + { + _dashboards = dashboards; + } + + public IEnumerable
GetSections() + { + var sections = Services.SectionService.GetAllowedSections(Security.GetUserId().ResultOr(0)); + + var sectionModels = sections.Select(Mapper.Map).ToArray(); + + // this is a bit nasty since we'll be proxying via the app tree controller but we sort of have to do that + // since tree's by nature are controllers and require request contextual data - and then we have to + // remember to inject properties - nasty indeed + // fixme - this controller could/should be able to be created from the container and/or from webapi's IHttpControllerTypeResolver + var appTreeController = new ApplicationTreeController(); + Current.Container.InjectProperties(appTreeController); + appTreeController.ControllerContext = ControllerContext; + + var dashboards = _dashboards.GetDashboards(Security.CurrentUser); + + //now we can add metadata for each section so that the UI knows if there's actually anything at all to render for + //a dashboard for a given section, then the UI can deal with it accordingly (i.e. redirect to the first tree) + foreach (var section in sectionModels) + { + var hasDashboards = dashboards.TryGetValue(section.Alias, out var dashboardsForSection) && dashboardsForSection.Any(); + if (hasDashboards) continue; + + // get the first tree in the section and get its root node route path + var sectionRoot = appTreeController.GetApplicationTrees(section.Alias, null, null).Result; + section.RoutePath = GetRoutePathForFirstTree(sectionRoot); + } + + return sectionModels; + } + + /// + /// Returns the first non root/group node's route path + /// + /// + /// + private string GetRoutePathForFirstTree(TreeRootNode rootNode) + { + if (!rootNode.IsContainer || !rootNode.ContainsTrees) + return rootNode.RoutePath; + + foreach(var node in rootNode.Children) + { + if (node is TreeRootNode groupRoot) + return GetRoutePathForFirstTree(groupRoot);//recurse to get the first tree in the group + else + return node.RoutePath; + } + + return string.Empty; + } + + /// + /// Returns all the sections that the user has access to + /// + /// + public IEnumerable
GetAllSections() + { + var sections = Services.SectionService.GetSections(); + var mapped = sections.Select(Mapper.Map); + if (Security.CurrentUser.IsAdmin()) + return mapped; + + return mapped.Where(x => Security.CurrentUser.AllowedSections.Contains(x.Alias)).ToArray(); + } + + } +} diff --git a/src/Umbraco.Web/HealthCheck/Checks/DataIntegrity/XmlDataIntegrityHealthCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/DataIntegrity/XmlDataIntegrityHealthCheck.cs deleted file mode 100644 index c53793274f..0000000000 --- a/src/Umbraco.Web/HealthCheck/Checks/DataIntegrity/XmlDataIntegrityHealthCheck.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Composing; -using Umbraco.Core.Services; -using Umbraco.Web.PublishedCache; - -namespace Umbraco.Web.HealthCheck.Checks.DataIntegrity -{ - /// - /// This moves the functionality from the XmlIntegrity check dashboard into a health check - /// - [HealthCheck( - "D999EB2B-64C2-400F-B50C-334D41F8589A", - "XML Data Integrity", - Description = "This checks the data integrity for the xml structures for content, media and members that are stored in the cmsContentXml table. This does not check the data integrity of the xml cache file, only the xml structures stored in the database used to create the xml cache file.", - Group = "Data Integrity")] - [HideFromTypeFinder] // only if running the Xml cache! added by XmlCacheComponent! - public class XmlDataIntegrityHealthCheck : HealthCheck - { - private readonly ILocalizedTextService _textService; - private readonly PublishedCache.XmlPublishedCache.PublishedSnapshotService _publishedSnapshotService; - - private const string CheckContentXmlTableAction = "checkContentXmlTable"; - private const string CheckMediaXmlTableAction = "checkMediaXmlTable"; - private const string CheckMembersXmlTableAction = "checkMembersXmlTable"; - - public XmlDataIntegrityHealthCheck(ILocalizedTextService textService, IPublishedSnapshotService publishedSnapshotService) - { - _textService = textService; - - _publishedSnapshotService = publishedSnapshotService as PublishedCache.XmlPublishedCache.PublishedSnapshotService; - if (_publishedSnapshotService == null) - throw new NotSupportedException("Unsupported IPublishedSnapshotService, only the Xml one is supported."); - } - - /// - /// Get the status for this health check - /// - /// - public override IEnumerable GetStatus() - { - //return the statuses - return new[] { CheckContent(), CheckMedia(), CheckMembers() }; - } - - /// - /// Executes the action and returns it's status - /// - /// - /// - public override HealthCheckStatus ExecuteAction(HealthCheckAction action) - { - switch (action.Alias) - { - case CheckContentXmlTableAction: - _publishedSnapshotService.RebuildContentAndPreviewXml(); - return CheckContent(); - case CheckMediaXmlTableAction: - _publishedSnapshotService.RebuildMediaXml(); - return CheckMedia(); - case CheckMembersXmlTableAction: - _publishedSnapshotService.RebuildMemberXml(); - return CheckMembers(); - default: - throw new ArgumentOutOfRangeException(); - } - } - - private HealthCheckStatus CheckMembers() - { - return Check(_publishedSnapshotService.VerifyMemberXml(), CheckMembersXmlTableAction, "healthcheck/xmlDataIntegrityCheckMembers"); - } - - private HealthCheckStatus CheckMedia() - { - return Check(_publishedSnapshotService.VerifyMediaXml(), CheckMediaXmlTableAction, "healthcheck/xmlDataIntegrityCheckMedia"); - } - - private HealthCheckStatus CheckContent() - { - return Check(_publishedSnapshotService.VerifyContentAndPreviewXml(), CheckContentXmlTableAction, "healthcheck/xmlDataIntegrityCheckContent"); - } - - private HealthCheckStatus Check(bool ok, string action, string text) - { - var actions = new List(); - if (ok == false) - actions.Add(new HealthCheckAction(action, Id)); - - return new HealthCheckStatus(_textService.Localize(text, new[] { ok ? "ok" : "not ok" })) - { - ResultType = ok ? StatusResultType.Success : StatusResultType.Error, - Actions = actions - }; - } - } -} diff --git a/src/Umbraco.Web/HealthCheck/NotificationMethods/EmailNotificationMethod.cs b/src/Umbraco.Web/HealthCheck/NotificationMethods/EmailNotificationMethod.cs index a38a15608d..b697c87335 100644 --- a/src/Umbraco.Web/HealthCheck/NotificationMethods/EmailNotificationMethod.cs +++ b/src/Umbraco.Web/HealthCheck/NotificationMethods/EmailNotificationMethod.cs @@ -1,9 +1,11 @@ using System; +using System.Collections.Generic; using System.Net.Mail; using System.Threading; using System.Threading.Tasks; using Umbraco.Core; using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; using Umbraco.Core.Services; namespace Umbraco.Web.HealthCheck.NotificationMethods @@ -12,8 +14,10 @@ namespace Umbraco.Web.HealthCheck.NotificationMethods public class EmailNotificationMethod : NotificationMethodBase { private readonly ILocalizedTextService _textService; + private readonly IRuntimeState _runtimeState; + private readonly ILogger _logger; - public EmailNotificationMethod(ILocalizedTextService textService) + public EmailNotificationMethod(ILocalizedTextService textService, IRuntimeState runtimeState, ILogger logger) { var recipientEmail = Settings["recipientEmail"]?.Value; if (string.IsNullOrWhiteSpace(recipientEmail)) @@ -25,6 +29,8 @@ namespace Umbraco.Web.HealthCheck.NotificationMethods RecipientEmail = recipientEmail; _textService = textService ?? throw new ArgumentNullException(nameof(textService)); + _runtimeState = runtimeState; + _logger = logger; } public string RecipientEmail { get; } @@ -48,7 +54,11 @@ namespace Umbraco.Web.HealthCheck.NotificationMethods results.ResultsAsHtml(Verbosity) }); - var subject = _textService.Localize("healthcheck/scheduledHealthCheckEmailSubject"); + // Include the umbraco Application URL host in the message subject so that + // you can identify the site that these results are for. + var host = _runtimeState.ApplicationUrl; + + var subject = _textService.Localize("healthcheck/scheduledHealthCheckEmailSubject", new[] { host.ToString() }); var mailSender = new EmailSender(); using (var mailMessage = CreateMailMessage(subject, message)) diff --git a/src/Umbraco.Web/Models/ContentEditing/DashboardControl.cs b/src/Umbraco.Web/Models/ContentEditing/DashboardControl.cs index d51084fb16..aad6bf2d64 100644 --- a/src/Umbraco.Web/Models/ContentEditing/DashboardControl.cs +++ b/src/Umbraco.Web/Models/ContentEditing/DashboardControl.cs @@ -10,15 +10,6 @@ namespace Umbraco.Web.Models.ContentEditing [DataContract(Name = "control", Namespace = "")] public class DashboardControl { - [DataMember(Name = "showOnce")] - public bool ShowOnce { get; set; } - - [DataMember(Name = "addPanel")] - public bool AddPanel { get; set; } - - [DataMember(Name = "serverSide")] - public bool ServerSide { get; set; } - [DataMember(Name = "path")] public string Path { get; set; } diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheComponent.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheComponent.cs deleted file mode 100644 index 496818ab28..0000000000 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheComponent.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Components; -using Umbraco.Core.Services; -using Umbraco.Core.Strings; -using Umbraco.Core.Logging; -using Umbraco.Core.Scoping; -using Umbraco.Web.HealthCheck.Checks.DataIntegrity; -using LightInject; -using Umbraco.Core.Configuration; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.Persistence.Repositories; -using Umbraco.Web.Routing; - -namespace Umbraco.Web.PublishedCache.XmlPublishedCache -{ - [DisableComponent] // is not enabled by default - public class XmlCacheComponent : UmbracoComponentBase, IUmbracoCoreComponent - { - public override void Compose(Composition composition) - { - base.Compose(composition); - - // register the XML facade service - composition.SetPublishedSnapshotService(factory => new PublishedSnapshotService( - factory.GetInstance(), - factory.GetInstance(), - factory.GetInstance(), - factory.GetInstance().RequestCache, - factory.GetInstance(), - factory.GetInstance(), - factory.GetInstance(), - factory.GetInstance(), - factory.GetInstance(), - factory.GetInstance(), - factory.GetInstance(), - factory.GetInstance(), - factory.GetInstance(), - factory.GetInstance(), - factory.GetInstance())); - - // add the Xml cache health check (hidden from type finder) - composition.HealthChecks().Add(); - } - - public void Initialize(IPublishedSnapshotService service) - { - // nothing - this just ensures that the service is created at boot time - } - } -} diff --git a/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs b/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs index ee495b6d7d..e2b174753a 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs @@ -38,14 +38,6 @@ namespace Umbraco.Web.Routing var redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl(route); - // From: http://stackoverflow.com/a/22468386/5018 - // See http://issues.umbraco.org/issue/U4-8361#comment=67-30532 - // Setting automatic 301 redirects to not be cached because browsers cache these very aggressively which then leads - // to problems if you rename a page back to it's original name or create a new page with the original name - frequest.Cacheability = HttpCacheability.NoCache; - frequest.CacheExtensions = new List { "no-store, must-revalidate" }; - frequest.Headers = new Dictionary { { "Pragma", "no-cache" }, { "Expires", "0" } }; - if (redirectUrl == null) { _logger.Debug("No match for route: {Route}", route); @@ -60,8 +52,21 @@ namespace Umbraco.Web.Routing return false; } + // Apending any querystring from the incoming request to the redirect url. + url = string.IsNullOrEmpty(frequest.Uri.Query) ? url : url + frequest.Uri.Query; + _logger.Debug("Route {Route} matches content {ContentId} with url '{Url}', redirecting.", route, content.Id, url); frequest.SetRedirectPermanent(url); + + + // From: http://stackoverflow.com/a/22468386/5018 + // See http://issues.umbraco.org/issue/U4-8361#comment=67-30532 + // Setting automatic 301 redirects to not be cached because browsers cache these very aggressively which then leads + // to problems if you rename a page back to it's original name or create a new page with the original name + frequest.Cacheability = HttpCacheability.NoCache; + frequest.CacheExtensions = new List { "no-store, must-revalidate" }; + frequest.Headers = new Dictionary { { "Pragma", "no-cache" }, { "Expires", "0" } }; + return true; } } diff --git a/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs b/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs index 10b1e22eed..97cebdb076 100644 --- a/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs +++ b/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs @@ -122,6 +122,8 @@ namespace Umbraco.Web.Runtime composition.Container.EnableMvc(); // does container.EnablePerWebRequestScope() composition.Container.ScopeManagerProvider = smp; // reverts - we will do it last (in WebRuntime) + composition.Container.RegisterSingleton(); + composition.Container.RegisterUmbracoControllers(typeLoader, GetType().Assembly); composition.Container.EnableWebApi(GlobalConfiguration.Configuration); diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index 0788b36d42..e98f723501 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -391,6 +391,11 @@ namespace Umbraco.Web.Trees foreach (var m in notAllowed) { menuWithAllItems.Items.Remove(m); + // if the disallowed action is set as default action, make sure to reset the default action as well + if (menuWithAllItems.DefaultMenuAlias == m.Alias) + { + menuWithAllItems.DefaultMenuAlias = null; + } } } diff --git a/src/Umbraco.Web/UI/JavaScript/JsInitialize.js b/src/Umbraco.Web/UI/JavaScript/JsInitialize.js index eef1d25909..75ca46437a 100644 --- a/src/Umbraco.Web/UI/JavaScript/JsInitialize.js +++ b/src/Umbraco.Web/UI/JavaScript/JsInitialize.js @@ -1,7 +1,7 @@ [ 'lib/jquery/jquery.min.js', 'lib/jquery-ui/jquery-ui.min.js', - 'lib/jquery-ui-touch-punch/jquery.ui.touch-punch.js', + 'lib/jquery-ui-touch-punch/jquery.ui.touch-punch.min.js', 'lib/angular/angular.js', 'lib/underscore/underscore-min.js', @@ -23,7 +23,6 @@ 'lib/ng-file-upload/ng-file-upload.min.js', 'lib/angular-local-storage/angular-local-storage.min.js', - 'lib/bootstrap/js/bootstrap.2.3.2.min.js', 'lib/umbraco/Extensions.js', 'lib/umbraco/NamespaceManager.js', diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 32134e7a45..c2f674f929 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -190,7 +190,7 @@ - + @@ -272,7 +272,6 @@ - @@ -471,7 +470,6 @@ - @@ -1237,12 +1235,6 @@ FeedProxy.aspx - - EditRelationType.aspx - - - EditRelationType.aspx - NewRelationType.aspx ASPXCodeBehind @@ -1321,9 +1313,6 @@ - - ASPXCodeBehind - ASPXCodeBehind diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/EditRelationType.aspx b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/EditRelationType.aspx deleted file mode 100644 index b81a8c4e5f..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/EditRelationType.aspx +++ /dev/null @@ -1,146 +0,0 @@ -<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="EditRelationType.aspx.cs" Inherits="umbraco.cms.presentation.developer.RelationTypes.EditRelationType" MasterPageFile="../../masterpages/umbracoPage.Master" %> -<%@ Register TagPrefix="umb" Namespace="Umbraco.Web._Legacy.Controls" %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Parent  ChildCreatedComment
 <%# DataBinder.Eval(Container.DataItem, "ParentText") %>  <%# DataBinder.Eval(Container.DataItem, "ChildText") %><%# DataBinder.Eval(Container.DataItem, "DateTime") %><%# DataBinder.Eval(Container.DataItem, "Comment") %>
- -
- - -
- -
- - -
\ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/EditRelationType.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/EditRelationType.aspx.cs deleted file mode 100644 index 74c639b4a7..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/EditRelationType.aspx.cs +++ /dev/null @@ -1,287 +0,0 @@ -//TODO: Rebuild with new tree format and apis and then remove -//using umbraco.uicontrols; - -//using System; -//using System.Collections.Generic; -//using System.Web.UI; -//using System.Web.UI.WebControls; -// -//using umbraco.BusinessLogic; -//using Umbraco.Core; -//using Umbraco.Core.Models; -//using RelationType = umbraco.cms.businesslogic.relation.RelationType; - -//namespace umbraco.cms.presentation.developer.RelationTypes -//{ -// /// -// /// Edit an existing RelationType -// /// -// [WebformsPageTreeAuthorize(Constants.Trees.RelationTypes)] -// public partial class EditRelationType : UmbracoEnsuredPage -// { - -// /// -// /// Class scope reference to the current RelationType being edited -// /// -// private IRelationType _relationType; - -// /// -// /// Class scope reference to the relations associated with the current RelationType -// /// -// private List _relations; - -// /// -// /// Umbraco ObjectType used to represent all parent items in this relation type -// /// -// /// -// private UmbracoObjectTypes _parentObjectType; - -// /// -// /// Umbraco ObjectType used to represent all child items in this relation type -// /// -// private UmbracoObjectTypes _childObjectType; - -// /// -// /// Gets the name of the parent object type for all relations in this relation type -// /// -// protected string ParentObjectType -// { -// get -// { -// return this._parentObjectType.GetName(); //UmbracoHelper.GetName(this.parentObjectType); -// } -// } - -// /// -// /// Gets the name of the child object type for all relations in this relation type -// /// -// protected string ChildObjectType -// { -// get -// { -// return this._childObjectType.GetName(); //UmbracoHelper.GetName(this.childObjectType); -// } -// } - -// /// -// /// Gets a string representing the current relation type direction -// /// -// protected string RelationTypeDirection -// { -// get -// { -// return this._relationType.IsBidirectional ? "bidirectional" : "parentToChild"; -// } -// } - -// /// -// /// Gets the Relations for this RelationType, via lazy load -// /// -// private List Relations -// { -// get -// { -// if (this._relations == null) -// { -// this._relations = new List(); - -// using (var reader = uQuery.SqlHelper.ExecuteReader(@" -// SELECT A.id, -// A.parentId, -// B.[text] AS parentText, -// A.childId, -// C.[text] AS childText, -// A.relType, -// A.[datetime], -// A.comment -// FROM umbracoRelation A -// LEFT OUTER JOIN umbracoNode B ON A.parentId = B.id -// LEFT OUTER JOIN umbracoNode C ON A.childId = C.id -// WHERE A.relType = " + this._relationType.Id.ToString())) -// { -// while (reader.Read()) -// { -// var readOnlyRelation = new ReadOnlyRelation(); - -// readOnlyRelation.Id = reader.GetInt("id"); -// readOnlyRelation.ParentId = reader.GetInt("parentId"); -// readOnlyRelation.ParentText = reader.GetString("parentText"); -// readOnlyRelation.ChildId = reader.GetInt("childId"); -// readOnlyRelation.ChildText = reader.GetString("childText"); -// readOnlyRelation.RelType = reader.GetInt("relType"); -// readOnlyRelation.DateTime = reader.GetDateTime("datetime"); -// readOnlyRelation.Comment = reader.GetString("comment"); - -// this._relations.Add(readOnlyRelation); -// } -// } -// } - -// return this._relations; -// } -// } - -// /// -// /// On Load event -// /// -// /// this aspx page -// /// EventArgs (expect empty) -// protected void Page_Load(object sender, EventArgs e) -// { -// int id; -// if (int.TryParse(Request.QueryString["id"], out id)) -// { -// var relationService = Services.RelationService; - -// this._relationType = relationService.GetRelationTypeById(id); -// if (this._relationType != null) -// { -// this._parentObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(this._relationType.ParentObjectType); -// this._childObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(this._relationType.ChildObjectType); - -// // ----------- - -// if (!this.IsPostBack) -// { -// this.EnsureChildControls(); - -// this.idLiteral.Text = this._relationType.Id.ToString(); -// this.nameTextBox.Text = this._relationType.Name; -// this.aliasTextBox.Text = this._relationType.Alias; - -// if (this._relationType.IsBidirectional) -// { -// this.dualRadioButtonList.Items.FindByValue("1").Selected = true; -// } -// else -// { -// this.dualRadioButtonList.Items.FindByValue("0").Selected = true; -// } - -// this.parentLiteral.Text = this._parentObjectType.GetFriendlyName(); -// // UmbracoHelper.GetFriendlyName(this.parentObjectType); -// this.childLiteral.Text = this._childObjectType.GetFriendlyName(); -// // UmbracoHelper.GetFriendlyName(this.childObjectType); - -// this.relationsCountLiteral.Text = this.Relations.Count.ToString(); - -// this.relationsRepeater.DataSource = this.Relations; -// this.relationsRepeater.DataBind(); -// } -// } -// else -// { -// throw new Exception("Unable to get RelationType where ID = " + id); -// } -// } -// else -// { -// throw new Exception("Invalid RelationType ID"); -// } -// } - -// /// -// /// Creates the child controls used in this page -// /// -// protected override void CreateChildControls() -// { -// base.CreateChildControls(); -/* - var save = tabControl.Menu.NewButton(); - save.Click +=saveMenuImageButton_Click; - save.CausesValidation = true; - save.Text = ui.Text("save"); - save.ButtonType = MenuButtonType.Primary; - save.ID = "save"; - save.ValidationGroup = "RelationType";*/ - -// var relationsTabPage = this.tabControl.NewTabPage("Relations"); -// saveMenuImageButton.ToolTip = "save relation type"; -// saveMenuImageButton.Click +=saveMenuImageButton_Click; -// saveMenuImageButton.CausesValidation = true; -// saveMenuImageButton.Text = Services.TextService.Localize("save"); -// saveMenuImageButton.ValidationGroup = "RelationType"; - -// var relationsTabPage = this.tabControl.NewTabPage("Relations"); -// relationsTabPage.Controls.Add(this.relationsCountPane); -// relationsTabPage.Controls.Add(this.relationsPane); - -// /* -// var refreshMenuImageButton = relationsTabPage.Menu.NewImageButton(); -// refreshMenuImageButton.AlternateText = "refresh relations"; -// refreshMenuImageButton.Click += this.RefreshMenuImageButton_Click; -// refreshMenuImageButton.ImageUrl = "/umbraco/developer/RelationTypes/Images/Refresh.gif"; -// refreshMenuImageButton.CausesValidation = false;*/ -// } - -// /// -// /// check that alias hasn't been changed to clash with another (except itself) -// /// -// /// the aliasCustomValidator control -// /// to set validation respose -// protected void AliasCustomValidator_ServerValidate(object source, ServerValidateEventArgs args) -// { -// args.IsValid = (RelationType.GetByAlias(this.aliasTextBox.Text.Trim()) == null) || -// (this.aliasTextBox.Text.Trim() == this._relationType.Alias); -// } - -// /// -// /// Reload the relations, in case they have changed -// /// -// /// expects refreshMenuImageButton -// /// expects ImageClickEventArgs -// private void RefreshMenuImageButton_Click(object sender, ImageClickEventArgs e) -// { -// } - -// /// -// /// Save button in Umbraco menu -// /// -// /// expects saveMenuImageButton object -// /// expects ImageClickEventArgs -// void saveMenuImageButton_Click(object sender, EventArgs e) -// { -// if (this.Page.IsValid) -// { -// var nameChanged = this._relationType.Name != this.nameTextBox.Text.Trim(); -// var aliasChanged = this._relationType.Alias != this.aliasTextBox.Text.Trim(); -// var directionChanged = this._relationType.IsBidirectional != (this.dualRadioButtonList.SelectedValue == "1"); - -// if (nameChanged || aliasChanged || directionChanged) -// { -// string bubbleBody = string.Empty; - -// if (nameChanged) -// { -// bubbleBody += "Name, "; - -// this._relationType.Name = this.nameTextBox.Text.Trim(); - -// // Refresh tree, as the name as changed -// ClientTools.SyncTree(this._relationType.Id.ToString(), true); -// } - -// if (directionChanged) -// { -// bubbleBody += "Direction, "; -// this._relationType.IsBidirectional = this.dualRadioButtonList.SelectedValue == "1"; -// } - -// if (aliasChanged) -// { -// bubbleBody += "Alias, "; -// this._relationType.Alias = this.aliasTextBox.Text.Trim(); -// } - -// bubbleBody = bubbleBody.Remove(bubbleBody.LastIndexOf(','), 1); -// bubbleBody = bubbleBody + "Changed"; - -// var relationService = Services.RelationService; -// relationService.Save(this._relationType); - -// ClientTools.ShowSpeechBubble(SpeechBubbleIcon.Save, "Relation Type Updated", bubbleBody); -// } -// } -// } -// } -//} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/EditRelationType.aspx.designer.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/EditRelationType.aspx.designer.cs deleted file mode 100644 index 7e8475fdfb..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/EditRelationType.aspx.designer.cs +++ /dev/null @@ -1,249 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace umbraco.cms.presentation.developer.RelationTypes { - - - public partial class EditRelationType { - - /// - /// tabControl control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.TabView tabControl; - - /// - /// idPane control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.Pane idPane; - - /// - /// idPropertyPanel control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.PropertyPanel idPropertyPanel; - - /// - /// idLiteral control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Literal idLiteral; - - /// - /// nameAliasPane control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.Pane nameAliasPane; - - /// - /// nameProperyPanel control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.PropertyPanel nameProperyPanel; - - /// - /// nameTextBox control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.TextBox nameTextBox; - - /// - /// nameRequiredFieldValidator control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.RequiredFieldValidator nameRequiredFieldValidator; - - /// - /// aliasPropertyPanel control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.PropertyPanel aliasPropertyPanel; - - /// - /// aliasTextBox control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.TextBox aliasTextBox; - - /// - /// aliasRequiredFieldValidator control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.RequiredFieldValidator aliasRequiredFieldValidator; - - /// - /// aliasCustomValidator control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.CustomValidator aliasCustomValidator; - - /// - /// directionPane control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.Pane directionPane; - - /// - /// dualPropertyPanel control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.PropertyPanel dualPropertyPanel; - - /// - /// dualRadioButtonList control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.RadioButtonList dualRadioButtonList; - - /// - /// objectTypePane control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.Pane objectTypePane; - - /// - /// parentPropertyPanel control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.PropertyPanel parentPropertyPanel; - - /// - /// parentLiteral control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Literal parentLiteral; - - /// - /// childPropertyPanel control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.PropertyPanel childPropertyPanel; - - /// - /// childLiteral control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Literal childLiteral; - - /// - /// relationsCountPane control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.Pane relationsCountPane; - - /// - /// relationsCountPropertyPanel control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.PropertyPanel relationsCountPropertyPanel; - - /// - /// relationsCountLiteral control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Literal relationsCountLiteral; - - /// - /// relationsPane control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.Pane relationsPane; - - /// - /// relationsPropertyPanel control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.PropertyPanel relationsPropertyPanel; - - /// - /// relationsRepeater control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Repeater relationsRepeater; - } -}