diff --git a/src/Umbraco.Core/Cache/HttpRequestAppCache.cs b/src/Umbraco.Core/Cache/HttpRequestAppCache.cs index a255fea133..0fdf70ac27 100644 --- a/src/Umbraco.Core/Cache/HttpRequestAppCache.cs +++ b/src/Umbraco.Core/Cache/HttpRequestAppCache.cs @@ -31,7 +31,7 @@ namespace Umbraco.Core.Cache /// /// /// Will use HttpContext.Current. - /// fixme/task: use IHttpContextAccessor NOT HttpContext.Current + /// TODO - https://github.com/umbraco/Umbraco-CMS/issues/4239 - use IHttpContextAccessor NOT HttpContext.Current /// public HttpRequestAppCache() { } diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index 7cb1a61cf1..bc5b6993e3 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -81,7 +81,7 @@ namespace Umbraco.Core.Configuration { try { - // fixme/task - stop having version in web.config appSettings + // TODO - https://github.com/umbraco/Umbraco-CMS/issues/4238 - stop having version in web.config appSettings var value = ConfigurationManager.AppSettings["umbracoConfigurationStatus"]; return value.IsNullOrWhiteSpace() ? null : SemVersion.TryParse(value, out var semver) ? semver : null; } diff --git a/src/Umbraco.Core/Constants-PropertyEditors.cs b/src/Umbraco.Core/Constants-PropertyEditors.cs index 8b637fe90e..b9f20fb449 100644 --- a/src/Umbraco.Core/Constants-PropertyEditors.cs +++ b/src/Umbraco.Core/Constants-PropertyEditors.cs @@ -118,7 +118,12 @@ namespace Umbraco.Core /// RadioButton list. /// public const string RadioButtonList = "Umbraco.RadioButtonList"; - + + /// + /// Related Links. + /// + public const string RelatedLinks = "Umbraco.RelatedLinks"; + /// /// Slider. /// diff --git a/src/Umbraco.Core/Deploy/IDataTypeConfigurationConnector.cs b/src/Umbraco.Core/Deploy/IDataTypeConfigurationConnector.cs new file mode 100644 index 0000000000..f2a08575b5 --- /dev/null +++ b/src/Umbraco.Core/Deploy/IDataTypeConfigurationConnector.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace Umbraco.Core.Deploy +{ + /// + /// Defines methods that can convert a data type configurations to / from an environment-agnostic string. + /// + /// Configurations may contain values such as content identifiers, that would be local + /// to one environment, and need to be converted in order to be deployed. + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "This is actual only used by Deploy, but we dont want third parties to have references on deploy, thats why this interface is part of core.")] + public interface IDataTypeConfigurationConnector + { + /// + /// Gets the property editor aliases that the value converter supports by default. + /// + IEnumerable PropertyEditorAliases { get; } + + /// + /// Gets the environment-agnostic data type configurations corresponding to environment-specific configurations. + /// + /// The environment-specific configuration. + /// The dependencies. + /// + IDictionary ConvertToDeploy(IDictionary configuration, ICollection dependencies); + + /// + /// Gets the environment-specific data type configurations corresponding to environment-agnostic configurations. + /// + /// The environment-agnostic configuration. + /// + IDictionary ConvertToLocalEnvironment(IDictionary configuration); + } +} diff --git a/src/Umbraco.Core/Deploy/IPreValueConnector.cs b/src/Umbraco.Core/Deploy/IPreValueConnector.cs deleted file mode 100644 index 2a8f4e622d..0000000000 --- a/src/Umbraco.Core/Deploy/IPreValueConnector.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Collections.Generic; - -namespace Umbraco.Core.Deploy -{ - /// - /// Defines methods that can convert a preValue to / from an environment-agnostic string. - /// - /// PreValues may contain values such as content identifiers, that would be local - /// to one environment, and need to be converted in order to be deployed. - public interface IPreValueConnector // fixme/task: rename to IDataTypeConfigurationConnector + kill all "preValues" name usage - { - /// - /// Gets the property editor aliases that the value converter supports by default. - /// - IEnumerable PropertyEditorAliases { get; } - - /// - /// Gets the environment-agnostic preValues corresponding to environment-specific preValues. - /// - /// The environment-specific preValues. - /// The dependencies. - /// - IDictionary ConvertToDeploy(IDictionary preValues, ICollection dependencies); - - /// - /// Gets the environment-specific preValues corresponding to environment-agnostic preValues. - /// - /// The environment-agnostic preValues. - /// - IDictionary ConvertToLocalEnvironment(IDictionary preValues); - } -} diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs index 4e0b5dfa0f..f32ea1cb6f 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs @@ -135,7 +135,7 @@ namespace Umbraco.Core.Migrations.Install _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1047, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1047", SortOrder = 2, UniqueId = new Guid("1EA2E01F-EBD8-4CE1-8D71-6B1149E63548"), Text = "Member Picker", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1048, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1048", SortOrder = 2, UniqueId = new Guid("135D60E0-64D9-49ED-AB08-893C9BA44AE5"), Text = "Media Picker", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1049, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1049", SortOrder = 2, UniqueId = new Guid("9DBBCBBB-2327-434A-B355-AF1B84E5010A"), Text = "Multiple Media Picker", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1050, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1050", SortOrder = 2, UniqueId = new Guid("B4E3535A-1753-47E2-8568-602CF8CFEE6F"), Text = "Multi URL Picker", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1050, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1050", SortOrder = 2, UniqueId = new Guid("B4E3535A-1753-47E2-8568-602CF8CFEE6F"), Text = "Related Links", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); } private void CreateLockData() @@ -301,7 +301,7 @@ namespace Umbraco.Core.Migrations.Install _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1048, EditorAlias = Constants.PropertyEditors.Aliases.MediaPicker, DbType = "Ntext" }); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1049, EditorAlias = Constants.PropertyEditors.Aliases.MediaPicker, DbType = "Ntext", Configuration = "{\"multiPicker\":1}" }); - _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1050, EditorAlias = Constants.PropertyEditors.Aliases.MultiUrlPicker, DbType = "Ntext" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1050, EditorAlias = Constants.PropertyEditors.Aliases.RelatedLinks, DbType = "Ntext" }); } private void CreateRelationTypeData() diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigration.cs index 064ffc7228..ee439088be 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigration.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigration.cs @@ -16,6 +16,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 RenameDataType(Constants.PropertyEditors.Aliases.MediaPicker + "2", Constants.PropertyEditors.Aliases.MediaPicker); RenameDataType(Constants.PropertyEditors.Aliases.MemberPicker + "2", Constants.PropertyEditors.Aliases.MemberPicker); RenameDataType(Constants.PropertyEditors.Aliases.MultiNodeTreePicker + "2", Constants.PropertyEditors.Aliases.MultiNodeTreePicker); + RenameDataType(Constants.PropertyEditors.Aliases.RelatedLinks + "2", Constants.PropertyEditors.Aliases.RelatedLinks); RenameDataType("Umbraco.TextboxMultiple", Constants.PropertyEditors.Aliases.TextArea, false); RenameDataType("Umbraco.Textbox", Constants.PropertyEditors.Aliases.TextBox, false); } diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs index c5fa0330f2..e50fb2c396 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs @@ -153,8 +153,12 @@ namespace Umbraco.Core.Models.PublishedContent /// bool IsDraft(string culture = null); - // fixme/task - consider having an IsPublished flag too - // so that when IsDraft is true, we can check whether there is a published version? + /// + /// Gets a value indicating whether the content is published. + /// + /// + bool IsPublished(string culture = null); + #endregion diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs index 36755c8944..42ff16bae5 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs @@ -111,6 +111,10 @@ namespace Umbraco.Core.Models.PublishedContent /// public virtual bool IsDraft(string culture = null) => _content.IsDraft(culture); + /// + public virtual bool IsPublished(string culture = null) => _content.IsPublished(culture); + + #endregion #region Tree diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs index f4f0da8bee..0e6ff0310d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs @@ -26,7 +26,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { private readonly Lazy _editors; - // fixme/task - get rid of Lazy injection and fix circular dependencies + // TODO - https://github.com/umbraco/Umbraco-CMS/issues/4237 - get rid of Lazy injection and fix circular dependencies public DataTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, Lazy editors, ILogger logger) : base(scopeAccessor, cache, logger) { diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 344c0b9489..8bee55060b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -260,7 +260,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(IContent entity) { - // fixme/task - sort out IContent vs Content + // TODO: https://github.com/umbraco/Umbraco-CMS/issues/4234 - sort out IContent vs Content // however, it's not just so we have access to AddingEntity // there are tons of things at the end of the methods, that can only work with a true Content // and basically, the repository requires a Content, not an IContent diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 0ff88da2f3..9a52cb8dc5 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -331,6 +331,7 @@ + @@ -564,7 +565,6 @@ - diff --git a/src/Umbraco.Tests.Benchmarks/XmlPublishedContentInitBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/XmlPublishedContentInitBenchmarks.cs index 751767f8e7..860dc0ac4b 100644 --- a/src/Umbraco.Tests.Benchmarks/XmlPublishedContentInitBenchmarks.cs +++ b/src/Umbraco.Tests.Benchmarks/XmlPublishedContentInitBenchmarks.cs @@ -94,6 +94,7 @@ namespace Umbraco.Tests.Benchmarks Guid key, version; string name, urlName, writerName, creatorName, docTypeAlias, path; bool isDraft; + bool isPublished; DateTime createDate, updateDate; PublishedContentType publishedContentType; Dictionary properties; @@ -104,7 +105,7 @@ namespace Umbraco.Tests.Benchmarks OriginalInitializeNode(_xml10.DocumentElement, false, false, out id, out key, out template, out sortOrder, out name, out writerName, out urlName, out creatorName, out creatorId, out writerId, out docTypeAlias, out nodeType, out path, - out version, out createDate, out updateDate, out level, out isDraft, out publishedContentType, + out version, out createDate, out updateDate, out level, out isDraft, out isPublished, out publishedContentType, out properties); } @@ -114,7 +115,7 @@ namespace Umbraco.Tests.Benchmarks OriginalInitializeNode(_xml100.DocumentElement, false, false, out id, out key, out template, out sortOrder, out name, out writerName, out urlName, out creatorName, out creatorId, out writerId, out docTypeAlias, out nodeType, out path, - out version, out createDate, out updateDate, out level, out isDraft, out publishedContentType, + out version, out createDate, out updateDate, out level, out isDraft, out isPublished, out publishedContentType, out properties); } @@ -124,7 +125,7 @@ namespace Umbraco.Tests.Benchmarks OriginalInitializeNode(_xml1000.DocumentElement, false, false, out id, out key, out template, out sortOrder, out name, out writerName, out urlName, out creatorName, out creatorId, out writerId, out docTypeAlias, out nodeType, out path, - out version, out createDate, out updateDate, out level, out isDraft, out publishedContentType, + out version, out createDate, out updateDate, out level, out isDraft, out isPublished, out publishedContentType, out properties); } @@ -134,7 +135,7 @@ namespace Umbraco.Tests.Benchmarks OriginalInitializeNode(_xml10000.DocumentElement, false, false, out id, out key, out template, out sortOrder, out name, out writerName, out urlName, out creatorName, out creatorId, out writerId, out docTypeAlias, out nodeType, out path, - out version, out createDate, out updateDate, out level, out isDraft, out publishedContentType, + out version, out createDate, out updateDate, out level, out isDraft, out isPublished, out publishedContentType, out properties); } @@ -144,7 +145,7 @@ namespace Umbraco.Tests.Benchmarks XmlPublishedContent.InitializeNode(null, _xml10.DocumentElement, false, out id, out key, out template, out sortOrder, out name, out writerName, out urlName, out creatorName, out creatorId, out writerId, out docTypeAlias, out nodeType, out path, - out createDate, out updateDate, out level, out isDraft, out publishedContentType, + out createDate, out updateDate, out level, out isDraft, out isPublished, out publishedContentType, out properties, GetPublishedContentType); } @@ -154,7 +155,7 @@ namespace Umbraco.Tests.Benchmarks XmlPublishedContent.InitializeNode(null, _xml100.DocumentElement, false, out id, out key, out template, out sortOrder, out name, out writerName, out urlName, out creatorName, out creatorId, out writerId, out docTypeAlias, out nodeType, out path, - out createDate, out updateDate, out level, out isDraft, out publishedContentType, + out createDate, out updateDate, out level, out isDraft, out isPublished,out publishedContentType, out properties, GetPublishedContentType); } @@ -164,7 +165,7 @@ namespace Umbraco.Tests.Benchmarks XmlPublishedContent.InitializeNode(null, _xml1000.DocumentElement, false, out id, out key, out template, out sortOrder, out name, out writerName, out urlName, out creatorName, out creatorId, out writerId, out docTypeAlias, out nodeType, out path, - out createDate, out updateDate, out level, out isDraft, out publishedContentType, + out createDate, out updateDate, out level, out isDraft, out isPublished,out publishedContentType, out properties, GetPublishedContentType); } @@ -174,7 +175,7 @@ namespace Umbraco.Tests.Benchmarks XmlPublishedContent.InitializeNode(null, _xml10000.DocumentElement, false, out id, out key, out template, out sortOrder, out name, out writerName, out urlName, out creatorName, out creatorId, out writerId, out docTypeAlias, out nodeType, out path, - out createDate, out updateDate, out level, out isDraft, out publishedContentType, + out createDate, out updateDate, out level, out isDraft, out isPublished,out publishedContentType, out properties, GetPublishedContentType); } @@ -182,7 +183,7 @@ namespace Umbraco.Tests.Benchmarks internal static void OriginalInitializeNode(XmlNode xmlNode, bool legacy, bool isPreviewing, out int id, out Guid key, out int template, out int sortOrder, out string name, out string writerName, out string urlName, out string creatorName, out int creatorId, out int writerId, out string docTypeAlias, out int docTypeId, out string path, - out Guid version, out DateTime createDate, out DateTime updateDate, out int level, out bool isDraft, + out Guid version, out DateTime createDate, out DateTime updateDate, out int level, out bool isDraft, out bool isPublished, out PublishedContentType contentType, out Dictionary properties) { //initialize the out params with defaults: @@ -193,6 +194,7 @@ namespace Umbraco.Tests.Benchmarks name = writerName = urlName = creatorName = docTypeAlias = path = null; createDate = updateDate = default(DateTime); isDraft = false; + isPublished = true; contentType = null; properties = null; diff --git a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs index add3424599..6b3ce3eb4e 100644 --- a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs @@ -279,7 +279,7 @@ AnotherContentFinder public void GetDataEditors() { var types = _typeLoader.GetDataEditors(); - Assert.AreEqual(39, types.Count()); + Assert.AreEqual(40, types.Count()); } /// diff --git a/src/Umbraco.Tests/Published/NestedContentTests.cs b/src/Umbraco.Tests/Published/NestedContentTests.cs index b5d6182c28..91810d6de1 100644 --- a/src/Umbraco.Tests/Published/NestedContentTests.cs +++ b/src/Umbraco.Tests/Published/NestedContentTests.cs @@ -262,6 +262,7 @@ namespace Umbraco.Tests.Published // ReSharper disable UnassignedGetOnlyAutoProperty public override PublishedItemType ItemType { get; } public override bool IsDraft(string culture = null) => false; + public override bool IsPublished(string culture = null) => true; public override IPublishedContent Parent { get; } public override IEnumerable Children { get; } public override PublishedContentType ContentType { get; } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs index 73f3cd1537..283ed1edd9 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs @@ -217,6 +217,7 @@ namespace Umbraco.Tests.PublishedContent public Guid Version { get; set; } public int Level { get; set; } public bool IsDraft(string culture = null) => false; + public bool IsPublished(string culture = null) => true; public IEnumerable Properties { get; set; } diff --git a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs index f7077ecb3a..a838e06a9a 100644 --- a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs +++ b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs @@ -192,6 +192,7 @@ namespace Umbraco.Tests.PublishedContent public PublishedItemType ItemType { get { return PublishedItemType.Content; } } public bool IsDraft(string culture = null) => false; + public bool IsPublished(string culture = null) => true; #endregion diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 9490213d62..039bcaed24 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -2215,7 +2215,7 @@ namespace Umbraco.Tests.Services Assert.That(sut.GetValue("contentPicker"), Is.EqualTo(Udi.Create(Constants.UdiEntityType.Document, new Guid("74ECA1D4-934E-436A-A7C7-36CC16D4095C")))); Assert.That(sut.GetValue("mediaPicker"), Is.EqualTo(Udi.Create(Constants.UdiEntityType.Media, new Guid("44CB39C8-01E5-45EB-9CF8-E70AAF2D1691")))); Assert.That(sut.GetValue("memberPicker"), Is.EqualTo(Udi.Create(Constants.UdiEntityType.Member, new Guid("9A50A448-59C0-4D42-8F93-4F1D55B0F47D")))); - Assert.That(sut.GetValue("multiUrlPicker"), Is.EqualTo("[{\"name\":\"https://test.com\",\"url\":\"https://test.com\"}]")); + Assert.That(sut.GetValue("relatedLinks"), Is.EqualTo("")); Assert.That(sut.GetValue("tags"), Is.EqualTo("this,is,tags")); } diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs index 19a57d7775..faf4acf8a4 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs @@ -132,7 +132,7 @@ namespace Umbraco.Tests.TestHelpers.Entities content.SetValue("contentPicker", Udi.Create(Constants.UdiEntityType.Document, new Guid("74ECA1D4-934E-436A-A7C7-36CC16D4095C")).ToString()); content.SetValue("mediaPicker", Udi.Create(Constants.UdiEntityType.Media, new Guid("44CB39C8-01E5-45EB-9CF8-E70AAF2D1691")).ToString()); content.SetValue("memberPicker", Udi.Create(Constants.UdiEntityType.Member, new Guid("9A50A448-59C0-4D42-8F93-4F1D55B0F47D")).ToString()); - content.SetValue("multiUrlPicker", "[{\"name\":\"https://test.com\",\"url\":\"https://test.com\"}]"); + content.SetValue("relatedLinks", ""); content.SetValue("tags", "this,is,tags"); return content; diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs index d7dcf8e79a..14b967b1c9 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs @@ -370,7 +370,7 @@ namespace Umbraco.Tests.TestHelpers.Entities contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.ContentPicker, ValueStorageType.Integer) { Alias = "contentPicker", Name = "Content Picker", Mandatory = false, SortOrder = 16, DataTypeId = 1046 }); contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.MediaPicker, ValueStorageType.Integer) { Alias = "mediaPicker", Name = "Media Picker", Mandatory = false, SortOrder = 17, DataTypeId = 1048 }); contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.MemberPicker, ValueStorageType.Integer) { Alias = "memberPicker", Name = "Member Picker", Mandatory = false, SortOrder = 18, DataTypeId = 1047 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.MultiUrlPicker, ValueStorageType.Nvarchar) { Alias = "multiUrlPicker", Name = "Multi URL Picker", Mandatory = false, SortOrder = 21, DataTypeId = 1050 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.RelatedLinks, ValueStorageType.Ntext) { Alias = "relatedLinks", Name = "Related Links", Mandatory = false, SortOrder = 21, DataTypeId = 1050 }); contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Tags, ValueStorageType.Ntext) { Alias = "tags", Name = "Tags", Mandatory = false, SortOrder = 22, DataTypeId = 1041 }); contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs index 58c8e37cbf..a9abe96232 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs @@ -48,6 +48,7 @@ namespace Umbraco.Tests.TestHelpers.Stubs public string GetUrl(string culture = null) => throw new NotSupportedException(); public PublishedItemType ItemType => ContentType.ItemType; public bool IsDraft(string culture = null) => false; + public bool IsPublished(string culture = null) => true; public IPublishedContent Parent { get; set; } public IEnumerable Children { get; set; } diff --git a/src/Umbraco.Tests/Web/Mvc/HtmlHelperExtensionMethodsTests.cs b/src/Umbraco.Tests/Web/Mvc/HtmlHelperExtensionMethodsTests.cs index ba19f41e74..cc83dcb1c9 100644 --- a/src/Umbraco.Tests/Web/Mvc/HtmlHelperExtensionMethodsTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/HtmlHelperExtensionMethodsTests.cs @@ -29,5 +29,30 @@ namespace Umbraco.Tests.Web.Mvc var output = _htmlHelper.Wrap("div", "hello world", new {style = "color:red;", onclick = "void();"}); Assert.AreEqual("
hello world
", output.ToHtmlString()); } + + [Test] + public void GetRelatedLinkHtml_Simple() + { + var relatedLink = new Umbraco.Web.Models.RelatedLink { + Caption = "Link Caption", + NewWindow = true, + Link = "https://www.google.com/" + }; + var output = _htmlHelper.GetRelatedLinkHtml(relatedLink); + Assert.AreEqual("Link Caption", output.ToHtmlString()); + } + + [Test] + public void GetRelatedLinkHtml_HtmlAttributes() + { + var relatedLink = new Umbraco.Web.Models.RelatedLink + { + Caption = "Link Caption", + NewWindow = true, + Link = "https://www.google.com/" + }; + var output = _htmlHelper.GetRelatedLinkHtml(relatedLink, new { @class = "test-class"}); + Assert.AreEqual("Link Caption", output.ToHtmlString()); + } } } diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index ed28a93caf..fe657ae470 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -937,7 +937,7 @@ }, "ansi-colors": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", "dev": true, "requires": { @@ -955,7 +955,7 @@ }, "ansi-escapes": { "version": "3.1.0", - "resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", "dev": true }, @@ -1170,7 +1170,7 @@ "array-slice": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha1-42jqFfibxwaff/uJrsOmx9SsItQ=", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", "dev": true }, "array-sort": { @@ -1216,7 +1216,7 @@ "arraybuffer.slice": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", - "integrity": "sha1-O7xCdd1YTMGxCAm4nU6LY6aednU=", + "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", "dev": true }, "asap": { @@ -1269,7 +1269,7 @@ "async-limiter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha1-ePrtjD0HSrgfIrTphdeehzj3IPg=", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", "dev": true }, "asynckit": { @@ -2379,7 +2379,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -2459,7 +2459,7 @@ "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", "dev": true }, "continuable-cache": { @@ -2502,7 +2502,7 @@ "core-js": { "version": "2.5.7", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", - "integrity": "sha1-+XJgj/DOrWi4QaFqky0LGDeRgU4=", + "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==", "dev": true }, "core-util-is": { @@ -3425,7 +3425,7 @@ "doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha1-XNAfwQFiG0LEzX9dGmYkNxbT850=", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "requires": { "esutils": "^2.0.2" @@ -3953,7 +3953,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { "ms": "2.0.0" @@ -3989,7 +3989,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { "ms": "2.0.0" @@ -4153,7 +4153,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "optional": true } @@ -4248,7 +4248,7 @@ "eslint-scope": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", - "integrity": "sha1-UL8wcekzi83EMzF5Sgy1M/ATYXI=", + "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", "dev": true, "requires": { "esrecurse": "^4.1.0", @@ -4258,13 +4258,13 @@ "eslint-utils": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", - "integrity": "sha1-moUbqJ7nxGA0b5fPiTnHKYgn5RI=", + "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", "dev": true }, "eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha1-PzGA+y4pEBdxastMnW1bXDSmqB0=", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", "dev": true }, "espree": { @@ -4287,7 +4287,7 @@ "esquery": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha1-QGxRZYsfWZGl+bYrHcJbAOPlxwg=", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", "dev": true, "requires": { "estraverse": "^4.0.0" @@ -4296,7 +4296,7 @@ "esrecurse": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha1-AHo7n9vCs7uH5IeeoZyS/b05Qs8=", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", "dev": true, "requires": { "estraverse": "^4.1.0" @@ -4356,7 +4356,7 @@ "eventemitter3": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", - "integrity": "sha1-CQtNbNvWRe0Qv3UNS1QHlC17oWM=", + "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==", "dev": true }, "exec-buffer": { @@ -4571,7 +4571,7 @@ "fill-range": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "integrity": "sha1-6x53OrsFbc2N8r/favWbizqTZWU=", "dev": true, "requires": { "is-number": "^2.1.0", @@ -5891,7 +5891,7 @@ "global-modules": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha1-bXcPDrUjrHgWTXK15xqIdyZcw+o=", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", "dev": true, "requires": { "global-prefix": "^1.0.1", @@ -6461,7 +6461,7 @@ "gulp-eslint": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/gulp-eslint/-/gulp-eslint-5.0.0.tgz", - "integrity": "sha1-KiaECV93Syz3kxAmIHjFbMehK1I=", + "integrity": "sha512-9GUqCqh85C7rP9120cpxXuZz2ayq3BZc85pCTuPJS03VQYxne0aWPIXWx6LSvsGPa3uRqtSO537vaugOh+5cXg==", "dev": true, "requires": { "eslint": "^5.0.1", @@ -7415,7 +7415,7 @@ "has-binary2": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", - "integrity": "sha1-d3asYn8+p3JQz8My2rfd9eT10R0=", + "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", "dev": true, "requires": { "isarray": "2.0.1" @@ -7566,7 +7566,7 @@ "http-proxy": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", - "integrity": "sha1-etOElGWPhGBeL220Q230EPTlvpo=", + "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", "dev": true, "requires": { "eventemitter3": "^3.0.0", @@ -7860,7 +7860,7 @@ "is-absolute": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha1-OV4a6EsR8mrReV5zwXN45IowFXY=", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", "dev": true, "requires": { "is-relative": "^1.0.0", @@ -8155,7 +8155,7 @@ "is-relative": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha1-obtpNc6MXboei5dUubLcwCDiJg0=", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", "dev": true, "requires": { "is-unc-path": "^1.0.0" @@ -8164,7 +8164,7 @@ "is-resolvable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha1-+xj4fOH+uSUWnJpAfBkxijIG7Yg=", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", "dev": true }, "is-retry-allowed": { @@ -8212,7 +8212,7 @@ "is-unc-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha1-1zHoiY7QkKEsNSrS6u1Qla0yLJ0=", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", "dev": true, "requires": { "unc-path-regex": "^0.1.2" @@ -8369,7 +8369,7 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, "json-stable-stringify-without-jsonify": { @@ -8496,7 +8496,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true } } @@ -9146,7 +9146,7 @@ "make-iterator": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", - "integrity": "sha1-KbM/MSqo9UfEpeSQ9Wr87JkTOtY=", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", "dev": true, "requires": { "kind-of": "^6.0.2" @@ -9327,7 +9327,7 @@ "mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha1-ggyGo5M0ZA6ZUWkovQP8qIBX0CI=", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", "dev": true }, "minimatch": { @@ -12855,7 +12855,7 @@ "pluralize": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", - "integrity": "sha1-KYuJ34uTsCIdv0Ia0rGx6iP8Z3c=", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", "dev": true }, "posix-character-classes": { @@ -13345,7 +13345,7 @@ "qjobs": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha1-xF6cYYAL0IfviNfiVkI73Unl0HE=", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", "dev": true }, "qs": { @@ -13541,7 +13541,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -14039,7 +14039,7 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, "sax": { @@ -14226,7 +14226,7 @@ "setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha1-0L2FU2iHtv58DYGMuWLZ2RxU5lY=", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", "dev": true }, "shebang-command": { @@ -14516,7 +14516,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { "ms": "2.0.0" @@ -14796,7 +14796,7 @@ "stream-consume": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/stream-consume/-/stream-consume-0.1.1.tgz", - "integrity": "sha1-0721mMK9CugrjKx6xQsRB6eZbEg=", + "integrity": "sha512-tNa3hzgkjEP7XbCkbRXe1jpg+ievoa0O4SCFlMOYEscGSS4JJsckGL8swUyAa/ApGU3Ae4t6Honor4HhL+tRyg==", "dev": true }, "stream-shift": { @@ -14808,7 +14808,7 @@ "streamroller": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-0.7.0.tgz", - "integrity": "sha1-odG3z4PTmvsNYwSaWsv5NJO99ks=", + "integrity": "sha512-WREzfy0r0zUqp3lGO096wRuUp7ho1X6uo/7DJfTlEi0Iv/4gT7YHqXDjKC2ioVGBZtE8QzsQD9nx1nIuoZ57jQ==", "dev": true, "requires": { "date-format": "^1.2.0", @@ -14835,7 +14835,7 @@ "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -14850,7 +14850,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -14867,7 +14867,7 @@ "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", @@ -15409,7 +15409,7 @@ "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, "requires": { "os-tmpdir": "~1.0.2" @@ -15573,7 +15573,7 @@ "type-is": { "version": "1.6.16", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", - "integrity": "sha1-+JzjQVQcZysl7nrjxz3uOyvlAZQ=", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", "dev": true, "requires": { "media-typer": "0.3.0", @@ -15615,7 +15615,7 @@ "ultron": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha1-n+FTahCmZKZSZqHjzPhf02MCvJw=", + "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", "dev": true }, "unc-path-regex": { @@ -15777,13 +15777,13 @@ "upath": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", - "integrity": "sha1-NSVll+RqWB20eT0M5H+prr/J+r0=", + "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", "dev": true }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha1-lMVA4f93KVbiKZUHwBCupsiDjrA=", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", "dev": true, "requires": { "punycode": "^2.1.0" @@ -16218,7 +16218,7 @@ "ws": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha1-8c+E/i1ekB686U767OeF8YeiKPI=", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", "dev": true, "requires": { "async-limiter": "~1.0.0", diff --git a/src/Umbraco.Web.UI.Client/src/less/components/html/umb-expansion-panel.less b/src/Umbraco.Web.UI.Client/src/less/components/html/umb-expansion-panel.less index 26347c8501..2d1cbd10ff 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/html/umb-expansion-panel.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/html/umb-expansion-panel.less @@ -13,6 +13,10 @@ cursor: pointer; justify-content: space-between; color: @black; + + &:hover .umb-expansion-panel__expand { + color: @gray-6; + } } .umb-expansion-panel__expand { @@ -22,4 +26,4 @@ .umb-expansion-panel__content { padding: 20px; border-top: 1px solid @gray-9; -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/KeepAliveController.cs b/src/Umbraco.Web/Editors/KeepAliveController.cs index fa33abea44..b15621ee23 100644 --- a/src/Umbraco.Web/Editors/KeepAliveController.cs +++ b/src/Umbraco.Web/Editors/KeepAliveController.cs @@ -1,16 +1,13 @@ using System.Runtime.Serialization; using System.Web.Http; +using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; namespace Umbraco.Web.Editors { - // fixme/task - deal with this - // this is not authenticated, and therefore public, and therefore reveals we - // are running Umbraco - but, all requests should come from localhost really, - // so there should be a way to 404 when the request comes from the outside. - public class KeepAliveController : UmbracoApiController { + [OnlyLocalRequests] [HttpGet] public KeepAlivePingResult Ping() { diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs index 1186102bc8..626a19a369 100644 --- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs @@ -830,5 +830,39 @@ namespace Umbraco.Web } #endregion + + + #region RelatedLink + + /// + /// Renders an anchor element for a RelatedLink instance. + /// Format: <a href="relatedLink.Link" target="_blank/_self">relatedLink.Caption</a> + /// + /// The HTML helper instance that this method extends. + /// The RelatedLink instance + /// An anchor element + public static MvcHtmlString GetRelatedLinkHtml(this HtmlHelper htmlHelper, RelatedLink relatedLink) + { + return htmlHelper.GetRelatedLinkHtml(relatedLink, null); + } + + /// + /// Renders an anchor element for a RelatedLink instance, accepting htmlAttributes. + /// Format: <a href="relatedLink.Link" target="_blank/_self" htmlAttributes>relatedLink.Caption</a> + /// + /// The HTML helper instance that this method extends. + /// The RelatedLink instance + /// An object that contains the HTML attributes to set for the element. + /// + public static MvcHtmlString GetRelatedLinkHtml(this HtmlHelper htmlHelper, RelatedLink relatedLink, object htmlAttributes) + { + var tagBuilder = new TagBuilder("a"); + tagBuilder.MergeAttributes(HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)); + tagBuilder.MergeAttribute("href", relatedLink.Link); + tagBuilder.MergeAttribute("target", relatedLink.NewWindow ? "_blank" : "_self"); + tagBuilder.InnerHtml = HttpUtility.HtmlEncode(relatedLink.Caption); + return MvcHtmlString.Create(tagBuilder.ToString(TagRenderMode.Normal)); + } + #endregion } } diff --git a/src/Umbraco.Web/Models/PublishedContentBase.cs b/src/Umbraco.Web/Models/PublishedContentBase.cs index c772f6ca85..12acb6b0c8 100644 --- a/src/Umbraco.Web/Models/PublishedContentBase.cs +++ b/src/Umbraco.Web/Models/PublishedContentBase.cs @@ -144,6 +144,8 @@ namespace Umbraco.Web.Models /// public abstract bool IsDraft(string culture = null); + public abstract bool IsPublished(string culture = null); + #endregion #region Tree diff --git a/src/Umbraco.Web/Models/RelatedLink.cs b/src/Umbraco.Web/Models/RelatedLink.cs new file mode 100644 index 0000000000..1e1d7636ad --- /dev/null +++ b/src/Umbraco.Web/Models/RelatedLink.cs @@ -0,0 +1,11 @@ +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Web.Models +{ + public class RelatedLink : RelatedLinkBase + { + public int? Id { get; internal set; } + internal bool IsDeleted { get; set; } + public IPublishedContent Content { get; set; } + } +} diff --git a/src/Umbraco.Web/Models/RelatedLinkBase.cs b/src/Umbraco.Web/Models/RelatedLinkBase.cs new file mode 100644 index 0000000000..c2077ce4a9 --- /dev/null +++ b/src/Umbraco.Web/Models/RelatedLinkBase.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; + +namespace Umbraco.Web.Models +{ + public abstract class RelatedLinkBase + { + [JsonProperty("caption")] + public string Caption { get; set; } + [JsonProperty("link")] + public string Link { get; set; } + [JsonProperty("newWindow")] + public bool NewWindow { get; set; } + [JsonProperty("isInternal")] + public bool IsInternal { get; set; } + [JsonProperty("type")] + public RelatedLinkType Type { get; set; } + } +} diff --git a/src/Umbraco.Web/Models/RelatedLinkType.cs b/src/Umbraco.Web/Models/RelatedLinkType.cs new file mode 100644 index 0000000000..eec7817ab6 --- /dev/null +++ b/src/Umbraco.Web/Models/RelatedLinkType.cs @@ -0,0 +1,27 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Umbraco +// +// +// Defines the RelatedLinkType type. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace Umbraco.Web.Models +{ + /// + /// The related link type. + /// + public enum RelatedLinkType + { + /// + /// Internal link type + /// + Internal, + + /// + /// External link type + /// + External + } +} diff --git a/src/Umbraco.Web/Models/RelatedLinks.cs b/src/Umbraco.Web/Models/RelatedLinks.cs new file mode 100644 index 0000000000..22cdcd11b6 --- /dev/null +++ b/src/Umbraco.Web/Models/RelatedLinks.cs @@ -0,0 +1,42 @@ +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; + +namespace Umbraco.Web.Models +{ + [TypeConverter(typeof(RelatedLinksTypeConverter))] + public class RelatedLinks : IEnumerable + { + private readonly string _propertyData; + + private readonly IEnumerable _relatedLinks; + + public RelatedLinks(IEnumerable relatedLinks, string propertyData) + { + _relatedLinks = relatedLinks; + _propertyData = propertyData; + } + + /// + /// Gets the property data. + /// + internal string PropertyData + { + get + { + return this._propertyData; + } + } + + public IEnumerator GetEnumerator() + { + return _relatedLinks.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + } +} diff --git a/src/Umbraco.Web/Mvc/OnlyLocalRequestsAttribute.cs b/src/Umbraco.Web/Mvc/OnlyLocalRequestsAttribute.cs new file mode 100644 index 0000000000..ed36e6e3df --- /dev/null +++ b/src/Umbraco.Web/Mvc/OnlyLocalRequestsAttribute.cs @@ -0,0 +1,20 @@ + +using System.Net; +using System.Net.Http; +using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.Filters; + +namespace Umbraco.Web.Mvc +{ + public class OnlyLocalRequestsAttribute : ActionFilterAttribute + { + public override void OnActionExecuting(HttpActionContext actionContext) + { + if (!actionContext.Request.IsLocal()) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/RelatedLinksConfiguration.cs b/src/Umbraco.Web/PropertyEditors/RelatedLinksConfiguration.cs new file mode 100644 index 0000000000..5db14c6842 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/RelatedLinksConfiguration.cs @@ -0,0 +1,13 @@ +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration for the related links value editor. + /// + public class RelatedLinksConfiguration + { + [ConfigurationField("max", "Maximum number of links", "number", Description = "Enter the maximum amount of links to be added, enter 0 for unlimited")] + public int Maximum { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/RelatedLinksConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/RelatedLinksConfigurationEditor.cs new file mode 100644 index 0000000000..07ff359a82 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/RelatedLinksConfigurationEditor.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration editor for the related links value editor. + /// + public class RelatedLinksConfigurationEditor : ConfigurationEditor + { + public override IDictionary ToValueEditor(object configuration) + { + var d = base.ToValueEditor(configuration); + d["idType"] = "udi"; + return d; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs new file mode 100644 index 0000000000..b450fcc67f --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs @@ -0,0 +1,16 @@ +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + [DataEditor(Constants.PropertyEditors.Aliases.RelatedLinks, "Related links", "relatedlinks", ValueType = ValueTypes.Json, Icon = "icon-thumbnail-list", Group = "pickers")] + public class RelatedLinksPropertyEditor : DataEditor + { + public RelatedLinksPropertyEditor(ILogger logger) + : base(logger) + { } + + protected override IConfigurationEditor CreateConfigurationEditor() => new RelatedLinksConfigurationEditor(); + } +} diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksLegacyValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksLegacyValueConverter.cs new file mode 100644 index 0000000000..6c2a4331d0 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksLegacyValueConverter.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.PropertyEditors.ValueConverters; +using Umbraco.Core.Services; + +namespace Umbraco.Web.PropertyEditors.ValueConverters +{ + [DefaultPropertyValueConverter(typeof(JsonValueConverter))] //this shadows the JsonValueConverter + public class RelatedLinksLegacyValueConverter : PropertyValueConverterBase + { + private static readonly string[] MatchingEditors = { + Constants.PropertyEditors.Aliases.RelatedLinks + }; + + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly ILogger _logger; + private readonly ServiceContext _services; + + public RelatedLinksLegacyValueConverter(IUmbracoContextAccessor umbracoContextAccessor, ServiceContext services, ILogger logger) + { + _umbracoContextAccessor = umbracoContextAccessor; + _services = services; + _logger = logger; + } + + public override bool IsConverter(PublishedPropertyType propertyType) + => MatchingEditors.Contains(propertyType.EditorAlias); + + public override Type GetPropertyValueType(PublishedPropertyType propertyType) + => typeof (JArray); + + public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + => PropertyCacheLevel.Element; + + public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + { + if (source == null) return null; + var sourceString = source.ToString(); + + if (sourceString.DetectIsJson()) + { + try + { + var obj = JsonConvert.DeserializeObject(sourceString); + //update the internal links if we have a context + if (UmbracoContext.Current != null) + { + var helper = new UmbracoHelper(_umbracoContextAccessor.UmbracoContext, _services); + foreach (var a in obj) + { + var type = a.Value("type"); + if (type.IsNullOrWhiteSpace() == false) + { + if (type == "internal") + { + switch (propertyType.EditorAlias) + { + case Constants.PropertyEditors.Aliases.RelatedLinks: + var strLinkId = a.Value("link"); + var udiAttempt = strLinkId.TryConvertTo(); + if (udiAttempt) + { + var content = helper.PublishedContent(udiAttempt.Result); + if (content == null) break; + a["link"] = helper.Url(content.Id); + } + break; + } + } + } + } + } + return obj; + } + catch (Exception ex) + { + _logger.Error(ex, "Could not parse the string '{Json}' to a json object", sourceString); + } + } + + //it's not json, just return the string + return sourceString; + } + + public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object source, bool preview) + { + if (source == null) return null; + var sourceString = source.ToString(); + + if (sourceString.DetectIsJson()) + { + try + { + var obj = JsonConvert.DeserializeObject(sourceString); + + var d = new XmlDocument(); + var e = d.CreateElement("links"); + d.AppendChild(e); + + foreach (dynamic link in obj) + { + var ee = d.CreateElement("link"); + ee.SetAttribute("title", link.title); + ee.SetAttribute("link", link.link); + ee.SetAttribute("type", link.type); + ee.SetAttribute("newwindow", link.newWindow); + + e.AppendChild(ee); + } + + return d.CreateNavigator(); + } + catch (Exception ex) + { + _logger.Error(ex, "Could not parse the string '{Json}' to a json object", sourceString); + } + } + + //it's not json, just return the string + return sourceString; + } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksValueConverter.cs new file mode 100644 index 0000000000..983d122a83 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksValueConverter.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; +using System.Xml; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.PropertyEditors.ValueConverters; +using Umbraco.Web.Models; +using Umbraco.Web.PublishedCache; + +namespace Umbraco.Web.PropertyEditors.ValueConverters +{ + /// + /// The related links property value converter. + /// + [DefaultPropertyValueConverter(typeof(RelatedLinksLegacyValueConverter), typeof(JsonValueConverter))] + public class RelatedLinksValueConverter : PropertyValueConverterBase + { + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly ILogger _logger; + + public RelatedLinksValueConverter(IPublishedSnapshotAccessor publishedSnapshotAccessor, IUmbracoContextAccessor umbracoContextAccessor, ILogger logger) + { + _publishedSnapshotAccessor = publishedSnapshotAccessor; + _umbracoContextAccessor = umbracoContextAccessor; + _logger = logger; + } + + /// + /// Checks if this converter can convert the property editor and registers if it can. + /// + /// + /// The property type. + /// + /// + /// The . + /// + public override bool IsConverter(PublishedPropertyType propertyType) + => propertyType.EditorAlias.Equals(Constants.PropertyEditors.Aliases.RelatedLinks); + + public override Type GetPropertyValueType(PublishedPropertyType propertyType) + => typeof (JArray); + + public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + => PropertyCacheLevel.Element; + + public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + { + if (source == null) return null; + var sourceString = source.ToString(); + + var relatedLinksData = JsonConvert.DeserializeObject>(sourceString); + var relatedLinks = new List(); + + foreach (var linkData in relatedLinksData) + { + var relatedLink = new RelatedLink + { + Caption = linkData.Caption, + NewWindow = linkData.NewWindow, + IsInternal = linkData.IsInternal, + Type = linkData.Type, + Link = linkData.Link + }; + + int contentId; + if (int.TryParse(relatedLink.Link, out contentId)) + { + relatedLink.Id = contentId; + relatedLink = CreateLink(relatedLink); + } + else + { + var strLinkId = linkData.Link; + var udiAttempt = strLinkId.TryConvertTo(); + if (udiAttempt.Success && udiAttempt.Result != null) + { + var content = _publishedSnapshotAccessor.PublishedSnapshot.Content.GetById(udiAttempt.Result.Guid); + if (content != null) + { + relatedLink.Id = content.Id; + relatedLink = CreateLink(relatedLink); + relatedLink.Content = content; + } + } + } + + if (relatedLink.IsDeleted == false) + { + relatedLinks.Add(relatedLink); + } + else + { + _logger.Warn("Related Links value converter skipped a link as the node has been unpublished/deleted (Internal Link NodeId: {RelatedLinkNodeId}, Link Caption: '{RelatedLinkCaption}')", relatedLink.Link, relatedLink.Caption); + } + } + + return new RelatedLinks(relatedLinks, sourceString); + } + + private RelatedLink CreateLink(RelatedLink link) + { + var umbracoContext = _umbracoContextAccessor.UmbracoContext; + + if (link.IsInternal && link.Id != null) + { + if (umbracoContext == null) + return null; + + var urlProvider = umbracoContext.UrlProvider; + + link.Link = urlProvider.GetUrl((int)link.Id); + if (link.Link.Equals("#")) + { + link.IsDeleted = true; + link.Link = link.Id.ToString(); + } + else + { + link.IsDeleted = false; + } + } + + return link; + } + + public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + { + if (inter == null) return null; + var sourceString = inter.ToString(); + + if (sourceString.DetectIsJson()) + { + try + { + var obj = JsonConvert.DeserializeObject(sourceString); + + var d = new XmlDocument(); + var e = d.CreateElement("links"); + d.AppendChild(e); + + foreach (dynamic link in obj) + { + var ee = d.CreateElement("link"); + ee.SetAttribute("title", link.title); + ee.SetAttribute("link", link.link); + ee.SetAttribute("type", link.type); + ee.SetAttribute("newwindow", link.newWindow); + + e.AppendChild(ee); + } + + return d.CreateNavigator(); + } + catch (Exception ex) + { + _logger.Error(ex, "Could not parse the string {Json} to a json object", sourceString); + } + } + + //it's not json, just return the string + return sourceString; + } + } +} diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs index d1e1d63630..69c672ab98 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs @@ -293,6 +293,23 @@ namespace Umbraco.Web.PublishedCache.NuCache return _contentData.CultureInfos.TryGetValue(culture, out var cvar) && cvar.IsDraft; } + public override bool IsPublished(string culture = null) + { + if (!ContentType.VariesByCulture()) + { + return _contentData.Published; + } + + // handle context culture + if (culture == null) + { + culture = VariationContextAccessor?.VariationContext?.Culture ?? ""; + } + + //If the current culture is not a draft, it must be the published version + return _contentData.CultureInfos.TryGetValue(culture, out var cvar) && !cvar.IsDraft; + } + #endregion #region Tree diff --git a/src/Umbraco.Web/PublishedCache/PublishedMember.cs b/src/Umbraco.Web/PublishedCache/PublishedMember.cs index ef95b2846c..cbe2250920 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedMember.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedMember.cs @@ -78,6 +78,8 @@ namespace Umbraco.Web.PublishedCache public override bool IsDraft(string culture = null) => false; + public override bool IsPublished(string culture = null) => true; + public override IPublishedContent Parent => null; public override IEnumerable Children => Enumerable.Empty(); diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/DictionaryPublishedContent.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/DictionaryPublishedContent.cs index 83bd84dab6..a395db1da5 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/DictionaryPublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/DictionaryPublishedContent.cs @@ -179,6 +179,8 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache public override bool IsDraft(string culture = null) => false; + public override bool IsPublished(string culture = null) => true; + public override IEnumerable Properties => _properties; public override IEnumerable Children => _getChildren.Value; diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs index 2b3bc39452..c3cf22559d 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs @@ -63,6 +63,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache private int _sortOrder; private int _level; private bool _isDraft; + private bool _isPublished; public override IEnumerable Children { @@ -228,6 +229,12 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache return _isDraft; // bah } + public override bool IsPublished(string culture = null) + { + EnsureNodeInitialized(); + return _isPublished; + } + public override IEnumerable Properties { get @@ -280,7 +287,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache InitializeNode(this, _xmlNode, _isPreviewing, out _id, out _key, out _template, out _sortOrder, out _name, out _writerName, out _urlName, out _creatorName, out _creatorId, out _writerId, out _docTypeAlias, out _docTypeId, out _path, - out _createDate, out _updateDate, out _level, out _isDraft, out _contentType, out _properties, + out _createDate, out _updateDate, out _level, out _isDraft, out _isPublished, out _contentType, out _properties, _contentTypeCache.Get); _nodeInitialized = true; @@ -290,7 +297,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache internal static void InitializeNode(XmlPublishedContent node, XmlNode xmlNode, bool isPreviewing, out int id, out Guid key, out int template, out int sortOrder, out string name, out string writerName, out string urlName, out string creatorName, out int creatorId, out int writerId, out string docTypeAlias, out int docTypeId, out string path, - out DateTime createDate, out DateTime updateDate, out int level, out bool isDraft, + out DateTime createDate, out DateTime updateDate, out int level, out bool isDraft,out bool isPublished, out PublishedContentType contentType, out Dictionary properties, Func getPublishedContentType) { @@ -302,6 +309,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache name = writerName = urlName = creatorName = docTypeAlias = path = null; createDate = updateDate = default(DateTime); isDraft = false; + isPublished = false; contentType = null; properties = null; diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 530c9bd9b4..1284cf513d 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -799,7 +799,7 @@ namespace Umbraco.Web return content.DescendantsOrSelf(false, p => p.Level >= level, culture); } - public static IEnumerable Descendants(this IPublishedContent content, string contentTypeAlias, string culture = null) + public static IEnumerable Descendants(this IPublishedContent content, string contentTypeAlias, string culture) { return content.DescendantsOrSelf(false, p => p.ContentType.Alias == contentTypeAlias, culture); } @@ -826,7 +826,7 @@ namespace Umbraco.Web return content.DescendantsOrSelf(true, p => p.Level >= level, culture); } - public static IEnumerable DescendantsOrSelf(this IPublishedContent content, string contentTypeAlias, string culture = null) + public static IEnumerable DescendantsOrSelf(this IPublishedContent content, string contentTypeAlias, string culture) { return content.DescendantsOrSelf(true, p => p.ContentType.Alias == contentTypeAlias, culture); } @@ -853,7 +853,7 @@ namespace Umbraco.Web return content.EnumerateDescendants(false, culture).FirstOrDefault(x => x.Level == level); } - public static IPublishedContent Descendant(this IPublishedContent content, string contentTypeAlias, string culture = null) + public static IPublishedContent Descendant(this IPublishedContent content, string contentTypeAlias, string culture) { return content.EnumerateDescendants(false, culture).FirstOrDefault(x => x.ContentType.Alias == contentTypeAlias); } @@ -880,7 +880,7 @@ namespace Umbraco.Web return content.EnumerateDescendants(true, culture).FirstOrDefault(x => x.Level == level); } - public static IPublishedContent DescendantOrSelf(this IPublishedContent content, string contentTypeAlias, string culture = null) + public static IPublishedContent DescendantOrSelf(this IPublishedContent content, string contentTypeAlias, string culture) { return content.EnumerateDescendants(true, culture).FirstOrDefault(x => x.ContentType.Alias == contentTypeAlias); } @@ -954,14 +954,7 @@ namespace Umbraco.Web /// public static IEnumerable Children(this IPublishedContent content, string culture = null) { - if (content == null) throw new ArgumentNullException(nameof(content)); // fixme/task wtf is this? - - return content.Children.Where(x => - { - if (!x.ContentType.VariesByCulture()) return true; // invariant = always ok - return x.HasCulture(culture); - return false; - }); + if (content == null) throw new ArgumentNullException(nameof(content)); return content.Children.WhereIsInvariantOrHasCulture(culture); } @@ -1017,7 +1010,7 @@ namespace Umbraco.Web /// /// Gets the first child of the content, of a given content type. /// - public static IPublishedContent FirstChild(this IPublishedContent content, string alias, string culture = null) // fixme/task oops + public static IPublishedContent FirstChild(this IPublishedContent content, string alias, string culture) { return content.Children(culture,alias).FirstOrDefault(); } diff --git a/src/Umbraco.Web/RelatedLinksTypeConverter.cs b/src/Umbraco.Web/RelatedLinksTypeConverter.cs new file mode 100644 index 0000000000..647959b920 --- /dev/null +++ b/src/Umbraco.Web/RelatedLinksTypeConverter.cs @@ -0,0 +1,98 @@ +using System; +using System.ComponentModel; +using System.Globalization; +using System.Linq; + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Composing; +using Umbraco.Web.Models; + +namespace Umbraco.Web +{ + public class RelatedLinksTypeConverter : TypeConverter + { + private readonly UmbracoHelper _umbracoHelper; + + public RelatedLinksTypeConverter(UmbracoHelper umbracoHelper) + { + _umbracoHelper = umbracoHelper; + } + + public RelatedLinksTypeConverter() + { + + } + + private static readonly Type[] ConvertableTypes = new[] + { + typeof(JArray) + }; + + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + { + return ConvertableTypes.Any(x => TypeHelper.IsTypeAssignableFrom(x, destinationType)) + || base.CanConvertFrom(context, destinationType); + } + + public override object ConvertTo( + ITypeDescriptorContext context, + CultureInfo culture, + object value, + Type destinationType) + { + var relatedLinks = value as RelatedLinks; + if (relatedLinks == null) + return null; + + if (TypeHelper.IsTypeAssignableFrom(destinationType)) + { + // Conversion to JArray taken from old value converter + + var obj = JsonConvert.DeserializeObject(relatedLinks.PropertyData); + + var umbracoHelper = GetUmbracoHelper(); + + //update the internal links if we have a context + if (umbracoHelper != null) + { + foreach (var a in obj) + { + var type = a.Value("type"); + if (type.IsNullOrWhiteSpace() == false) + { + if (type == "internal") + { + var linkId = a.Value("link"); + var link = umbracoHelper.Url(linkId); + a["link"] = link; + } + } + } + } + return obj; + + } + + return base.ConvertTo(context, culture, value, destinationType); + } + + private UmbracoHelper GetUmbracoHelper() + { + if (_umbracoHelper != null) + return _umbracoHelper; + + if (UmbracoContext.Current == null) + { + Current.Logger.Warn("Cannot create an UmbracoHelper the UmbracoContext is null"); + return null; + } + + //DO NOT assign to _umbracoHelper variable, this is a singleton class and we cannot assign this based on an UmbracoHelper which is request based + return new UmbracoHelper(UmbracoContext.Current, Current.Services); + } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index bf91933745..ca5d242d5f 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -172,6 +172,7 @@ + @@ -404,6 +405,10 @@ + + + + @@ -449,6 +454,9 @@ + + + @@ -464,6 +472,7 @@ + @@ -527,6 +536,7 @@ + @@ -851,6 +861,7 @@ + diff --git a/src/Umbraco.Web/umbraco.presentation/page.cs b/src/Umbraco.Web/umbraco.presentation/page.cs index 7b591640b5..0c9b0c6ff3 100644 --- a/src/Umbraco.Web/umbraco.presentation/page.cs +++ b/src/Umbraco.Web/umbraco.presentation/page.cs @@ -469,6 +469,11 @@ namespace umbraco throw new NotImplementedException(); } + public bool IsPublished(string culture = null) + { + throw new NotImplementedException(); + } + public IPublishedContent Parent { get { return _parent; }