diff --git a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDatabaseCreator.cs b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDatabaseCreator.cs index 54d6063ad7..c666547c1d 100644 --- a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDatabaseCreator.cs +++ b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteDatabaseCreator.cs @@ -69,7 +69,9 @@ public class SqliteDatabaseCreator : IDatabaseCreator * always initializing in this way and it probably helps for non azure scenarios also (anytime persisting on a cifs mount for example). */ - var tempFile = Path.GetTempFileName(); + // Create a random file name using cryptographically strong random number generator (RNGCryptoServiceProvider) + var tempFile = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + var tempConnectionString = new SqliteConnectionStringBuilder { DataSource = tempFile, Pooling = false }; using (var connection = new SqliteConnection(tempConnectionString.ConnectionString)) diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index 03240db872..7047e16437 100644 --- a/src/Umbraco.Core/Constants-Conventions.cs +++ b/src/Umbraco.Core/Constants-Conventions.cs @@ -276,7 +276,7 @@ public static partial class Constants public const string RelateParentMediaFolderOnDeleteAlias = "relateParentMediaFolderOnDelete"; /// - /// Returns the types of relations that are automatically tracked + /// Returns the types of relations that are automatically tracked. /// /// /// Developers should not manually use these relation types since they will all be cleared whenever an entity @@ -284,7 +284,7 @@ public static partial class Constants /// public static string[] AutomaticRelationTypes { get; } = { RelatedMediaAlias, RelatedDocumentAlias }; - // TODO: return a list of built in types so we can use that to prevent deletion in the uI + // TODO: return a list of built in types so we can use that to prevent deletion in the UI } public static class Udi diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/cs.xml b/src/Umbraco.Core/EmbeddedResources/Lang/cs.xml index 29b242d67e..0057d3d9a6 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/cs.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/cs.xml @@ -1194,7 +1194,6 @@ Vyberte verzi, kterou chcete porovnat s aktuální verzí - Současná verze Červený text nebude ve vybrané verzi zobrazen, zelený znamená přidaný].]]> Dokument byl vrácen na starší verzi Tohle zobrazuje vybranou verzi jako html, jestliže chcete vidět rozdíly mezi 2 verzemi najednou, použijte rozdílové zobrazení diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/cy.xml b/src/Umbraco.Core/EmbeddedResources/Lang/cy.xml index e34cc2ab29..012a1bc0b9 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/cy.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/cy.xml @@ -1523,8 +1523,6 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Dewis fersiwn i gymharu efo fersiwn bresennol Newidiadau - Creuwyd - Fersiwn bresennol Ni fydd testun coch yn cael ei ddangos yn y fersiwn dewiswyd. , mae gwyrdd yn golygu wedi'i ychwanegu]]> Dogfen wedi'i rolio yn ôl Mae hyn yn dangos y fersiwn dewiswyd ar ffurf HTML, os hoffwch weld y gwahaniaeth rhwng 2 fersiwn ar yr un pryd, defnyddiwch y wedd gwahaniaethol diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml index 879a6b4a62..7fbc7d0b5b 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml @@ -1238,11 +1238,10 @@ Mange hilsner fra Umbraco robotten Ændringer - Oprettet Vælg en version at sammenligne med den nuværende version - Nuværende version Rød tekst vil ikke blive vist i den valgte version. Grøn betyder tilføjet]]> + Der er ingen forskelle mellem den nuværende version og den valgte version Dokument tilbagerullet Her vises den valgte version som html. Hvis du ønsker at se forskellen mellem de 2 versioner på samme tid, brug 'diff'-oversigten diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/de.xml b/src/Umbraco.Core/EmbeddedResources/Lang/de.xml index 24999a1187..448453778b 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/de.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/de.xml @@ -1260,7 +1260,6 @@ Wählen Sie eine Version, um diese mit der aktuellen zu vergleichen - Aktuelle Version Zeigt die Unterschiede zwischen der aktuellen und der ausgewählten Version an.<br />Text in <del>rot</del> fehlen in der ausgewählten Version, <ins>grün</ins> markierter Text wurde hinzugefügt. Dokument wurde zurückgesetzt Zeigt die ausgewählte Version als HTML an. Wenn Sie sich die Unterschiede zwischen zwei Versionen anzeigen lassen wollen, benutzen Sie bitte die Vergleichsansicht. diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml index af7b39849c..0bf175ca1a 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml @@ -1450,10 +1450,9 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Changes - Created - Current version - Red text will be removed in the selected version, green text will be added]]> + Red text will be removed in the selected version, green text will be added]]> + There are no differences between the current (draft) version and the selected version Document has been rolled back Select a version to compare with the current version This displays the selected version as HTML, if you wish to see the difference between 2 @@ -2786,6 +2785,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Allow in areas Make this block available by default within the areas of other Blocks (unless explicit permissions are set for these areas). When empty all Blocks allowed for Areas can be created. + When empty all Blocks allowed for Areas can be created. Areas Grid Columns for Areas Define how many columns that will be available for areas. If not defined, the number of columns defined for the entire layout will be used. diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml index 4678acad16..23faf3747b 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml @@ -1490,11 +1490,10 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Changes - Created Select a version to compare with the current version - Current version - Red text will be removed in the selected version, green text will be added]]> + Red text will be removed in the selected version, green text will be added]]> + There are no differences between the current (draft) version and the selected version Document has been rolled back This displays the selected version as HTML, if you wish to see the difference between 2 versions at the same time, use the diff view @@ -1504,7 +1503,6 @@ To manage your website, simply open the Umbraco backoffice and start adding cont View Versions - Current draft version Current published version @@ -2889,6 +2887,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Allow in areas Make this block available by default within the areas of other Blocks (unless explicit permissions are set for these areas). When empty all Blocks allowed for Areas can be created. + When empty all Blocks allowed for Areas can be created. Areas Grid Columns for Areas Define how many columns that will be available for areas. If not defined, the number of columns defined for the entire layout will be used. diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/es.xml b/src/Umbraco.Core/EmbeddedResources/Lang/es.xml index 1ed96d8807..4a111302b3 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/es.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/es.xml @@ -898,7 +898,6 @@ Reiniciar - Versión actual Red el texto de la versión seleccionada no se mostrará. , el verde significa añadido]]> Se ha recuperado la última versión del documento. Esto muestra la versión seleccionada como html, si deseas ver la diferencia entre 2 versiones al mismo tiempo, por favor usa la vista diff diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/fr.xml b/src/Umbraco.Core/EmbeddedResources/Lang/fr.xml index 24d5f565e5..384981e467 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/fr.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/fr.xml @@ -1251,7 +1251,6 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Sélectionnez une version à comparer avec la version actuelle - Version actuelle Le texte en Rouge signifie qu'il a été supprimé de la version choisie, vert signifie ajouté]]> Le document a été restauré à une version antérieure Ceci affiche la version choisie en tant que HTML, si vous souhaitez voir les différences entre les deux versions en même temps, utilisez la vue différentielle diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/he.xml b/src/Umbraco.Core/EmbeddedResources/Lang/he.xml index c52961307d..819e71aec9 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/he.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/he.xml @@ -589,7 +589,6 @@ To manage your website, simply open the Umbraco backoffice and start adding cont הסר קישור - גירסה עדכנית טקסט אדום לא יוצג בגרסא שנבחרה, טקסט ירוק מייצט טקסט שנוסף.]]> המסמך שוחזר בהצלחה להלן הגרסא שנבחרה כHTML, אם הינך לצפות בשינויים בין שתי הגרסאות בו זמנית, בחר ב diff diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/it.xml b/src/Umbraco.Core/EmbeddedResources/Lang/it.xml index cea82fc4e0..c85274b80f 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/it.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/it.xml @@ -1530,9 +1530,7 @@ Per gestire il tuo sito web, è sufficiente aprire il backoffice di Umbraco e in Modifiche - Creato Seleziona una versione da confrontare con la versione corrente - Il testo in rosso non verrà mostrato nella versione selezionata, quello in verde verrà aggiunto]]> diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/ja.xml b/src/Umbraco.Core/EmbeddedResources/Lang/ja.xml index bcf9e8c9a9..28d512973f 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/ja.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/ja.xml @@ -730,7 +730,6 @@ Runwayをインストールして作られた新しいウェブサイトがど リセット - 現在の版 の文字列は以前の版にはない部分で、緑の文字列は以前の版にのみある部分です。]]> ドキュメントは以前の版に戻りました 選択した版をhtmlで表示します。2つの版の比較を表示したいときは、Diff を選択してください。 diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/ko.xml b/src/Umbraco.Core/EmbeddedResources/Lang/ko.xml index 6a20975bb1..dfe848387c 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/ko.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/ko.xml @@ -575,7 +575,6 @@ 링크 삭제 - 현재 버전 빨간 텍스트는 선택한 버전에선 보이지 않습니다. 녹색은 추가되었음을 의미합니다]]> 문서가 롤백되었습니다. 선택한 버전을 html로 보여줍니다. 두 버전의 차이점을 동시에 보시려면, 차이점 보기를 사용하세요 diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/nb.xml b/src/Umbraco.Core/EmbeddedResources/Lang/nb.xml index 87bcb3138a..c19aa1df4f 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/nb.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/nb.xml @@ -621,7 +621,6 @@ Vennlig hilsen Umbraco roboten Nullstill - Gjeldende versjon Rød tekst vil ikke bli vist i den valgte versjonen. , grønn betyr lagt til]]> Dokumentet er tilbakeført til en tidligere versjon Dette viser den valgte versjonen som HTML, bruk avviksvisningen hvis du ønsker å se forksjellene mellom to versjoner samtidig. diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml b/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml index b2cbb5bda0..735302e951 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml @@ -1253,9 +1253,7 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Wijzigingen - Aangemaakt Selecteer een versie om te vergelijken met de huidige versie - Huidige versie Rode tekst wordt niet getoond in de geselecteerde versie, groen betekent toegevoegd]]> Document is teruggezet @@ -1267,7 +1265,6 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Bekijk Versies - Conceptversie Gepubliceerde versie diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/pl.xml b/src/Umbraco.Core/EmbeddedResources/Lang/pl.xml index 4801371fd8..a4d891fd8f 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/pl.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/pl.xml @@ -881,7 +881,6 @@ Naciśnij przycisk instaluj, aby zainstalować bazę danych Umb Resetuj - Aktualna wersja Czerwony tekst nie będzie pokazany w wybranej wersji, zielony tekst został dodany]]> Dokument został przywrócony Tu widać wybraną wersję jako html, jeżeli chcesz zobaczyć różnicę pomiędzy 2 wersjami w tym samym czasie, użyj podglądu różnic diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/pt.xml b/src/Umbraco.Core/EmbeddedResources/Lang/pt.xml index 25060a4bd3..b5fd920365 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/pt.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/pt.xml @@ -565,7 +565,6 @@ Você pode publicar esta página e todas suas sub-páginas ao selecionar pub Remover Link - Versão atual Texto vermelho não será mostrado na versão selecionada; verde significa adicionado]]> Documento foi revertido Isto mostra a versão selecionada como html se você deseja ver as diferenças entre as 2 versões ao mesmo tempo use a visão em diff diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/ru.xml b/src/Umbraco.Core/EmbeddedResources/Lang/ru.xml index d5b31e47d2..f83dd5285c 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/ru.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/ru.xml @@ -1195,7 +1195,6 @@ '%0%' была переименована в '%1%' - Текущая версия Красным отмечен текст, которого уже нет в последней версии, зеленым - текст, который добавлен]]> Произведен откат к ранней версии Текущая версия показана в виде HTML. Для просмотра различий в версиях выберите режим сравнения diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/sv.xml b/src/Umbraco.Core/EmbeddedResources/Lang/sv.xml index fa359fbbbc..e2e7cbcd76 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/sv.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/sv.xml @@ -675,7 +675,6 @@ Återställ - Nuvarande version Röd text kommer inte att synas i den valda versionen. , Grön betyder att den har tillkommit]]> Dokumentet har återgått till en tidigare version Här visas den valda sidversionen i HTML. Om du vill se skillnaden mellan två versioner samtidigt, välj istället "Diff". diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/tr.xml b/src/Umbraco.Core/EmbeddedResources/Lang/tr.xml index 47549f5f40..30a38ba6c7 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/tr.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/tr.xml @@ -1319,8 +1319,6 @@ Web sitenizi yönetmek için, Umbraco'nun arka ofisini açın ve içerik eklemey Değişiklikler - Oluşturuldu - Mevcut sürüm Kırmızı metin seçili sürümde gösterilmeyecektir. , yeşil eklendi demektir ]]> Belge geri alındı ​​ Mevcut sürümle karşılaştırmak için bir sürüm seçin diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/zh.xml b/src/Umbraco.Core/EmbeddedResources/Lang/zh.xml index 5d9a6e9ab3..199b185ed3 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/zh.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/zh.xml @@ -763,7 +763,6 @@ Reset - 当前版本 红色是选中版本中没有的。绿色是新增的]]> 文档已回滚 diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/zh_tw.xml b/src/Umbraco.Core/EmbeddedResources/Lang/zh_tw.xml index b3e3b7bdcf..d57c1c614d 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/zh_tw.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/zh_tw.xml @@ -753,7 +753,6 @@ 重設 - 當前版本 紅色 文字將不會顯示於所選版本,而綠色表示增加部分。]]> 文檔已回滾 這顯示所選版本的HTML格式,如果您想要比較兩版本的差異,請使用比較檢視 diff --git a/src/Umbraco.Core/Models/PropertyTagsExtensions.cs b/src/Umbraco.Core/Models/PropertyTagsExtensions.cs index d36baed604..b12cbe58ef 100644 --- a/src/Umbraco.Core/Models/PropertyTagsExtensions.cs +++ b/src/Umbraco.Core/Models/PropertyTagsExtensions.cs @@ -23,19 +23,15 @@ public static class PropertyTagsExtensions IDataEditor? editor = propertyEditors[property.PropertyType?.PropertyEditorAlias]; TagsPropertyEditorAttribute? tagAttribute = editor?.GetTagAttribute(); - if (tagAttribute == null) - { - return null; - } var configurationObject = property.PropertyType is null ? null : dataTypeService.GetDataType(property.PropertyType.DataTypeId)?.Configuration; - TagConfiguration? configuration = ConfigurationEditor.ConfigurationAs(configurationObject); + TagConfiguration? configuration = configurationObject as TagConfiguration; if (configuration is not null && configuration.Delimiter == default) { - configuration.Delimiter = tagAttribute.Delimiter; + configuration.Delimiter = tagAttribute?.Delimiter ?? ','; } return configuration; diff --git a/src/Umbraco.Core/PropertyEditors/BlockListConfiguration.cs b/src/Umbraco.Core/PropertyEditors/BlockListConfiguration.cs index 1dec9946d3..1184f2524f 100644 --- a/src/Umbraco.Core/PropertyEditors/BlockListConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/BlockListConfiguration.cs @@ -16,6 +16,13 @@ public class BlockListConfiguration [ConfigurationField("validationLimit", "Amount", "numberrange", Description = "Set a required range of blocks")] public NumberRange ValidationLimit { get; set; } = new(); + [ConfigurationField("useSingleBlockMode", "Single block mode", "boolean", + Description = @"When in Single block mode, the output will be BlockListItem<>, instead of BlockListModel. + +**NOTE:** +Single block mode requires a maximum of one available block, and an amount set to minimum 1 and maximum 1 blocks.")] + public bool UseSingleBlockMode { get; set; } + [ConfigurationField("useLiveEditing", "Live editing mode", "boolean", Description = "Live editing in editor overlays for live updated custom views or labels using custom expression.")] public bool UseLiveEditing { get; set; } diff --git a/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs b/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs index 39d7d7e130..6c863d66ff 100644 --- a/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs +++ b/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs @@ -14,4 +14,10 @@ public interface IDataValueReference /// /// IEnumerable GetReferences(object? value); + + /// + /// Returns all reference types that are automatically tracked. + /// + /// + IEnumerable GetAutomaticRelationTypesAliases() => Enumerable.Empty(); } diff --git a/src/Umbraco.Core/PropertyEditors/IDataValueTags.cs b/src/Umbraco.Core/PropertyEditors/IDataValueTags.cs new file mode 100644 index 0000000000..f809e787bc --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/IDataValueTags.cs @@ -0,0 +1,18 @@ +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Resolve tags from values +/// +public interface IDataValueTags +{ + /// + /// Returns any tags contained in the value + /// + /// + /// + /// + /// + IEnumerable GetTags(object? value, object? dataTypeConfiguration, int? languageId); +} diff --git a/src/Umbraco.Core/PropertyEditors/PropertyEditorTagsExtensions.cs b/src/Umbraco.Core/PropertyEditors/PropertyEditorTagsExtensions.cs index ff92c2012f..8206ab538b 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyEditorTagsExtensions.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyEditorTagsExtensions.cs @@ -5,6 +5,7 @@ namespace Umbraco.Extensions; /// /// Provides extension methods for the interface to manage tags. /// +[Obsolete] public static class PropertyEditorTagsExtensions { /// diff --git a/src/Umbraco.Core/PropertyEditors/TagsPropertyEditorAttribute.cs b/src/Umbraco.Core/PropertyEditors/TagsPropertyEditorAttribute.cs index 849d6446a9..d6f1584e8d 100644 --- a/src/Umbraco.Core/PropertyEditors/TagsPropertyEditorAttribute.cs +++ b/src/Umbraco.Core/PropertyEditors/TagsPropertyEditorAttribute.cs @@ -6,6 +6,7 @@ namespace Umbraco.Cms.Core.PropertyEditors; /// Marks property editors that support tags. /// [AttributeUsage(AttributeTargets.Class)] +[Obsolete("Implement a custom IDataValueEditor with the IDataValueTags interface instead")] public class TagsPropertyEditorAttribute : Attribute { /// diff --git a/src/Umbraco.Infrastructure/Mapping/UmbracoMapper.cs b/src/Umbraco.Infrastructure/Mapping/UmbracoMapper.cs index 09cfcf5aaf..8ebd803aac 100644 --- a/src/Umbraco.Infrastructure/Mapping/UmbracoMapper.cs +++ b/src/Umbraco.Infrastructure/Mapping/UmbracoMapper.cs @@ -1,7 +1,10 @@ using System.Collections; using System.Collections.Concurrent; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Exceptions; using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Web.Common.DependencyInjection; namespace Umbraco.Cms.Core.Mapping; @@ -44,19 +47,32 @@ public class UmbracoMapper : IUmbracoMapper private readonly ConcurrentDictionary>> _ctors = new(); - private readonly ConcurrentDictionary>> _maps = + private readonly ConcurrentDictionary>> _maps = new(); private readonly ICoreScopeProvider _scopeProvider; + private readonly ILogger _logger; /// /// Initializes a new instance of the class. /// /// /// - public UmbracoMapper(MapDefinitionCollection profiles, ICoreScopeProvider scopeProvider) + [Obsolete("Please use ctor that takes an ILogger")] + public UmbracoMapper(MapDefinitionCollection profiles, ICoreScopeProvider scopeProvider) : this(profiles, scopeProvider, StaticServiceProvider.Instance.GetRequiredService>()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The MapDefinitionCollection + /// The scope provider + /// The logger + public UmbracoMapper(MapDefinitionCollection profiles, ICoreScopeProvider scopeProvider, ILogger logger) { _scopeProvider = scopeProvider; + _logger = logger; foreach (IMapDefinition profile in profiles) { @@ -119,15 +135,15 @@ public class UmbracoMapper : IUmbracoMapper sourceCtors[targetType] = (source, context) => ctor((TSource)source, context)!; } - Dictionary> sourceMaps = DefineMaps(sourceType); + ConcurrentDictionary> sourceMaps = DefineMaps(sourceType); sourceMaps[targetType] = (source, target, context) => map((TSource)source, (TTarget)target, context); } private Dictionary> DefineCtors(Type sourceType) => _ctors.GetOrAdd(sourceType, _ => new Dictionary>()); - private Dictionary> DefineMaps(Type sourceType) => - _maps.GetOrAdd(sourceType, _ => new Dictionary>()); + private ConcurrentDictionary> DefineMaps(Type sourceType) => + _maps.GetOrAdd(sourceType, _ => new ConcurrentDictionary>()); #endregion @@ -428,7 +444,7 @@ public class UmbracoMapper : IUmbracoMapper return null; } - if (_maps.TryGetValue(sourceType, out Dictionary>? sourceMap) && + if (_maps.TryGetValue(sourceType, out ConcurrentDictionary>? sourceMap) && sourceMap.TryGetValue(targetType, out Action? map)) { return map; @@ -436,7 +452,7 @@ public class UmbracoMapper : IUmbracoMapper // we *may* run this more than once but it does not matter map = null; - foreach ((Type stype, Dictionary> smap) in _maps) + foreach ((Type stype, ConcurrentDictionary> smap) in _maps) { if (!stype.IsAssignableFrom(sourceType)) { @@ -462,9 +478,9 @@ public class UmbracoMapper : IUmbracoMapper { foreach (KeyValuePair> m in sourceMap) { - if (!_maps[sourceType].TryGetValue(m.Key, out _)) + if (!_maps[sourceType].TryAdd(m.Key, m.Value)) { - _maps[sourceType].Add(m.Key, m.Value); + _logger.LogDebug("Duplicate key was found, don't add to dictionary"); } } } diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs index 1b01a3ba47..cc2e20fa02 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs @@ -1,3 +1,5 @@ +using System.Diagnostics.CodeAnalysis; +using System.Globalization; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using NPoco; @@ -1745,13 +1747,62 @@ internal class DatabaseDataCreator } } - private void CreateLanguageData() => - ConditionalInsert( - Constants.Configuration.NamedOptions.InstallDefaultData.Languages, - "en-us", - new LanguageDto { Id = 1, IsoCode = "en-US", CultureName = "English (United States)", IsDefault = true }, - Constants.DatabaseSchema.Tables.Language, - "id"); + private void CreateLanguageData() + { + // For languages we support the installation of records that are additional to the default installed data. + // We can do this as they are specified by ISO code, which is enough to fully detail them. + // All other customizable install data is specified by GUID, and hence we only know about the set that are installed by default. + InstallDefaultDataSettings? languageInstallDefaultDataSettings = _installDefaultDataSettings.Get(Constants.Configuration.NamedOptions.InstallDefaultData.Languages); + if (languageInstallDefaultDataSettings?.InstallData == InstallDefaultDataOption.Values) + { + // Insert the specified languages, ensuring the first is marked as default. + bool isDefault = true; + foreach (var isoCode in languageInstallDefaultDataSettings.Values) + { + if (!TryCreateCulture(isoCode, out CultureInfo? culture)) + { + continue; + } + + var dto = new LanguageDto + { + IsoCode = culture.Name, + CultureName = culture.EnglishName, + IsDefault = isDefault, + }; + _database.Insert(Constants.DatabaseSchema.Tables.Language, "id", true, dto); + isDefault = false; + } + } + else + { + // Conditionally insert the default language. + if (TryCreateCulture("en-US", out CultureInfo? culture)) + { + ConditionalInsert( + Constants.Configuration.NamedOptions.InstallDefaultData.Languages, + culture.Name, + new LanguageDto { Id = 1, IsoCode = culture.Name, CultureName = culture.EnglishName, IsDefault = true }, + Constants.DatabaseSchema.Tables.Language, + "id"); + } + } + } + + private bool TryCreateCulture(string isoCode, [NotNullWhen(true)] out CultureInfo? culture) + { + try + { + culture = CultureInfo.GetCultureInfo(isoCode); + return true; + } + catch (CultureNotFoundException ex) + { + _logger.LogWarning(ex, "CultureInfo could not be created because culture '{IsoCode}' is not available. The language will not be created.", isoCode); + culture = null; + return false; + } + } private void CreateContentChildTypeData() { diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs index 0162ba8d52..ce14f1ec1e 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -274,29 +274,59 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { foreach (IProperty property in entity.Properties) { - TagConfiguration? tagConfiguration = property.GetTagConfiguration(PropertyEditors, DataTypeService); - if (tagConfiguration == null) + if (PropertyEditors.TryGet(property.PropertyType.PropertyEditorAlias, out var editor) is false) { - continue; // not a tags property + continue; } + if (editor.GetValueEditor() is not IDataValueTags tagsProvider) + { + // support for legacy tag editors, everything from here down to the last continue can be removed when TagsPropertyEditorAttribute is removed + TagConfiguration? tagConfiguration = property.GetTagConfiguration(PropertyEditors, DataTypeService); + if (tagConfiguration == null) + { + continue; + } + + if (property.PropertyType.VariesByCulture()) + { + var tags = new List(); + foreach (IPropertyValue pvalue in property.Values) + { + IEnumerable tagsValue = property.GetTagsValue(PropertyEditors, DataTypeService, serializer, pvalue.Culture); + var languageId = LanguageRepository.GetIdByIsoCode(pvalue.Culture); + IEnumerable cultureTags = tagsValue.Select(x => new Tag { Group = tagConfiguration.Group, Text = x, LanguageId = languageId }); + tags.AddRange(cultureTags); + } + + tagRepo.Assign(entity.Id, property.PropertyTypeId, tags); + } + else + { + IEnumerable tagsValue = property.GetTagsValue(PropertyEditors, DataTypeService, serializer); // strings + IEnumerable tags = tagsValue.Select(x => new Tag { Group = tagConfiguration.Group, Text = x }); + tagRepo.Assign(entity.Id, property.PropertyTypeId, tags); + } + + continue; // not implementing IDataValueTags, continue + } + + object? configuration = DataTypeService.GetDataType(property.PropertyType.DataTypeId)?.Configuration; + if (property.PropertyType.VariesByCulture()) { var tags = new List(); foreach (IPropertyValue pvalue in property.Values) { - IEnumerable tagsValue = property.GetTagsValue(PropertyEditors, DataTypeService, serializer, pvalue.Culture); var languageId = LanguageRepository.GetIdByIsoCode(pvalue.Culture); - IEnumerable cultureTags = tagsValue.Select(x => new Tag { Group = tagConfiguration.Group, Text = x, LanguageId = languageId }); - tags.AddRange(cultureTags); + tags.AddRange(tagsProvider.GetTags(pvalue.EditedValue, configuration, languageId)); } tagRepo.Assign(entity.Id, property.PropertyTypeId, tags); } else { - IEnumerable tagsValue = property.GetTagsValue(PropertyEditors, DataTypeService, serializer); // strings - IEnumerable tags = tagsValue.Select(x => new Tag { Group = tagConfiguration.Group, Text = x }); + IEnumerable tags = tagsProvider.GetTags(property.GetValue(), configuration, null); tagRepo.Assign(entity.Id, property.PropertyTypeId, tags); } } @@ -1042,8 +1072,10 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement var trackedRelations = new List(); trackedRelations.AddRange(_dataValueReferenceFactories.GetAllReferences(entity.Properties, PropertyEditors)); + var relationTypeAliases = GetAutomaticRelationTypesAliases(entity.Properties, PropertyEditors).ToArray(); + // First delete all auto-relations for this entity - RelationRepository.DeleteByParent(entity.Id, Constants.Conventions.RelationTypes.AutomaticRelationTypes); + RelationRepository.DeleteByParent(entity.Id, relationTypeAliases); if (trackedRelations.Count == 0) { @@ -1055,7 +1087,10 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement .ToDictionary(x => (Udi)x!, x => x!.Guid); // lookup in the DB all INT ids for the GUIDs and chuck into a dictionary - var keyToIds = Database.Fetch(Sql().Select(x => x.NodeId, x => x.UniqueId).From().WhereIn(x => x.UniqueId, udiToGuids.Values)) + var keyToIds = Database.Fetch(Sql() + .Select(x => x.NodeId, x => x.UniqueId) + .From() + .WhereIn(x => x.UniqueId, udiToGuids.Values)) .ToDictionary(x => x.UniqueId, x => x.NodeId); var allRelationTypes = RelationTypeRepository.GetMany(Array.Empty())? @@ -1085,6 +1120,31 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement RelationRepository.SaveBulk(toSave); } + private IEnumerable GetAutomaticRelationTypesAliases( + IPropertyCollection properties, + PropertyEditorCollection propertyEditors) + { + var automaticRelationTypesAliases = new HashSet(Constants.Conventions.RelationTypes.AutomaticRelationTypes); + + foreach (IProperty property in properties) + { + if (propertyEditors.TryGet(property.PropertyType.PropertyEditorAlias, out IDataEditor? editor) is false ) + { + continue; + } + + if (editor.GetValueEditor() is IDataValueReference reference) + { + foreach (var alias in reference.GetAutomaticRelationTypesAliases()) + { + automaticRelationTypesAliases.Add(alias); + } + } + } + + return automaticRelationTypesAliases; + } + /// /// Inserts property values for the content entity /// diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs index 11b3810640..fbf2239828 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs @@ -13,7 +13,7 @@ using Umbraco.Cms.Core.Strings; namespace Umbraco.Cms.Core.PropertyEditors; -internal abstract class BlockEditorPropertyValueEditor : DataValueEditor, IDataValueReference +internal abstract class BlockEditorPropertyValueEditor : DataValueEditor, IDataValueReference, IDataValueTags { private BlockEditorValues? _blockEditorValues; private readonly IDataTypeService _dataTypeService; @@ -77,6 +77,40 @@ internal abstract class BlockEditorPropertyValueEditor : DataValueEditor, IDataV return result; } + /// + public IEnumerable GetTags(object? value, object? dataTypeConfiguration, int? languageId) + { + var rawJson = value == null ? string.Empty : value is string str ? str : value.ToString(); + + BlockEditorData? blockEditorData = BlockEditorValues.DeserializeAndClean(rawJson); + if (blockEditorData == null) + { + return Enumerable.Empty(); + } + + var result = new List(); + // loop through all content and settings data + foreach (BlockItemData row in blockEditorData.BlockValue.ContentData.Concat(blockEditorData.BlockValue.SettingsData)) + { + foreach (KeyValuePair prop in row.PropertyValues) + { + IDataEditor? propEditor = _propertyEditors[prop.Value.PropertyType.PropertyEditorAlias]; + + IDataValueEditor? valueEditor = propEditor?.GetValueEditor(); + if (valueEditor is not IDataValueTags tagsProvider) + { + continue; + } + + object? configuration = _dataTypeService.GetDataType(prop.Value.PropertyType.DataTypeKey)?.Configuration; + + result.AddRange(tagsProvider.GetTags(prop.Value.Value, configuration, languageId)); + } + } + + return result; + } + #region Convert database // editor // note: there is NO variant support here diff --git a/src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyEditor.cs index cf85339d48..29a3f57a92 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyEditor.cs @@ -69,7 +69,7 @@ public class NestedContentPropertyEditor : DataEditor protected override IDataValueEditor CreateValueEditor() => DataValueEditorFactory.Create(Attribute!); - internal class NestedContentPropertyValueEditor : DataValueEditor, IDataValueReference + internal class NestedContentPropertyValueEditor : DataValueEditor, IDataValueReference, IDataValueTags { private readonly IDataTypeService _dataTypeService; private readonly ILogger _logger; @@ -150,6 +150,36 @@ public class NestedContentPropertyEditor : DataEditor return result; } + /// + public IEnumerable GetTags(object? value, object? dataTypeConfiguration, int? languageId) + { + IReadOnlyList rows = + _nestedContentValues.GetPropertyValues(value); + + var result = new List(); + + foreach (NestedContentValues.NestedContentRowValue row in rows.ToList()) + { + foreach (KeyValuePair prop in row.PropertyValues + .ToList()) + { + IDataEditor? propEditor = _propertyEditors[prop.Value.PropertyType.PropertyEditorAlias]; + + IDataValueEditor? valueEditor = propEditor?.GetValueEditor(); + if (valueEditor is not IDataValueTags tagsProvider) + { + continue; + } + + object? configuration = _dataTypeService.GetDataType(prop.Value.PropertyType.DataTypeKey)?.Configuration; + + result.AddRange(tagsProvider.GetTags(prop.Value.Value, configuration, languageId)); + } + } + + return result; + } + #region DB to String public override string ConvertDbToString(IPropertyType propertyType, object? propertyValue) diff --git a/src/Umbraco.Infrastructure/PropertyEditors/TagsPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/TagsPropertyEditor.cs index 88648c47fd..ff646a039d 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/TagsPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/TagsPropertyEditor.cs @@ -3,6 +3,7 @@ using System.ComponentModel.DataAnnotations; using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; @@ -68,18 +69,67 @@ public class TagsPropertyEditor : DataEditor protected override IConfigurationEditor CreateConfigurationEditor() => new TagConfigurationEditor(_validators, _ioHelper, _localizedTextService, _editorConfigurationParser); - internal class TagPropertyValueEditor : DataValueEditor + internal class TagPropertyValueEditor : DataValueEditor, IDataValueTags { + private readonly IDataTypeService _dataTypeService; + public TagPropertyValueEditor( ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, IIOHelper ioHelper, - DataEditorAttribute attribute) + DataEditorAttribute attribute, + IDataTypeService dataTypeService) : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute) { + _dataTypeService = dataTypeService; } + /// + public IEnumerable GetTags(object? value, object? dataTypeConfiguration, int? languageId) + { + var strValue = value?.ToString(); + if (string.IsNullOrWhiteSpace(strValue)) return Enumerable.Empty(); + + var tagConfiguration = ConfigurationEditor.ConfigurationAs(dataTypeConfiguration) ?? new TagConfiguration(); + + if (tagConfiguration.Delimiter == default) + tagConfiguration.Delimiter = ','; + + IEnumerable tags; + + switch (tagConfiguration.StorageType) + { + case TagsStorageType.Csv: + tags = strValue.Split(new[] { tagConfiguration.Delimiter }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()); + break; + + case TagsStorageType.Json: + try + { + tags = JsonConvert.DeserializeObject(strValue)?.Select(x => x.Trim()) ?? Enumerable.Empty(); + } + catch (JsonException) + { + //cannot parse, malformed + tags = Enumerable.Empty(); + } + + break; + + default: + throw new NotSupportedException($"Value \"{tagConfiguration.StorageType}\" is not a valid TagsStorageType."); + } + + return tags.Select(x => new Tag + { + Group = tagConfiguration.Group, + Text = x, + LanguageId = languageId, + }); + } + + /// public override IValueRequiredValidator RequiredValidator => new RequiredJsonValueValidator(); @@ -93,14 +143,33 @@ public class TagsPropertyEditor : DataEditor return null; } + var tagConfiguration = editorValue.DataTypeConfiguration as TagConfiguration ?? new TagConfiguration(); + if (tagConfiguration.Delimiter == default) + tagConfiguration.Delimiter = ','; + + string[] trimmedTags = Array.Empty(); + if (editorValue.Value is JArray json) { - return json.HasValues ? json.Select(x => x.Value()) : null; + trimmedTags = json.HasValues ? json.Select(x => x.Value()).OfType().ToArray() : Array.Empty(); + } + else if (string.IsNullOrWhiteSpace(value) == false) + { + trimmedTags = value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries); } - if (string.IsNullOrWhiteSpace(value) == false) + if (trimmedTags.Length == 0) { - return value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries); + return null; + } + + switch (tagConfiguration.StorageType) + { + case TagsStorageType.Csv: + return string.Join(tagConfiguration.Delimiter.ToString(), trimmedTags).NullOrWhiteSpaceAsNull(); + + case TagsStorageType.Json: + return trimmedTags.Length == 0 ? null : JsonConvert.SerializeObject(trimmedTags); } return null; diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs index af1c51c37e..b56f43e0ef 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs @@ -1,9 +1,14 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Blocks; using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; using static Umbraco.Cms.Core.PropertyEditors.BlockListConfiguration; @@ -12,18 +17,70 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; [DefaultPropertyValueConverter(typeof(JsonValueConverter))] public class BlockListPropertyValueConverter : BlockPropertyValueConverterBase { + private readonly IContentTypeService _contentTypeService; + private readonly BlockEditorConverter _blockConverter; + private readonly BlockListEditorDataConverter _blockListEditorDataConverter; private readonly IProfilingLogger _proflog; - public BlockListPropertyValueConverter(IProfilingLogger proflog, BlockEditorConverter blockConverter) + [Obsolete("Use the constructor with the IContentTypeService")] + public BlockListPropertyValueConverter(IProfilingLogger proflog, BlockEditorConverter blockConverter) : this(proflog, blockConverter, StaticServiceProvider.Instance.GetRequiredService()) { } + + public BlockListPropertyValueConverter(IProfilingLogger proflog, BlockEditorConverter blockConverter, IContentTypeService contentTypeService) : base(blockConverter) { _proflog = proflog; + _blockConverter = blockConverter; + _blockListEditorDataConverter = new BlockListEditorDataConverter(); + _contentTypeService = contentTypeService; } /// public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.BlockList); + /// + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) + { + var isSingleBlockMode = IsSingleBlockMode(propertyType.DataType); + if (isSingleBlockMode) + { + BlockListConfiguration.BlockConfiguration? block = + ConfigurationEditor.ConfigurationAs(propertyType.DataType.Configuration)?.Blocks.FirstOrDefault(); + + ModelType? contentElementType = block?.ContentElementTypeKey is Guid contentElementTypeKey && _contentTypeService.Get(contentElementTypeKey) is IContentType contentType ? ModelType.For(contentType.Alias) : null; + ModelType? settingsElementType = block?.SettingsElementTypeKey is Guid settingsElementTypeKey && _contentTypeService.Get(settingsElementTypeKey) is IContentType settingsType ? ModelType.For(settingsType.Alias) : null; + + if (contentElementType is not null) + { + if (settingsElementType is not null) + { + return typeof(BlockListItem<,>).MakeGenericType(contentElementType, settingsElementType); + } + + return typeof(BlockListItem<>).MakeGenericType(contentElementType); + } + + return typeof(BlockListItem); + } + + return typeof(BlockListModel); + } + + private bool IsSingleBlockMode(PublishedDataType dataType) + { + BlockListConfiguration? config = + ConfigurationEditor.ConfigurationAs(dataType.Configuration); + return (config?.UseSingleBlockMode ?? false) && config?.Blocks.Length == 1 && config?.ValidationLimit?.Min == 1 && config?.ValidationLimit?.Max == 1; + } + + /// + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) + => PropertyCacheLevel.Element; + + /// + public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) + => source?.ToString(); + /// public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) { @@ -44,7 +101,7 @@ public class BlockListPropertyValueConverter : BlockPropertyValueConverterBase(propertyDto.DataType?.Configuration); diff --git a/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs b/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs index 55001ca28c..0748f5cbb4 100644 --- a/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs +++ b/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs @@ -51,17 +51,23 @@ public class PreviewAuthenticationMiddleware : IMiddleware // If we've gotten this far it means a preview cookie has been set and a front-end umbraco document request is executing. // In this case, authentication will not have occurred for an Umbraco back office User, however we need to perform the authentication // for the user here so that the preview capability can be authorized otherwise only the non-preview page will be rendered. - if (cookieOptions.Cookie.Name is not null && - request.Cookies.TryGetValue(cookieOptions.Cookie.Name, out var cookie)) + if (cookieOptions.Cookie.Name != null) { - AuthenticationTicket? unprotected = cookieOptions.TicketDataFormat.Unprotect(cookie); - ClaimsIdentity? backOfficeIdentity = unprotected?.Principal.GetUmbracoIdentity(); - if (backOfficeIdentity != null) + var chunkingCookieManager = new ChunkingCookieManager(); + var cookie = chunkingCookieManager.GetRequestCookie(context, cookieOptions.Cookie.Name); + + if (!string.IsNullOrEmpty(cookie)) { - // Ok, we've got a real ticket, now we can add this ticket's identity to the current - // Principal, this means we'll have 2 identities assigned to the principal which we can - // use to authorize the preview and allow for a back office User. - context.User.AddIdentity(backOfficeIdentity); + AuthenticationTicket? unprotected = cookieOptions.TicketDataFormat.Unprotect(cookie); + ClaimsIdentity? backOfficeIdentity = unprotected?.Principal.GetUmbracoIdentity(); + + if (backOfficeIdentity != null) + { + // Ok, we've got a real ticket, now we can add this ticket's identity to the current + // Principal, this means we'll have 2 identities assigned to the principal which we can + // use to authorize the preview and allow for a back office User. + context.User.AddIdentity(backOfficeIdentity); + } } } } diff --git a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj index bab408bde2..93aa295716 100644 --- a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj +++ b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj @@ -15,6 +15,8 @@ + + diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index bddd95fbbe..02f6164f64 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -323,7 +323,6 @@ $scope.defaultButton = buttons.defaultButton; $scope.subButtons = buttons.subButtons; $scope.page.showPreviewButton = true; - } /** Syncs the content item to it's tree node - this occurs on first load and after saving */ @@ -938,22 +937,25 @@ }; $scope.preview = function (content) { - // Chromes popup blocker will kick in if a window is opened - // without the initial scoped request. This trick will fix that. - // - var previewWindow = $window.open('preview/?init=true', 'umbpreview'); - // Build the correct path so both /#/ and #/ work. - var query = 'id=' + content.id; - if ($scope.culture) { - query += "#?culture=" + $scope.culture; + const openPreviewWindow = () => { + // Chromes popup blocker will kick in if a window is opened + // without the initial scoped request. This trick will fix that. + // + const previewWindow = $window.open('preview/?init=true', 'umbpreview'); + + // Build the correct path so both /#/ and #/ work. + let query = 'id=' + content.id; + if ($scope.culture) { + query += "#?culture=" + $scope.culture; + } + previewWindow.location.href = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/?' + query; } - var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/?' + query; //The user cannot save if they don't have access to do that, in which case we just want to preview //and that's it otherwise they'll get an unauthorized access message if (!_.contains(content.allowedActions, "A")) { - previewWindow.location.href = redirect; + openPreviewWindow(); } else { var selectedVariant = $scope.content.variants[0]; @@ -967,10 +969,12 @@ } } - //ensure the save flag is set + //reset save flag for all variants + $scope.content.variants.forEach(variant => variant.save = false); + //ensure the save flag is set for the active variant selectedVariant.save = true; performSave({ saveMethod: $scope.saveMethod(), action: "save" }).then(function (data) { - previewWindow.location.href = redirect; + openPreviewWindow() }, function (err) { //validation issues .... }); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js b/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js index 864e07f17c..d461a3b0bc 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js @@ -206,7 +206,6 @@ * Used to highlight unsupported properties for the user, changes unsupported properties into a unsupported-property. */ var notSupportedProperties = [ - "Umbraco.Tags", "Umbraco.UploadField", "Umbraco.ImageCropper", "Umbraco.NestedContent" @@ -654,7 +653,7 @@ blockObject.__scope.$evalAsync(); }); }); - + observer.observe(labelElement[0], {characterData: true, subtree:true}); blockObject.__watchers.push(() => { @@ -671,9 +670,9 @@ $index: this.index + 1, ... this.data }; - + this.__labelScope = Object.assign(this.__labelScope, labelVars); - + $compile(labelElement.contents())(this.__labelScope); }.bind(blockObject) } else { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockpicker/blockpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockpicker/blockpicker.controller.js index ec30c7e450..b453127613 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockpicker/blockpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockpicker/blockpicker.controller.js @@ -38,12 +38,18 @@ angular.module("umbraco") "disabled": vm.model.clipboardItems.length === 0 }]; - if (vm.model.openClipboard === true) { + if (vm.model.singleBlockMode === true && vm.model.openClipboard === true) { + vm.navigation.splice(0,1); + vm.activeTab = vm.navigation[0]; + } + else if (vm.model.openClipboard === true) { vm.activeTab = vm.navigation[1]; } else { vm.activeTab = vm.navigation[0]; } + + vm.activeTab.active = true; } ); @@ -55,10 +61,16 @@ angular.module("umbraco") }; vm.clickClearClipboard = function () { - vm.onNavigationChanged(vm.navigation[0]); - vm.navigation[1].disabled = true;// disabled ws determined when creating the navigation, so we need to update it here. vm.model.clipboardItems = [];// This dialog is not connected via the clipboardService events, so we need to update manually. vm.model.clickClearClipboard(); + if (vm.model.singleBlockMode !== true && vm.model.openClipboard !== true) + { + vm.onNavigationChanged(vm.navigation[0]); + vm.navigation[1].disabled = true;// disabled ws determined when creating the navigation, so we need to update it here. + } + else { + vm.close(); + } }; vm.model = $scope.model; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js index d270a7171e..d59bbcefcf 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js @@ -73,11 +73,13 @@ getVersions(); } + function canRollback(version) { + return !version.currentDraftVersion; + } + function changeVersion(version) { - const canRollback = !version.currentDraftVersion && !version.currentPublishedVersion; - - if (canRollback === false) { + if (canRollback(version) === false) { return; } @@ -92,6 +94,7 @@ vm.loadingDiff = true; const culture = $scope.model.node.variants.length > 1 ? vm.currentVersion.language.culture : null; + vm.previousVersion = null; contentResource.getRollbackVersion(version.versionId, culture) .then(function(data) { vm.previousVersion = data; @@ -99,8 +102,11 @@ vm.previousVersion.displayValue = version.displayValue + ' - ' + version.username; createDiff(vm.currentVersion, vm.previousVersion); + const changed = (part) => part.added || part.removed; + vm.diffHasChanges = vm.diff.name.some(changed) || vm.diff.properties.some((property) => property.diff.some(changed)); + vm.loadingDiff = false; - vm.rollbackButtonDisabled = false; + vm.rollbackButtonDisabled = !vm.diffHasChanges; }, function () { vm.loadingDiff = false; }); @@ -131,11 +137,14 @@ // get current backoffice user and format dates userService.getCurrentUser().then(function (currentUser) { - vm.previousVersions = data.items.map(version => { - var timestampFormatted = dateHelper.getLocalDate(version.versionDate, currentUser.locale, 'LLL'); - version.displayValue = timestampFormatted; - return version; - }); + vm.previousVersions = data.items + // we don't ever want to show the draft version in the rollback list + .filter(version => version.currentDraftVersion === false) + .map(version => { + var timestampFormatted = dateHelper.getLocalDate(version.versionDate, currentUser.locale, 'LLL'); + version.displayValue = timestampFormatted; + return version; + }); }); }); } @@ -173,7 +182,7 @@ // copy existing properties, so it doesn't manipulate existing properties on page oldProperty = Utilities.copy(oldProperty); property = Utilities.copy(property); - + // we have to make properties storing values as object into strings (Grid, nested content, etc.) if (property.value instanceof Object) { property.value = JSON.stringify(property.value, null, 1); @@ -188,14 +197,14 @@ // diff requires a string property.value = property.value ? property.value + '' : ''; oldProperty.value = oldProperty.value ? oldProperty.value + '' : ''; - + const diffProperty = { 'alias': property.alias, 'label': property.label, 'diff': property.isObject ? Diff.diffJson(property.value, oldProperty.value) : Diff.diffWords(property.value, oldProperty.value), 'isObject': property.isObject || oldProperty.isObject }; - + vm.diff.properties.push(diffProperty); } }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html index 2ad16e83f6..e6f2a1c3a5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html @@ -21,7 +21,7 @@ - @@ -42,17 +42,12 @@
-
- Current version: - {{vm.currentVersion.name}} (Created: {{vm.currentVersion.createDate}}) -
-
-
@@ -61,12 +56,11 @@
{{version.username}}
Current version - Current version
-
- + - - This shows the differences between the current version and the selected version
Red text will be + + This shows the differences between the current (draft) version and the selected version
Red text will be removed in the selected version, green text will be added
+ + There are no differences between the current (draft) version and the selected version +
- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.less b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.less index e7953a4fea..50c66fb9f4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.less +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.less @@ -14,12 +14,6 @@ position: relative; } - .current-version { - background: @gray-10; - padding: 15px; - margin-bottom: 12px; - } - .culture-select { margin-bottom: 12px; } @@ -29,4 +23,4 @@ font-size: 13px; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.overlay.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.overlay.html index 85668c0786..cecc3caab9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.overlay.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.overlay.html @@ -132,6 +132,7 @@ +
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridentries.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridentries.component.js index a7be634d0c..acf4dd16ad 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridentries.component.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridentries.component.js @@ -72,7 +72,6 @@ const unsubscribe = []; const vm = this; - vm.invalidAmount = false; vm.areaConfig = null; vm.locallyAvailableBlockTypes = 0; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umb-block-list-property-editor.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umb-block-list-property-editor.html index 30151accc9..a13826b86b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umb-block-list-property-editor.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umb-block-list-property-editor.html @@ -13,6 +13,7 @@ ng-click="vm.requestShowCreate($index, $event)" ng-controller="Umbraco.PropertyEditors.BlockListPropertyEditor.CreateButtonController as inlineCreateButtonCtrl" ng-mousemove="inlineCreateButtonCtrl.onMouseMove($event)" + ng-if="!vm.singleBlockMode" ng-show="!vm.readonly">
@@ -28,7 +29,7 @@
-
+
-