Compare commits

...
Author SHA1 Message Date
Nikolaj 9799d722f6 Bump version 2023-05-03 09:20:16 +02:00
Nikolaj GeisleandGitHub 7c9364a532 V8: Encode imagepath to handle special characters (#14192)
* Use webutility to encode imagepath

* Align encoding with same as other versions
2023-05-03 07:55:04 +02:00
Nikolaj ca1453ab4e Bump version 2023-03-23 08:45:55 +01:00
Nikolaj 8962c1e636 Convert upload path to absolute 2023-03-23 08:39:44 +01:00
MAIN\KirillandSebastiaan Janssen 196b29c5eb Complete fix for https://github.com/umbraco/Umbraco-CMS/issues/8090 issue.
(cherry picked from commit 309e7fe371)
2023-03-15 08:19:03 +01:00
Nikolaj 7e4060e90c Bump version 2023-03-14 12:48:04 +01:00
Nikolaj 2d1e40ed5e Merge remote-tracking branch 'origin/v8/dev' into v8/8.18 2023-03-14 12:45:38 +01:00
Bjarke BergandGitHub f4fdd07eff Add allow list for absolute urls (#13962)
* Add allowlist of media hosts.
Not added to the interface to avoid breaking change

* Cleanup
2023-03-14 12:44:28 +01:00
Nikolaj 3ab1957a74 Check the tmpimg path is in the expected folder 2023-03-14 10:38:38 +01:00
MoleandGitHub 352ef47225 Don't replace hyphens and underscores with empty (#13897) 2023-03-07 11:32:34 +01:00
JesseandSebastiaan Janssen c0c106a107 added fix for ncNodeName filter in list view (#13086)
(cherry picked from commit e87701c482)
(cherry picked from commit a3c259482c)
2023-01-17 09:54:21 +01:00
Bjarke Berg 65e3f001c0 https://github.com/umbraco/Umbraco-CMS/issues/12276 (#12295)
Skip surrogate pairs (e.g. emojis) when making clean stings.

(cherry picked from commit 285c7e75a2)
2022-10-04 07:12:19 +02:00
Jacob OvergaardandGitHub db5a0bad7c V8: upgrade backoffice dependencies (#12919) 2022-09-05 15:30:48 +02:00
Nikolaj f29b6d93e6 Bump version to 8.18.5 2022-08-10 10:52:28 +02:00
Nikolaj b934fb7d90 Merge branch 'v8/dev' into v8/8.18 2022-08-10 08:58:03 +02:00
Bjarke BergandGitHub d0f9c9e931 Merge pull request #12812 from umbraco/v8/bugfix/do_not_expose_full_path_when_uploading_content_types
V8: Only expose the filename when uploading content types
2022-08-09 15:58:01 +02:00
Nikolaj 263b9c74f5 Port e2dee8b8c5 to V8 2022-08-09 15:20:34 +02:00
Nikolaj cbc8724df7 Use key as user alias 2022-08-05 13:43:08 +02:00
Bjarke BergandGitHub a4dcdbc5cd Merge pull request #12779 from umbraco/v8/feature/use-RandomNumberGenerator_instead_of_Random_when_generating_passwords
V8: Replace usage of Random().Next with RNGCryptoServiceProvider
2022-08-05 10:02:16 +02:00
nikolajlauridsen 21d5f1b754 Replace usage of Random().Next with RNGCryptoServiceProvider 2022-08-04 10:53:00 +02:00
Sebastiaan Janssen 907a5a69d3 Force the allowed avatar image types 2022-08-02 11:22:34 +02:00
Ronald BarendseandGitHub 087e682f79 v8: Fix Block List settings exception and optimize PVCs (#12594)
* Don't use MapModelType to get model type

* Optimize block list item activation (cache constructors)

* Fix exceptions in NestedContentSingleValueConverter (zero content types or multiple stored items)
2022-06-21 13:47:36 +02:00
Ronald Barendse 6c7053c449 Change web projects TargetFrameworkMoniker to 4.5.2 to stop VS 2022 err 2022-06-20 16:47:28 +02:00
Jacob OvergaardandGitHub 211c86ccbc update jquery to 3.6.0 and jquery-ui to 1.13.1 because of CVE-2021-41184 (#12534) 2022-06-10 08:43:32 +02:00
Bjarke Berg 22df4190bb Fix bug in ReportSiteTask, that means it only send telemetry once per reboot. 2022-06-05 10:52:49 +02:00
MoleandGitHub 7d99708065 Look for the password prop in memberShipProperties (#12395) 2022-05-18 15:02:29 +02:00
Sebastiaan Janssen 59a6abaa84 Bump version to 8.18.4 2022-05-11 11:32:35 +02:00
Sebastiaan Janssen ee1fe5f2d8 Merge branch 'v8/dev' into v8/8.18 2022-05-11 11:31:28 +02:00
74f17eb3f6 Added the Logic to fire OnSaved And OnChanged events when copying (#12330)
* Added the Logic to fire OnSaved And OnChanged events when copying

* Call OnSaving during copy

Co-authored-by: Aleksander <alf@umrabco.dk>
2022-05-11 11:30:20 +02:00
Sebastiaan Janssen 796eb9b25a Merge branch 'v8/dev' into v8/8.18 2022-05-11 11:29:04 +02:00
Paul JohnsonandSebastiaan Janssen 76bcf58953 Avoid creating indexes/keys for tables which don't exist yet. 2022-05-11 11:26:32 +02:00
Søren KottalandSebastiaan Janssen e8c18cda56 Porting fix from #12109
Possible NullReferenceException in MultiUrlPickerValueConverter
2022-05-11 11:24:20 +02:00
9daa15f1fc Ported over the fixes in #11858 "Check media Parent for permissions when setting correct MediaType" to target v8 (#12233)
* Ported over the fixes in the v9 PR #11858 "Check media Parent for permissions when setting correct MediaType" to v8

* reverted weird formatting in lang file

* Fixes

Co-authored-by: Elitsa Marinovska <elm@umbraco.dk>
2022-04-21 13:44:54 +02:00
MoleandGitHub 400f323db4 Do full save on first member login (#12216) 2022-04-20 14:21:36 +02:00
Sebastiaan Janssen 2c0a380d8b Relaxes newtonsoft.json version range, as per #12231 2022-04-07 09:21:42 +02:00
Sebastiaan Janssen 8a73bbb900 Bump version to 8.18.3 2022-04-04 13:12:57 +02:00
Paul JohnsonandSebastiaan Janssen 99629e4712 Fix issue - changing a document type broke the nucache data structure 2022-04-04 13:10:56 +02:00
Sebastiaan Janssen 27ae938099 Bump to 8.18.2 2022-03-23 10:17:26 +01:00
Sebastiaan Janssen edb69eea36 Cherry pick change away from Umbraco TV from v9 2022-03-21 16:16:40 +01:00
30bb416ff8 Add missing null checks for redirect URLs (#12154)
* Fixes missing null checks as reported in #10066

* Fixed check for null or empty in NPoco where clause.

Co-authored-by: Andy Butland <abutland73@gmail.com>
2022-03-21 14:48:55 +01:00
Sebastiaan Janssen a9b8090d6b Bump version to 8.18.1 2022-03-16 16:49:16 +01:00
Anders BjernerandSebastiaan Janssen 76ecb295cc Fixed issue with TryConvertValueToCrlType method incorrectly enclosing string values in double quotes 2022-03-16 16:42:22 +01:00
Sebastiaan Janssen ce817551a5 Merge branch 'v8/contrib' into v8/dev 2022-03-07 11:13:13 +01:00
inetzoandGitHub 32cd9fed49 Make GridPropertyEditor dirty if a control is removed (#12097)
The currentForm.$setDirty(); was missing when removing an control from a grid.
2022-03-05 17:09:35 +00:00
Nikolaj GeisleandGitHub cabb91dbe1 Merge pull request #12058 from umbraco/v8/bugfix/history-cleanup-make-contenttype-dirty
V8: Fix history cleanup not making content type dirty
2022-03-02 12:22:38 +01:00
Nikolaj GeisleandGitHub 633bc8fd8a Merge pull request #12069 from umbraco/v8/bugfix/missing-information-on-saving-variants
V8: Fix missing EditedCultures and Name in content saving notifications
2022-03-02 11:06:40 +01:00
9de4cf4444 Update src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs
Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>
2022-03-02 10:45:28 +01:00
nikolajlauridsen ffe9a14664 Remove unused helper method 2022-03-02 09:58:30 +01:00
nikolajlauridsen 1fb4b905c2 Remove pointless assert 2022-03-02 09:58:03 +01:00
nikolajlauridsen 4b8a16af7c Add null check when mapping history cleanup 2022-03-02 09:25:31 +01:00
nikolajlauridsen f6c4af745f Remove no longer relevant unit tests 2022-03-02 08:32:14 +01:00
Bjarke Berg 6dd8cb496b Aligned IsDirty behavior of HsitoryCleanup with how PropertyGroups and types behave.. 2022-03-01 12:51:20 +01:00
Bjarke Berg 8d6394e8e3 Fixed other issues with isDirty, that was true even when no changes was done 2022-03-01 10:34:13 +01:00
159f92d6f8 V8: Fix missing site identifier (#12035)
* Add SiteIdentifierService

* Use SiteIdentifier service to create site identifier if it doesn't exist

* Use default timeout for telemetry request

* Use SiteIdentifierService in TelemetryIdentifierStep

* Send SiteId when requesting dashboard

* Make empty GUID an invalid site identifier

Co-authored-by: nikolajlauridsen <nel@umbraco.dk>
2022-02-28 14:11:59 +01:00
nikolajlauridsen 75f8c09e85 Map dirty cultures to EditedCultures on content before saving 2022-02-28 13:04:08 +01:00
nikolajlauridsen 62de668380 Set the name on the content when setting default culture variant name 2022-02-28 13:03:40 +01:00
Andy ButlandandGitHub 96349e4754 Extended check for locally installed LocalDb to include the most recent version 15. (#12031) 2022-02-27 18:10:31 +01:00
Bjarne FyrstenborgandSebastiaan Janssen 1ec98139c6 Media entry editor listview children (#12061)
(cherry picked from commit 6fd45f653c)

# Conflicts:
#	src/Umbraco.Web.UI.Client/src/common/services/mediapreview.service.js
2022-02-27 17:59:10 +01:00
Mole 7bb85c9ed6 Handle setting HistoryCleanup to null 2022-02-25 13:09:31 +01:00
Mole 25ac6b275e Remove ResetWereDirtyProperties override 2022-02-25 11:44:00 +01:00
Mole db8fe55d08 Remove dirty HistoryCleanup properties when HistoryCleanup is replaced 2022-02-25 11:30:34 +01:00
Mole 86fcb8e715 Ensure replacing the entire HistoryCleanup registers as dirty 2022-02-25 09:41:20 +01:00
Mole c85486b866 Fix tests 2022-02-25 09:39:35 +01:00
Mole 4e2302ab6e Remove overrides 2022-02-25 09:39:04 +01:00
Mole a87e7a12d0 Flow changed properties back to ContentType using PropertyChanged 2022-02-25 08:37:53 +01:00
nikolajlauridsen 2fc8f62195 Implement more overrides for BeingDirtyBase 2022-02-24 14:31:40 +01:00
nikolajlauridsen 586c4e8db3 Add tests for content type being dirty 2022-02-24 13:13:38 +01:00
nikolajlauridsen 5c663fd0f0 Override IsDirty and ResetDirtyProperties in ContentType
In order to call it in both HistoryCleanup and base.
2022-02-24 13:13:08 +01:00
nikolajlauridsen 4f89cb1a34 Make HistoryCleanup inherit from BeingDirtyBase and implement dirty properties 2022-02-24 13:12:02 +01:00
Bjarke BergandSebastiaan Janssen 424db16ab1 Fix issue with namespace in api docs 2022-02-24 12:54:44 +01:00
Sebastiaan Janssen e9be3dd4a5 Bump version to 8.18.0 2022-02-24 11:24:19 +01:00
Bjarke Berg 8ff1d49346 Added noop migration just to change state, so this database do not just directly to 9.3 when migrating 2022-02-17 14:09:26 +01:00
MoleandGitHub 1473e41a20 V8: Fix deleting file on external filesystem (#11998)
* Use IFileSystem to check if folder exists when deleting file

* Rename CanDeleteDirectory back to IsDirectory
2022-02-17 09:35:10 +01:00
Sebastiaan Janssen 2038898f4c Bump version to 8.18.0-rc2 2022-02-17 09:25:11 +01:00
Ronald BarendseandGitHub 40c08f81c4 Add support for readonly properties (#11730) 2022-02-16 11:42:34 +01:00
66 changed files with 30728 additions and 16791 deletions
+1 -1
View File
@@ -30,7 +30,7 @@
<dependency id="Microsoft.AspNet.WebApi.Client" version="[5.2.7,5.999999)" />
<dependency id="Microsoft.Owin" version="[4.0.1,4.999999)" />
<dependency id="MiniProfiler" version="[4.0.138,4.999999)" />
<dependency id="Newtonsoft.Json" version="[12.0.1,12.999999)" />
<dependency id="Newtonsoft.Json" version="[12.0.1,13.999999)" />
<dependency id="Semver" version="[2.0.4,2.999999)" />
<dependency id="Serilog" version="[2.10.0,2.999999)" />
<dependency id="Serilog.Enrichers.Process" version="[2.0.2,2.999999)" />
@@ -15,8 +15,8 @@
<div class="level{{item.level}}"><span class="xref">{{item.name.0.value}}</span></div>
</div>
{{/inheritance.0}}
<h6><strong>{{__global.namespace}}</strong>:{{namespace}}</h6>
<h6><strong>{{__global.assembly}}</strong>:{{assemblies.0}}.dll</h6>
<h6><strong>{{__global.namespace}}</strong>: {{{namespace.specName.0.value}}}</h6>
<h6><strong>{{__global.assembly}}</strong>: {{assemblies.0}}.dll</h6>
<h5 id="{{id}}_syntax">{{__global.syntax}}</h5>
<div class="codewrapper">
<pre><code class="lang-{{_lang}} hljs">{{syntax.content.0.value}}</code></pre>
+2 -2
View File
@@ -18,5 +18,5 @@ using System.Resources;
[assembly: AssemblyVersion("8.0.0")]
// these are FYI and changed automatically
[assembly: AssemblyFileVersion("8.18.0")]
[assembly: AssemblyInformationalVersion("8.18.0-rc")]
[assembly: AssemblyFileVersion("8.18.8")]
[assembly: AssemblyInformationalVersion("8.18.8")]
@@ -79,7 +79,7 @@ namespace Umbraco.Core.Compose
get
{
var httpContext = HttpContext.Current == null ? (HttpContextBase) null : new HttpContextWrapper(HttpContext.Current);
var ip = httpContext.GetCurrentRequestIpAddress();
var ip = httpContext?.GetCurrentRequestIpAddress();
if (ip == null || ip.ToLowerInvariant().StartsWith("unknown")) ip = "";
return ip;
}
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Configuration;
using Umbraco.Core.Macros;
@@ -34,6 +35,9 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
[ConfigurationProperty("disallowedUploadFiles")]
internal CommaDelimitedConfigurationElement DisallowedUploadFiles => GetOptionalDelimitedElement("disallowedUploadFiles", new[] {"ashx", "aspx", "ascx", "config", "cshtml", "vbhtml", "asmx", "air", "axd", "xamlx"});
[ConfigurationProperty("allowedMediaHosts")]
internal CommaDelimitedConfigurationElement AllowedMediaHosts => GetOptionalDelimitedElement("allowedMediaHosts", new string[0]);
[ConfigurationProperty("allowedUploadFiles")]
internal CommaDelimitedConfigurationElement AllowedUploadFiles => GetOptionalDelimitedElement("allowedUploadFiles", new string[0]);
@@ -49,6 +53,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
[ConfigurationProperty("hideBackofficeLogo")]
internal InnerTextConfigurationElement<bool> HideBackOfficeLogo => GetOptionalTextElement("hideBackofficeLogo", false);
string IContentSection.NotificationEmailAddress => Notifications.NotificationEmailAddress;
bool IContentSection.DisableHtmlEmail => Notifications.DisableHtmlEmail;
@@ -0,0 +1,27 @@
using System;
using System.Security.Cryptography;
namespace Umbraco.Core
{
public static class CryptoServiceProviderExtensions
{
/// <summary>
/// Generates a random int withing a specified range.
/// </summary>
/// <param name="provider">Random bytes provider.</param>
/// <param name="minValue">The minimum value of the resulting int.</param>
/// <param name="maxValue">The maximum value of the resulting int.</param>
/// <returns>A random integer that falls withing the specified range.</returns>
public static int GetInt32(this RNGCryptoServiceProvider provider, int minValue, int maxValue)
{
var randomBytes = new byte[4];
provider.GetBytes(randomBytes);
var randomInt = Math.Abs(BitConverter.ToInt32(randomBytes, 0));
// We call do mod to ensure that the value is within the specified range.
return randomInt % (maxValue - minValue + 1) + minValue;
}
}
}
@@ -1,4 +1,6 @@
using Umbraco.Core.Migrations.Install;
using System.Linq;
using NPoco;
using Umbraco.Core.Migrations.Install;
namespace Umbraco.Core.Migrations.Upgrade.Common
{
@@ -14,9 +16,20 @@ namespace Umbraco.Core.Migrations.Upgrade.Common
Delete.KeysAndIndexes(Constants.DatabaseSchema.Tables.KeyValue).Do();
Delete.KeysAndIndexes(Constants.DatabaseSchema.Tables.PropertyData).Do();
var existingTables = SqlSyntax.GetTablesInSchema(Context.Database).ToHashSet();
// re-create *all* keys and indexes
foreach (var x in DatabaseSchemaCreator.OrderedTables)
Create.KeysAndIndexes(x).Do();
foreach (var entityClass in DatabaseSchemaCreator.OrderedTables)
{
var tableNameAttribute = entityClass.FirstAttribute<TableNameAttribute>();
if (tableNameAttribute == null)
continue;
if (!existingTables.Contains(tableNameAttribute.Value))
continue;
Create.KeysAndIndexes(entityClass).Do();
}
}
}
}
@@ -213,6 +213,7 @@ namespace Umbraco.Core.Migrations.Upgrade
//FINAL
To<AddContentVersionCleanupFeature>("{8BAF5E6C-DCB7-41AE-824F-4215AE4F1F98}");
To<NoopMigration>("{03482BB0-CF13-475C-845E-ECB8319DBE3C}");
}
}
}
@@ -1,17 +1,37 @@
using System.Runtime.Serialization;
using Umbraco.Core.Models.Entities;
namespace Umbraco.Core.Models.ContentEditing
{
[DataContract(Name = "historyCleanup", Namespace = "")]
public class HistoryCleanup
public class HistoryCleanup : BeingDirtyBase
{
private bool _preventCleanup;
[DataMember(Name = "preventCleanup")]
public bool PreventCleanup { get; set; }
public bool PreventCleanup
{
get => _preventCleanup;
set => SetPropertyValueAndDetectChanges(value, ref _preventCleanup, nameof(PreventCleanup));
}
private int? _keepAllVersionsNewerThanDays;
[DataMember(Name = "keepAllVersionsNewerThanDays")]
public int? KeepAllVersionsNewerThanDays { get; set; }
public int? KeepAllVersionsNewerThanDays
{
get => _keepAllVersionsNewerThanDays;
set => SetPropertyValueAndDetectChanges(value, ref _keepAllVersionsNewerThanDays, nameof(KeepAllVersionsNewerThanDays));
}
private int? _keepLatestVersionPerDayForDays;
[DataMember(Name = "keepLatestVersionPerDayForDays")]
public int? KeepLatestVersionPerDayForDays { get; set; }
public int? KeepLatestVersionPerDayForDays
{
get => _keepLatestVersionPerDayForDays;
set => SetPropertyValueAndDetectChanges(value, ref _keepLatestVersionPerDayForDays,
nameof(KeepLatestVersionPerDayForDays));
}
}
}
+19 -4
View File
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using Umbraco.Core.Models.ContentEditing;
@@ -50,8 +50,6 @@ namespace Umbraco.Core.Models
/// <inheritdoc />
public override bool SupportsPublishing => SupportsPublishingConst;
//Custom comparer for enumerable
private static readonly DelegateEqualityComparer<IEnumerable<ITemplate>> TemplateComparer = new DelegateEqualityComparer<IEnumerable<ITemplate>>(
(templates, enumerable) => templates.UnsortedSequenceEqual(enumerable),
@@ -98,7 +96,14 @@ namespace Umbraco.Core.Models
}
}
public HistoryCleanup HistoryCleanup { get; set; }
private HistoryCleanup _historyCleanup;
public HistoryCleanup HistoryCleanup
{
get => _historyCleanup;
set => SetPropertyValueAndDetectChanges(value, ref _historyCleanup, nameof(HistoryCleanup));
}
/// <summary>
/// Determines if AllowedTemplates contains templateId
@@ -165,5 +170,15 @@ namespace Umbraco.Core.Models
/// <inheritdoc />
IContentType IContentType.DeepCloneWithResetIdentities(string newAlias) => (IContentType)DeepCloneWithResetIdentities(newAlias);
public override bool IsDirty()
{
bool dirtyEntity = base.IsDirty();
bool dirtyHistoryCleanup = HistoryCleanup.IsDirty();
return dirtyEntity || dirtyHistoryCleanup;
}
}
}
@@ -15,6 +15,7 @@ namespace Umbraco.Core.Models.Entities
public abstract class BeingDirtyBase : IRememberBeingDirty
{
private bool _withChanges = true; // should we track changes?
private bool _withChangedEvent = true; // should we fire the event when changes are registered
private Dictionary<string, bool> _currentChanges; // which properties have changed?
private Dictionary<string, bool> _savedChanges; // which properties had changed at last commit?
@@ -117,7 +118,10 @@ namespace Umbraco.Core.Models.Entities
_currentChanges[propertyName] = true;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
if (_withChangedEvent)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
/// <summary>
@@ -136,6 +140,22 @@ namespace Umbraco.Core.Models.Entities
_withChanges = true;
}
/// <summary>
/// Disables the property changed event.
/// </summary>
protected void DisablePropertyChangedEvent()
{
_withChangedEvent = false;
}
/// <summary>
/// Enables the property changed event.
/// </summary>
protected void EnablePropertyChangedEvent()
{
_withChangedEvent = true;
}
/// <summary>
/// Sets a property value, detects changes and manages the dirty flag.
/// </summary>
+2 -2
View File
@@ -103,8 +103,8 @@ namespace Umbraco.Core.Persistence
if (string.IsNullOrWhiteSpace(programFiles)) return;
// detect 14, 13, 12, 11
for (var i = 14; i > 10; i--)
// detect (17, 16) 15, 14, 13, 12, 11 - future-proofing by a couple of versions
for (var i = 17; i > 10; i--)
{
var exe = Path.Combine(programFiles, $@"Microsoft SQL Server\{i}0\Tools\Binn\SqlLocalDB.exe");
if (File.Exists(exe) == false) continue;
@@ -174,13 +174,13 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID");
var urlHash = url.GenerateHash<SHA1>();
var sql = GetBaseQuery(false)
.Where<RedirectUrlDto>(x => x.Url == url && x.UrlHash == urlHash &&
(x.Culture == culture.ToLower() || x.Culture == string.Empty))
(x.Culture == culture.ToLower() || x.Culture == null || x.Culture == string.Empty))
.OrderByDescending<RedirectUrlDto>(x => x.CreateDateUtc);
var dtos = Database.Fetch<RedirectUrlDto>(sql);
var dto = dtos.FirstOrDefault(f => f.Culture == culture.ToLower());
if (dto == null)
dto = dtos.FirstOrDefault(f => f.Culture == string.Empty);
dto = dtos.FirstOrDefault(f => string.IsNullOrWhiteSpace(f.Culture));
return dto == null ? null : Map(dto);
}
@@ -145,16 +145,25 @@ namespace Umbraco.Core.PropertyEditors
/// <returns></returns>
internal Attempt<object> TryConvertValueToCrlType(object value)
{
if (value is JToken jsonValue)
if (value is JValue jsonValue)
{
if (jsonValue is JContainer && jsonValue.HasValues == false)
// Calling the "ToString(Formatting)" method on a string value results in a new
// string enclosed in double quotes (which we don't want), as the method is declared
// in the JToken class. Calling either of the "ToString()" or "ToString(CultureInfo)"
// methods hits the overridden methods in the JValue class, which correctly doesn't
// enclose the value in double quotes.
value = jsonValue.ToString(CultureInfo.InvariantCulture);
}
else if (value is JToken jsonToken)
{
if (jsonToken is JContainer && jsonToken.HasValues == false)
{
// Empty JSON array/object
value = null;
}
else
{
value = jsonValue.ToString(Formatting.None);
value = jsonToken.ToString(Formatting.None);
}
}
@@ -965,7 +965,7 @@ namespace Umbraco.Core.Security
protected string GetCurrentRequestIpAddress()
{
var httpContext = HttpContext.Current == null ? (HttpContextBase) null : new HttpContextWrapper(HttpContext.Current);
return httpContext.GetCurrentRequestIpAddress();
return httpContext?.GetCurrentRequestIpAddress();
}
}
@@ -704,7 +704,29 @@ namespace Umbraco.Core.Services.Implement
}
copy.ParentId = containerId;
var saveEventArgs = new SaveEventArgs<TItem>(copy);
if (OnSavingCancelled(scope, saveEventArgs))
{
scope.Complete();
return OperationResult.Attempt.Fail<MoveOperationStatusType, TItem>(MoveOperationStatusType.FailedCancelledByEvent, evtMsgs); // causes rollback
}
Repository.Save(copy);
// handle events for the copied node
// figure out impacted content types
var changes = ComposeContentTypeChanges(copy).ToArray();
var args = changes.ToEventArgs();
OnUowRefreshedEntity(args);
OnChanged(scope, args);
saveEventArgs.CanCancel = false;
OnSaved(scope, saveEventArgs);
// Since this overload doesn't accept a user id, set the super users.
Audit(AuditType.Save, Constants.Security.SuperUserId, copy.Id);
scope.Complete();
}
catch (DataOperationException<MoveOperationStatusType> ex)
@@ -343,11 +343,14 @@ namespace Umbraco.Core.Strings
var isUpper = char.IsUpper(c); // false for digits, symbols...
//var isLower = char.IsLower(c); // false for digits, symbols...
// what should I do with surrogates?
// no idea, really, so they are not supported at the moment
// what should I do with surrogates? - E.g emojis like 🎈
// no idea, really, so they are not supported at the moment and we just continue
var isPair = char.IsSurrogate(c);
if (isPair)
throw new NotSupportedException("Surrogate pairs are not supported.");
{
continue;
}
switch (state)
{
@@ -0,0 +1,29 @@
using System;
namespace Umbraco.Core.Telemetry
{
/// <summary>
/// Used to get and create the site identifier
/// </summary>
public interface ISiteIdentifierService
{
/// <summary>
/// Tries to get the site identifier
/// </summary>
/// <returns></returns>
bool TryGetSiteIdentifier(out Guid siteIdentifier);
/// <summary>
/// Tries to get the site identifier or otherwise create it if it doesn't exist.
/// </summary>
/// <param name="siteIdentifier"></param>
/// <returns></returns>
bool TryGetOrCreateSiteIdentifier(out Guid siteIdentifier);
/// <summary>
/// Creates the site identifier and writes it to config.
/// </summary>
bool TryCreateSiteIdentifier(out Guid createdGuid);
}
}
+4 -19
View File
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Manifest;
using Umbraco.Core.Telemetry.Models;
@@ -10,7 +9,7 @@ namespace Umbraco.Core.Telemetry
/// <inheritdoc/>
internal class TelemetryService : ITelemetryService
{
private readonly IUmbracoSettingsSection _settings;
private readonly ISiteIdentifierService _siteIdentifierService;
private readonly ManifestParser _manifestParser;
/// <summary>
@@ -18,16 +17,16 @@ namespace Umbraco.Core.Telemetry
/// </summary>
public TelemetryService(
ManifestParser manifestParser,
IUmbracoSettingsSection settings)
ISiteIdentifierService siteIdentifierService)
{
_manifestParser = manifestParser;
_settings = settings;
_siteIdentifierService = siteIdentifierService;
}
/// <inheritdoc/>
public bool TryGetTelemetryReportData(out TelemetryReportData telemetryReportData)
{
if (TryGetTelemetryId(out Guid telemetryId) is false)
if (_siteIdentifierService.TryGetOrCreateSiteIdentifier(out Guid telemetryId) is false)
{
telemetryReportData = null;
return false;
@@ -42,20 +41,6 @@ namespace Umbraco.Core.Telemetry
return true;
}
private bool TryGetTelemetryId(out Guid telemetryId)
{
// Parse telemetry string as a GUID & verify its a GUID and not some random string
// since users may have messed with or decided to empty the app setting or put in something random
if (Guid.TryParse(_settings.BackOffice.Id, out var parsedTelemetryId) is false)
{
telemetryId = Guid.Empty;
return false;
}
telemetryId = parsedTelemetryId;
return true;
}
private IEnumerable<PackageTelemetry> GetPackageTelemetry()
{
List<PackageTelemetry> packages = new ();
+2
View File
@@ -136,6 +136,7 @@
<Compile Include="Collections\EventClearingObservableCollection.cs" />
<Compile Include="Constants-Sql.cs" />
<Compile Include="Constants-SqlTemplates.cs" />
<Compile Include="CryptoServiceProviderExtensions.cs" />
<Compile Include="Events\UnattendedInstallEventArgs.cs" />
<Compile Include="Help\HelpPageSettings.cs" />
<Compile Include="Help\IHelpPageSettings.cs" />
@@ -400,6 +401,7 @@
<Compile Include="Services\DateTypeServiceExtensions.cs" />
<Compile Include="Services\PropertyValidationService.cs" />
<Compile Include="Composing\TypeCollectionBuilderBase.cs" />
<Compile Include="Telemetry\ISiteIdentifierService.cs" />
<Compile Include="Telemetry\ITelemetryService.cs" />
<Compile Include="Telemetry\Models\PackageTelemetry.cs" />
<Compile Include="Telemetry\Models\TelemetryReportData.cs" />
@@ -0,0 +1,107 @@
using System.Linq;
using NUnit.Framework;
using Umbraco.Core.Models;
using Umbraco.Core.Models.ContentEditing;
using Umbraco.Tests.TestHelpers.Entities;
using Umbraco.Tests.Testing;
namespace Umbraco.Tests.Models
{
[TestFixture]
public class ContentTypeHistoryCleanupTests : UmbracoTestBase
{
[Test]
public void Changing_Keep_all_Makes_ContentType_Dirty()
{
var contentType = MockedContentTypes.CreateBasicContentType();
Assert.IsFalse(contentType.IsDirty());
var newValue = 2;
contentType.HistoryCleanup.KeepAllVersionsNewerThanDays = newValue;
Assert.IsTrue(contentType.IsDirty());
Assert.AreEqual(newValue, contentType.HistoryCleanup.KeepAllVersionsNewerThanDays);
}
[Test]
public void Changing_Keep_latest_Makes_ContentType_Dirty()
{
var contentType = MockedContentTypes.CreateBasicContentType();
Assert.IsFalse(contentType.IsDirty());
var newValue = 2;
contentType.HistoryCleanup.KeepLatestVersionPerDayForDays = newValue;
Assert.IsTrue(contentType.IsDirty());
Assert.AreEqual(newValue, contentType.HistoryCleanup.KeepLatestVersionPerDayForDays);
}
[Test]
public void Changing_Prevent_Cleanup_Makes_ContentType_Dirty()
{
var contentType = MockedContentTypes.CreateBasicContentType();
Assert.IsFalse(contentType.IsDirty());
var newValue = true;
contentType.HistoryCleanup.PreventCleanup = newValue;
Assert.IsTrue(contentType.IsDirty());
Assert.AreEqual(newValue, contentType.HistoryCleanup.PreventCleanup);
}
[Test]
public void Replacing_History_Cleanup_Registers_As_Dirty()
{
var contentType = MockedContentTypes.CreateBasicContentType();
Assert.IsFalse(contentType.IsDirty());
contentType.HistoryCleanup = new HistoryCleanup();
Assert.IsTrue(contentType.IsDirty());
Assert.IsTrue(contentType.IsPropertyDirty(nameof(contentType.HistoryCleanup)));
}
[Test]
public void Replacing_History_Cleanup_Removes_Old_Dirty_History_Properties()
{
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Alias = "NewValue";
contentType.HistoryCleanup.KeepAllVersionsNewerThanDays = 2;
contentType.PropertyChanged += (sender, args) =>
{
// Ensure that property changed is only invoked for history cleanup
Assert.AreEqual(nameof(contentType.HistoryCleanup), args.PropertyName);
};
// Since we're replacing the entire HistoryCleanup the changed property is no longer dirty, the entire HistoryCleanup is
contentType.HistoryCleanup = new HistoryCleanup();
Assert.Multiple(() =>
{
Assert.IsTrue(contentType.IsDirty());
Assert.IsFalse(contentType.WasDirty());
Assert.AreEqual(2, contentType.GetDirtyProperties().Count());
Assert.IsTrue(contentType.IsPropertyDirty(nameof(contentType.HistoryCleanup)));
Assert.IsTrue(contentType.IsPropertyDirty(nameof(contentType.Alias)));
});
}
[Test]
public void Old_History_Cleanup_Reference_Doesnt_Make_Content_Type_Dirty()
{
var contentType = MockedContentTypes.CreateBasicContentType();
var oldHistoryCleanup = contentType.HistoryCleanup;
contentType.HistoryCleanup = new HistoryCleanup();
contentType.ResetDirtyProperties();
contentType.ResetWereDirtyProperties();
oldHistoryCleanup.KeepAllVersionsNewerThanDays = 2;
Assert.IsFalse(contentType.IsDirty());
Assert.IsFalse(contentType.WasDirty());
}
}
}
+1
View File
@@ -145,6 +145,7 @@
<Compile Include="ModelsBuilder\StringExtensions.cs" />
<Compile Include="ModelsBuilder\UmbracoApplicationTests.cs" />
<Compile Include="Models\ContentScheduleTests.cs" />
<Compile Include="Models\ContentTypeHistoryCleanupTests.cs" />
<Compile Include="Models\CultureImpactTests.cs" />
<Compile Include="Models\ImageProcessorImageUrlGeneratorTest.cs" />
<Compile Include="Models\PathValidationTests.cs" />
+29684 -16413
View File
File diff suppressed because it is too large Load Diff
+16 -17
View File
@@ -10,23 +10,23 @@
"watch": "gulp watch"
},
"engines": {
"node": ">=10.00.0"
"node": ">=16.0.0"
},
"dependencies": {
"ace-builds": "1.4.2",
"angular": "1.8.2",
"angular-animate": "1.7.5",
"angular-aria": "1.7.9",
"angular-chart.js": "^1.1.1",
"angular-cookies": "1.7.5",
"angular-dynamic-locale": "0.1.37",
"angular-i18n": "1.7.5",
"angular-animate": "1.8.2",
"angular-aria": "1.8.2",
"angular-chart.js": "1.1.1",
"angular-cookies": "1.8.2",
"angular-dynamic-locale": "0.1.38",
"angular-i18n": "1.8.2",
"angular-local-storage": "0.7.1",
"angular-messages": "1.7.5",
"angular-mocks": "1.7.5",
"angular-route": "1.7.5",
"angular-sanitize": "1.7.5",
"angular-touch": "1.7.5",
"angular-messages": "1.8.2",
"angular-mocks": "1.8.2",
"angular-route": "1.8.2",
"angular-sanitize": "1.8.2",
"angular-touch": "1.8.2",
"angular-ui-sortable": "0.19.0",
"animejs": "2.2.0",
"bootstrap-social": "5.1.1",
@@ -35,15 +35,14 @@
"diff": "3.5.0",
"flatpickr": "4.6.9",
"font-awesome": "4.7.0",
"jquery": "^3.6.0",
"jquery-ui-dist": "1.12.1",
"jquery": "3.6.1",
"jquery-ui-dist": "1.13.2",
"jquery-ui-touch-punch": "0.2.3",
"lazyload-js": "1.0.0",
"moment": "2.22.2",
"moment": "2.29.4",
"ng-file-upload": "12.2.13",
"nouislider": "15.5.0",
"npm": "^6.14.7",
"signalr": "2.4.0",
"signalr": "2.4.3",
"spectrum-colorpicker2": "2.0.8",
"tinymce": "4.9.11",
"typeahead.js": "0.11.1",
@@ -182,23 +182,31 @@
}
);
$scope.propertyEditorDisabled = function (property) {
$scope.propertyEditorInherited = function (property) {
if (property.unlockInvariantValue) {
return false;
return false;
}
var contentLanguage = $scope.content.language;
var canEditCulture = !contentLanguage ||
// If the property culture equals the content culture it can be edited
property.culture === contentLanguage.culture ||
// A culture-invariant property can only be edited by the default language variant
(property.culture == null && contentLanguage.isDefault);
// If the property culture equals the content culture it can be edited
property.culture === contentLanguage.culture ||
// A culture-invariant property can only be edited by the default language variant
(property.culture == null && contentLanguage.isDefault);
var canEditSegment = property.segment === $scope.content.segment;
return !canEditCulture || !canEditSegment;
}
};
$scope.propertyEditorDisabled = function (property) {
if (property.readonly) {
return true;
}
return $scope.propertyEditorInherited(property);
};
}
var directive = {
@@ -80,7 +80,7 @@
});
var saveProperties = _.map(realProperties, function (p) {
var saveProperty = _.pick(p, 'id', 'alias', 'description', 'validation', 'label', 'sortOrder', 'dataTypeId', 'groupId', 'memberCanEdit', 'showOnMemberProfile', 'isSensitiveData', 'allowCultureVariant', 'allowSegmentVariant', 'labelOnTop');
var saveProperty = _.pick(p, 'id', 'alias', 'description', 'validation', 'label', 'sortOrder', 'dataTypeId', 'dataTypeKey', 'groupId', 'memberCanEdit', 'showOnMemberProfile', 'isSensitiveData', 'allowCultureVariant', 'allowSegmentVariant', 'labelOnTop');
return saveProperty;
});
@@ -128,13 +128,13 @@
<!-- Links -->
<div class="umb-help-section" data-element="help-links" ng-if="vm.hasAccessToSettings">
<a data-element="help-link-umbraco-tv" class="umb-help-badge" href="https://umbraco.tv?utm_source=core&utm_medium=help&utm_content=link&utm_campaign=tv" target="_blank" rel="noopener">
<a data-element="help-link-umbraco-tv" class="umb-help-badge" href="https://umbra.co/ulb" target="_blank" rel="noopener">
<umb-icon icon="icon-tv-old" class="umb-help-badge__icon"></umb-icon>
<div class="umb-help-badge__title">
<localize key="help_umbracoTv">Visit umbraco.tv</localize>
<localize key="help_umbracoLearningBase">Watch our free tutorial videos</localize>
</div>
<small>
<localize key="help_theBestUmbracoVideoTutorials">The best Umbraco video tutorials</localize>
<localize key="help_umbracoLearningBaseDescription">on the Umbraco Learning Base</localize>
</small>
</a>
@@ -3,25 +3,35 @@ angular.module("umbraco")
function ($scope, localizationService, entityResource, editorService, overlayService, eventsService, mediaHelper) {
var unsubscribe = [];
var vm = this;
const vm = this;
vm.loading = true;
vm.model = $scope.model;
vm.mediaEntry = vm.model.mediaEntry;
vm.currentCrop = null;
localizationService.localizeMany([
vm.model.createFlow ? "general_cancel" : "general_close",
vm.model.createFlow ? "general_create" : "buttons_submitChanges"
]).then(function (data) {
vm.closeLabel = data[0];
vm.submitLabel = data[1];
});
vm.title = "";
vm.focalPointChanged = focalPointChanged;
vm.onImageLoaded = onImageLoaded;
vm.openMedia = openMedia;
vm.repickMedia = repickMedia;
vm.selectCrop = selectCrop;
vm.deselectCrop = deselectCrop;
vm.resetCrop = resetCrop;
vm.submitAndClose = submitAndClose;
vm.close = close;
function init() {
localizationService.localizeMany([
vm.model.createFlow ? "general_cancel" : "general_close",
vm.model.createFlow ? "general_create" : "buttons_submitChanges"
]).then(data => {
vm.closeLabel = data[0];
vm.submitLabel = data[1];
});
updateMedia();
unsubscribe.push(eventsService.on("editors.media.saved", function(name, args) {
@@ -35,6 +45,7 @@ angular.module("umbraco")
function updateMedia() {
vm.loading = true;
entityResource.getById(vm.mediaEntry.mediaKey, "Media").then(function (mediaEntity) {
vm.media = mediaEntity;
vm.imageSrc = mediaHelper.resolveFileFromEntity(mediaEntity, true);
@@ -42,11 +53,11 @@ angular.module("umbraco")
vm.hasDimensions = false;
vm.isCroppable = false;
localizationService.localize("mediaPicker_editMediaEntryLabel", [vm.media.name, vm.model.documentName]).then(function (data) {
localizationService.localize("mediaPicker_editMediaEntryLabel", [vm.media.name, vm.model.documentName]).then(data => {
vm.title = data;
});
}, function () {
localizationService.localize("mediaPicker_deletedItem").then(function (localized) {
localizationService.localize("mediaPicker_deletedItem").then(localized => {
vm.media = {
name: localized,
icon: "icon-picture",
@@ -58,15 +69,12 @@ angular.module("umbraco")
});
});
}
vm.onImageLoaded = onImageLoaded;
function onImageLoaded(isCroppable, hasDimensions) {
vm.isCroppable = isCroppable;
vm.hasDimensions = hasDimensions;
};
vm.repickMedia = repickMedia;
}
function repickMedia() {
vm.model.propertyEditor.changeMediaFor(vm.model.mediaEntry, onMediaReplaced);
}
@@ -82,24 +90,23 @@ angular.module("umbraco")
//
updateMedia();
}
vm.openMedia = openMedia;
function openMedia() {
var mediaEditor = {
const mediaEditor = {
id: vm.mediaEntry.mediaKey,
submit: function () {
submit: () => {
editorService.close();
},
close: function () {
close: () => {
editorService.close();
}
};
editorService.mediaEditor(mediaEditor);
}
vm.focalPointChanged = function(left, top) {
function focalPointChanged(left, top) {
//update the model focalpoint value
vm.mediaEntry.focalPoint = {
left: left,
@@ -109,22 +116,17 @@ angular.module("umbraco")
//set form to dirty to track changes
setDirty();
}
vm.selectCrop = selectCrop;
function selectCrop(targetCrop) {
vm.currentCrop = targetCrop;
setDirty();
// TODO: start watchin values of crop, first when changed set to dirty.
};
vm.deselectCrop = deselectCrop;
}
function deselectCrop() {
vm.currentCrop = null;
};
vm.resetCrop = resetCrop;
}
function resetCrop() {
if (vm.currentCrop) {
$scope.$evalAsync( () => {
@@ -138,18 +140,22 @@ angular.module("umbraco")
vm.imageCropperForm.$setDirty();
}
vm.submitAndClose = function () {
function submitAndClose() {
if (vm.model && vm.model.submit) {
vm.model.submit(vm.model);
}
}
vm.close = function () {
if (vm.model && vm.model.close) {
if (vm.model.createFlow === true || vm.imageCropperForm.$dirty === true) {
var labels = vm.model.createFlow === true ? ["mediaPicker_confirmCancelMediaEntryCreationHeadline", "mediaPicker_confirmCancelMediaEntryCreationMessage"] : ["prompt_discardChanges", "mediaPicker_confirmCancelMediaEntryHasChanges"];
localizationService.localizeMany(labels).then(function (localizations) {
function close() {
if (vm.model && vm.model.close)
{
if (vm.model.createFlow === true || vm.imageCropperForm.$dirty === true)
{
const labelKeys = vm.model.createFlow === true
? ["mediaPicker_confirmCancelMediaEntryCreationHeadline", "mediaPicker_confirmCancelMediaEntryCreationMessage"]
: ["prompt_discardChanges", "mediaPicker_confirmCancelMediaEntryHasChanges"];
localizationService.localizeMany(labelKeys).then(localizations => {
const confirm = {
title: localizations[0],
view: "default",
@@ -157,11 +163,11 @@ angular.module("umbraco")
submitButtonLabelKey: "general_discard",
submitButtonStyle: "danger",
closeButtonLabelKey: "prompt_stay",
submit: function () {
submit: () => {
overlayService.close();
vm.model.close(vm.model);
},
close: function () {
close: () => {
overlayService.close();
}
};
@@ -175,6 +181,7 @@ angular.module("umbraco")
}
init();
$scope.$on("$destroy", function () {
unsubscribe.forEach(x => x());
});
@@ -11,7 +11,7 @@
data-element="property-{{property.alias}}"
ng-repeat="property in tab.properties track by property.alias"
property="property"
show-inherit="propertyEditorDisabled(property)"
show-inherit="propertyEditorInherited(property)"
inherits-from="defaultVariant.displayName">
<div ng-class="{'o-40 cursor-not-allowed': propertyEditorDisabled(property) }">
@@ -41,7 +41,7 @@
data-element="property-{{property.alias}}"
ng-repeat="property in group.properties track by property.alias"
property="property"
show-inherit="propertyEditorDisabled(property)"
show-inherit="propertyEditorInherited(property)"
inherits-from="defaultVariant.displayName">
<div ng-class="{'o-40 cursor-not-allowed': propertyEditorDisabled(property) }">
@@ -45,10 +45,10 @@
show-inherit="vm.model.variants.length > 1 && !property.culture && !activeVariant.language.isDefault"
inherits-from="defaultVariant.language.name">
<div ng-class="{'o-40 cursor-not-allowed': vm.model.variants.length > 1 && !activeVariant.language.isDefault && !property.culture && !property.unlockInvariantValue}">
<div ng-class="{'o-40 cursor-not-allowed': property.readonly || (vm.model.variants.length > 1 && !activeVariant.language.isDefault && !property.culture && !property.unlockInvariantValue)}">
<umb-property-editor
model="property"
preview="vm.model.variants.length > 1 && !activeVariant.language.isDefault && !property.culture && !property.unlockInvariantValue">
preview="property.readonly || (vm.model.variants.length > 1 && !activeVariant.language.isDefault && !property.culture && !property.unlockInvariantValue)">
</umb-property-editor>
</div>
@@ -23,7 +23,7 @@
</li>
<li class="umb-content-grid__details-item" ng-repeat="property in contentProperties">
<div class="umb-content-grid__details-label">{{ property.header }}:</div>
<div class="umb-content-grid__details-value">{{ item[property.alias] }}</div>
<div class="umb-content-grid__details-value">{{ item[property.alias].expression ? item[property.alias].expression({value: item[property.alias].value}) : item[property.alias] }}</div>
</li>
</ul>
@@ -57,10 +57,10 @@
</umb-variant-state>
</div>
<div class="umb-table-cell" ng-repeat="column in vm.itemProperties track by column.alias">
<span title="{{column.header}}: {{item[column.alias]}}">
<span title="{{column.header}}: {{item[column.alias].expression ? item[column.alias].expression({value: item[column.alias].value}) : item[column.alias]}}">
<div ng-if="!column.isSensitive">
{{item[column.alias]}}
{{item[column.alias].expression ? item[column.alias].expression({value: item[column.alias].value}) : item[column.alias]}}
</div>
<em ng-show="column.isSensitive" class="muted">
@@ -17,7 +17,7 @@
<localize key="settingsDashboard_bulletPointTwo">Ask a question in the <a class="btn-link -underline" href="https://our.umbraco.com/forum" target="_blank" rel="noopener">Community Forum</a></localize>
</li>
<li>
<localize key="settingsDashboard_bulletPointThree">Watch our <a class="btn-link -underline" href="https://umbraco.tv" target="_blank" rel="noopener">tutorial videos</a> (some are free, some require a subscription)</localize>
<localize key="settingsDashboard_bulletPointTutorials">Watch our free <a class="btn-link -underline" href="https://umbra.co/ulb" target="_blank" rel="noopener">tutorial videos on the Umbraco Learning Base</a></localize>
</li>
<li>
<localize key="settingsDashboard_bulletPointFour">Find out about our <a class="btn-link -underline" href="https://umbraco.com/products/" target="_blank" rel="noopener">productivity boosting tools and commercial support</a></localize>
@@ -158,7 +158,7 @@ function MemberEditController($scope, $routeParams, $location, $http, $q, appSta
//it's a child item, just sync the ui node to the parent
navigationService.syncTree({ tree: "member", path: path.substring(0, path.lastIndexOf(",")).split(","), forceReload: initialLoad !== true });
//if this is a child of a list view and it's the initial load of the editor, we need to get the tree node
//if this is a child of a list view and it's the initial load of the editor, we need to get the tree node
// from the server so that we can load in the actions menu.
umbRequestHelper.resourcePromise(
$http.get(content.treeNodeUrl),
@@ -189,7 +189,7 @@ function MemberEditController($scope, $routeParams, $location, $http, $q, appSta
$scope.page.saveButtonState = "busy";
//anytime a user is changing a member's password without the oldPassword, we are in effect resetting it so we need to set that flag here
var passwordProp = _.find(contentEditingHelper.getAllProps($scope.content), function (e) { return e.alias === '_umb_password' });
var passwordProp = _.find($scope.content.membershipProperties, function (e) { return e.alias === '_umb_password' });
if (passwordProp && passwordProp.value && (typeof passwordProp.value.reset !== 'undefined') && !passwordProp.value.reset) {
//so if the admin is not explicitly resetting the password, flag it for resetting if a new password is being entered
passwordProp.value.reset = !passwordProp.value.oldPassword && passwordProp.config.allowManuallyChangingPassword;
@@ -667,6 +667,7 @@ angular.module("umbraco")
$scope.removeControl = function (cell, $index) {
$scope.currentControl = null;
cell.controls.splice($index, 1);
currentForm.$setDirty();
};
$scope.percentage = function (spans) {
@@ -146,6 +146,7 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time
}
var listParamsForCurrent = $routeParams.id == $routeParams.list;
$scope.options = {
useInfiniteEditor: $scope.model.config.useInfiniteEditor === true,
pageSize: $scope.model.config.pageSize ? $scope.model.config.pageSize : 10,
@@ -169,6 +170,7 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time
allowBulkDelete: $scope.model.config.bulkActionPermissions.allowBulkDelete,
cultureName: $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture
};
_.each($scope.options.includeProperties, function (property) {
property.nameExp = !!property.nameTemplate
? $interpolate(property.nameTemplate)
@@ -268,12 +270,13 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time
$scope.getContent = function (contentId) {
$scope.reloadView($scope.contentId, true);
}
};
$scope.reloadView = function (id, reloadActiveNode) {
if (!id) {
return;
}
$scope.viewLoaded = false;
$scope.folders = [];
@@ -327,7 +330,7 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time
$scope.options.pageNumber = 1;
$scope.reloadView($scope.contentId);
}
}
};
$scope.onSearchStartTyping = function() {
$scope.viewLoaded = false;
@@ -689,9 +692,18 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time
}
if (e.nameExp) {
var newValue = e.nameExp({ value });
if (newValue && (newValue = newValue.trim())) {
value = newValue;
if (/{{.*\s*\w+\s*\|\s*\w+\s*.*}}/.test(e.nameTemplate)) { //check whether the name template has a filter
value = {
value,
expression: e.nameExp
};
}
else {
var newValue = e.nameExp({ value });
if (newValue && (newValue = newValue.trim())) {
value = newValue;
}
}
}
@@ -708,13 +720,15 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time
}
function initView() {
var id = $routeParams.id;
if (id === undefined) {
// no ID found in route params - don't list anything as we don't know for sure where we are
return;
}
$scope.contentId = id;
// Get current id for node to load it's children
$scope.contentId = editorState.current ? editorState.current.id : id;
$scope.isTrashed = editorState.current ? editorState.current.trashed : id === "-20" || id === "-21";
$scope.options.allowBulkPublish = $scope.options.allowBulkPublish && !$scope.isTrashed;
+2 -2
View File
@@ -348,9 +348,9 @@
<WebProjectProperties>
<UseIIS>False</UseIIS>
<AutoAssignPort>True</AutoAssignPort>
<DevelopmentServerPort>8180</DevelopmentServerPort>
<DevelopmentServerPort>8188</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>http://localhost:8180</IISUrl>
<IISUrl>http://localhost:8188</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>
<UseCustomServer>False</UseCustomServer>
<CustomServerUrl>
@@ -329,6 +329,7 @@
<key alias="orClickHereToUpload">or click here to choose files</key>
<key alias="dragFilesHereToUpload">You can drag files here to upload</key>
<key alias="disallowedFileType">Cannot upload this file, it does not have an approved file type</key>
<key alias="disallowedMediaType">Cannot upload this file, the media type with alias '%0%' is not allowed here</key>
<key alias="invalidFileName">Cannot upload this file, it does not have a valid file name</key>
<key alias="maxFileSize">Max file size is</key>
<key alias="mediaRoot">Media root</key>
@@ -1415,6 +1416,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
<key alias="theBestUmbracoVideoTutorials">The best Umbraco video tutorials</key>
<key alias="umbracoForum">Visit our.umbraco.com</key>
<key alias="umbracoTv">Visit umbraco.tv</key>
<key alias="umbracoLearningBase">Watch our free tutorial videos</key>
<key alias="umbracoLearningBaseDescription">on the Umbraco Learning Base</key>
</area>
<area alias="settings">
<key alias="defaulttemplate">Default template</key>
@@ -1448,6 +1451,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
<key alias="invalidUserPermissionsText">Insufficient user permissions, could not complete the operation</key>
<key alias="operationCancelledHeader">Cancelled</key>
<key alias="operationCancelledText">Operation was cancelled by a 3rd party add-in</key>
<key alias="folderUploadNotAllowed">This file is being uploaded as part of a folder, but creating a new folder is not allowed here</key>
<key alias="folderCreationNotAllowed">Creating a new folder is not allowed here</key>
<key alias="contentPublishedFailedByEvent">Publishing was cancelled by a 3rd party add-in</key>
<key alias="contentTypeDublicatePropertyType">Property type already exists</key>
<key alias="contentTypePropertyTypeCreated">Property type created</key>
@@ -2506,9 +2511,9 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
Ask a question in the <a class="btn-link -underline" href="https://our.umbraco.com/forum" target="_blank" rel="noopener">Community Forum</a>
]]>
</key>
<key alias="bulletPointThree">
<key alias="bulletPointTutorials">
<![CDATA[
Watch our <a class="btn-link -underline" href="https://umbraco.tv" target="_blank" rel="noopener">tutorial videos</a> (some are free, some require a subscription)
Watch our free <a class="btn-link -underline" href="https://umbra.co/ulb" target="_blank" rel="noopener">tutorial videos on the Umbraco Learning Base</a>
]]>
</key>
<key alias="bulletPointFour">
@@ -331,8 +331,9 @@
<area alias="media">
<key alias="clickToUpload">Click to upload</key>
<key alias="orClickHereToUpload">or click here to choose files</key>
<key alias="dragFilesHereToUpload">You can drag files here to upload.</key>
<key alias="dragFilesHereToUpload">You can drag files here to upload.</key>
<key alias="disallowedFileType">Cannot upload this file, it does not have an approved file type</key>
<key alias="disallowedMediaType">Cannot upload this file, the media type with alias '%0%' is not allowed here</key>
<key alias="invalidFileName">Cannot upload this file, it does not have a valid file name</key>
<key alias="maxFileSize">Max file size is</key>
<key alias="mediaRoot">Media root</key>
@@ -1425,6 +1426,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
<key alias="theBestUmbracoVideoTutorials">The best Umbraco video tutorials</key>
<key alias="umbracoForum">Visit our.umbraco.com</key>
<key alias="umbracoTv">Visit umbraco.tv</key>
<key alias="umbracoLearningBase">Watch our free tutorial videos</key>
<key alias="umbracoLearningBaseDescription">on the Umbraco Learning Base</key>
</area>
<area alias="settings">
<key alias="defaulttemplate">Default template</key>
@@ -1460,6 +1463,9 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
<key alias="invalidUserPermissionsText">Insufficient user permissions, could not complete the operation</key>
<key alias="operationCancelledHeader">Cancelled</key>
<key alias="operationCancelledText">Operation was cancelled by a 3rd party add-in</key>
<key alias="folderUploadNotAllowed">This file is being uploaded as part of a folder, but creating a new folder is not allowed here</key>
<key alias="folderCreationNotAllowed">Creating a new folder is not allowed here</key>
<key alias="contentPublishedFailedByEvent">Publishing was cancelled by a 3rd party add-in</key>
<key alias="contentTypeDublicatePropertyType">Property type already exists</key>
<key alias="contentTypePropertyTypeCreated">Property type created</key>
<key alias="contentTypePropertyTypeCreatedText"><![CDATA[Name: %0% <br /> DataType: %1%]]></key>
@@ -2544,9 +2550,9 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
Ask a question in the <a class="btn-link -underline" href="https://our.umbraco.com/forum" target="_blank" rel="noopener">Community Forum</a>
]]>
</key>
<key alias="bulletPointThree">
<key alias="bulletPointTutorials">
<![CDATA[
Watch our <a class="btn-link -underline" href="https://umbraco.tv" target="_blank" rel="noopener">tutorial videos</a> (some are free, some require a subscription)
Watch our free <a class="btn-link -underline" href="https://umbra.co/ulb" target="_blank" rel="noopener">tutorial videos on the Umbraco Learning Base</a>
]]>
</key>
<key alias="bulletPointFour">
+18 -9
View File
@@ -306,7 +306,7 @@ namespace Umbraco.Web.Editors
switch (type)
{
case Core.Constants.Trees.PartialViews:
if (IsDirectory(virtualPath, SystemDirectories.PartialViews))
if (IsDirectory(virtualPath, SystemDirectories.PartialViews, Current.FileSystems.PartialViewsFileSystem))
{
Services.FileService.DeletePartialViewFolder(virtualPath);
return Request.CreateResponse(HttpStatusCode.OK);
@@ -318,7 +318,7 @@ namespace Umbraco.Web.Editors
return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No Partial View or folder found with the specified path");
case Core.Constants.Trees.PartialViewMacros:
if (IsDirectory(virtualPath, SystemDirectories.MacroPartials))
if (IsDirectory(virtualPath, SystemDirectories.MacroPartials, Current.FileSystems.MacroPartialsFileSystem))
{
Services.FileService.DeletePartialViewMacroFolder(virtualPath);
return Request.CreateResponse(HttpStatusCode.OK);
@@ -330,7 +330,7 @@ namespace Umbraco.Web.Editors
return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No Partial View Macro or folder found with the specified path");
case Core.Constants.Trees.Scripts:
if (IsDirectory(virtualPath, SystemDirectories.Scripts))
if (IsDirectory(virtualPath, SystemDirectories.Scripts, Current.FileSystems.ScriptsFileSystem))
{
Services.FileService.DeleteScriptFolder(virtualPath);
return Request.CreateResponse(HttpStatusCode.OK);
@@ -343,7 +343,7 @@ namespace Umbraco.Web.Editors
return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No Script or folder found with the specified path");
case Core.Constants.Trees.Stylesheets:
if (IsDirectory(virtualPath, SystemDirectories.Css))
if (IsDirectory(virtualPath, SystemDirectories.Css, Current.FileSystems.StylesheetsFileSystem))
{
Services.FileService.DeleteStyleSheetFolder(virtualPath);
return Request.CreateResponse(HttpStatusCode.OK);
@@ -634,14 +634,23 @@ namespace Umbraco.Web.Editors
return value;
}
private bool IsDirectory(string virtualPath, string systemDirectory)
private bool IsDirectory(string virtualPath, string systemDirectory, IFileSystem fileSystem)
{
var path = IOHelper.MapPath(systemDirectory + "/" + virtualPath);
var dirInfo = new DirectoryInfo(path);
// If you turn off indexing in Windows this will have the attribute:
// `FileAttributes.Directory | FileAttributes.NotContentIndexed`
return (dirInfo.Attributes & FileAttributes.Directory) != 0;
// If it's a physical filesystem check with directory info
if (fileSystem.CanAddPhysical)
{
var dirInfo = new DirectoryInfo(path);
// If you turn off indexing in Windows this will have the attribute:
// `FileAttributes.Directory | FileAttributes.NotContentIndexed`
return (dirInfo.Attributes & FileAttributes.Directory) != 0;
}
// Otherwise check the filesystem abstraction to see if the folder exists
// Since this is used for delete, it presumably exists if we're trying to delete it
return fileSystem.DirectoryExists(path);
}
// this is an internal class for passing stylesheet data from the client to the controller while editing
+16 -2
View File
@@ -376,7 +376,7 @@ namespace Umbraco.Web.Editors
[HttpPost]
public IDictionary<string, ContentItemDisplay> GetEmptyByAliases(ContentTypesByAliases contentTypesByAliases)
{
// It's important to do this operation within a scope to reduce the amount of readlock queries.
// It's important to do this operation within a scope to reduce the amount of readlock queries.
using var scope = _scopeProvider.CreateScope(autoComplete: true);
var contentTypes = contentTypesByAliases.ContentTypeAliases.Select(alias => Services.ContentTypeService.Get(alias));
return GetEmpties(contentTypes, contentTypesByAliases.ParentId).ToDictionary(x => x.ContentTypeAlias);
@@ -2019,6 +2019,8 @@ namespace Umbraco.Web.Editors
var variantIndex = 0;
var defaultCulture = _allLangs.Value.Values.FirstOrDefault(x => x.IsDefault)?.IsoCode;
//loop through each variant, set the correct name and property values
foreach (var variant in contentSave.Variants)
{
@@ -2033,6 +2035,12 @@ namespace Umbraco.Web.Editors
if (variant.Culture.IsNullOrWhiteSpace())
throw new InvalidOperationException($"Cannot set culture name without a culture.");
contentSave.PersistedContent.SetCultureName(variant.Name, variant.Culture);
// If the variant culture is the default culture we also want to update the name on the Content itself.
if (variant.Culture.Equals(defaultCulture, StringComparison.InvariantCultureIgnoreCase))
{
contentSave.PersistedContent.Name = variant.Name;
}
}
else
{
@@ -2072,6 +2080,12 @@ namespace Umbraco.Web.Editors
variantIndex++;
}
// Map IsDirty cultures to edited cultures, to make it easier to verify changes on specific variants on Saving and Saved events.
var editedCultures = contentSave.PersistedContent.CultureInfos.Values
.Where(x => x.IsDirty())
.Select(x => x.Culture);
contentSave.PersistedContent.SetCultureEdited(editedCultures);
// handle template
if (string.IsNullOrWhiteSpace(contentSave.TemplateAlias)) // cleared: clear if not already null
{
@@ -2423,7 +2437,7 @@ namespace Umbraco.Web.Editors
Items = results.Select(x => new ContentVersionMetaViewModel(x))
};
}
[HttpPost]
[EnsureUserPermissionForContent("contentId", ActionUpdate.ActionLetter)]
public HttpResponseMessage PostSetContentVersionPreventCleanup(int contentId, int versionId, bool preventCleanup)
@@ -521,7 +521,7 @@ namespace Umbraco.Web.Editors
[HttpPost]
public HttpResponseMessage Import(string file)
{
var filePath = Path.Combine(IOHelper.MapPath(SystemDirectories.Data), file);
var filePath = Path.Combine(IOHelper.MapPath(SystemDirectories.TempFileUploads), file);
if (string.IsNullOrEmpty(file) || !System.IO.File.Exists(filePath))
{
return Request.CreateResponse(HttpStatusCode.NotFound);
@@ -576,35 +576,35 @@ namespace Umbraco.Web.Editors
var fileName = file.Headers.ContentDisposition.FileName.Trim(Constants.CharArrays.DoubleQuote);
var ext = fileName.Substring(fileName.LastIndexOf('.') + 1).ToLower();
var destFileName = Path.Combine(root, fileName);
if (Path.GetFullPath(destFileName).StartsWith(Path.GetFullPath(root)))
var destFilePath = Path.Combine(root, fileName);
if (Path.GetFullPath(destFilePath).StartsWith(Path.GetFullPath(root)))
{
try
{
// due to a bug before 8.7.0 we didn't delete temp files, so we need to make sure to delete before
// moving else you get errors and the upload fails without a message in the UI (there's a JS error)
if(System.IO.File.Exists(destFileName))
System.IO.File.Delete(destFileName);
if(System.IO.File.Exists(destFilePath))
System.IO.File.Delete(destFilePath);
// renaming the file because MultipartFormDataStreamProvider has created a random fileName instead of using the name from the
// content-disposition for more than 6 years now. Creating a CustomMultipartDataStreamProvider deriving from MultipartFormDataStreamProvider
// seems like a cleaner option, but I'm not sure where to put it and renaming only takes one line of code.
System.IO.File.Move(result.FileData[0].LocalFileName, destFileName);
System.IO.File.Move(result.FileData[0].LocalFileName, destFilePath);
}
catch (Exception ex)
{
Logger.Error<ContentTypeController, string>(ex, "Error uploading udt file to App_Data: {File}", destFileName);
Logger.Error<ContentTypeController, string>(ex, "Error uploading udt file to App_Data: {File}", destFilePath);
}
if (ext.InvariantEquals("udt"))
{
model.TempFileName = destFileName;
model.TempFileName = fileName;
var xd = new XmlDocument
{
XmlResolver = null
};
xd.Load(model.TempFileName);
xd.Load(destFilePath);
model.Alias = xd.DocumentElement?.SelectSingleNode("//DocumentType/Info/Alias")?.FirstChild.Value;
model.Name = xd.DocumentElement?.SelectSingleNode("//DocumentType/Info/Name")?.FirstChild.Value;
@@ -612,7 +612,7 @@ namespace Umbraco.Web.Editors
else
{
// Cleanup the temp file
System.IO.File.Delete(destFileName);
System.IO.File.Delete(destFilePath);
model.Notifications.Add(new Notification(
Services.TextService.Localize("speechBubbles", "operationFailedHeader"),
Services.TextService.Localize("media", "disallowedFileType"),
+19 -6
View File
@@ -20,6 +20,7 @@ using Umbraco.Core.Dashboards;
using Umbraco.Core.Models;
using Umbraco.Web.Services;
using System.Web.Http;
using Umbraco.Core.Telemetry;
namespace Umbraco.Web.Editors
{
@@ -34,18 +35,28 @@ namespace Umbraco.Web.Editors
{
private readonly IDashboardService _dashboardService;
private readonly IContentDashboardSettings _dashboardSettings;
private readonly ISiteIdentifierService _siteIdentifierService;
/// <summary>
/// Initializes a new instance of the <see cref="DashboardController"/> with all its dependencies.
/// </summary>
public DashboardController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor,
ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger,
IRuntimeState runtimeState, IDashboardService dashboardService, UmbracoHelper umbracoHelper,
IContentDashboardSettings dashboardSettings)
public DashboardController(
IGlobalSettings globalSettings,
IUmbracoContextAccessor umbracoContextAccessor,
ISqlContext sqlContext,
ServiceContext services,
AppCaches appCaches,
IProfilingLogger logger,
IRuntimeState runtimeState,
IDashboardService dashboardService,
UmbracoHelper umbracoHelper,
IContentDashboardSettings dashboardSettings,
ISiteIdentifierService siteIdentifierService)
: base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper)
{
_dashboardService = dashboardService;
_dashboardSettings = dashboardSettings;
_siteIdentifierService = siteIdentifierService;
}
//we have just one instance of HttpClient shared for the entire application
@@ -60,17 +71,19 @@ namespace Umbraco.Web.Editors
var language = user.Language;
var version = UmbracoVersion.SemanticVersion.ToSemanticString();
var isAdmin = user.IsAdmin();
_siteIdentifierService.TryGetOrCreateSiteIdentifier(out var siteIdentifier);
VerifyDashboardSource(baseUrl);
var url = string.Format("{0}{1}?section={2}&allowed={3}&lang={4}&version={5}&admin={6}",
var url = string.Format("{0}{1}?section={2}&allowed={3}&lang={4}&version={5}&admin={6}&siteid={7}",
baseUrl,
_dashboardSettings.ContentDashboardPath,
section,
allowedSections,
language,
version,
isAdmin);
isAdmin,
siteIdentifier.ToString());
var key = "umbraco-dynamic-dashboard-" + language + allowedSections.Replace(",", "-") + section;
var content = AppCaches.RuntimeCache.GetCacheItem<JObject>(key);
+38 -6
View File
@@ -2,14 +2,13 @@
using System.IO;
using System.Net;
using System.Net.Http;
using System.Web;
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
using Umbraco.Core.Models;
using Umbraco.Web.Media;
using Umbraco.Web.Mvc;
using Umbraco.Web.WebApi;
using Constants = Umbraco.Core.Constants;
namespace Umbraco.Web.Editors
{
@@ -60,20 +59,29 @@ namespace Umbraco.Web.Editors
/// </remarks>
public HttpResponseMessage GetResized(string imagePath, int width)
{
// We have to use HttpUtility to encode the path here, for non-ASCII characters
// We cannot use the WebUtility, as we only want to encode the path, and not the entire string
var encodedImagePath = HttpUtility.UrlPathEncode(imagePath);
var ext = Path.GetExtension(imagePath);
// check if imagePath is local to prevent open redirect
if (!IsAllowed(encodedImagePath))
{
return Request.CreateResponse(HttpStatusCode.Unauthorized);
}
// we need to check if it is an image by extension
if (_contentSection.IsImageFile(ext) == false)
return Request.CreateResponse(HttpStatusCode.NotFound);
//redirect to ImageProcessor thumbnail with rnd generated from last modified time of original media file
var response = Request.CreateResponse(HttpStatusCode.Found);
DateTimeOffset? imageLastModified = null;
try
{
imageLastModified = _mediaFileSystem.GetLastModified(imagePath);
}
catch (Exception)
{
@@ -84,11 +92,35 @@ namespace Umbraco.Web.Editors
}
var rnd = imageLastModified.HasValue ? $"&rnd={imageLastModified:yyyyMMddHHmmss}" : null;
var imageUrl = _imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(imagePath) { UpScale = false, Width = width, AnimationProcessMode = "first", ImageCropMode = "max", CacheBusterValue = rnd });
var imageUrl = _imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(encodedImagePath) { UpScale = false, Width = width, AnimationProcessMode = "first", ImageCropMode = "max", CacheBusterValue = rnd });
var response = Request.CreateResponse(HttpStatusCode.Found);
response.Headers.Location = new Uri(imageUrl, UriKind.RelativeOrAbsolute);
return response;
}
private bool IsAllowed(string encodedImagePath)
{
if(Uri.IsWellFormedUriString(encodedImagePath, UriKind.Relative))
{
return true;
}
if (_contentSection is ContentElement contentElement)
{
var builder = new UriBuilder(encodedImagePath);
foreach (var allowedMediaHost in contentElement.AllowedMediaHosts)
{
if (string.Equals(builder.Host, allowedMediaHost, StringComparison.InvariantCultureIgnoreCase))
{
return true;
}
}
}
return false;
}
}
}
+139 -52
View File
@@ -598,6 +598,13 @@ namespace Umbraco.Web.Editors
{
var intParentId = GetParentIdAsInt(folder.ParentId, validatePermissions:true);
var isFolderAllowed = IsFolderCreationAllowedHere(intParentId);
if (isFolderAllowed == false)
{
throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse(
Services.TextService.Localize("speechBubbles", "folderCreationNotAllowed")));
}
var mediaService = Services.MediaService;
var f = mediaService.CreateMedia(folder.Name, intParentId, Constants.Conventions.MediaTypes.Folder);
@@ -640,10 +647,16 @@ namespace Umbraco.Web.Editors
var tempFiles = new PostedFiles();
var mediaService = Services.MediaService;
var localizedTextService = Services.TextService;
//in case we pass a path with a folder in it, we will create it and upload media to it.
if (result.FormData.ContainsKey("path"))
{
if (!IsFolderCreationAllowedHere(parentId))
{
AddCancelMessage(tempFiles, message: "speechBubbles/folderUploadNotAllowed");
return Request.CreateResponse(HttpStatusCode.OK, tempFiles);
}
var folders = result.FormData["path"].Split(Constants.CharArrays.ForwardSlash);
@@ -653,7 +666,7 @@ namespace Umbraco.Web.Editors
IMedia folderMediaItem;
//if uploading directly to media root and not a subfolder
if (parentId == -1)
if (parentId == Constants.System.Root)
{
//look for matching folder
folderMediaItem =
@@ -691,6 +704,44 @@ namespace Umbraco.Web.Editors
}
}
var mediaTypeAlias = string.Empty;
var allMediaTypes = Services.MediaTypeService.GetAll().ToList();
var allowedContentTypes = new HashSet<IMediaType>();
if (parentId != Constants.System.Root)
{
var mediaFolderItem = mediaService.GetById(parentId);
var mediaFolderType = allMediaTypes.FirstOrDefault(x => x.Alias == mediaFolderItem.ContentType.Alias);
if (mediaFolderType != null)
{
IMediaType mediaTypeItem = null;
foreach (ContentTypeSort allowedContentType in mediaFolderType.AllowedContentTypes)
{
IMediaType checkMediaTypeItem = allMediaTypes.FirstOrDefault(x => x.Id == allowedContentType.Id.Value);
allowedContentTypes.Add(checkMediaTypeItem);
var fileProperty = checkMediaTypeItem?.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == Constants.Conventions.Media.File);
if (fileProperty != null)
{
mediaTypeItem = checkMediaTypeItem;
}
}
//Only set the permission-based mediaType if we only allow 1 specific file under this parent.
if (allowedContentTypes.Count == 1 && mediaTypeItem != null)
{
mediaTypeAlias = mediaTypeItem.Alias;
}
}
}
else
{
var typesAllowedAtRoot = allMediaTypes.Where(x => x.AllowedAsRoot).ToList();
allowedContentTypes.UnionWith(typesAllowedAtRoot);
}
//get the files
foreach (var file in result.FileData)
{
@@ -698,81 +749,94 @@ namespace Umbraco.Web.Editors
var safeFileName = fileName.ToSafeFileName();
var ext = safeFileName.Substring(safeFileName.LastIndexOf('.') + 1).ToLower();
if (Current.Configs.Settings().Content.IsFileAllowedForUpload(ext))
if (!Current.Configs.Settings().Content.IsFileAllowedForUpload(ext))
{
var mediaType = Constants.Conventions.MediaTypes.File;
tempFiles.Notifications.Add(new Notification(
localizedTextService.Localize("speechBubbles", "operationFailedHeader"),
localizedTextService.Localize("media", "disallowedFileType"),
NotificationStyle.Warning));
continue;
}
if (string.IsNullOrEmpty(mediaTypeAlias))
{
mediaTypeAlias = Constants.Conventions.MediaTypes.File;
if (result.FormData["contentTypeAlias"] == Constants.Conventions.MediaTypes.AutoSelect)
{
var mediaTypes = Services.MediaTypeService.GetAll();
// Look up MediaTypes
foreach (var mediaTypeItem in mediaTypes)
foreach (var mediaTypeItem in allMediaTypes)
{
var fileProperty = mediaTypeItem.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == "umbracoFile");
if (fileProperty != null) {
var dataTypeKey = fileProperty.DataTypeKey;
var dataType = Services.DataTypeService.GetDataType(dataTypeKey);
if (dataType != null && dataType.Configuration is IFileExtensionsConfig fileExtensionsConfig) {
var fileExtensions = fileExtensionsConfig.FileExtensions;
if (fileExtensions != null)
{
if (fileExtensions.Where(x => x.Value == ext).Count() != 0)
{
mediaType = mediaTypeItem.Alias;
break;
}
}
}
var fileProperty = mediaTypeItem.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == Constants.Conventions.Media.File);
if (fileProperty == null)
{
continue;
}
var dataTypeKey = fileProperty.DataTypeKey;
var dataType = Services.DataTypeService.GetDataType(dataTypeKey);
if (dataType == null || dataType.Configuration is not IFileExtensionsConfig fileExtensionsConfig)
{
continue;
}
var fileExtensions = fileExtensionsConfig.FileExtensions;
if (fileExtensions == null || fileExtensions.All(x => x.Value != ext))
{
continue;
}
mediaTypeAlias = mediaTypeItem.Alias;
break;
}
// If media type is still File then let's check if it's an image.
if (mediaType == Constants.Conventions.MediaTypes.File && Current.Configs.Settings().Content.ImageFileTypes.Contains(ext))
if (mediaTypeAlias == Constants.Conventions.MediaTypes.File && Current.Configs.Settings().Content.ImageFileTypes.Contains(ext))
{
mediaType = Constants.Conventions.MediaTypes.Image;
mediaTypeAlias = Constants.Conventions.MediaTypes.Image;
}
}
else
{
mediaType = result.FormData["contentTypeAlias"];
mediaTypeAlias = result.FormData["contentTypeAlias"];
}
}
var mediaItemName = fileName.ToFriendlyName();
if (allowedContentTypes.Any(x => x.Alias == mediaTypeAlias) == false)
{
tempFiles.Notifications.Add(new Notification(
localizedTextService.Localize("speechBubbles", "operationFailedHeader"),
localizedTextService.Localize("media", "disallowedMediaType", new[] { mediaTypeAlias }),
NotificationStyle.Warning));
continue;
}
var f = mediaService.CreateMedia(mediaItemName, parentId, mediaType, Security.CurrentUser.Id);
var mediaItemName = fileName.ToFriendlyName();
var fileInfo = new FileInfo(file.LocalFileName);
var fs = fileInfo.OpenReadWithRetry();
if (fs == null) throw new InvalidOperationException("Could not acquire file stream");
using (fs)
{
f.SetValue(Services.ContentTypeBaseServices, Constants.Conventions.Media.File,fileName, fs);
}
var createdMediaItem = mediaService.CreateMedia(mediaItemName, parentId, mediaTypeAlias, Security.CurrentUser.Id);
var saveResult = mediaService.Save(f, Security.CurrentUser.Id);
if (saveResult == false)
{
AddCancelMessage(tempFiles,
message: Services.TextService.Localize("speechBubbles", "operationCancelledText") + " -- " + mediaItemName);
}
else
{
tempFiles.UploadedFiles.Add(new ContentPropertyFile
{
FileName = fileName,
PropertyAlias = Constants.Conventions.Media.File,
TempFilePath = file.LocalFileName
});
}
var fileInfo = new FileInfo(file.LocalFileName);
var fs = fileInfo.OpenReadWithRetry();
if (fs == null) throw new InvalidOperationException("Could not acquire file stream");
using (fs)
{
createdMediaItem.SetValue(Services.ContentTypeBaseServices, Constants.Conventions.Media.File, fileName, fs);
}
var saveResult = mediaService.Save(createdMediaItem, Security.CurrentUser.Id);
if (saveResult == false)
{
AddCancelMessage(tempFiles, message: "speechBubbles/operationCancelledText" + " -- " + mediaItemName);
}
else
{
tempFiles.Notifications.Add(new Notification(
Services.TextService.Localize("speechBubbles", "operationFailedHeader"),
Services.TextService.Localize("media", "disallowedFileType"),
NotificationStyle.Warning));
tempFiles.UploadedFiles.Add(new ContentPropertyFile
{
FileName = fileName,
PropertyAlias = Constants.Conventions.Media.File,
TempFilePath = file.LocalFileName
});
}
}
@@ -792,6 +856,29 @@ namespace Umbraco.Web.Editors
return Request.CreateResponse(HttpStatusCode.OK, tempFiles);
}
private bool IsFolderCreationAllowedHere(int parentId)
{
var allMediaTypes = Services.MediaTypeService.GetAll().ToList();
var isFolderAllowed = false;
if (parentId == Constants.System.Root)
{
var typesAllowedAtRoot = allMediaTypes.Where(ct => ct.AllowedAsRoot).ToList();
isFolderAllowed = typesAllowedAtRoot.Any(x => x.Alias == Constants.Conventions.MediaTypes.Folder);
}
else
{
var parentMediaType = Services.MediaService.GetById(parentId);
var mediaFolderType = allMediaTypes.FirstOrDefault(x => x.Alias == parentMediaType.ContentType.Alias);
if (mediaFolderType != null)
{
isFolderAllowed =
mediaFolderType.AllowedContentTypes.Any(x => x.Alias == Constants.Conventions.MediaTypes.Folder);
}
}
return isFolderAllowed;
}
private IMedia FindInChildren(int mediaId, string nameToFind, string contentTypeAlias)
{
const int pageSize = 500;
+2 -1
View File
@@ -120,8 +120,9 @@ namespace Umbraco.Web.Editors
var fileName = file.Headers.ContentDisposition.FileName.Trim(Constants.CharArrays.DoubleQuote).TrimEnd();
var safeFileName = fileName.ToSafeFileName();
var ext = safeFileName.Substring(safeFileName.LastIndexOf('.') + 1).ToLower();
const string allowedAvatarFileTypes = "jpeg,jpg,gif,bmp,png,tiff,tif,webp";
if (Current.Configs.Settings().Content.DisallowedUploadFiles.Contains(ext) == false)
if (allowedAvatarFileTypes.Contains(ext) == true && Current.Configs.Settings().Content.DisallowedUploadFiles.Contains(ext) == false)
{
//generate a path of known data, we don't want this path to be guessable
user.Avatar = "UserAvatars/" + (user.Id + safeFileName).GenerateHash<SHA1>() + "." + ext;
@@ -1,10 +1,6 @@
using System;
using System.IO;
using System.Threading.Tasks;
using System.Xml.Linq;
using System.Threading.Tasks;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Telemetry;
using Umbraco.Web.Install.Models;
namespace Umbraco.Web.Install.InstallSteps
@@ -14,54 +10,20 @@ namespace Umbraco.Web.Install.InstallSteps
PerformsAppRestart = false)]
internal class TelemetryIdentifierStep : InstallSetupStep<object>
{
private readonly IProfilingLogger _logger;
private readonly IUmbracoSettingsSection _settings;
private readonly ISiteIdentifierService _siteIdentifierService;
public TelemetryIdentifierStep(IProfilingLogger logger, IUmbracoSettingsSection settings)
public TelemetryIdentifierStep(
IUmbracoSettingsSection settings,
ISiteIdentifierService siteIdentifierService)
{
_logger = logger;
_settings = settings;
_siteIdentifierService = siteIdentifierService;
}
public override Task<InstallSetupResult> ExecuteAsync(object model)
{
// Generate GUID
var telemetrySiteIdentifier = Guid.NewGuid();
// Modify the XML to add a new GUID site identifier
// hack: ensure this does not trigger a restart
using (ChangesMonitor.Suspended())
{
var umbracoSettingsPath = IOHelper.MapPath(SystemFiles.UmbracoSettings);
if(File.Exists(umbracoSettingsPath) == false)
{
// Log an error
_logger.Error<TelemetryIdentifierStep>("Unable to find umbracoSettings.config file to add telemetry site identifier");
return Task.FromResult<InstallSetupResult>(null);
}
try
{
var umbracoConfigXml = XDocument.Load(umbracoSettingsPath, LoadOptions.PreserveWhitespace);
if (umbracoConfigXml.Root != null)
{
var backofficeElement = umbracoConfigXml.Root.Element("backOffice");
if (backofficeElement == null)
return Task.FromResult<InstallSetupResult>(null);
// Will add ID attribute if it does not exist
backofficeElement.SetAttributeValue("id", telemetrySiteIdentifier.ToString());
// Save file back down
umbracoConfigXml.Save(umbracoSettingsPath, SaveOptions.DisableFormatting);
}
}
catch (Exception ex)
{
_logger.Error<TelemetryIdentifierStep>(ex, "Couldn't update umbracoSettings.config with a backoffice with a telemetry site identifier");
}
}
_siteIdentifierService.TryCreateSiteIdentifier(out _);
return Task.FromResult<InstallSetupResult>(null);
}
@@ -87,8 +87,7 @@ namespace Umbraco.Web.Models.Mapping
{
MapSaveToTypeBase<DocumentTypeSave, PropertyTypeBasic>(source, target, context);
MapComposition(source, target, alias => _contentTypeService.Get(alias));
target.HistoryCleanup = source.HistoryCleanup;
MapHistoryCleanup(source, target);
target.AllowedTemplates = source.AllowedTemplates
.Where(x => x != null)
@@ -99,6 +98,34 @@ namespace Umbraco.Web.Models.Mapping
target.SetDefaultTemplate(source.DefaultTemplate == null ? null : _fileService.GetTemplate(source.DefaultTemplate));
}
private static void MapHistoryCleanup(DocumentTypeSave source, IContentType target)
{
// If source history cleanup is null we don't have to map all properties
if (source.HistoryCleanup is null)
{
target.HistoryCleanup = null;
return;
}
// We need to reset the dirty properties, because it is otherwise true, just because the json serializer has set properties
target.HistoryCleanup.ResetDirtyProperties(false);
if (target.HistoryCleanup.PreventCleanup != source.HistoryCleanup.PreventCleanup)
{
target.HistoryCleanup.PreventCleanup = source.HistoryCleanup.PreventCleanup;
}
if (target.HistoryCleanup.KeepAllVersionsNewerThanDays != source.HistoryCleanup.KeepAllVersionsNewerThanDays)
{
target.HistoryCleanup.KeepAllVersionsNewerThanDays = source.HistoryCleanup.KeepAllVersionsNewerThanDays;
}
if (target.HistoryCleanup.KeepLatestVersionPerDayForDays !=
source.HistoryCleanup.KeepLatestVersionPerDayForDays)
{
target.HistoryCleanup.KeepLatestVersionPerDayForDays = source.HistoryCleanup.KeepLatestVersionPerDayForDays;
}
}
// no MapAll - take care
private void Map(MediaTypeSave source, IMediaType target, MapperContext context)
{
@@ -253,7 +280,12 @@ namespace Umbraco.Web.Models.Mapping
target.Id = source.Id;
if (source.GroupId > 0)
target.PropertyGroupId = new Lazy<int>(() => source.GroupId, false);
{
if (target.PropertyGroupId?.Value != source.GroupId)
{
target.PropertyGroupId = new Lazy<int>(() => source.GroupId, false);
}
}
target.Alias = source.Alias;
target.Description = source.Description;
@@ -476,13 +508,23 @@ namespace Umbraco.Web.Models.Mapping
// ensure no duplicate alias, then assign the group properties collection
EnsureUniqueAliases(destProperties);
destGroup.PropertyTypes = new PropertyTypeCollection(isPublishing, destProperties);
if (destGroup.PropertyTypes.SupportsPublishing != isPublishing || destGroup.PropertyTypes.SequenceEqual(destProperties) is false)
{
destGroup.PropertyTypes = new PropertyTypeCollection(isPublishing, destProperties);
}
destGroups.Add(destGroup);
}
// ensure no duplicate name, then assign the groups collection
EnsureUniqueAliases(destGroups);
target.PropertyGroups = new PropertyGroupCollection(destGroups);
if (target.PropertyGroups.SequenceEqual(destGroups) is false)
{
target.PropertyGroups = new PropertyGroupCollection(destGroups);
}
// because the property groups collection was rebuilt, there is no need to remove
// the old groups - they are just gone and will be cleared by the repository
@@ -96,7 +96,7 @@ namespace Umbraco.Web.Models.Mapping
// Umbraco.Code.MapAll -Udi -Trashed
private static void Map(IUser source, EntityBasic target, MapperContext context)
{
target.Alias = source.Username;
target.Alias = source.Key.ToString();
target.Icon = Constants.Icons.User;
target.Id = source.Id;
target.Key = source.Key;
@@ -18,6 +18,7 @@ namespace Umbraco.Web.PropertyEditors
private readonly ILogger _logger;
private readonly IMediaService _mediaService;
private readonly IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider;
private readonly string _tempFolderAbsolutePath;
const string TemporaryImageDataAttribute = "data-tmpimg";
@@ -27,6 +28,7 @@ namespace Umbraco.Web.PropertyEditors
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_mediaService = mediaService ?? throw new ArgumentNullException(nameof(mediaService));
_contentTypeBaseServiceProvider = contentTypeBaseServiceProvider ?? throw new ArgumentNullException(nameof(contentTypeBaseServiceProvider));
_tempFolderAbsolutePath = Path.GetFullPath(IOHelper.MapPath(SystemDirectories.TempImageUploads));
}
/// <summary>
@@ -59,7 +61,13 @@ namespace Umbraco.Web.PropertyEditors
if (string.IsNullOrEmpty(tmpImgPath))
continue;
var absoluteTempImagePath = IOHelper.MapPath(tmpImgPath);
var absoluteTempImagePath = Path.GetFullPath(IOHelper.MapPath(tmpImgPath));
if (IsValidPath(absoluteTempImagePath) == false)
{
continue;
}
var fileName = Path.GetFileName(absoluteTempImagePath);
var safeFileName = fileName.ToSafeFileName();
@@ -139,5 +147,10 @@ namespace Umbraco.Web.PropertyEditors
return htmlDoc.DocumentNode.OuterHtml;
}
private bool IsValidPath(string imagePath)
{
return imagePath.StartsWith(_tempFolderAbsolutePath, StringComparison.OrdinalIgnoreCase);
}
}
}
@@ -49,11 +49,14 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
public Type GetModelType(Guid contentTypeKey)
{
var publishedContentType = GetContentType(contentTypeKey);
if (publishedContentType != null)
if (publishedContentType != null && publishedContentType.IsElement)
{
var modelType = ModelType.For(publishedContentType.Alias);
return _publishedModelFactory.MapModelType(modelType);
// TODO Get the model type without having to construct a list
var listType = _publishedModelFactory.CreateModelList(publishedContentType.Alias).GetType();
if (listType.GenericTypeArguments.Length == 1)
{
return listType.GenericTypeArguments[0];
}
}
return typeof(IPublishedElement);
@@ -10,7 +10,6 @@ using Umbraco.Core.PropertyEditors.ValueConverters;
namespace Umbraco.Web.PropertyEditors.ValueConverters
{
[DefaultPropertyValueConverter(typeof(JsonValueConverter))]
public class BlockListPropertyValueConverter : PropertyValueConverterBase
{
@@ -30,7 +29,8 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
=> propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.BlockList);
/// <inheritdoc />
public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof(BlockListModel);
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
=> typeof(BlockListModel);
/// <inheritdoc />
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)
@@ -38,24 +38,27 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
/// <inheritdoc />
public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview)
{
return source?.ToString();
}
=> source?.ToString();
/// <inheritdoc />
public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview)
{
// NOTE: The intermediate object is just a json string, we don't actually convert from source -> intermediate since source is always just a json string
// NOTE: The intermediate object is just a JSON string, we don't actually convert from source -> intermediate since source is always just a JSON string
using (_proflog.DebugDuration<BlockListPropertyValueConverter>($"ConvertPropertyToBlockList ({propertyType.DataType.Id})"))
{
var value = (string)inter;
// Short-circuit on empty values
if (string.IsNullOrWhiteSpace(value)) return BlockListModel.Empty;
if (string.IsNullOrWhiteSpace(value))
{
return BlockListModel.Empty;
}
var converted = _blockListEditorDataConverter.Deserialize(value);
if (converted.BlockValue.ContentData.Count == 0) return BlockListModel.Empty;
if (converted.BlockValue.ContentData.Count == 0)
{
return BlockListModel.Empty;
}
var blockListLayout = converted.Layout.ToObject<IEnumerable<BlockListLayoutItem>>();
@@ -68,77 +71,133 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
var contentPublishedElements = new Dictionary<Guid, IPublishedElement>();
foreach (var data in converted.BlockValue.ContentData)
{
if (!blockConfigMap.ContainsKey(data.ContentTypeKey)) continue;
if (!blockConfigMap.ContainsKey(data.ContentTypeKey))
{
continue;
}
var element = _blockConverter.ConvertToElement(data, referenceCacheLevel, preview);
if (element == null) continue;
if (element == null)
{
continue;
}
contentPublishedElements[element.Key] = element;
}
// If there are no content elements, it doesn't matter what is stored in layout
if (contentPublishedElements.Count == 0) return BlockListModel.Empty;
if (contentPublishedElements.Count == 0)
{
return BlockListModel.Empty;
}
// Convert the settings data
var settingsPublishedElements = new Dictionary<Guid, IPublishedElement>();
foreach (var data in converted.BlockValue.SettingsData)
{
if (!validSettingsElementTypes.Contains(data.ContentTypeKey)) continue;
if (!validSettingsElementTypes.Contains(data.ContentTypeKey))
{
continue;
}
var element = _blockConverter.ConvertToElement(data, referenceCacheLevel, preview);
if (element == null) continue;
if (element == null)
{
continue;
}
settingsPublishedElements[element.Key] = element;
}
var layout = new List<BlockListItem>();
// Cache constructors locally (it's tied to the current IPublishedSnapshot and IPublishedModelFactory)
var blockListItemActivator = new BlockListItemActivator(_blockConverter);
var list = new List<BlockListItem>();
foreach (var layoutItem in blockListLayout)
{
// Get the content reference
var contentGuidUdi = (GuidUdi)layoutItem.ContentUdi;
if (!contentPublishedElements.TryGetValue(contentGuidUdi.Guid, out var contentData))
{
continue;
}
if (!contentData.ContentType.TryGetKey(out var contentTypeKey))
{
throw new InvalidOperationException("The content type was not of type " + typeof(IPublishedContentType2));
}
if (!blockConfigMap.TryGetValue(contentTypeKey, out var blockConfig))
{
continue;
}
// Get the setting reference
IPublishedElement settingsData = null;
var settingGuidUdi = layoutItem.SettingsUdi != null ? (GuidUdi)layoutItem.SettingsUdi : null;
var settingGuidUdi = (GuidUdi)layoutItem.SettingsUdi;
if (settingGuidUdi != null)
{
settingsPublishedElements.TryGetValue(settingGuidUdi.Guid, out settingsData);
}
// This can happen if they have a settings type, save content, remove the settings type, and display the front-end page before saving the content again
// We also ensure that the content types match, since maybe the settings type has been changed after this has been persisted
if (settingsData != null)
{
if (!settingsData.ContentType.TryGetKey(out var settingsElementTypeKey))
{
throw new InvalidOperationException("The settings element type was not of type " + typeof(IPublishedContentType2));
}
if (!blockConfig.SettingsElementTypeKey.HasValue || settingsElementTypeKey != blockConfig.SettingsElementTypeKey)
{
settingsData = null;
}
}
// Get settings type from configuration
var settingsType = blockConfig.SettingsElementTypeKey.HasValue
? _blockConverter.GetModelType(blockConfig.SettingsElementTypeKey.Value)
: typeof(IPublishedElement);
// Create instance (use content/settings type from configuration)
var layoutRef = blockListItemActivator.CreateInstance(blockConfig.ContentElementTypeKey, blockConfig.SettingsElementTypeKey, contentGuidUdi, contentData, settingGuidUdi, settingsData);
// TODO: This should be optimized/cached, as calling Activator.CreateInstance is slow
var layoutType = typeof(BlockListItem<,>).MakeGenericType(contentData.GetType(), settingsType);
var layoutRef = (BlockListItem)Activator.CreateInstance(layoutType, contentGuidUdi, contentData, settingGuidUdi, settingsData);
layout.Add(layoutRef);
list.Add(layoutRef);
}
var model = new BlockListModel(layout);
return model;
return new BlockListModel(list);
}
}
private class BlockListItemActivator
{
private readonly BlockEditorConverter _blockConverter;
private readonly Dictionary<(Guid, Guid?), Func<Udi, IPublishedElement, Udi, IPublishedElement, BlockListItem>> _contructorCache = new();
public BlockListItemActivator(BlockEditorConverter blockConverter)
=> _blockConverter = blockConverter;
public BlockListItem CreateInstance(Guid contentTypeKey, Guid? settingsTypeKey, Udi contentUdi, IPublishedElement contentData, Udi settingsUdi, IPublishedElement settingsData)
{
if (!_contructorCache.TryGetValue((contentTypeKey, settingsTypeKey), out var constructor))
{
constructor = _contructorCache[(contentTypeKey, settingsTypeKey)] = EmitConstructor(contentTypeKey, settingsTypeKey);
}
return constructor(contentUdi, contentData, settingsUdi, settingsData);
}
private Func<Udi, IPublishedElement, Udi, IPublishedElement, BlockListItem> EmitConstructor(Guid contentTypeKey, Guid? settingsTypeKey)
{
var contentType = _blockConverter.GetModelType(contentTypeKey);
var settingsType = settingsTypeKey.HasValue ? _blockConverter.GetModelType(settingsTypeKey.Value) : typeof(IPublishedElement);
var type = typeof(BlockListItem<,>).MakeGenericType(contentType, settingsType);
var constructor = type.GetConstructor(new[] { typeof(Udi), contentType, typeof(Udi), settingsType });
if (constructor == null)
{
throw new InvalidOperationException($"Could not find the required public constructor on {type}.");
}
// We use unsafe here, because we know the contructor parameter count and types match
return ReflectionUtilities.EmitConstructorUnsafe<Func<Udi, IPublishedElement, Udi, IPublishedElement, BlockListItem>>(constructor);
}
}
}
}
@@ -42,7 +42,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
{
var maxNumber = propertyType.DataType.ConfigurationAs<MultiUrlPickerConfiguration>().MaxNumber;
if (inter == null)
if (string.IsNullOrWhiteSpace(inter?.ToString()))
{
return maxNumber == 1 ? null : Enumerable.Empty<Link>();
}
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
@@ -25,9 +25,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
/// </summary>
public NestedContentManyValueConverter(IPublishedSnapshotAccessor publishedSnapshotAccessor, IPublishedModelFactory publishedModelFactory, IProfilingLogger proflog)
: base(publishedSnapshotAccessor, publishedModelFactory)
{
_proflog = proflog;
}
=> _proflog = proflog;
/// <inheritdoc />
public override bool IsConverter(IPublishedPropertyType propertyType)
@@ -37,6 +35,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
{
var contentTypes = propertyType.DataType.ConfigurationAs<NestedContentConfiguration>().ContentTypes;
return contentTypes.Length == 1
? typeof(IEnumerable<>).MakeGenericType(ModelType.For(contentTypes[0].Alias))
: typeof(IEnumerable<IPublishedElement>);
@@ -48,9 +47,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
/// <inheritdoc />
public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview)
{
return source?.ToString();
}
=> source?.ToString();
/// <inheritdoc />
public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview)
@@ -64,16 +61,24 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
: new List<IPublishedElement>();
var value = (string)inter;
if (string.IsNullOrWhiteSpace(value)) return elements;
if (string.IsNullOrWhiteSpace(value))
{
return elements;
}
var objects = JsonConvert.DeserializeObject<List<JObject>>(value);
if (objects.Count == 0) return elements;
if (objects.Count == 0)
{
return elements;
}
foreach (var sourceObject in objects)
{
var element = ConvertToElement(sourceObject, referenceCacheLevel, preview);
if (element != null)
{
elements.Add(element);
}
}
return elements;
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -24,9 +24,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
/// </summary>
public NestedContentSingleValueConverter(IPublishedSnapshotAccessor publishedSnapshotAccessor, IPublishedModelFactory publishedModelFactory, IProfilingLogger proflog)
: base(publishedSnapshotAccessor, publishedModelFactory)
{
_proflog = proflog;
}
=> _proflog = proflog;
/// <inheritdoc />
public override bool IsConverter(IPublishedPropertyType propertyType)
@@ -36,9 +34,10 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
{
var contentTypes = propertyType.DataType.ConfigurationAs<NestedContentConfiguration>().ContentTypes;
return contentTypes.Length > 1
? typeof(IPublishedElement)
: ModelType.For(contentTypes[0].Alias);
return contentTypes.Length == 1
? ModelType.For(contentTypes[0].Alias)
: typeof(IPublishedElement);
}
/// <inheritdoc />
@@ -47,9 +46,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
/// <inheritdoc />
public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview)
{
return source?.ToString();
}
=> source?.ToString();
/// <inheritdoc />
public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview)
@@ -57,14 +54,18 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
using (_proflog.DebugDuration<NestedContentSingleValueConverter>($"ConvertPropertyToNestedContent ({propertyType.DataType.Id})"))
{
var value = (string)inter;
if (string.IsNullOrWhiteSpace(value)) return null;
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
var objects = JsonConvert.DeserializeObject<List<JObject>>(value);
if (objects.Count == 0)
{
return null;
if (objects.Count > 1)
throw new InvalidOperationException();
}
// Only return the first (existing data might contain more than is currently configured)
return ConvertToElement(objects[0], referenceCacheLevel, preview);
}
}
@@ -12,52 +12,54 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
{
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
protected IPublishedModelFactory PublishedModelFactory { get; }
protected NestedContentValueConverterBase(IPublishedSnapshotAccessor publishedSnapshotAccessor, IPublishedModelFactory publishedModelFactory)
{
_publishedSnapshotAccessor = publishedSnapshotAccessor;
PublishedModelFactory = publishedModelFactory;
}
protected IPublishedModelFactory PublishedModelFactory { get; }
public static bool IsNested(IPublishedPropertyType publishedProperty)
{
return publishedProperty.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.NestedContent);
}
=> publishedProperty.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.NestedContent);
public static bool IsNestedSingle(IPublishedPropertyType publishedProperty)
private static bool IsSingle(IPublishedPropertyType publishedProperty)
{
if (!IsNested(publishedProperty))
return false;
var config = publishedProperty.DataType.ConfigurationAs<NestedContentConfiguration>();
return config.MinItems == 1 && config.MaxItems == 1;
}
public static bool IsNestedSingle(IPublishedPropertyType publishedProperty)
=> IsNested(publishedProperty) && IsSingle(publishedProperty);
public static bool IsNestedMany(IPublishedPropertyType publishedProperty)
{
return IsNested(publishedProperty) && !IsNestedSingle(publishedProperty);
}
=> IsNested(publishedProperty) && !IsSingle(publishedProperty);
protected IPublishedElement ConvertToElement(JObject sourceObject, PropertyCacheLevel referenceCacheLevel, bool preview)
{
var elementTypeAlias = sourceObject[NestedContentPropertyEditor.ContentTypeAliasPropertyKey]?.ToObject<string>();
if (string.IsNullOrEmpty(elementTypeAlias))
{
return null;
}
// only convert element types - content types will cause an exception when PublishedModelFactory creates the model
// Only convert element types - content types will cause an exception when PublishedModelFactory creates the model
var publishedContentType = _publishedSnapshotAccessor.PublishedSnapshot.Content.GetContentType(elementTypeAlias);
if (publishedContentType == null || publishedContentType.IsElement == false)
{
return null;
}
var propertyValues = sourceObject.ToObject<Dictionary<string, object>>();
if (!propertyValues.TryGetValue("key", out var keyo)
|| !Guid.TryParse(keyo.ToString(), out var key))
if (!propertyValues.TryGetValue("key", out var keyo) || !Guid.TryParse(keyo.ToString(), out var key))
{
key = Guid.Empty;
}
IPublishedElement element = new PublishedElement(publishedContentType, key, propertyValues, preview, referenceCacheLevel, _publishedSnapshotAccessor);
element = PublishedModelFactory.CreateModel(element);
return element;
}
}
@@ -433,10 +433,18 @@ namespace Umbraco.Web.PublishedCache.NuCache
refreshedIdsA.Contains(x.ContentTypeId) &&
BuildKit(x, out _)))
{
// replacing the node: must preserve the parents
// replacing the node: must preserve the relations
var node = GetHead(_contentNodes, kit.Node.Id)?.Value;
if (node != null)
{
// Preserve children
kit.Node.FirstChildContentId = node.FirstChildContentId;
kit.Node.LastChildContentId = node.LastChildContentId;
// Also preserve siblings
kit.Node.NextSiblingContentId = node.NextSiblingContentId;
kit.Node.PreviousSiblingContentId = node.PreviousSiblingContentId;
}
SetValueLocked(_contentNodes, kit.Node.Id, kit.Node);
@@ -39,7 +39,9 @@ using Umbraco.Web.WebApi;
using Current = Umbraco.Web.Composing.Current;
using Umbraco.Web.PropertyEditors;
using Umbraco.Core.Models;
using Umbraco.Core.Telemetry;
using Umbraco.Web.Models;
using Umbraco.Web.Telemetry;
namespace Umbraco.Web.Runtime
{
@@ -58,6 +60,8 @@ namespace Umbraco.Web.Runtime
composition.ComposeWebMappingProfiles();
composition.RegisterUnique<ISiteIdentifierService, SiteIdentifierService>();
//register the install components
//NOTE: i tried to not have these registered if we weren't installing or upgrading but post install when the site restarts
//it still needs to use the install controller so we can't do that
@@ -239,7 +239,9 @@ namespace Umbraco.Web.Search
var allLangs = _languageService.GetAllLanguages().Select(x => x.IsoCode.ToLowerInvariant()).ToList();
// the chars [*-_] in the query will mess everything up so let's remove those
query = Regex.Replace(query, "[\\*\\-_]", "");
// However we cannot just remove - and _ since these signify a space, so we instead replace them with that.
query = Regex.Replace(query, "[\\*]", string.Empty);
query = Regex.Replace(query, "[\\-_]", " ");
//check if text is surrounded by single or double quotes, if so, then exact match
var surroundedByQuotes = Regex.IsMatch(query, "^\".*?\"$")
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Threading.Tasks;
using System.Web;
using System.Web.Security;
@@ -294,21 +295,22 @@ namespace Umbraco.Web.Security
passwordValidator.RequiredLength,
passwordValidator.RequireNonLetterOrDigit ? 2 : 0);
var random = new Random();
var passwordChars = password.ToCharArray();
if (passwordValidator.RequireDigit && passwordChars.ContainsAny(Enumerable.Range(48, 58).Select(x => (char)x)))
password += Convert.ToChar(random.Next(48, 58)); // 0-9
using (var numberGenerator = new RNGCryptoServiceProvider())
{
if (passwordValidator.RequireDigit && passwordChars.ContainsAny(Enumerable.Range(48, 58).Select(x => (char)x)))
password += Convert.ToChar(numberGenerator.GetInt32(48, 58)); // 0-9
if (passwordValidator.RequireLowercase && passwordChars.ContainsAny(Enumerable.Range(97, 123).Select(x => (char)x)))
password += Convert.ToChar(random.Next(97, 123)); // a-z
if (passwordValidator.RequireLowercase && passwordChars.ContainsAny(Enumerable.Range(97, 123).Select(x => (char)x)))
password += Convert.ToChar(numberGenerator.GetInt32(97, 123)); // a-z
if (passwordValidator.RequireUppercase && passwordChars.ContainsAny(Enumerable.Range(65, 91).Select(x => (char)x)))
password += Convert.ToChar(random.Next(65, 91)); // A-Z
if (passwordValidator.RequireUppercase && passwordChars.ContainsAny(Enumerable.Range(65, 91).Select(x => (char)x)))
password += Convert.ToChar(numberGenerator.GetInt32(65, 91)); // A-Z
if (passwordValidator.RequireNonLetterOrDigit && passwordChars.ContainsAny(Enumerable.Range(33, 48).Select(x => (char)x)))
password += Convert.ToChar(random.Next(33, 48)); // symbols !"#$%&'()*+,-./
if (passwordValidator.RequireNonLetterOrDigit && passwordChars.ContainsAny(Enumerable.Range(33, 48).Select(x => (char)x)))
password += Convert.ToChar(numberGenerator.GetInt32(33, 48)); // symbols !"#$%&'()*+,-./
}
return password;
}
@@ -587,6 +587,13 @@ namespace Umbraco.Web.Security.Providers
requiresFullSave = true;
}
// If the last login date is default prior to setting it, it means that this value has never been set
// and therefore there's no property data created for it yet, which means that we can't just update that property
// and need to do a full save.
if (member.LastLoginDate == default)
{
requiresFullSave = true;
}
member.LastLoginDate = DateTime.Now;
Current.Logger.Info<UmbracoMembershipProviderBase, string, string>("Login attempt succeeded for username {Username} from IP address {IpAddress}", username, GetCurrentRequestIpAddress());
+7 -7
View File
@@ -48,13 +48,16 @@ namespace Umbraco.Web.Telemetry
try
{
// Send data to LIVE telemetry
_httpClient.BaseAddress = new Uri("https://telemetry.umbraco.com/");
if (_httpClient.BaseAddress == null)
{
// Send data to LIVE telemetry
_httpClient.BaseAddress = new Uri("https://telemetry.umbraco.com/");
#if DEBUG
// Send data to DEBUG telemetry service
_httpClient.BaseAddress = new Uri("https://telemetry.rainbowsrock.net/");
// Send data to DEBUG telemetry service
_httpClient.BaseAddress = new Uri("https://telemetry.rainbowsrock.net/");
#endif
}
_httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json");
@@ -62,9 +65,6 @@ namespace Umbraco.Web.Telemetry
{
request.Content = new StringContent(JsonConvert.SerializeObject(telemetryReportData), Encoding.UTF8, "application/json"); //CONTENT-TYPE header
// Set a low timeout - no need to use a larger default timeout for this POST request
_httpClient.Timeout = new TimeSpan(0, 0, 1);
// Make a HTTP Post to telemetry service
// https://telemetry.umbraco.com/installs/
// Fire & Forget, do not need to know if its a 200, 500 etc
@@ -0,0 +1,100 @@
using System;
using System.IO;
using System.Xml.Linq;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Telemetry;
using Umbraco.Web.Install;
namespace Umbraco.Web.Telemetry
{
internal class SiteIdentifierService : ISiteIdentifierService
{
private readonly IUmbracoSettingsSection _settings;
private readonly ILogger _logger;
public SiteIdentifierService(IUmbracoSettingsSection settings, ILogger logger)
{
_settings = settings;
_logger = logger;
}
public bool TryGetSiteIdentifier(out Guid siteIdentifier)
{
// Parse telemetry string as a GUID & verify its a GUID and not some random string
// since users may have messed with or decided to empty the app setting or put in something random
if (Guid.TryParse(_settings.BackOffice.Id, out var parsedTelemetryId) is false
|| parsedTelemetryId == Guid.Empty)
{
siteIdentifier = Guid.Empty;
return false;
}
siteIdentifier = parsedTelemetryId;
return true;
}
public bool TryGetOrCreateSiteIdentifier(out Guid siteIdentifier)
{
if (TryGetSiteIdentifier(out var existingId))
{
siteIdentifier = existingId;
return true;
}
if (TryCreateSiteIdentifier(out var createdId))
{
siteIdentifier = createdId;
return true;
}
siteIdentifier = Guid.Empty;
return false;
}
public bool TryCreateSiteIdentifier(out Guid createdGuid)
{
createdGuid = Guid.NewGuid();
// Modify the XML to add a new GUID site identifier
// hack: ensure this does not trigger a restart
using (ChangesMonitor.Suspended())
{
var umbracoSettingsPath = IOHelper.MapPath(SystemFiles.UmbracoSettings);
if (File.Exists(umbracoSettingsPath) is false)
{
_logger.Error<SiteIdentifierService>("Unable to find umbracoSettings.config file to add telemetry site identifier");
return false;
}
try
{
var umbracoConfigXml = XDocument.Load(umbracoSettingsPath, LoadOptions.PreserveWhitespace);
if (umbracoConfigXml.Root != null)
{
var backofficeElement = umbracoConfigXml.Root.Element("backOffice");
if (backofficeElement is null)
{
return false;
}
// Will add ID attribute if it does not exist
backofficeElement.SetAttributeValue("id", createdGuid.ToString());
// Save file back down
umbracoConfigXml.Save(umbracoSettingsPath, SaveOptions.DisableFormatting);
}
}
catch (Exception ex)
{
_logger.Error<SiteIdentifierService>(ex, "Couldn't update umbracoSettings.config with a backoffice with a telemetry site identifier");
return false;
}
}
return true;
}
}
}
@@ -12,7 +12,7 @@ namespace Umbraco.Web.Telemetry
private readonly ITelemetryService _telemetryService;
private BackgroundTaskRunner<IBackgroundTask> _telemetryReporterRunner;
public TelemetryComponent(IProfilingLogger logger, IUmbracoSettingsSection settings, ITelemetryService telemetryService)
public TelemetryComponent(IProfilingLogger logger, ITelemetryService telemetryService)
{
_logger = logger;
_telemetryService = telemetryService;
+1
View File
@@ -347,6 +347,7 @@
<Compile Include="Models\LinkType.cs" />
<Compile Include="Models\TemplateQuery\OperatorFactory.cs" />
<Compile Include="Telemetry\ReportSiteTask.cs" />
<Compile Include="Telemetry\SiteIdentifierService.cs" />
<Compile Include="Telemetry\TelemetryComponent.cs" />
<Compile Include="Telemetry\TelemetryComposer.cs" />
<Compile Include="Templates\HtmlLocalLinkParser.cs" />
+2 -2
View File
@@ -38,7 +38,7 @@ EndProject
Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "Umbraco.Web.UI.Client", "http://localhost:3961", "{3819A550-DCEC-4153-91B4-8BA9F7F0B9B4}"
ProjectSection(WebsiteProperties) = preProject
UseIISExpress = "true"
TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.5"
TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.5.2"
Debug.AspNetCompiler.VirtualPath = "/localhost_3961"
Debug.AspNetCompiler.PhysicalPath = "Umbraco.Web.UI.Client\"
Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_3961\"
@@ -60,7 +60,7 @@ Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "Umbraco.Web.UI.Client", "ht
EndProject
Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "Umbraco.Tests.AcceptanceTest", "Umbraco.Tests.AcceptanceTest\", "{9E4C8A12-FBE0-4673-8CE2-DF99D5D57817}"
ProjectSection(WebsiteProperties) = preProject
TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.0"
TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.5.2"
Debug.AspNetCompiler.VirtualPath = "/localhost_62926"
Debug.AspNetCompiler.PhysicalPath = "Umbraco.Tests.AcceptanceTest\"
Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_62926\"