Skip to content

Commit 3adfb12

Browse files
authored
Merge pull request #524 from Kentico/feat/media-folder-restrictions
Check XbyK media folder restrictions
2 parents 47d3659 + 7c25f3a commit 3adfb12

File tree

6 files changed

+74
-6
lines changed

6 files changed

+74
-6
lines changed

KVA/Migration.Tool.Source/Services/MediaFileMigrator.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -186,11 +186,7 @@ private void RequireMigratedMediaFiles(List<(IMediaLibrary sourceLibrary, ICmsSi
186186

187187
try
188188
{
189-
if (newInstance)
190-
{
191-
mediaFileFacade.EnsureMediaFilePathExistsInLibrary(mf, targetMediaLibrary.LibraryID);
192-
}
193-
189+
mediaFileFacade.EnsureMediaFilePathExistsInLibrary(mf, targetMediaLibrary.LibraryID);
194190
mediaFileFacade.SetMediaFile(mf, newInstance);
195191

196192
protocol.Success(ksMediaFile, mf, mapped);

Migration.Tool.CLI/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,7 @@ Add the options under the `Settings` section in the configuration file.
430430
| MigrateOnlyMediaFileInfo | If set to `true`, only the database representations of media files are migrated, without the files in the media folder in the project's file system. For example, enable this option if your media library files are mapped to a shared directory or Cloud storage.<br /><br />If `false`, media files are migrated based on the `KxCmsDirPath` location. |
431431
| MigrateMediaToMediaLibrary | Determines whether media library files and attachments from the source instance are migrated to the target instance as media libraries or as [content item assets](https://docs.kentico.com/x/content_item_assets_xp) in the content hub. The default value is `false` – media files and attachments are migrated as content item assets. <br /><br /> See [Convert attachments and media library files to media libraries instead of content item assets](#convert-attachments-and-media-library-files-to-media-libraries-instead-of-content-item-assets) |
432432
| LegacyFlatAssetTree | Use legacy behavior of versions up to 2.3.0. Content folders for asset content items will be created in a flat structure (all under root folder) |
433+
| LegacyPermissiveMediaLibrarySubfolders | XbyK has restrictions on media library subfolder name. It must contain only alphanumeric characters, underscores ('_'), hyphens ('-'), and cannot contain file names reserved by the operating system (e.g., ‘CON’, ‘PRN’, ‘AUX’ for Windows). If set to `true`, name checks will be bypassed. Note that this may impair some XbyK functionality like migration of media libraries to content hub. User may prefer using this option e.g. for repeated migration after initially migrating with previous versions of Migration Tool, and then manually changing the names in XbyK | |
433434
| AssetRootFolders | Dictionary defining the root folder for Asset content items per site: Key is site name (CMS_Site.SiteName). Value is in format _/FolderDisplayName1/FolderDisplayName2/..._ |
434435
| TargetWorkspaceName | The code name of the [workspace](https://docs.kentico.com/x/workspaces_xp) to which content items, content folders and other related entities are migrated. This workspace is used if another workspace is not specified explicitly, for example by the content item director API in [migration customizations](../Migration.Tool.Extensions/README.md). This configuration is not necessary if the target project only contains a single workspace. |
435436
| MemberIncludeUserSystemFields | Determines which system fields from the _CMS_User_ and _CMS_UserSettings_ tables are migrated to _CMS_Member_ in Xperience by Kentico. Fields that do not exist in _CMS_Member_ are automatically created. <br /><br />The sample `appsettings.json` file included with the tool by default includes all user fields that can be migrated from Kentico Xperience 13. Exclude specific fields from the migration by removing them from this configuration option. |

Migration.Tool.CLI/appsettings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"MigrateOnlyMediaFileInfo": false,
2929
"MigrateMediaToMediaLibrary": false,
3030
"LegacyFlatAssetTree": false,
31+
"LegacyPermissiveMediaLibrarySubfolders": false,
3132
"AssetRootFolders": {},
3233
"UseDeprecatedFolderPageType": false,
3334
"ConvertClassesToContentHub": "",

Migration.Tool.Common/ConfigurationNames.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public class ConfigurationNames
2424

2525
public const string MigrateMediaToMediaLibrary = "MigrateMediaToMediaLibrary";
2626
public const string LegacyFlatAssetTree = "LegacyFlatAssetTree";
27+
public const string LegacyPermissiveMediaLibrarySubfolders = "LegacyPermissiveMediaLibrarySubfolders";
2728
public const string AssetRootFolders = "AssetRootFolders";
2829

2930
public const string UseDeprecatedFolderPageType = "UseDeprecatedFolderPageType";

Migration.Tool.Common/ToolConfiguration.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ public class ToolConfiguration
4949
[ConfigurationKeyName(ConfigurationNames.LegacyFlatAssetTree)]
5050
public bool? LegacyFlatAssetTree { get; set; }
5151

52+
[ConfigurationKeyName(ConfigurationNames.LegacyPermissiveMediaLibrarySubfolders)]
53+
public bool? LegacyPermissiveMediaLibrarySubfolders { get; set; }
54+
5255
[ConfigurationKeyName(ConfigurationNames.AssetRootFolders)]
5356
public Dictionary<string, string>? AssetRootFolders { get; set; }
5457

Migration.Tool.KXP.Api/KxpMediaFileFacade.cs

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
using System.Diagnostics;
2+
using System.Runtime.InteropServices;
3+
using System.Text.RegularExpressions;
24
using CMS.DataEngine;
35
using CMS.MediaLibrary;
46
using Microsoft.Extensions.Logging;
7+
using Migration.Tool.Common;
58

69
namespace Migration.Tool.KXP.Api;
710

811
public class KxpMediaFileFacade
912
{
1013
private readonly ILogger<KxpMediaFileFacade> logger;
14+
private readonly ToolConfiguration toolConfiguration;
1115

12-
public KxpMediaFileFacade(ILogger<KxpMediaFileFacade> logger, KxpApiInitializer kxpApiInitializer)
16+
public KxpMediaFileFacade(ILogger<KxpMediaFileFacade> logger, KxpApiInitializer kxpApiInitializer, ToolConfiguration toolConfiguration)
1317
{
1418
this.logger = logger;
19+
this.toolConfiguration = toolConfiguration;
1520
kxpApiInitializer.EnsureApiIsInitialized();
1621
}
1722

@@ -42,11 +47,72 @@ public void SetMediaFile(MediaFileInfo mfi, bool newInstance)
4247
public MediaLibraryInfo GetMediaLibraryInfo(Guid mediaLibraryGuid) => MediaLibraryInfoProvider.ProviderObject.Get(mediaLibraryGuid);
4348
#pragma warning restore CS0618 // Type or member is obsolete
4449

50+
public static bool IsDirectoryNameAllowed(string name)
51+
{
52+
if (string.IsNullOrWhiteSpace(name))
53+
{
54+
return false;
55+
}
56+
57+
if (!name.All(x => char.IsLetterOrDigit(x) || new char[] { '-', '_', System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar }.Contains(x)))
58+
{
59+
return false;
60+
}
61+
62+
// Reserved device names (Windows only)
63+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
64+
{
65+
string[] reserved = {
66+
"CON", "PRN", "AUX", "NUL",
67+
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
68+
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"
69+
};
70+
71+
string upper = name.ToUpperInvariant();
72+
if (reserved.Contains(upper) || reserved.Any(r => upper.StartsWith(r + ".", StringComparison.Ordinal)))
73+
{
74+
return false;
75+
}
76+
}
77+
78+
return true;
79+
}
80+
81+
private readonly HashSet<string> fixedFolderPaths = [];
82+
4583
#pragma warning disable CS0618 // Type or member is obsolete
4684
public void EnsureMediaFilePathExistsInLibrary(MediaFileInfo mfi, int libraryId)
4785
#pragma warning restore CS0618 // Type or member is obsolete
4886
{
4987
string? librarySubDir = System.IO.Path.GetDirectoryName(mfi.FilePath);
88+
if (toolConfiguration.LegacyFlatAssetTree != true)
89+
{
90+
if (!string.IsNullOrEmpty(librarySubDir) && !IsDirectoryNameAllowed(librarySubDir))
91+
{
92+
// Try an automatic correction - replacing stretches of whitespaces with underscore.
93+
// If that succeeds, inform user by a warning. Otherwise raise error and defer fixing the issue to user.
94+
string fixedLibrarySubdir = Regex.Replace(librarySubDir.Trim(), @"\s+", "-");
95+
96+
string bypassMessage = $"If you want to bypass this, use {nameof(toolConfiguration.LegacyPermissiveMediaLibrarySubfolders)} appsettings option. Please refer to README for potential sideeffects.";
97+
98+
if (IsDirectoryNameAllowed(fixedLibrarySubdir))
99+
{
100+
librarySubDir = fixedLibrarySubdir;
101+
mfi.FilePath = $"{librarySubDir}/{System.IO.Path.GetFileName(mfi.FilePath)}"; // Don't use Path.Combine, because it doesn't work correctly with subsequent internal CMS logic
102+
103+
if (!fixedFolderPaths.Contains(librarySubDir))
104+
{
105+
fixedFolderPaths.Add(librarySubDir);
106+
logger.LogWarning($"Media library subfolder '{{MediaLibrarySubfolder}}' was transformed to XbyK allowed format '{{FixedMediaLibrarySubfolder}}'. {bypassMessage}", librarySubDir, fixedLibrarySubdir);
107+
}
108+
}
109+
else
110+
{
111+
throw new InvalidOperationException($"Media library subfolder name {librarySubDir} is not in XbyK allowed format. It must contain only alphanumeric characters, underscores ('_'), hyphens ('-'), and cannot contain file names reserved by the operating system. {bypassMessage}");
112+
}
113+
}
114+
}
115+
50116
#pragma warning disable CS0618 // Type or member is obsolete
51117
MediaLibraryInfoProvider.CreateMediaLibraryFolder(libraryId, $"{librarySubDir}");
52118
#pragma warning restore CS0618 // Type or member is obsolete

0 commit comments

Comments
 (0)