Compare commits

...
40 Commits
Author SHA1 Message Date
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
37 changed files with 547 additions and 184 deletions
@@ -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.3")]
[assembly: AssemblyInformationalVersion("8.18.3")]
@@ -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);
}
}
@@ -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 ();
+1
View File
@@ -400,6 +400,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" />
@@ -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>
@@ -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>
@@ -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;
@@ -708,13 +711,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>8183</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>http://localhost:8180</IISUrl>
<IISUrl>http://localhost:8183</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>
<UseCustomServer>False</UseCustomServer>
<CustomServerUrl>
@@ -1415,6 +1415,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>
@@ -2506,9 +2508,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">
@@ -1425,6 +1425,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>
@@ -2544,9 +2546,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)
+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);
@@ -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
@@ -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
@@ -62,9 +62,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" />