diff --git a/src/Umbraco.Cms.Api.Management/Controllers/OEmbed/OEmbedControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/OEmbed/OEmbedControllerBase.cs new file mode 100644 index 0000000000..403b5ba41a --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/OEmbed/OEmbedControllerBase.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Routing; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Api.Management.Controllers.OEmbed; + +[VersionedApiBackOfficeRoute("oembed")] +[ApiExplorerSettings(GroupName = "oEmbed")] +public abstract class OEmbedControllerBase : ManagementApiControllerBase +{ + protected IActionResult OEmbedOperationStatusResult(OEmbedOperationStatus status) + => OperationStatusResult(status, problemDetailsBuilder => status switch + { + OEmbedOperationStatus.NoSupportedProvider => BadRequest(problemDetailsBuilder + .WithTitle("The specified url is not supported.") + .WithDetail("No oEmbed provider was found for the specified url.") + .Build()), + _ => StatusCode(StatusCodes.Status500InternalServerError, problemDetailsBuilder + .WithTitle("Unknown oEmbed operation status.") + .Build()), + }); + +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/OEmbed/QueryOEmbedController.cs b/src/Umbraco.Cms.Api.Management/Controllers/OEmbed/QueryOEmbedController.cs new file mode 100644 index 0000000000..ba9c621912 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/OEmbed/QueryOEmbedController.cs @@ -0,0 +1,35 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.ViewModels.OEmbed; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Web.Common.Authorization; + +namespace Umbraco.Cms.Api.Management.Controllers.OEmbed; + +[Authorize(Policy = AuthorizationPolicies.SectionAccessContent)] +[ApiVersion("1.0")] +public class QueryOEmbedController : OEmbedControllerBase +{ + private readonly IOEmbedService _oEmbedService; + + public QueryOEmbedController(IOEmbedService oEmbedService) + { + _oEmbedService = oEmbedService; + } + + [HttpGet("query")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(OEmbedResponseModel), StatusCodes.Status200OK)] + public async Task Query(CancellationToken cancellationToken, Uri url, int? maxWidth = null, int? maxHeight = null) + { + Attempt result = await _oEmbedService.GetMarkupAsync(url, maxWidth, maxHeight, cancellationToken); + + return result.Success + ? Ok(new OEmbedResponseModel() { Markup = result.Result }) + : OEmbedOperationStatusResult(result.Status); + } +} diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json index d2266c3078..fbdac267e7 100644 --- a/src/Umbraco.Cms.Api.Management/OpenApi.json +++ b/src/Umbraco.Cms.Api.Management/OpenApi.json @@ -8331,6 +8331,20 @@ } ], "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/PublicAccessResponseModel" + } + ] + } + } + } + }, "404": { "description": "Not Found", "content": { @@ -19479,6 +19493,67 @@ ] } }, + "/umbraco/management/api/v1/oembed/query": { + "get": { + "tags": [ + "oEmbed" + ], + "operationId": "GetOembedQuery", + "parameters": [ + { + "name": "url", + "in": "query", + "schema": { + "type": "string", + "format": "uri" + } + }, + { + "name": "maxWidth", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/OEmbedResponseModel" + } + ] + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + }, + "403": { + "description": "The authenticated user do not have access to this resource" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, "/umbraco/management/api/v1/package/{name}/run-migration": { "post": { "tags": [ @@ -38540,6 +38615,18 @@ }, "additionalProperties": false }, + "OEmbedResponseModel": { + "required": [ + "markup" + ], + "type": "object", + "properties": { + "markup": { + "type": "string" + } + }, + "additionalProperties": false + }, "ObjectTypeResponseModel": { "required": [ "id" @@ -40394,6 +40481,52 @@ }, "additionalProperties": false }, + "PublicAccessResponseModel": { + "required": [ + "errorDocument", + "groups", + "loginDocument", + "members" + ], + "type": "object", + "properties": { + "loginDocument": { + "oneOf": [ + { + "$ref": "#/components/schemas/ReferenceByIdModel" + } + ] + }, + "errorDocument": { + "oneOf": [ + { + "$ref": "#/components/schemas/ReferenceByIdModel" + } + ] + }, + "members": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/MemberItemResponseModel" + } + ] + } + }, + "groups": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/MemberGroupItemResponseModel" + } + ] + } + } + }, + "additionalProperties": false + }, "PublishDocumentRequestModel": { "required": [ "publishSchedules" diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/OEmbed/OEmbedResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/OEmbed/OEmbedResponseModel.cs new file mode 100644 index 0000000000..2556ab896f --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/OEmbed/OEmbedResponseModel.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.OEmbed; + +public class OEmbedResponseModel +{ + public required string Markup { get; set; } +} diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index 1d2c08e790..d4caf17c22 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -323,6 +323,7 @@ namespace Umbraco.Cms.Core.DependencyInjection Services.AddUnique(); Services.AddUnique(); Services.AddUnique(); + Services.AddUnique(); Services.AddUnique(); Services.AddUnique(); Services.AddUnique(); diff --git a/src/Umbraco.Core/Media/EmbedProviders/DailyMotion.cs b/src/Umbraco.Core/Media/EmbedProviders/DailyMotion.cs index 3953fdd2b7..9c75db64e2 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/DailyMotion.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/DailyMotion.cs @@ -23,11 +23,17 @@ public class DailyMotion : OEmbedProviderBase { "format", "xml" }, }; - public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + public override async Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) { var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - XmlDocument xmlDocument = base.GetXmlResponse(requestUrl); + XmlDocument xmlDocument = await base.GetXmlResponseAsync(requestUrl, cancellationToken); return GetXmlProperty(xmlDocument, "/oembed/html"); } + + [Obsolete("Use GetMarkupAsync instead. This will be removed in Umbraco 15.")] + public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + return GeOEmbedDataAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); + } } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Flickr.cs b/src/Umbraco.Core/Media/EmbedProviders/Flickr.cs index 7738981fe9..6838c0f73f 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Flickr.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Flickr.cs @@ -20,10 +20,16 @@ public class Flickr : OEmbedProviderBase public override Dictionary RequestParams => new(); - public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + [Obsolete("Use GetMarkupAsync instead. This will be removed in Umbraco 15.")] + public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + return GeOEmbedDataAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); + } + + public override async Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) { var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - XmlDocument xmlDocument = base.GetXmlResponse(requestUrl); + XmlDocument xmlDocument = await base.GetXmlResponseAsync(requestUrl, cancellationToken); var imageUrl = GetXmlProperty(xmlDocument, "/oembed/url"); var imageWidth = GetXmlProperty(xmlDocument, "/oembed/width"); diff --git a/src/Umbraco.Core/Media/EmbedProviders/GettyImages.cs b/src/Umbraco.Core/Media/EmbedProviders/GettyImages.cs index 5341580967..02631542d0 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/GettyImages.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/GettyImages.cs @@ -20,10 +20,16 @@ public class GettyImages : OEmbedProviderBase public override Dictionary RequestParams => new(); + [Obsolete("Use GetMarkupAsync instead. This will be removed in Umbraco 15.")] public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + return GeOEmbedDataAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); + } + + public override async Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) { var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - OEmbedResponse? oembed = base.GetJsonResponse(requestUrl); + OEmbedResponse? oembed = await base.GetJsonResponseAsync(requestUrl, cancellationToken); return oembed?.GetHtml(); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Giphy.cs b/src/Umbraco.Core/Media/EmbedProviders/Giphy.cs index a7fde54f18..e9938f3164 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Giphy.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Giphy.cs @@ -18,10 +18,16 @@ public class Giphy : OEmbedProviderBase public override Dictionary RequestParams => new(); + [Obsolete("Use GetMarkupAsync instead. This will be removed in Umbraco 15.")] public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + return GeOEmbedDataAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); + } + + public override async Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) { var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - OEmbedResponse? oembed = base.GetJsonResponse(requestUrl); + OEmbedResponse? oembed = await base.GetJsonResponseAsync(requestUrl, cancellationToken); return oembed?.GetHtml(); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Hulu.cs b/src/Umbraco.Core/Media/EmbedProviders/Hulu.cs index 85c1214fd1..5fdd22c667 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Hulu.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Hulu.cs @@ -18,10 +18,16 @@ public class Hulu : OEmbedProviderBase public override Dictionary RequestParams => new(); + [Obsolete("Use GetMarkupAsync instead. This will be removed in Umbraco 15.")] public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + return GeOEmbedDataAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); + } + + public override async Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) { var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - OEmbedResponse? oembed = base.GetJsonResponse(requestUrl); + OEmbedResponse? oembed = await base.GetJsonResponseAsync(requestUrl, cancellationToken); return oembed?.GetHtml(); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Issuu.cs b/src/Umbraco.Core/Media/EmbedProviders/Issuu.cs index 0fdb92e96f..adeb6b45f8 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Issuu.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Issuu.cs @@ -26,10 +26,16 @@ public class Issuu : OEmbedProviderBase { "format", "xml" }, }; - public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + [Obsolete("Use GetMarkupAsync instead. This will be removed in Umbraco 15.")] + public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + return GeOEmbedDataAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); + } + + public override async Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) { var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - XmlDocument xmlDocument = base.GetXmlResponse(requestUrl); + XmlDocument xmlDocument = await base.GetXmlResponseAsync(requestUrl, cancellationToken); return GetXmlProperty(xmlDocument, "/oembed/html"); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Kickstarter.cs b/src/Umbraco.Core/Media/EmbedProviders/Kickstarter.cs index daf1cc25f9..78c50253ec 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Kickstarter.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Kickstarter.cs @@ -18,10 +18,16 @@ public class Kickstarter : OEmbedProviderBase public override Dictionary RequestParams => new(); + [Obsolete("Use GetMarkupAsync instead. This will be removed in Umbraco 15.")] public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + return GeOEmbedDataAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); + } + + public override async Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) { var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - OEmbedResponse? oembed = base.GetJsonResponse(requestUrl); + OEmbedResponse? oembed = await base.GetJsonResponseAsync(requestUrl, cancellationToken); return oembed?.GetHtml(); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/LottieFiles.cs b/src/Umbraco.Core/Media/EmbedProviders/LottieFiles.cs index 95330a6467..56c2c8a1fd 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/LottieFiles.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/LottieFiles.cs @@ -19,10 +19,16 @@ public class LottieFiles : OEmbedProviderBase public override Dictionary RequestParams => new(); + [Obsolete("Use GetMarkupAsync instead. This will be removed in Umbraco 15.")] public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + return GeOEmbedDataAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); + } + + public override async Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) { var requestUrl = this.GetEmbedProviderUrl(url, maxWidth, maxHeight); - OEmbedResponse? oembed = this.GetJsonResponse(requestUrl); + OEmbedResponse? oembed = await this.GetJsonResponseAsync(requestUrl, cancellationToken); var html = oembed?.GetHtml(); // LottieFiles doesn't seem to support maxwidth and maxheight via oembed diff --git a/src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs b/src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs index e53935f49e..f0b0f3a217 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs @@ -18,8 +18,12 @@ public abstract class OEmbedProviderBase : IEmbedProvider public abstract Dictionary RequestParams { get; } + [Obsolete("Use GetMarkupAsync instead. This will be removed in Umbraco 15.")] public abstract string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0); + public virtual Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) => Task.FromResult(GetMarkup(url, maxWidth ?? 0, maxHeight ?? 0)); + + public virtual string GetEmbedProviderUrl(string url, int? maxWidth, int? maxHeight) => GetEmbedProviderUrl(url, maxWidth ?? 0, maxHeight ?? 0); public virtual string GetEmbedProviderUrl(string url, int maxWidth, int maxHeight) { if (Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute) == false) @@ -50,6 +54,7 @@ public abstract class OEmbedProviderBase : IEmbedProvider return fullUrl.ToString(); } + [Obsolete("Use DownloadResponseAsync instead. This will be removed in Umbraco 15.")] public virtual string DownloadResponse(string url) { if (_httpClient == null) @@ -65,6 +70,22 @@ public abstract class OEmbedProviderBase : IEmbedProvider } } + public virtual async Task DownloadResponseAsync(string url, CancellationToken cancellationToken) + { + if (_httpClient == null) + { + _httpClient = new HttpClient(); + _httpClient.DefaultRequestHeaders.UserAgent.TryParseAdd(Constants.HttpClients.Headers.UserAgentProductName); + } + + using (var request = new HttpRequestMessage(HttpMethod.Get, url)) + { + using HttpResponseMessage response = await _httpClient.SendAsync(request, cancellationToken); + return await response.Content.ReadAsStringAsync(cancellationToken); + } + } + + [Obsolete("Use GetJsonResponseAsync instead. This will be removed in Umbraco 15.")] public virtual T? GetJsonResponse(string url) where T : class { @@ -72,6 +93,23 @@ public abstract class OEmbedProviderBase : IEmbedProvider return _jsonSerializer.Deserialize(response); } + public virtual async Task GetJsonResponseAsync(string url, CancellationToken cancellationToken) + where T : class + { + var response = await DownloadResponseAsync(url, cancellationToken); + return _jsonSerializer.Deserialize(response); + } + + public virtual async Task GetXmlResponseAsync(string url, CancellationToken cancellationToken) + { + var response = await DownloadResponseAsync(url, cancellationToken); + var doc = new XmlDocument(); + doc.LoadXml(response); + + return doc; + } + + [Obsolete("Use GetXmlResponseAsync instead. This will be removed in Umbraco 15.")] public virtual XmlDocument GetXmlResponse(string url) { var response = DownloadResponse(url); diff --git a/src/Umbraco.Core/Media/EmbedProviders/Slideshare.cs b/src/Umbraco.Core/Media/EmbedProviders/Slideshare.cs index 33802fa059..c1b4731bfb 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Slideshare.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Slideshare.cs @@ -19,10 +19,16 @@ public class Slideshare : OEmbedProviderBase public override Dictionary RequestParams => new(); - public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + [Obsolete("Use GetMarkupAsync instead. This will be removed in Umbraco 15.")] + public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + return GeOEmbedDataAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); + } + + public override async Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) { var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - XmlDocument xmlDocument = base.GetXmlResponse(requestUrl); + XmlDocument xmlDocument = await base.GetXmlResponseAsync(requestUrl, cancellationToken); return GetXmlProperty(xmlDocument, "/oembed/html"); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/SoundCloud.cs b/src/Umbraco.Core/Media/EmbedProviders/SoundCloud.cs index 2d59031b63..43092dddaa 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/SoundCloud.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/SoundCloud.cs @@ -19,10 +19,17 @@ public class Soundcloud : OEmbedProviderBase public override Dictionary RequestParams => new(); - public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + + [Obsolete("Use GetMarkupAsync instead. This will be removed in Umbraco 15.")] + public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + return GeOEmbedDataAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); + } + + public override async Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) { var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - XmlDocument xmlDocument = base.GetXmlResponse(requestUrl); + XmlDocument xmlDocument = await base.GetXmlResponseAsync(requestUrl, cancellationToken); return GetXmlProperty(xmlDocument, "/oembed/html"); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Ted.cs b/src/Umbraco.Core/Media/EmbedProviders/Ted.cs index 5fcc7fcb42..8816382cf8 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Ted.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Ted.cs @@ -19,11 +19,18 @@ public class Ted : OEmbedProviderBase public override Dictionary RequestParams => new(); - public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + [Obsolete("Use GetMarkupAsync instead. This will be removed in Umbraco 15.")] + public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + return GeOEmbedDataAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); + } + + public override async Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) { var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - XmlDocument xmlDocument = base.GetXmlResponse(requestUrl); + XmlDocument xmlDocument = await base.GetXmlResponseAsync(requestUrl, cancellationToken); return GetXmlProperty(xmlDocument, "/oembed/html"); } + } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Twitter.cs b/src/Umbraco.Core/Media/EmbedProviders/Twitter.cs index 81aeb36491..495d74d3e7 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Twitter.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Twitter.cs @@ -18,10 +18,16 @@ public class Twitter : OEmbedProviderBase public override Dictionary RequestParams => new(); + [Obsolete("Use GetMarkupAsync instead. This will be removed in Umbraco 15.")] public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + return GeOEmbedDataAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); + } + + public override async Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) { var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - OEmbedResponse? oembed = base.GetJsonResponse(requestUrl); + OEmbedResponse? oembed = await base.GetJsonResponseAsync(requestUrl, cancellationToken); return oembed?.GetHtml(); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Vimeo.cs b/src/Umbraco.Core/Media/EmbedProviders/Vimeo.cs index e4d19d463a..f4db5b17c9 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Vimeo.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Vimeo.cs @@ -19,10 +19,16 @@ public class Vimeo : OEmbedProviderBase public override Dictionary RequestParams => new(); - public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + [Obsolete("Use GetMarkupAsync instead. This will be removed in Umbraco 15.")] + public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + return GeOEmbedDataAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); + } + + public override async Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) { var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - XmlDocument xmlDocument = base.GetXmlResponse(requestUrl); + XmlDocument xmlDocument = await base.GetXmlResponseAsync(requestUrl, cancellationToken); return GetXmlProperty(xmlDocument, "/oembed/html"); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Youtube.cs b/src/Umbraco.Core/Media/EmbedProviders/Youtube.cs index 2e6f29ff37..edaf37c0b0 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Youtube.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Youtube.cs @@ -22,10 +22,16 @@ public class YouTube : OEmbedProviderBase { "format", "json" }, }; + [Obsolete("Use GetMarkupAsync instead. This will be removed in Umbraco 15.")] public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + return GeOEmbedDataAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); + } + + public override async Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) { var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - OEmbedResponse? oembed = base.GetJsonResponse(requestUrl); + OEmbedResponse? oembed = await base.GetJsonResponseAsync(requestUrl, cancellationToken); return oembed?.GetHtml(); } diff --git a/src/Umbraco.Core/Media/IEmbedProvider.cs b/src/Umbraco.Core/Media/IEmbedProvider.cs index 06eb547365..fb27b4bb9b 100644 --- a/src/Umbraco.Core/Media/IEmbedProvider.cs +++ b/src/Umbraco.Core/Media/IEmbedProvider.cs @@ -18,5 +18,7 @@ public interface IEmbedProvider /// ?key=value&key2=value2 Dictionary RequestParams { get; } + [Obsolete("Use GetMarkupAsync instead. This will be removed in Umbraco 15.")] string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0); + Task GetMarkupAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) => Task.FromResult(GetMarkup(url, maxWidth ?? 0, maxHeight ?? 0)); } diff --git a/src/Umbraco.Core/Media/OEmbedResult.cs b/src/Umbraco.Core/Media/OEmbedResult.cs deleted file mode 100644 index 3e4834521d..0000000000 --- a/src/Umbraco.Core/Media/OEmbedResult.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Umbraco.Cms.Core.Media; - -public class OEmbedResult -{ - public OEmbedStatus OEmbedStatus { get; set; } - - public bool SupportsDimensions { get; set; } - - public string? Markup { get; set; } -} diff --git a/src/Umbraco.Core/Media/OEmbedStatus.cs b/src/Umbraco.Core/Media/OEmbedStatus.cs deleted file mode 100644 index 1903643d5e..0000000000 --- a/src/Umbraco.Core/Media/OEmbedStatus.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Umbraco.Cms.Core.Media; - -public enum OEmbedStatus -{ - NotSupported, - Error, - Success, -} diff --git a/src/Umbraco.Core/Services/IOEmbedService.cs b/src/Umbraco.Core/Services/IOEmbedService.cs new file mode 100644 index 0000000000..8dcc3979fa --- /dev/null +++ b/src/Umbraco.Core/Services/IOEmbedService.cs @@ -0,0 +1,8 @@ +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Core.Services; + +public interface IOEmbedService +{ + Task> GetMarkupAsync(Uri url, int? width, int? height, CancellationToken cancellationToken); +} diff --git a/src/Umbraco.Core/Services/OEmbedService.cs b/src/Umbraco.Core/Services/OEmbedService.cs new file mode 100644 index 0000000000..dfa164ec82 --- /dev/null +++ b/src/Umbraco.Core/Services/OEmbedService.cs @@ -0,0 +1,48 @@ +using System.Text.RegularExpressions; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Media; +using Umbraco.Cms.Core.Media.EmbedProviders; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Core.Services; + +public class OEmbedService : IOEmbedService +{ + private readonly EmbedProvidersCollection _embedProvidersCollection; + private readonly ILogger _logger; + + public OEmbedService(EmbedProvidersCollection embedProvidersCollection, ILogger logger) + { + _embedProvidersCollection = embedProvidersCollection; + _logger = logger; + } + + public async Task> GetMarkupAsync(Uri url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) + { + // Find the first provider that supports the URL + IEmbedProvider? matchedProvider = _embedProvidersCollection + .FirstOrDefault(provider => provider.UrlSchemeRegex.Any(regex=>new Regex(regex, RegexOptions.IgnoreCase).IsMatch(url.OriginalString))); + + if (matchedProvider is null) + { + return Attempt.FailWithStatus(OEmbedOperationStatus.NoSupportedProvider, string.Empty); + } + + try + { + var result = await matchedProvider.GetMarkupAsync(url.OriginalString, maxWidth, maxHeight, cancellationToken); + + if (result is not null) + { + return Attempt.SucceedWithStatus(OEmbedOperationStatus.Success, result); + } + } + catch (Exception e) + { + _logger.LogError(e, "Unexpected exception happened while trying to get oembed markup. Provider: {Provider}",matchedProvider.GetType().Name); + Attempt.FailWithStatus(OEmbedOperationStatus.UnexpectedException, string.Empty, e); + } + + return Attempt.FailWithStatus(OEmbedOperationStatus.ProviderReturnedInvalidResult, string.Empty); + } +} diff --git a/src/Umbraco.Core/Services/OperationStatus/OEmbedOperationStatus.cs b/src/Umbraco.Core/Services/OperationStatus/OEmbedOperationStatus.cs new file mode 100644 index 0000000000..0681eb7768 --- /dev/null +++ b/src/Umbraco.Core/Services/OperationStatus/OEmbedOperationStatus.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Cms.Core.Services.OperationStatus; + +public enum OEmbedOperationStatus +{ + Success, + NoSupportedProvider, + ProviderReturnedInvalidResult, + UnexpectedException +}