diff --git a/build/build.ps1 b/build/build.ps1 index 5f85284431..964c29fb63 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -298,7 +298,11 @@ # copy libs Write-Host "Copy SqlCE libraries" - $nugetPackages = [System.Environment]::ExpandEnvironmentVariables("%userprofile%\.nuget\packages") + $nugetPackages = $env:NUGET_PACKAGES + if (-not $nugetPackages) + { + $nugetPackages = [System.Environment]::ExpandEnvironmentVariables("%userprofile%\.nuget\packages") + } $this.CopyFiles("$nugetPackages\umbraco.sqlserverce\4.0.0.1\runtimes\win-x86\native", "*.*", "$tmp\bin\x86") $this.CopyFiles("$nugetPackages\umbraco.sqlserverce\4.0.0.1\runtimes\win-x64\native", "*.*", "$tmp\bin\amd64") $this.CopyFiles("$nugetPackages\umbraco.sqlserverce\4.0.0.1\runtimes\win-x86\native", "*.*", "$tmp\WebApp\bin\x86") diff --git a/docs/README.md b/docs/README.md index 067a4b83d0..5316ad0604 100644 --- a/docs/README.md +++ b/docs/README.md @@ -46,6 +46,6 @@ Umbraco is contribution focused and community driven. If you want to contribute ## Found a bug? -Another way you can contribute to Umbraco is by providing issue reports. For information on how to submit an issue report refer to our [online guide for reporting issues](https://github.com/umbraco/Umbraco-CMS/blob/dev-v7/docs/CONTRIBUTING.md). +Another way you can contribute to Umbraco is by providing issue reports. For information on how to submit an issue report refer to our [online guide for reporting issues](CONTRIBUTING_DETAILED.md#reporting-bugs). To view existing issues, please visit [http://issues.umbraco.org](http://issues.umbraco.org). diff --git a/src/Umbraco.Core/ContentVariationExtensions.cs b/src/Umbraco.Core/ContentVariationExtensions.cs index 092de4d6d6..d18fb4b091 100644 --- a/src/Umbraco.Core/ContentVariationExtensions.cs +++ b/src/Umbraco.Core/ContentVariationExtensions.cs @@ -75,6 +75,26 @@ namespace Umbraco.Core /// public static bool VariesByCultureAndSegment(this PublishedContentType contentType) => contentType.Variations.VariesByCultureAndSegment(); + /// + /// Determines whether the property type is invariant. + /// + public static bool VariesByNothing(this PublishedPropertyType propertyType) => propertyType.Variations.VariesByNothing(); + + /// + /// Determines whether the property type varies by culture. + /// + public static bool VariesByCulture(this PublishedPropertyType propertyType) => propertyType.Variations.VariesByCulture(); + + /// + /// Determines whether the property type varies by segment. + /// + public static bool VariesBySegment(this PublishedPropertyType propertyType) => propertyType.Variations.VariesBySegment(); + + /// + /// Determines whether the property type varies by culture and segment. + /// + public static bool VariesByCultureAndSegment(this PublishedPropertyType propertyType) => propertyType.Variations.VariesByCultureAndSegment(); + /// /// Determines whether a variation is invariant. /// diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index 3729ae1877..48ac39a630 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -113,6 +113,10 @@ namespace Umbraco.Core.Migrations.Upgrade Chain("{1350617A-4930-4D61-852F-E3AA9E692173}"); Chain("{39E5B1F7-A50B-437E-B768-1723AEC45B65}"); // from 7.12.0 + Chain("{0541A62B-EF87-4CA2-8225-F0EB98ECCC9F}"); // from 7.12.0 + Chain("{EB34B5DC-BB87-4005-985E-D983EA496C38}"); // from 7.12.0 + Chain("{517CE9EA-36D7-472A-BF4B-A0D6FB1B8F89}"); // from 7.12.0 + Chain("{BBD99901-1545-40E4-8A5A-D7A675C7D2F2}"); // from 7.12.0 //FINAL @@ -127,13 +131,13 @@ namespace Umbraco.Core.Migrations.Upgrade From("{init-7.11.0}").Chain("{init-7.10.0}"); // same as 7.10.0 From("{init-7.11.1}").Chain("{init-7.10.0}"); // same as 7.10.0 - // 7.12.0 has a migration, define a custom chain which copies the chain - // going from {init-7.10.0} to former final, and then goes straight to - // main chain, skipping the migration + // 7.12.0 has migrations, define a custom chain which copies the chain + // going from {init-7.10.0} to former final (1350617A) , and then goes straight to + // main chain, skipping the migrations // From("{init-7.12.0}"); // copy from copy to (former final) main chain - CopyChain("{init-7.10.0}", "{1350617A-4930-4D61-852F-E3AA9E692173}", "{39E5B1F7-A50B-437E-B768-1723AEC45B65}"); + CopyChain("{init-7.10.0}", "{1350617A-4930-4D61-852F-E3AA9E692173}", "{BBD99901-1545-40E4-8A5A-D7A675C7D2F2}"); } } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/IncreaseLanguageIsoCodeColumnLength.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/IncreaseLanguageIsoCodeColumnLength.cs index 302069855a..400fc0bcdd 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/IncreaseLanguageIsoCodeColumnLength.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/IncreaseLanguageIsoCodeColumnLength.cs @@ -1,14 +1,10 @@ -using Umbraco.Core.Logging; -using Umbraco.Core.Migrations; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenTwelveZero +namespace Umbraco.Core.Migrations.Upgrade.V_7_12_0 { public class IncreaseLanguageIsoCodeColumnLength : MigrationBase { - public IncreaseLanguageIsoCodeColumnLength(IMigrationContext context) : base(context) - { - } + public IncreaseLanguageIsoCodeColumnLength(IMigrationContext context) + : base(context) + { } public override void Migrate() { @@ -26,7 +22,5 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenTwelveZ .Unique() .Do(); } - - } } diff --git a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs index 46f024429e..09411f6d20 100644 --- a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs @@ -279,7 +279,7 @@ AnotherContentFinder public void Resolves_Trees() { var trees = _typeLoader.GetTrees(); - Assert.AreEqual(4, trees.Count()); + Assert.AreEqual(3, trees.Count()); } [Test] @@ -288,7 +288,7 @@ AnotherContentFinder var types = _typeLoader.GetDataEditors(); Assert.AreEqual(43, types.Count()); } - + /// /// This demonstrates this issue: http://issues.umbraco.org/issue/U4-3505 - the TypeList was returning a list of assignable types /// not explicit types which is sort of ideal but is confusing so we'll do it the less confusing way. diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorswatches.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorswatches.directive.js index 6ed65a9431..2b4004da11 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorswatches.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorswatches.directive.js @@ -29,9 +29,7 @@ Use this directive to generate color swatches to pick from. function link(scope, el, attr, ctrl) { scope.setColor = function (color) { - //scope.selectedColor({color: color }); scope.selectedColor = color; - if (scope.onSelect) { scope.onSelect(color); } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/check-circle.less b/src/Umbraco.Web.UI.Client/src/less/components/check-circle.less index a4c3a89c99..fadf9c7940 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/check-circle.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/check-circle.less @@ -1,6 +1,5 @@ .check_circle { display: flex; - opacity: 0; width: 20px; height: 20px; margin: 0 auto; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less index e27bd0d654..c7905879d8 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less @@ -18,12 +18,6 @@ box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); } - &.active { - .check_circle { - opacity: 1; - } - } - &.umb-color-box--m { width: 40px; height: 40px; diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index fd9296259f..283d538b75 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -139,12 +139,6 @@ ul.color-picker li { border: 2px solid transparent; width: 60px; - &.active { - .check_circle { - opacity: 1; - } - } - .thumbnail{ min-width: auto; width: inherit; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.controller.js index f428e64e3e..05c76beeae 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.controller.js @@ -13,6 +13,28 @@ function IconPickerController($scope, iconHelper, localizationService) { vm.selectIcon = selectIcon; vm.close = close; + vm.colors = [ + { name: "Black", value: "color-black" }, + { name: "Blue Grey", value: "color-blue-grey" }, + { name: "Grey", value: "color-grey" }, + { name: "Brown", value: "color-brown" }, + { name: "Blue", value: "color-blue" }, + { name: "Light Blue", value: "color-light-blue" }, + { name: "Indigo", value: "color-indigo" }, + { name: "Purple", value: "color-purple" }, + { name: "Deep Purple", value: "color-deep-purple" }, + { name: "Cyan", value: "color-cyan" }, + { name: "Green", value: "color-green" }, + { name: "Light Green", value: "color-light-green" }, + { name: "Lime", value: "color-lime" }, + { name: "Yellow", value: "color-yellow" }, + { name: "Amber", value: "color-amber" }, + { name: "Orange", value: "color-orange" }, + { name: "Deep Orange", value: "color-deep-orange" }, + { name: "Red", value: "color-red" }, + { name: "Pink", value: "color-pink" } + ]; + function onInit() { vm.loading = true; @@ -24,6 +46,12 @@ function IconPickerController($scope, iconHelper, localizationService) { vm.loading = false; }); + // set a default color if nothing is passed in + vm.color = $scope.model.color ? $scope.model.color : vm.colors[0].value; + + // if an icon is passed in - preselect it + vm.icon = $scope.model.icon ? $scope.model.icon : undefined; + } function setTitle() { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html index 320e0cee8a..8a7358116a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html @@ -32,35 +32,19 @@
- + +
-
    -
  • - +
      +
    • +
    • diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-color-swatches.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-color-swatches.html index eae299e579..d3105d52a1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-color-swatches.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-color-swatches.html @@ -1,7 +1,7 @@ 
      - 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 1cb61f4168..f726800696 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,9 +5,9 @@
        -
      • +
      • -
        +
        diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html index 7d88fb068f..a70ecabf35 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html @@ -1,4 +1,4 @@ -
        +
        diff --git a/src/Umbraco.Web.UI.Client/test/unit/common/services/template-helper.spec.js b/src/Umbraco.Web.UI.Client/test/unit/common/services/template-helper.spec.js index 0ccc9ab1dc..08a69b7e80 100644 --- a/src/Umbraco.Web.UI.Client/test/unit/common/services/template-helper.spec.js +++ b/src/Umbraco.Web.UI.Client/test/unit/common/services/template-helper.spec.js @@ -58,13 +58,14 @@ describe('service: templateHelper', function () { it('should return the snippet for a query', function () { var queryExpression = "queryExpression"; var snippet = "\n@{\n" + "\tvar selection = " + queryExpression + ";\n}\n"; - snippet += "
          \n" + - "\t@foreach(var item in selection){\n" + - "\t\t
        • \n" + - "\t\t\t@item.Name\n" + - "\t\t
        • \n" + - "\t}\n" + - "
        \n\n"; + snippet += "
          \n" + + "\t@foreach (var item in selection)\n" + + "\t{\n" + + "\t\t
        • \n" + + "\t\t\t@item.Name\n" + + "\t\t
        • \n" + + "\t}\n" + + "
        \n\n"; expect(templateHelper.getQuerySnippet(queryExpression)).toBe(snippet); }); diff --git a/src/Umbraco.Web.UI/umbraco_client/Dialogs/AssignDomain2.js b/src/Umbraco.Web.UI/umbraco_client/Dialogs/AssignDomain2.js index ddf38733e1..b5921b6576 100644 --- a/src/Umbraco.Web.UI/umbraco_client/Dialogs/AssignDomain2.js +++ b/src/Umbraco.Web.UI/umbraco_client/Dialogs/AssignDomain2.js @@ -1,145 +1,145 @@ -Umbraco.Sys.registerNamespace("Umbraco.Dialogs"); - -(function ($) { - - // register AssignDomain dialog - Umbraco.Dialogs.AssignDomain2 = base2.Base.extend({ - - _opts: null, - - _isRepeated: function (element) { - var inputs = $('form input.domain'); - var elementName = element.attr('name'); - var repeated = false; - inputs.each(function() { - var input = $(this); - if (input.attr('name') != elementName && input.val() == element.val()) - repeated = true; - }); - return repeated; - }, - - // constructor - constructor: function (opts) { - // merge options with default - this._opts = $.extend({ - invalidDomain: 'Invalid domain.', - duplicateDomain: 'Domain has already been assigned.' - }, opts); - }, - - // public methods/variables - - languages: null, - language: null, - domains: null, - - addDomain: function () { - this.domains.push({ - Name: "", - Lang: "" - }); - }, - - init: function () { - var self = this; - - self.domains = ko.observableArray(self._opts.domains); - self.languages = self._opts.languages; - self.language = self._opts.language; - self.removeDomain = function() { self.domains.remove(this); }; - - ko.applyBindings(self); - - $.validator.addMethod("domain", function (value, element, param) { - // beware! encode('test') == 'test-' - // read eg https://rt.cpan.org/Public/Bug/Display.html?id=94347 - value = punycode.encode(value); - // that regex is best-effort and certainly not exact - var re = /^(http[s]?:\/\/)?([-\w]+(\.[-\w]+)*)(:\d+)?(\/[-\w]*|-)?$/gi; - var isopt = this.optional(element); - var retest = re.test(value); - var ret = isopt || retest; - return ret; - }, self._opts.invalidDomain); - - function getDuplicateMessage(val, el) { - var other = $(el).nextAll('input').val(); - var msg = self._opts.duplicateDomain - if (other != "" && other != "!!!") - msg = msg + ' (' + other + ')'; - return msg; - } - - $.validator.addMethod("duplicate", function (value, element, param) { - return $(element).nextAll('input').val() == "" && !self._isRepeated($(element)); - }, getDuplicateMessage); - - $.validator.addClassRules({ - domain: { domain: true }, - duplicate: { duplicate: true } - }); - - $('form').validate({ - debug: true, - focusCleanup: true, - onkeyup: false - }); - - $('form input.domain').on('focus', function(event) { - if (event.type != 'focusin') return; - $(this).nextAll('input').val(""); - }); - - // force validation *now* - $('form').valid(); - - $('#btnSave').click(function () { - if (!$('form').valid()) - return false; - - var mask = $('#komask'); - var masked = mask.parent(); - mask.height(masked.height()); - mask.width(masked.width()); - mask.show(); - - var data = { nodeId: self._opts.nodeId, language: self.language ? self.language : 0, domains: self.domains }; - $.post(self._opts.restServiceLocation + 'PostSaveLanguageAndDomains', ko.toJSON(data), function (json) { - mask.hide(); - - if (json.Valid) { - UmbClientMgr.closeModalWindow(); - } - else { - var inputs = $('form input.domain'); - inputs.each(function() { $(this).nextAll('input').val(""); }); - for (var i = 0; i < json.Domains.length; i++) { - var d = json.Domains[i]; - if (d.Duplicate) - inputs.each(function() { - var input = $(this); - if (input.val() == d.Name) - input.nextAll('input').val(d.Other ? d.Other : "!!!"); - }); - } - $('form').valid(); - } - }) - .fail(function (xhr, textStatus, errorThrown) { - mask.css('opacity', 1).css('color', "#ff0000").html(xhr.responseText); - }); - return false; - }); - } - - }); - - // set defaults for jQuery ajax calls - $.ajaxSetup({ - dataType: 'json', - cache: false, - contentType: 'application/json; charset=utf-8' - }); - -})(jQuery); +Umbraco.Sys.registerNamespace("Umbraco.Dialogs"); + +(function ($) { + + // register AssignDomain dialog + Umbraco.Dialogs.AssignDomain2 = base2.Base.extend({ + + _opts: null, + + _isRepeated: function (element) { + var inputs = $('form input.domain'); + var elementName = element.attr('name'); + var repeated = false; + inputs.each(function() { + var input = $(this); + if (input.attr('name') != elementName && input.val() == element.val()) + repeated = true; + }); + return repeated; + }, + + // constructor + constructor: function (opts) { + // merge options with default + this._opts = $.extend({ + invalidDomain: 'Invalid domain.', + duplicateDomain: 'Domain has already been assigned.' + }, opts); + }, + + // public methods/variables + + languages: null, + language: null, + domains: null, + + addDomain: function () { + this.domains.push({ + Name: "", + Lang: "" + }); + }, + + init: function () { + var self = this; + + self.domains = ko.observableArray(self._opts.domains); + self.languages = self._opts.languages; + self.language = self._opts.language; + self.removeDomain = function() { self.domains.remove(this); }; + + ko.applyBindings(self); + + $.validator.addMethod("domain", function (value, element, param) { + // beware! encode('test') == 'test-' + // read eg https://rt.cpan.org/Public/Bug/Display.html?id=94347 + value = punycode.encode(value); + // that regex is best-effort and certainly not exact + var re = /^(http[s]?:\/\/)?([-\w]+(\.[-\w]+)*)(:\d+)?(\/[-\w]*|-)?$/gi; + var isopt = this.optional(element); + var retest = re.test(value); + var ret = isopt || retest; + return ret; + }, self._opts.invalidDomain); + + function getDuplicateMessage(val, el) { + var other = $(el).nextAll('input').val(); + var msg = self._opts.duplicateDomain + if (other != "" && other != "!!!") + msg = msg + ' (' + other + ')'; + return msg; + } + + $.validator.addMethod("duplicate", function (value, element, param) { + return $(element).nextAll('input').val() == "" && !self._isRepeated($(element)); + }, getDuplicateMessage); + + $.validator.addClassRules({ + domain: { domain: true }, + duplicate: { duplicate: true } + }); + + $('form').validate({ + debug: true, + focusCleanup: true, + onkeyup: false + }); + + $('form input.domain').on('focus', function(event) { + if (event.type != 'focusin') return; + $(this).nextAll('input').val(""); + }); + + // force validation *now* + $('form').valid(); + + $('#btnSave').click(function () { + if (!$('form').valid()) + return false; + + var mask = $('#komask'); + var masked = mask.parent(); + mask.height(masked.height()); + mask.width(masked.width()); + mask.show(); + + var data = { nodeId: self._opts.nodeId, language: self.language ? self.language : 0, domains: self.domains }; + $.post(self._opts.restServiceLocation + 'PostSaveLanguageAndDomains', ko.toJSON(data), function (json) { + mask.hide(); + + if (json.Valid) { + UmbClientMgr.closeModalWindow(); + } + else { + var inputs = $('form input.domain'); + inputs.each(function() { $(this).nextAll('input').val(""); }); + for (var i = 0; i < json.Domains.length; i++) { + var d = json.Domains[i]; + if (d.Duplicate) + inputs.each(function() { + var input = $(this); + if (input.val() == d.Name) + input.nextAll('input').val(d.Other ? d.Other : "!!!"); + }); + } + $('form').valid(); + } + }) + .fail(function (xhr, textStatus, errorThrown) { + mask.css('opacity', 1).css('color', "#ff0000").html(xhr.responseText); + }); + return false; + }); + } + + }); + + // set defaults for jQuery ajax calls + $.ajaxSetup({ + dataType: 'json', + cache: false, + contentType: 'application/json; charset=utf-8' + }); + +})(jQuery); diff --git a/src/Umbraco.Web/Mvc/UrlHelperExtensions.cs b/src/Umbraco.Web/Mvc/UrlHelperExtensions.cs index 3e29d88d5f..6eb568c6af 100644 --- a/src/Umbraco.Web/Mvc/UrlHelperExtensions.cs +++ b/src/Umbraco.Web/Mvc/UrlHelperExtensions.cs @@ -1,79 +1,79 @@ -using System; -using System.Web.Mvc; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.IO; -using Umbraco.Core.Xml; - -namespace Umbraco.Web.Mvc -{ - /// - /// Extension methods for UrlHelper - /// - public static class UrlHelperExtensions - { - /// - /// Utility method for checking for valid proxy urls or redirect urls to prevent Open Redirect security issues - /// - /// - /// The url to validate - /// The url of the current local domain (to ensure we can validate if the requested url is local without dependency on the request) - /// True if it's an allowed url - public static bool ValidateProxyUrl(this UrlHelper urlHelper, string url, string callerUrl) - { - if (Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute) == false) - { - return false; - } - - if (url.StartsWith("//")) - return false; - - Uri requestUri; - if (Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out requestUri)) - { - if (string.IsNullOrEmpty(callerUrl) == false) - { - Uri localUri; - if (Uri.TryCreate(callerUrl, UriKind.RelativeOrAbsolute, out localUri)) - { - // check for local urls - - //Cannot start with // since that is not a local url - if (requestUri.OriginalString.StartsWith("//") == false - //cannot be non-absolute and also contain the char : since that will indicate a protocol - && (requestUri.IsAbsoluteUri == false && requestUri.OriginalString.Contains(":") == false) - //needs to be non-absolute or the hosts must match the current request - && (requestUri.IsAbsoluteUri == false || requestUri.Host == localUri.Host)) - { - return true; - } - } - else - { - return false; - } - } - - //we cannot continue if the url is not absolute - if (requestUri.IsAbsoluteUri == false) - { - return false; - } - - // check for valid proxy urls - var feedProxyXml = XmlHelper.OpenAsXmlDocument(IOHelper.MapPath(SystemFiles.FeedProxyConfig)); - if (feedProxyXml != null && - feedProxyXml.SelectSingleNode(string.Concat("//allow[@host = '", requestUri.Host, "']")) != null) - { - return true; - } - } - else - { - return false; - } - return false; - } - } -} +using System; +using System.Web.Mvc; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; +using Umbraco.Core.Xml; + +namespace Umbraco.Web.Mvc +{ + /// + /// Extension methods for UrlHelper + /// + public static class UrlHelperExtensions + { + /// + /// Utility method for checking for valid proxy urls or redirect urls to prevent Open Redirect security issues + /// + /// + /// The url to validate + /// The url of the current local domain (to ensure we can validate if the requested url is local without dependency on the request) + /// True if it's an allowed url + public static bool ValidateProxyUrl(this UrlHelper urlHelper, string url, string callerUrl) + { + if (Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute) == false) + { + return false; + } + + if (url.StartsWith("//")) + return false; + + Uri requestUri; + if (Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out requestUri)) + { + if (string.IsNullOrEmpty(callerUrl) == false) + { + Uri localUri; + if (Uri.TryCreate(callerUrl, UriKind.RelativeOrAbsolute, out localUri)) + { + // check for local urls + + //Cannot start with // since that is not a local url + if (requestUri.OriginalString.StartsWith("//") == false + //cannot be non-absolute and also contain the char : since that will indicate a protocol + && (requestUri.IsAbsoluteUri == false && requestUri.OriginalString.Contains(":") == false) + //needs to be non-absolute or the hosts must match the current request + && (requestUri.IsAbsoluteUri == false || requestUri.Host == localUri.Host)) + { + return true; + } + } + else + { + return false; + } + } + + //we cannot continue if the url is not absolute + if (requestUri.IsAbsoluteUri == false) + { + return false; + } + + // check for valid proxy urls + var feedProxyXml = XmlHelper.OpenAsXmlDocument(IOHelper.MapPath(SystemFiles.FeedProxyConfig)); + if (feedProxyXml != null && + feedProxyXml.SelectSingleNode(string.Concat("//allow[@host = '", requestUri.Host, "']")) != null) + { + return true; + } + } + else + { + return false; + } + return false; + } + } +} diff --git a/src/Umbraco.Web/Properties/Settings1.Designer.cs b/src/Umbraco.Web/Properties/Settings.Designer.cs similarity index 98% rename from src/Umbraco.Web/Properties/Settings1.Designer.cs rename to src/Umbraco.Web/Properties/Settings.Designer.cs index 2f68c9774c..5a5a863f4f 100644 --- a/src/Umbraco.Web/Properties/Settings1.Designer.cs +++ b/src/Umbraco.Web/Properties/Settings.Designer.cs @@ -12,7 +12,7 @@ namespace Umbraco.Web.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.6.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.7.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); @@ -44,7 +44,7 @@ namespace Umbraco.Web.Properties { [global::System.Configuration.ApplicationScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("Somthing")] + [global::System.Configuration.DefaultSettingValueAttribute("Something")] public string test { get { return ((string)(this["test"])); diff --git a/src/Umbraco.Web/Properties/Settings.settings b/src/Umbraco.Web/Properties/Settings.settings index d6489e3251..757b363da2 100644 --- a/src/Umbraco.Web/Properties/Settings.settings +++ b/src/Umbraco.Web/Properties/Settings.settings @@ -9,7 +9,7 @@ http://update.umbraco.org/checkforupgrade.asmx - Somthing + Something https://our.umbraco.com/umbraco/webservices/api/repository.asmx diff --git a/src/Umbraco.Web/PropertyEditors/ColorListPreValueEditor.cs b/src/Umbraco.Web/PropertyEditors/ColorListPreValueEditor.cs deleted file mode 100644 index 60785d283e..0000000000 --- a/src/Umbraco.Web/PropertyEditors/ColorListPreValueEditor.cs +++ /dev/null @@ -1,183 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Text.RegularExpressions; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors -{ - internal class ColorListPreValueEditor : ValueListPreValueEditor - { - - public ColorListPreValueEditor() - { - var field = Fields.First(); - - //use a custom editor too - field.View = "views/propertyeditors/colorpicker/colorpicker.prevalues.html"; - //change the description - field.Description = "Add, remove or sort colors."; - //change the label - field.Name = "Colors"; - //need to have some custom validation happening here - field.Validators.Add(new ColorListValidator()); - - Fields.Insert(0, new PreValueField - { - Name = "Include labels?", - View = "boolean", - Key = "useLabel", - Description = "Stores colors as a Json object containing both the color hex string and label, rather than just the hex string." - }); - } - - public override IDictionary ConvertDbToEditor(IDictionary defaultPreVals, PreValueCollection persistedPreVals) - { - var dictionary = persistedPreVals.FormatAsDictionary(); - var items = dictionary.Where(x => x.Key != "useLabel") - .OrderBy(x => x.Value.SortOrder); - - var items2 = new Dictionary(); - foreach (var item in items) - { - var valueItem = new ColorPickerColor - { - Color = item.Value.Value, - Label = item.Value.Value, - SortOrder = item.Value.SortOrder - }; - - if (item.Value.Value.DetectIsJson()) - { - try - { - var valueObject = JsonConvert.DeserializeObject(item.Value.Value); - valueItem = new ColorPickerColor - { - Color = valueObject.Color, - Label = valueObject.Label, - SortOrder = valueObject.SortOrder - }; - } - catch - { - // let's say parsing Json failed, we'll not do anything, - // we'll just use the valueItem we built in the first place - } - } - - items2[item.Value.Id] = new JObject - { - { "value", valueItem.Color }, - { "label", valueItem.Label }, - { "sortOrder", valueItem.SortOrder } - }; - } - - var result = new Dictionary { { "items", items2 } }; - var useLabel = dictionary.ContainsKey("useLabel") && dictionary["useLabel"].Value == "1"; - if (useLabel) - result["useLabel"] = dictionary["useLabel"].Value; - - return result; - } - - public override IDictionary ConvertEditorToDb(IDictionary editorValue, PreValueCollection currentValue) - { - var val = editorValue["items"] as JArray; - var result = new Dictionary(); - if (val == null) return result; - - try - { - var useLabel = false; - if (editorValue.TryGetValue("useLabel", out var useLabelObj)) - { - useLabel = useLabelObj is string && (string) useLabelObj == "1"; - result["useLabel"] = new PreValue(useLabel ? "1" : "0"); - } - - // get all non-empty values - var index = 0; - // items get submitted in the sorted order, so just count them up - var sortOrder = -1; - foreach (var preValue in val.OfType() - .Where(x => x["value"] != null) - .Select(x => - { - var idString = x["id"] == null ? "0" : x["id"].ToString(); - int.TryParse(idString, out var id); - - var color = x["value"].ToString(); - if (string.IsNullOrWhiteSpace(color)) return null; - - var label = x["label"].ToString(); - - sortOrder++; - var value = useLabel - ? JsonConvert.SerializeObject(new { value = color, label = label, sortOrder = sortOrder }) - : color; - - return new PreValue(id, value, sortOrder); - }) - .WhereNotNull()) - { - result.Add(index.ToInvariantString(), preValue); - index++; - } - } - catch (Exception ex) - { - LogHelper.Error("Could not deserialize the posted value: " + val, ex); - } - - return result; - } - - internal class ColorListValidator : IPropertyValidator - { - public IEnumerable Validate(object value, PreValueCollection preValues, PropertyEditor editor) - { - var json = value as JArray; - if (json == null) yield break; - - //validate each item which is a json object - for (var index = 0; index < json.Count; index++) - { - var i = json[index]; - var jItem = i as JObject; - if (jItem == null || jItem["value"] == null) continue; - - //NOTE: we will be removing empty values when persisting so no need to validate - var asString = jItem["value"].ToString(); - if (asString.IsNullOrWhiteSpace()) continue; - - if (Regex.IsMatch(asString, "^([0-9a-f]{3}|[0-9a-f]{6})$", RegexOptions.IgnoreCase) == false) - { - yield return new ValidationResult("The value " + asString + " is not a valid hex color", new[] - { - //we'll make the server field the index number of the value so it can be wired up to the view - "item_" + index.ToInvariantString() - }); - } - } - } - } - } - - internal class ColorPickerColor - { - [JsonProperty("value")] - public string Color { get; set; } - [JsonProperty("label")] - public string Label { get; set; } - [JsonProperty("sortOrder")] - public int SortOrder { get; set; } - } -} diff --git a/src/Umbraco.Web/PropertyEditors/ColorPickerConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/ColorPickerConfigurationEditor.cs index ddc4b50858..cbd4e69a9e 100644 --- a/src/Umbraco.Web/PropertyEditors/ColorPickerConfigurationEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ColorPickerConfigurationEditor.cs @@ -17,39 +17,85 @@ namespace Umbraco.Web.PropertyEditors // customize the items field items.View = "views/propertyeditors/colorpicker/colorpicker.prevalues.html"; - items.Description = "Add and remove colors"; - items.Name = "Add color"; + items.Description = "Add, remove or sort colors"; + items.Name = "Colors"; items.Validators.Add(new ColorListValidator()); } public override Dictionary ToConfigurationEditor(ColorPickerConfiguration configuration) { - var items = configuration?.Items.ToDictionary(x => x.Id.ToString(), x => GetItemValue(x, configuration.UseLabel)) ?? new object(); + var configuredItems = configuration?.Items; // ordered + object editorItems; + + if (configuredItems == null) + { + editorItems = new object(); + } + else + { + var d = new Dictionary(); + editorItems = d; + var sortOrder = 0; + foreach (var item in configuredItems) + d[item.Id.ToString()] = GetItemValue(item, configuration.UseLabel, sortOrder++); + } + var useLabel = configuration?.UseLabel ?? false; return new Dictionary { - { "items", items }, + { "items", editorItems }, { "useLabel", useLabel } }; } - private object GetItemValue(ValueListConfiguration.ValueListItem item, bool useLabel) + private object GetItemValue(ValueListConfiguration.ValueListItem item, bool useLabel, int sortOrder) { - if (useLabel) + // in: ValueListItem, Id = , Value = | { "value": "", "label": "