Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Media file & attachment url replacement in Rich Text Editor values + Direct media path support #254

Merged
merged 4 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions KVA/Migration.Toolkit.Source/Helpers/MediaHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using Microsoft.Data.SqlClient;
using Migration.Toolkit.Common.Helpers;
using Migration.Toolkit.Source.Model;

namespace Migration.Toolkit.Source.Helpers;

public static class MediaHelper
{
public static IMediaFile? GetMediaFile(MatchMediaLinkResult matchResult, ModelFacade modelFacade)
{
switch (matchResult)
{
case { Success: true, LinkKind: MediaLinkKind.Guid, MediaGuid: var mediaGuid, MediaKind: MediaKind.MediaFile, LinkSiteId: var linkSiteId }:
{
var mediaFile = modelFacade.SelectWhere<IMediaFile>("FileGUID = @mediaFileGuid AND FileSiteID = @fileSiteID",
new SqlParameter("mediaFileGuid", mediaGuid),
new SqlParameter("fileSiteID", linkSiteId)
)
.FirstOrDefault();

return mediaFile;
}
case { Success: true, LinkKind: MediaLinkKind.DirectMediaPath, LibraryDir: var libraryDir, Path: var path, LinkSiteId: var linkSiteId }:
{
if (path == null)
{
throw new InvalidOperationException($"Cannot determine media file for link match {matchResult}");
}

var mediaLibraries = modelFacade.SelectWhere<IMediaLibrary>(
"LibraryUseDirectPathForContent = 1 AND LibraryFolder = @libraryFolder AND LibrarySiteID = @librarySiteID",
new SqlParameter("libraryFolder", libraryDir),
new SqlParameter("librarySiteID", linkSiteId)
).ToList();
switch (mediaLibraries)
{
case [var mediaLibrary]:
{
string filePath = path.Replace($"/{mediaLibrary.LibraryFolder}/", "", StringComparison.InvariantCultureIgnoreCase);
return modelFacade.SelectWhere<IMediaFile>("FileLibraryID = @fileLibraryID AND FilePath = @filePath AND FileSiteID = @fileSiteID",
new SqlParameter("fileLibraryID", mediaLibrary.LibraryID),
new SqlParameter("filePath", filePath),
new SqlParameter("fileSiteID", linkSiteId))
.ToList() switch
{
[var mediaFile] => mediaFile,
{ Count: > 1 } => throw new InvalidOperationException($"Multiple media file were found for path {path}, site {linkSiteId} and library {libraryDir}"),
{ Count: 0 } =>
// this may happen and is valid scenaria
null,
_ => null
};
}
case { Count: > 1 }:
{
break;
}
case { Count: 0 }:
default:
{
break;
}
}

return null;
}
default:
{
return null;
}
}
}

public static ICmsAttachment? GetAttachment(MatchMediaLinkResult matchResult, ModelFacade modelFacade) =>
modelFacade.SelectWhere<ICmsAttachment>("AttachmentSiteID = @attachmentSiteID AND AttachmentGUID = @attachmentGUID",
new SqlParameter("attachmentSiteID", matchResult.LinkSiteId),
new SqlParameter("attachmentGUID", matchResult.MediaGuid)
)
.FirstOrDefault();
}
1 change: 1 addition & 0 deletions KVA/Migration.Toolkit.Source/KsCoreDiExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public static IServiceCollection UseKsToolkitCore(this IServiceCollection servic
services.AddSingleton<EntityIdentityFacade>();
services.AddSingleton<IdentityLocator>();
services.AddSingleton<IAssetFacade, AssetFacade>();
services.AddSingleton<MediaLinkServiceFactory>();

services.AddTransient<BulkDataCopyService>();
services.AddTransient<CmsRelationshipService>();
Expand Down
62 changes: 60 additions & 2 deletions KVA/Migration.Toolkit.Source/Mappers/ContentItemMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
using Migration.Toolkit.KXP.Api.Services.CmsClass;
using Migration.Toolkit.Source.Auxiliary;
using Migration.Toolkit.Source.Contexts;
using Migration.Toolkit.Source.Helpers;
using Migration.Toolkit.Source.Model;
using Migration.Toolkit.Source.Services;
using Migration.Toolkit.Source.Services.Model;
Expand Down Expand Up @@ -56,7 +57,8 @@ public class ContentItemMapper(
SpoiledGuidContext spoiledGuidContext,
EntityIdentityFacade entityIdentityFacade,
IAssetFacade assetFacade,
ToolkitConfiguration configuration
ToolkitConfiguration configuration,
MediaLinkServiceFactory mediaLinkServiceFactory
) : UmtMapperBase<CmsTreeMapperSource>
{
private const string CLASS_FIELD_CONTROL_NAME = "controlname";
Expand Down Expand Up @@ -627,6 +629,62 @@ ICmsSite site
{
target[columnName] = value;
}


var newField = newFormInfo.GetFormField(columnName);
if (newField == null)
{
var commonFields = UnpackReusableFieldSchemas(newFormInfo.GetFields<FormSchemaInfo>()).ToArray();
newField = commonFields
.FirstOrDefault(cf => ReusableSchemaService.RemoveClassPrefix(nodeClass.ClassName, cf.Name).Equals(columnName, StringComparison.InvariantCultureIgnoreCase));
}
string? newControlName = newField?.Settings[CLASS_FIELD_CONTROL_NAME]?.ToString()?.ToLowerInvariant();
if (newControlName?.Equals(FormComponents.AdminRichTextEditorComponent, StringComparison.InvariantCultureIgnoreCase) == true && target[columnName] is string { } html && !string.IsNullOrWhiteSpace(html) &&
!configuration.MigrateMediaToMediaLibrary)
{
var mediaLinkService = mediaLinkServiceFactory.Create();
var htmlProcessor = new HtmlProcessor(html, mediaLinkService);

target[columnName] = await htmlProcessor.ProcessHtml(site.SiteID, async (result, original) =>
{
switch (result)
{
case { LinkKind: MediaLinkKind.Guid or MediaLinkKind.DirectMediaPath, MediaKind: MediaKind.MediaFile }:
{
var mediaFile = MediaHelper.GetMediaFile(result, modelFacade);
if (mediaFile is null)
{
return original;
}

return assetFacade.GetAssetUri(mediaFile);
}
case { LinkKind: MediaLinkKind.Guid, MediaKind: MediaKind.Attachment, MediaGuid: { } mediaGuid, LinkSiteId: var linkSiteId }:
{
var attachment = MediaHelper.GetAttachment(result, modelFacade);
if (attachment is null)
{
return original;
}

await attachmentMigrator.MigrateAttachment(attachment);

string? culture = null;
if (attachment.AttachmentDocumentID is { } attachmentDocumentId)
{
culture = modelFacade.SelectById<ICmsDocument>(attachmentDocumentId)?.DocumentCulture;
}

return assetFacade.GetAssetUri(attachment, culture);
}

default:
break;
}

return original;
});
}
}
}

