Serverside generated preview URLs (#20021)
* Serverside generated preview URLs * Add URL provider notation to UrlInfo * Change preview URL generation to happen at preview time based on provider alias * Update XML docs * Always add culture (if available) to preview URL * Do not log user input (security vulnerability) * Fix typo * Re-generate TypeScript client from Management API * Deprecated `UmbDocumentPreviewRepository.enter()` (for v19) Fixed TS errors Added temp stub for `getPreviewUrl` * Adds `previewOption` extension-type * Adds "default" `previewOption` kind * Relocated "Save and Preview" workspace action reworked using the "default" `previewOption` kind. * Added stub for "urlProvider" `previewOption` kind * Renamed "workspace-action-default-kind.element.ts" to a more suitable filename. Exported element so can be reused in other packages, e.g. documents, for the new "save and preview" feature. * Refactored "Save and Preview" button to work with first action's manifest/API. * Reverted `previewOption` extension-type Re-engineered to make a "urlProvider" kind for `workspaceActionMenuItem`. This is to simplify the extension point and surrounding logic. * Modified `saveAndPreview` Document Workspace Context to accept a URL Provider Alias. * Refactored "Save and Preview" button to extend `UmbWorkspaceActionElement`. This did mean exposing certain methods/properties to be overridable. * Used `umbPeekError` to surface any errors to the user * Renamed `urlProvider` kind to `previewOption` * Relocated `urlProviderAlias` inside the `meta` property * also throw an error * Added missing `await` * Fix build errors after forward merge --------- Co-authored-by: leekelleher <leekelleher@gmail.com> Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>
This commit is contained in:
11
src/Umbraco.Core/Constants-UrlProviders.cs
Normal file
11
src/Umbraco.Core/Constants-UrlProviders.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Umbraco.Cms.Core;
|
||||
|
||||
public static partial class Constants
|
||||
{
|
||||
public static class UrlProviders
|
||||
{
|
||||
public const string Content = "umbDocumentUrlProvider";
|
||||
|
||||
public const string Media = "umbMediaUrlProvider";
|
||||
}
|
||||
}
|
||||
@@ -108,7 +108,7 @@ public class AddUnroutableContentWarningsWhenPublishingNotificationHandler : INo
|
||||
EventMessages eventMessages = _eventMessagesFactory.Get();
|
||||
foreach (var culture in successfulCultures)
|
||||
{
|
||||
if (urls.Where(u => u.Culture == culture || culture == "*").All(u => u.IsUrl is false))
|
||||
if (urls.Where(u => u.Culture == culture || culture == "*").All(u => u.Url is null))
|
||||
{
|
||||
eventMessages.Add(new EventMessage("Content published", "The document does not have a URL, possibly due to a naming collision with another document. More details can be found under Info.", EventMessageType.Warning));
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.Services.Navigation;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
@@ -40,6 +41,9 @@ public class AliasUrlProvider : IUrlProvider
|
||||
requestConfig.OnChange(x => _requestConfig = x);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Alias => $"{Constants.UrlProviders.Content}ByAlias";
|
||||
|
||||
// note - at the moment we seem to accept pretty much anything as an alias
|
||||
// without any form of validation ... could even prob. kill the XPath ...
|
||||
// ok, this is somewhat experimental and is NOT enabled by default
|
||||
@@ -120,7 +124,7 @@ public class AliasUrlProvider : IUrlProvider
|
||||
{
|
||||
var path = "/" + alias;
|
||||
var uri = new Uri(path, UriKind.Relative);
|
||||
yield return UrlInfo.Url(_uriUtility.UriFromUmbraco(uri, _requestConfig).ToString());
|
||||
yield return UrlInfo.FromUri(_uriUtility.UriFromUmbraco(uri, _requestConfig), Alias);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -152,9 +156,7 @@ public class AliasUrlProvider : IUrlProvider
|
||||
{
|
||||
var path = "/" + alias;
|
||||
var uri = new Uri(CombinePaths(domainUri.Uri.GetLeftPart(UriPartial.Authority), path));
|
||||
yield return UrlInfo.Url(
|
||||
_uriUtility.UriFromUmbraco(uri, _requestConfig).ToString(),
|
||||
domainUri.Culture);
|
||||
yield return UrlInfo.FromUri(_uriUtility.UriFromUmbraco(uri, _requestConfig), Alias, domainUri.Culture);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,6 +164,14 @@ public class AliasUrlProvider : IUrlProvider
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetPreviewUrl
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<UrlInfo?> GetPreviewUrlAsync(IContent content, string? culture, string? segment)
|
||||
=> Task.FromResult<UrlInfo?>(null);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Utilities
|
||||
|
||||
private string CombinePaths(string path1, string path2)
|
||||
|
||||
@@ -39,7 +39,7 @@ public class DefaultMediaUrlProvider : IMediaUrlProvider
|
||||
if (_mediaPathGenerators.TryGetMediaPath(propType?.EditorAlias, value, out var path))
|
||||
{
|
||||
Uri url = _urlAssembler.AssembleUrl(path!, current, mode);
|
||||
return UrlInfo.Url(url.ToString(), culture);
|
||||
return UrlInfo.FromUri(url, Constants.UrlProviders.Media, culture);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -4,8 +4,8 @@ using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.Navigation;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
@@ -16,6 +16,7 @@ namespace Umbraco.Cms.Core.Routing;
|
||||
/// <summary>
|
||||
/// Provides urls.
|
||||
/// </summary>
|
||||
[Obsolete("Use NewDefaultUrlProvider instead. Scheduled for removal in V18.")]
|
||||
public class DefaultUrlProvider : IUrlProvider
|
||||
{
|
||||
private readonly ILocalizationService _localizationService;
|
||||
@@ -76,6 +77,9 @@ public class DefaultUrlProvider : IUrlProvider
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Alias => $"{Constants.UrlProviders.Content}Legacy";
|
||||
|
||||
#region GetOtherUrls
|
||||
|
||||
/// <summary>
|
||||
@@ -136,7 +140,7 @@ public class DefaultUrlProvider : IUrlProvider
|
||||
|
||||
var uri = new Uri(CombinePaths(d.Uri.GetLeftPart(UriPartial.Path), path));
|
||||
uri = _uriUtility.UriFromUmbraco(uri, _requestSettings);
|
||||
yield return UrlInfo.Url(uri.ToString(), culture);
|
||||
yield return UrlInfo.FromUri(uri, Alias, culture);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,8 +200,8 @@ public class DefaultUrlProvider : IUrlProvider
|
||||
if (domainUri is not null || string.IsNullOrEmpty(culture) ||
|
||||
culture.Equals(defaultCulture, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
var url = AssembleUrl(domainUri, path, current, mode).ToString();
|
||||
return UrlInfo.Url(url, culture);
|
||||
Uri url = AssembleUrl(domainUri, path, current, mode);
|
||||
return UrlInfo.FromUri(url, Alias, culture);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -205,6 +209,14 @@ public class DefaultUrlProvider : IUrlProvider
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetPreviewUrl
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<UrlInfo?> GetPreviewUrlAsync(IContent content, string? culture, string? segment)
|
||||
=> Task.FromResult<UrlInfo?>(null);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Utilities
|
||||
|
||||
private Uri AssembleUrl(DomainAndUri? domainUri, string path, Uri current, UrlMode mode)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
|
||||
namespace Umbraco.Cms.Core.Routing;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
|
||||
namespace Umbraco.Cms.Core.Routing;
|
||||
@@ -38,4 +39,18 @@ public interface IUrlProvider
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
IEnumerable<UrlInfo> GetOtherUrls(int id, Uri current);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the preview URL of a content item.
|
||||
/// </summary>
|
||||
/// <param name="content">The content item.</param>
|
||||
/// <param name="culture">The culture to preview (null means invariant).</param>
|
||||
/// <param name="segment">The segment to preview (null means no specific segment).</param>
|
||||
/// <returns>The preview URLs of the content item.</returns>
|
||||
Task<UrlInfo?> GetPreviewUrlAsync(IContent content, string? culture, string? segment);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the alias for the URL provider.
|
||||
/// </summary>
|
||||
public string Alias { get; }
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
using System.Globalization;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
@@ -25,7 +23,7 @@ public class NewDefaultUrlProvider : IUrlProvider
|
||||
private readonly IDocumentUrlService _documentUrlService;
|
||||
private readonly IDocumentNavigationQueryService _navigationQueryService;
|
||||
private readonly IPublishedContentStatusFilteringService _publishedContentStatusFilteringService;
|
||||
private readonly ILogger<DefaultUrlProvider> _logger;
|
||||
private readonly ILogger<NewDefaultUrlProvider> _logger;
|
||||
private readonly ISiteDomainMapper _siteDomainMapper;
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
private readonly UriUtility _uriUtility;
|
||||
@@ -39,7 +37,7 @@ public class NewDefaultUrlProvider : IUrlProvider
|
||||
/// </summary>
|
||||
public NewDefaultUrlProvider(
|
||||
IOptionsMonitor<RequestHandlerSettings> requestSettings,
|
||||
ILogger<DefaultUrlProvider> logger,
|
||||
ILogger<NewDefaultUrlProvider> logger,
|
||||
ISiteDomainMapper siteDomainMapper,
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
UriUtility uriUtility,
|
||||
@@ -67,6 +65,9 @@ public class NewDefaultUrlProvider : IUrlProvider
|
||||
requestSettings.OnChange(x => _requestSettings = x);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Alias => Constants.UrlProviders.Content;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the other URLs of a published content.
|
||||
/// </summary>
|
||||
@@ -133,10 +134,23 @@ public class NewDefaultUrlProvider : IUrlProvider
|
||||
|
||||
var uri = new Uri(CombinePaths(d.Uri.GetLeftPart(UriPartial.Path), path));
|
||||
uri = _uriUtility.UriFromUmbraco(uri, _requestSettings);
|
||||
yield return UrlInfo.Url(uri.ToString(), culture);
|
||||
yield return UrlInfo.FromUri(uri, Alias, culture);
|
||||
}
|
||||
}
|
||||
|
||||
#region GetPreviewUrl
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<UrlInfo?> GetPreviewUrlAsync(IContent content, string? culture, string? segment)
|
||||
=> Task.FromResult<UrlInfo?>(
|
||||
UrlInfo.AsUrl(
|
||||
$"/{Constants.System.UmbracoPathSegment}/preview?id={content.Key}&culture={culture}&segment={segment}",
|
||||
Alias,
|
||||
culture,
|
||||
isExternal: false));
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Gets the legacy route format by id
|
||||
/// </summary>
|
||||
@@ -221,8 +235,8 @@ public class NewDefaultUrlProvider : IUrlProvider
|
||||
string.IsNullOrEmpty(culture) ||
|
||||
culture.Equals(defaultCulture, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
var url = AssembleUrl(domainUri, path, current, mode).ToString();
|
||||
return UrlInfo.Url(url, culture);
|
||||
Uri url = AssembleUrl(domainUri, path, current, mode);
|
||||
return UrlInfo.FromUri(url, Alias, culture);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -10,6 +10,8 @@ namespace Umbraco.Cms.Core.Routing;
|
||||
|
||||
public class PublishedUrlInfoProvider : IPublishedUrlInfoProvider
|
||||
{
|
||||
private const string UrlProviderAlias = Constants.UrlProviders.Content;
|
||||
|
||||
private readonly IPublishedUrlProvider _publishedUrlProvider;
|
||||
private readonly ILanguageService _languageService;
|
||||
private readonly IPublishedRouter _publishedRouter;
|
||||
@@ -53,7 +55,7 @@ public class PublishedUrlInfoProvider : IPublishedUrlInfoProvider
|
||||
// Handle "could not get URL"
|
||||
if (url is "#" or "#ex")
|
||||
{
|
||||
urlInfos.Add(UrlInfo.Message(_localizedTextService.Localize("content", "getUrlException"), culture));
|
||||
urlInfos.Add(UrlInfo.AsMessage(_localizedTextService.Localize("content", "getUrlException"), UrlProviderAlias, culture));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -66,7 +68,7 @@ public class PublishedUrlInfoProvider : IPublishedUrlInfoProvider
|
||||
continue;
|
||||
}
|
||||
|
||||
urlInfos.Add(UrlInfo.Url(url, culture));
|
||||
urlInfos.Add(UrlInfo.AsUrl(url, UrlProviderAlias, culture));
|
||||
}
|
||||
|
||||
// If the content is trashed, we can't get the other URLs, as we have no parent structure to navigate through.
|
||||
@@ -77,7 +79,7 @@ public class PublishedUrlInfoProvider : IPublishedUrlInfoProvider
|
||||
|
||||
// Then get "other" urls - I.E. Not what you'd get with GetUrl(), this includes all the urls registered using domains.
|
||||
// for these 'other' URLs, we don't check whether they are routable, collide, anything - we just report them.
|
||||
foreach (UrlInfo otherUrl in _publishedUrlProvider.GetOtherUrls(content.Id).OrderBy(x => x.Text).ThenBy(x => x.Culture))
|
||||
foreach (UrlInfo otherUrl in _publishedUrlProvider.GetOtherUrls(content.Id).OrderBy(x => x.Message).ThenBy(x => x.Culture))
|
||||
{
|
||||
urlInfos.Add(otherUrl);
|
||||
}
|
||||
@@ -106,7 +108,7 @@ public class PublishedUrlInfoProvider : IPublishedUrlInfoProvider
|
||||
_logger.LogDebug(logMsg, url, uri, culture);
|
||||
}
|
||||
|
||||
var urlInfo = UrlInfo.Message(_localizedTextService.Localize("content", "routeErrorCannotRoute"), culture);
|
||||
var urlInfo = UrlInfo.AsMessage(_localizedTextService.Localize("content", "routeErrorCannotRoute"), UrlProviderAlias, culture);
|
||||
return Attempt.Succeed(urlInfo);
|
||||
}
|
||||
|
||||
@@ -119,7 +121,7 @@ public class PublishedUrlInfoProvider : IPublishedUrlInfoProvider
|
||||
{
|
||||
var collidingContent = publishedRequest.PublishedContent?.Key.ToString();
|
||||
|
||||
var urlInfo = UrlInfo.Message(_localizedTextService.Localize("content", "routeError", [collidingContent]), culture);
|
||||
var urlInfo = UrlInfo.AsMessage(_localizedTextService.Localize("content", "routeError", [collidingContent]), UrlProviderAlias, culture);
|
||||
return Attempt.Succeed(urlInfo);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.Routing;
|
||||
|
||||
@@ -11,18 +12,50 @@ public class UrlInfo : IEquatable<UrlInfo>
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UrlInfo" /> class.
|
||||
/// </summary>
|
||||
public UrlInfo(string text, bool isUrl, string? culture)
|
||||
public UrlInfo(Uri url, string provider, string? culture, string? message = null, bool isExternal = false)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
if (provider.IsNullOrWhiteSpace())
|
||||
{
|
||||
throw new ArgumentException("Value cannot be null or whitespace.", nameof(text));
|
||||
throw new ArgumentException("Value cannot be null or whitespace.", nameof(provider));
|
||||
}
|
||||
|
||||
IsUrl = isUrl;
|
||||
Text = text;
|
||||
Url = url;
|
||||
Provider = provider;
|
||||
Culture = culture;
|
||||
Message = message;
|
||||
IsExternal = isExternal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UrlInfo" /> class as a "message only" - that is, not an actual URL.
|
||||
/// </summary>
|
||||
public UrlInfo(string message, string provider, string? culture = null)
|
||||
{
|
||||
if (message.IsNullOrWhiteSpace())
|
||||
{
|
||||
throw new ArgumentException("Value cannot be null or whitespace.", nameof(message));
|
||||
}
|
||||
|
||||
if (provider.IsNullOrWhiteSpace())
|
||||
{
|
||||
throw new ArgumentException("Value cannot be null or whitespace.", nameof(provider));
|
||||
}
|
||||
|
||||
Url = null;
|
||||
Provider = provider;
|
||||
Message = message;
|
||||
Culture = culture;
|
||||
}
|
||||
|
||||
public static UrlInfo AsUrl(string url, string provider, string? culture = null, bool isExternal = false)
|
||||
=> new(new Uri(url, UriKind.RelativeOrAbsolute), provider, culture: culture, isExternal: isExternal);
|
||||
|
||||
public static UrlInfo AsMessage(string message, string provider, string? culture = null)
|
||||
=> new(message, provider, culture: culture);
|
||||
|
||||
public static UrlInfo FromUri(Uri uri, string provider, string? culture = null, bool isExternal = false)
|
||||
=> new(uri, provider, culture: culture, isExternal: isExternal);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the culture.
|
||||
/// </summary>
|
||||
@@ -30,34 +63,32 @@ public class UrlInfo : IEquatable<UrlInfo>
|
||||
public string? Culture { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the URL is a true URL.
|
||||
/// Gets the URL.
|
||||
/// </summary>
|
||||
/// <remarks>Otherwise, it is a message.</remarks>
|
||||
[DataMember(Name = "isUrl")]
|
||||
public bool IsUrl { get; }
|
||||
[DataMember(Name = "url")]
|
||||
public Uri? Url { get; }
|
||||
|
||||
public string Provider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the text, which is either the URL, or a message.
|
||||
/// Gets the message.
|
||||
/// </summary>
|
||||
[DataMember(Name = "text")]
|
||||
public string Text { get; }
|
||||
[DataMember(Name = "message")]
|
||||
public string? Message { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether this is considered an external or a local URL (remote or local host).
|
||||
/// </summary>
|
||||
[DataMember(Name = "isExternal")]
|
||||
public bool IsExternal { get; }
|
||||
|
||||
public static bool operator ==(UrlInfo left, UrlInfo right) => Equals(left, right);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="UrlInfo" /> instance representing a true URL.
|
||||
/// </summary>
|
||||
public static UrlInfo Url(string text, string? culture = null) => new(text, true, culture);
|
||||
|
||||
/// <summary>
|
||||
/// Checks equality
|
||||
/// </summary>
|
||||
/// <param name="other"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// Compare both culture and Text as invariant strings since URLs are not case sensitive, nor are culture names within
|
||||
/// Umbraco
|
||||
/// </remarks>
|
||||
public bool Equals(UrlInfo? other)
|
||||
{
|
||||
if (ReferenceEquals(null, other))
|
||||
@@ -70,15 +101,12 @@ public class UrlInfo : IEquatable<UrlInfo>
|
||||
return true;
|
||||
}
|
||||
|
||||
return string.Equals(Culture, other.Culture, StringComparison.InvariantCultureIgnoreCase) &&
|
||||
IsUrl == other.IsUrl && string.Equals(Text, other.Text, StringComparison.InvariantCultureIgnoreCase);
|
||||
return string.Equals(Culture, other.Culture, StringComparison.InvariantCultureIgnoreCase)
|
||||
&& Url == other.Url
|
||||
&& string.Equals(Message, other.Message, StringComparison.InvariantCultureIgnoreCase)
|
||||
&& IsExternal == other.IsExternal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="UrlInfo" /> instance representing a message.
|
||||
/// </summary>
|
||||
public static UrlInfo Message(string text, string? culture = null) => new(text, false, culture);
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj))
|
||||
@@ -104,14 +132,16 @@ public class UrlInfo : IEquatable<UrlInfo>
|
||||
unchecked
|
||||
{
|
||||
var hashCode = Culture != null ? StringComparer.InvariantCultureIgnoreCase.GetHashCode(Culture) : 0;
|
||||
hashCode = (hashCode * 397) ^ IsUrl.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^
|
||||
(Text != null ? StringComparer.InvariantCultureIgnoreCase.GetHashCode(Text) : 0);
|
||||
(Url != null ? Url.GetHashCode() : 0);
|
||||
hashCode = (hashCode * 397) ^
|
||||
(Message != null ? StringComparer.InvariantCultureIgnoreCase.GetHashCode(Message) : 0);
|
||||
hashCode = (hashCode * 397) ^ IsExternal.GetHashCode();
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator !=(UrlInfo left, UrlInfo right) => !Equals(left, right);
|
||||
|
||||
public override string ToString() => Text;
|
||||
public override string ToString() => Url?.ToString() ?? Message ?? "[empty]";
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Services.Navigation;
|
||||
@@ -143,7 +144,7 @@ namespace Umbraco.Cms.Core.Routing
|
||||
|
||||
UrlInfo? url = _urlProviders.Select(provider => provider.GetUrl(content, mode, culture, current))
|
||||
.FirstOrDefault(u => u is not null);
|
||||
return url?.Text ?? "#"; // legacy wants this
|
||||
return url?.Url?.ToString() ?? "#"; // legacy wants this
|
||||
}
|
||||
|
||||
public string GetUrlFromRoute(int id, string? route, string? culture)
|
||||
@@ -152,7 +153,7 @@ namespace Umbraco.Cms.Core.Routing
|
||||
NewDefaultUrlProvider? provider = _urlProviders.OfType<NewDefaultUrlProvider>().FirstOrDefault();
|
||||
var url = provider == null
|
||||
? route // what else?
|
||||
: provider.GetUrlFromRoute(route, id, umbracoContext.CleanedUmbracoUrl, Mode, culture)?.Text;
|
||||
: provider.GetUrlFromRoute(route, id, umbracoContext.CleanedUmbracoUrl, Mode, culture)?.Url?.ToString();
|
||||
return url ?? "#";
|
||||
}
|
||||
|
||||
@@ -261,7 +262,7 @@ namespace Umbraco.Cms.Core.Routing
|
||||
provider.GetMediaUrl(content, propertyAlias, mode, culture, current))
|
||||
.FirstOrDefault(u => u is not null);
|
||||
|
||||
return url?.Text ?? string.Empty;
|
||||
return url?.Url?.ToString() ?? string.Empty;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Extensions;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.Navigation;
|
||||
@@ -15,6 +12,8 @@ namespace Umbraco.Extensions;
|
||||
|
||||
public static class UrlProviderExtensions
|
||||
{
|
||||
private const string UrlProviderAlias = Constants.UrlProviders.Content;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the URLs of the content item.
|
||||
/// </summary>
|
||||
@@ -51,7 +50,7 @@ public static class UrlProviderExtensions
|
||||
|
||||
if (content.Published == false)
|
||||
{
|
||||
result.Add(UrlInfo.Message(textService.Localize("content", "itemNotPublished")));
|
||||
result.Add(UrlInfo.AsMessage(textService.Localize("content", "itemNotPublished"), UrlProviderAlias));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -77,14 +76,14 @@ public static class UrlProviderExtensions
|
||||
}
|
||||
|
||||
// return the real URLs first, then the messages
|
||||
foreach (IGrouping<bool, UrlInfo> urlGroup in urls.GroupBy(x => x.IsUrl).OrderByDescending(x => x.Key))
|
||||
foreach (IGrouping<bool, UrlInfo> urlGroup in urls.GroupBy(x => x.Url is not null).OrderByDescending(x => x.Key))
|
||||
{
|
||||
// in some cases there will be the same URL for multiple cultures:
|
||||
// * The entire branch is invariant
|
||||
// * If there are less domain/cultures assigned to the branch than the number of cultures/languages installed
|
||||
if (urlGroup.Key)
|
||||
{
|
||||
result.AddRange(urlGroup.DistinctBy(x => x.Text, StringComparer.OrdinalIgnoreCase).OrderBy(x => x.Text)
|
||||
result.AddRange(urlGroup.DistinctBy(x => x.Url?.ToString(), StringComparer.OrdinalIgnoreCase).OrderBy(x => x.Url?.ToString())
|
||||
.ThenBy(x => x.Culture));
|
||||
}
|
||||
else
|
||||
@@ -95,7 +94,7 @@ public static class UrlProviderExtensions
|
||||
|
||||
// get the 'other' URLs - ie not what you'd get with GetUrl() but URLs that would route to the document, nevertheless.
|
||||
// for these 'other' URLs, we don't check whether they are routable, collide, anything - we just report them.
|
||||
foreach (UrlInfo otherUrl in publishedUrlProvider.GetOtherUrls(content.Id).OrderBy(x => x.Text)
|
||||
foreach (UrlInfo otherUrl in publishedUrlProvider.GetOtherUrls(content.Id).OrderBy(x => x.Message)
|
||||
.ThenBy(x => x.Culture))
|
||||
{
|
||||
// avoid duplicates
|
||||
@@ -156,7 +155,7 @@ public static class UrlProviderExtensions
|
||||
|
||||
// deal with exceptions
|
||||
case "#ex":
|
||||
result.Add(UrlInfo.Message(textService.Localize("content", "getUrlException"), culture));
|
||||
result.Add(UrlInfo.AsMessage(textService.Localize("content", "getUrlException"), UrlProviderAlias, culture));
|
||||
break;
|
||||
|
||||
// got a URL, deal with collisions, add URL
|
||||
@@ -169,7 +168,7 @@ public static class UrlProviderExtensions
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Add(UrlInfo.Url(url, culture));
|
||||
result.Add(UrlInfo.AsUrl(url, UrlProviderAlias, culture));
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -194,18 +193,19 @@ public static class UrlProviderExtensions
|
||||
if (parent == null)
|
||||
{
|
||||
// oops, internal error
|
||||
return UrlInfo.Message(textService.Localize("content", "parentNotPublishedAnomaly"), culture);
|
||||
return UrlInfo.AsMessage(textService.Localize("content", "parentNotPublishedAnomaly"), UrlProviderAlias, culture);
|
||||
}
|
||||
|
||||
if (!parent.Published)
|
||||
{
|
||||
// totally not published
|
||||
return UrlInfo.Message(textService.Localize("content", "parentNotPublished", new[] { parent.Name }), culture);
|
||||
return UrlInfo.AsMessage(textService.Localize("content", "parentNotPublished", new[] { parent.Name }), UrlProviderAlias, culture);
|
||||
}
|
||||
|
||||
// culture not published
|
||||
return UrlInfo.Message(
|
||||
return UrlInfo.AsMessage(
|
||||
textService.Localize("content", "parentCultureNotPublished", new[] { parent.Name }),
|
||||
UrlProviderAlias,
|
||||
culture);
|
||||
}
|
||||
|
||||
@@ -243,7 +243,7 @@ public static class UrlProviderExtensions
|
||||
logger.LogDebug(logMsg, url, uri, culture);
|
||||
}
|
||||
|
||||
var urlInfo = UrlInfo.Message(textService.Localize("content", "routeErrorCannotRoute"), culture);
|
||||
var urlInfo = UrlInfo.AsMessage(textService.Localize("content", "routeErrorCannotRoute"), UrlProviderAlias, culture);
|
||||
return Attempt.Succeed(urlInfo);
|
||||
}
|
||||
|
||||
@@ -265,7 +265,7 @@ public static class UrlProviderExtensions
|
||||
l.Reverse();
|
||||
var s = "/" + string.Join("/", l) + " (id=" + pcr.PublishedContent?.Id + ")";
|
||||
|
||||
var urlInfo = UrlInfo.Message(textService.Localize("content", "routeError", new[] { s }), culture);
|
||||
var urlInfo = UrlInfo.AsMessage(textService.Localize("content", "routeError", new[] { s }), UrlProviderAlias, culture);
|
||||
return Attempt.Succeed(urlInfo);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user