Expand All @@ -635,7 +693,7 @@ private async Task ConvertToAsset(Dictionary<string, object?> target, ICmsTree c
List<object> mfis = [];
bool hasMigratedAsset = false;
if (value is string link &&
MediaHelper.MatchMediaLink(link) is (true, var mediaLinkKind, var mediaKind, var path, var mediaGuid) result)
mediaLinkServiceFactory.Create().MatchMediaLink(link, site.SiteID) is (true, var mediaLinkKind, var mediaKind, var path, var mediaGuid, var linkSiteId, var libraryDir) result)
{
if (mediaLinkKind == MediaLinkKind.Path)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ private void WalkProperties(int siteId, JObject properties, List<EditingFormCont
var nv = new List<object>();
foreach (var asi in items)
{
var attachment = modelFacade.SelectWhere<ICmsAttachment>("AttachmentSiteID = @siteId AND AttachmentGUID = @attachmentGUID", new SqlParameter("attachmentSiteID", siteId),
var attachment = modelFacade.SelectWhere<ICmsAttachment>("AttachmentSiteID = @attachmentSiteID AND AttachmentGUID = @attachmentGUID", new SqlParameter("attachmentSiteID", siteId),
new SqlParameter("attachmentGUID", asi.FileGuid))
.FirstOrDefault();
if (attachment != null)
Expand Down
112 changes: 81 additions & 31 deletions KVA/Migration.Toolkit.Source/Services/AssetFacade.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,12 @@ public interface IAssetFacade
/// <param name="attachment"></param>
/// <param name="contentLanguageName"></param>
/// <returns></returns>
(Guid ownerContentItemGuid, Guid assetGuid) GetRef(ICmsAttachment attachment, string? contentLanguageName = null);
(Guid ownerContentItemGuid, Guid assetGuid) GetRef(ICmsAttachment attachment, string? contentLanguageName);

Task PreparePrerequisites();

string GetAssetUri(IMediaFile mediaFile, string? contentLanguageName = null);
string GetAssetUri(ICmsAttachment attachment, string? contentLanguageName);
}

public class AssetFacade(
Expand Down Expand Up @@ -81,6 +84,10 @@ public async Task<ContentItemSimplifiedModel> FromMediaFile(IMediaFile mediaFile
Debug.Assert(mediaLibrary.LibrarySiteID == site.SiteID, "mediaLibrary.LibrarySiteID == site.SiteID");

string? mediaLibraryAbsolutePath = GetMediaLibraryAbsolutePath(toolkitConfiguration, site, mediaLibrary, modelFacade);
if (toolkitConfiguration.MigrateOnlyMediaFileInfo.GetValueOrDefault(false))
{
throw new InvalidOperationException($"Configuration 'Settings.MigrateOnlyMediaFileInfo' is set to to 'true', for migration of media files to content items this setting is required to be 'false'");
}
if (string.IsNullOrWhiteSpace(mediaLibraryAbsolutePath))
{
throw new InvalidOperationException($"Invalid media file path generated for {mediaFile} and {mediaLibrary} on {site}");
Expand Down Expand Up @@ -185,7 +192,7 @@ public async Task<ContentItemSimplifiedModel> FromAttachment(ICmsAttachment atta
ContentItemGUID = translatedAttachmentGuid,
ContentItemContentFolderGUID = (await EnsureFolderStructure(mediaFolder, folder))?.ContentFolderGUID ?? folder.ContentFolderGUID,
IsSecured = null,
ContentTypeName = LegacyMediaFileContentType.ClassName,
ContentTypeName = LegacyAttachmentContentType.ClassName,
Name = $"{attachment.AttachmentGUID}_{translatedAttachmentGuid}",
IsReusable = true,
LanguageData = languageData,
Expand Down Expand Up @@ -294,7 +301,7 @@ public async Task<ContentItemSimplifiedModel> FromAttachment(ICmsAttachment atta
}

/// <inheritdoc />
public (Guid ownerContentItemGuid, Guid assetGuid) GetRef(ICmsAttachment attachment, string? contentLanguageName = null)
public (Guid ownerContentItemGuid, Guid assetGuid) GetRef(ICmsAttachment attachment, string? contentLanguageName)
{
var (_, translatedAttachmentGuid) = entityIdentityFacade.Translate(attachment);
return (translatedAttachmentGuid, GuidHelper.CreateAssetGuid(translatedAttachmentGuid, contentLanguageName ?? DefaultContentLanguage));
Expand All @@ -310,6 +317,19 @@ public async Task PreparePrerequisites()
}
}

public string GetAssetUri(IMediaFile mediaFile, string? contentLanguageName = null)
{
string contentLanguageSafe = contentLanguageName ?? DefaultContentLanguage;
var (ownerContentItemGuid, _) = GetRef(mediaFile, contentLanguageName);
return $"/getContentAsset/{ownerContentItemGuid}/{LegacyMediaFileAssetField.Guid}/{mediaFile.FileName}?language={contentLanguageSafe}";
}

public string GetAssetUri(ICmsAttachment attachment, string? contentLanguageName)
{
var (ownerContentItemGuid, _) = GetRef(attachment, contentLanguageName ?? DefaultContentLanguage);
return $"/getContentAsset/{ownerContentItemGuid}/{LegacyAttachmentAssetField.Guid}/{attachment.AttachmentName}?language={contentLanguageName ?? DefaultContentLanguage}";
}

private void AssertSuccess(IImportResult importResult, IUmtModel model)
{
switch (importResult)
Expand Down Expand Up @@ -339,6 +359,17 @@ private void AssertSuccess(IImportResult importResult, IUmtModel model)
}
}

internal static readonly FormField LegacyMediaFileAssetField = new()
{
Column = "Asset",
ColumnType = "contentitemasset",
AllowEmpty = true,
Visible = true,
Enabled = true,
Guid = new Guid("DFC3D011-8F63-43F6-9ED8-4B444333A1D0"),
Properties = new FormFieldProperties { FieldCaption = "Asset", },
Settings = new FormFieldSettings { CustomProperties = new Dictionary<string, object?> { { "AllowedExtensions", "_INHERITED_" } }, ControlName = "Kentico.Administration.ContentItemAssetUploader" }
};
internal static readonly DataClassModel LegacyMediaFileContentType = new()
{
ClassName = "Legacy.MediaFile",
Expand All @@ -352,20 +383,21 @@ private void AssertSuccess(IImportResult importResult, IUmtModel model)
ClassWebPageHasUrl = false,
Fields =
[
new()
{
Column = "Asset",
ColumnType = "contentitemasset",
AllowEmpty = true,
Visible = true,
Enabled = true,
Guid = new Guid("DFC3D011-8F63-43F6-9ED8-4B444333A1D0"),
Properties = new FormFieldProperties { FieldCaption = "Asset", },
Settings = new FormFieldSettings { CustomProperties = new Dictionary<string, object?> { { "AllowedExtensions", "_INHERITED_" } }, ControlName = "Kentico.Administration.ContentItemAssetUploader" }
}
LegacyMediaFileAssetField
]
};

internal static readonly FormField LegacyAttachmentAssetField = new()
{
Column = "Asset",
ColumnType = "contentitemasset",
AllowEmpty = true,
Visible = true,
Enabled = true,
Guid = new Guid("50C2BC4C-A8FF-46BA-95C2-0E74752D147F"),
Properties = new FormFieldProperties { FieldCaption = "Asset", },
Settings = new FormFieldSettings { CustomProperties = new Dictionary<string, object?> { { "AllowedExtensions", "_INHERITED_" } }, ControlName = "Kentico.Administration.ContentItemAssetUploader" }
};
internal static readonly DataClassModel LegacyAttachmentContentType = new()
{
ClassName = "Legacy.Attachment",
Expand All @@ -379,17 +411,7 @@ private void AssertSuccess(IImportResult importResult, IUmtModel model)
ClassWebPageHasUrl = false,
Fields =
[
new()
{
Column = "Asset",
ColumnType = "contentitemasset",
AllowEmpty = true,
Visible = true,
Enabled = true,
Guid = new Guid("50C2BC4C-A8FF-46BA-95C2-0E74752D147F"),
Properties = new FormFieldProperties { FieldCaption = "Asset", },
Settings = new FormFieldSettings { CustomProperties = new Dictionary<string, object?> { { "AllowedExtensions", "_INHERITED_" } }, ControlName = "Kentico.Administration.ContentItemAssetUploader" }
}
LegacyAttachmentAssetField
]
};

Expand Down Expand Up @@ -417,34 +439,62 @@ internal static ContentFolderModel GetAssetFolder(ICmsSite site)
private const string DirMedia = "media";
public static string? GetMediaLibraryAbsolutePath(ToolkitConfiguration toolkitConfiguration, ICmsSite ksSite, IMediaLibrary ksMediaLibrary, ModelFacade modelFacade)
{
string? cmsMediaLibrariesFolder = KenticoHelper.GetSettingsKey(modelFacade, ksSite.SiteID, "CMSMediaLibrariesFolder");
bool cmsUseMediaLibrariesSiteFolder = !"false".Equals(KenticoHelper.GetSettingsKey(modelFacade, ksSite.SiteID, "CMSUseMediaLibrariesSiteFolder"), StringComparison.InvariantCultureIgnoreCase);

string? sourceMediaLibraryPath = null;
if (!toolkitConfiguration.MigrateOnlyMediaFileInfo.GetValueOrDefault(true) &&
if (!toolkitConfiguration.MigrateOnlyMediaFileInfo.GetValueOrDefault(false) &&
!string.IsNullOrWhiteSpace(toolkitConfiguration.KxCmsDirPath))
{
string? cmsMediaLibrariesFolder = KenticoHelper.GetSettingsKey(modelFacade, ksSite.SiteID, "CMSMediaLibrariesFolder");
var pathParts = new List<string>();
if (cmsMediaLibrariesFolder != null)
{
if (Path.IsPathRooted(cmsMediaLibrariesFolder))
{
sourceMediaLibraryPath = Path.Combine(cmsMediaLibrariesFolder, ksSite.SiteName, ksMediaLibrary.LibraryFolder);
pathParts.Add(cmsMediaLibrariesFolder);
if (cmsUseMediaLibrariesSiteFolder)
{
pathParts.Add(ksSite.SiteName);
}
pathParts.Add(ksMediaLibrary.LibraryFolder);
}
else
{
if (cmsMediaLibrariesFolder.StartsWith("~/"))
{
string cleared = $"{cmsMediaLibrariesFolder[2..]}".Replace("/", "\\");
sourceMediaLibraryPath = Path.Combine(toolkitConfiguration.KxCmsDirPath, cleared, ksSite.SiteName, ksMediaLibrary.LibraryFolder);
pathParts.Add(toolkitConfiguration.KxCmsDirPath);
pathParts.Add(cleared);
if (cmsUseMediaLibrariesSiteFolder)
{
pathParts.Add(ksSite.SiteName);
}
pathParts.Add(ksMediaLibrary.LibraryFolder);
}
else
{
sourceMediaLibraryPath = Path.Combine(toolkitConfiguration.KxCmsDirPath, cmsMediaLibrariesFolder, ksSite.SiteName, ksMediaLibrary.LibraryFolder);
pathParts.Add(toolkitConfiguration.KxCmsDirPath);
pathParts.Add(cmsMediaLibrariesFolder);
if (cmsUseMediaLibrariesSiteFolder)
{
pathParts.Add(ksSite.SiteName);
}
pathParts.Add(ksMediaLibrary.LibraryFolder);
}
}
}
else
{
sourceMediaLibraryPath = Path.Combine(toolkitConfiguration.KxCmsDirPath, ksSite.SiteName, DirMedia, ksMediaLibrary.LibraryFolder);
pathParts.Add(toolkitConfiguration.KxCmsDirPath);
if (cmsUseMediaLibrariesSiteFolder)
{
pathParts.Add(ksSite.SiteName);
}
pathParts.Add(DirMedia);
pathParts.Add(ksMediaLibrary.LibraryFolder);
}

sourceMediaLibraryPath = Path.Combine(pathParts.ToArray());
}

return sourceMediaLibraryPath;
Expand Down
Loading
Loading