11using System . Collections ;
2-
2+ using System . Diagnostics ;
33using CMS . ContentEngine ;
44using CMS . ContentEngine . Internal ;
55using CMS . DataEngine ;
1212
1313using Microsoft . Data . SqlClient ;
1414using Microsoft . Extensions . Logging ;
15-
1615using Migration . Tool . Common ;
1716using Migration . Tool . Common . Abstractions ;
17+ using Migration . Tool . Common . Builders ;
1818using Migration . Tool . Common . Helpers ;
19+ using Migration . Tool . KXP . Api ;
1920using Migration . Tool . Source . Mappers ;
2021using Migration . Tool . Source . Model ;
22+ using Migration . Tool . Source . Providers ;
2123using Migration . Tool . Source . Services ;
2224
2325using Newtonsoft . Json ;
@@ -30,7 +32,9 @@ public class MigrateCategoriesCommandHandler(
3032 IImporter importer ,
3133 ReusableSchemaService reusableSchemaService ,
3234 IUmtMapper < TagModelSource > tagModelMapper ,
33- SpoiledGuidContext spoiledGuidContext
35+ SpoiledGuidContext spoiledGuidContext ,
36+ KxpClassFacade kxpClassFacade ,
37+ ClassMappingProvider classMappingProvider
3438) : IRequestHandler < MigrateCategoriesCommand , CommandResult >
3539{
3640 public async Task < CommandResult > Handle ( MigrateCategoriesCommand request , CancellationToken cancellationToken )
@@ -48,26 +52,42 @@ public async Task<CommandResult> Handle(MigrateCategoriesCommand request, Cancel
4852 if ( result . Imported is TaxonomyInfo taxonomy )
4953 {
5054 string query = """
51- SELECT C.ClassName, C.ClassGuid, C.ClassID
55+ SELECT C.ClassName, C.ClassGuid, C.ClassID, CC.CategoryID
5256 FROM View_CMS_Tree_Joined [TJ]
5357 JOIN dbo.CMS_DocumentCategory [CDC] on [TJ].DocumentID = [CDC].DocumentID
5458 JOIN CMS_Class [C] ON TJ.NodeClassID = [C].ClassID
5559 JOIN dbo.CMS_Category CC on CDC.CategoryID = CC.CategoryID AND CC.CategoryUserID IS NULL
56- GROUP BY C.ClassName, C.ClassGuid, C.ClassID
60+ GROUP BY C.ClassName, C.ClassGuid, C.ClassID, CC.CategoryID
5761 """ ;
5862
59- var classesWithCategories = modelFacade . Select ( query , ( reader , version ) => new { ClassName = reader . Unbox < string > ( "ClassName" ) , ClassGuid = reader . Unbox < Guid > ( "ClassGuid" ) , ClassID = reader . Unbox < int > ( "ClassID" ) } ) ;
63+ var classesWithCategories = modelFacade . Select ( query , ( reader , version ) => new { ClassName = reader . Unbox < string > ( "ClassName" ) , ClassGuid = reader . Unbox < Guid > ( "ClassGuid" ) , ClassID = reader . Unbox < int > ( "ClassID" ) , CategoryID = reader . Unbox < int > ( "CategoryID" ) } )
64+ . GroupBy ( x => x . ClassGuid )
65+ . Select ( x => new { ClassGuid = x . Key , x . First ( ) . ClassName , x . First ( ) . ClassID , Categories = x . Select ( row => row . CategoryID ) } ) ;
6066
67+ // For each source instance class whose documents have some categories assigned, include taxonomy-storage reusable schema in the target class
68+ #region Ensure reusable schema
6169 var skippedClasses = new List < int > ( ) ;
6270 var schemaGuid = Guid . Empty ;
6371 string categoryFieldName = "Category_Legacy" ;
6472 foreach ( var classWithCategoryUsage in classesWithCategories )
6573 {
6674 var targetDataClass = DataClassInfoProvider . ProviderObject . Get ( classWithCategoryUsage . ClassGuid ) ;
67- if ( targetDataClass == null )
75+ if ( targetDataClass is null )
6876 {
69- skippedClasses . Add ( classWithCategoryUsage . ClassID ) ;
70- logger . LogWarning ( "Data class not found by ClassGuid {Guid}" , classWithCategoryUsage . ClassGuid ) ;
77+ // No direct-mapped target class found. Try to identify custom-mapped target class
78+ var classMapping = classMappingProvider . GetMapping ( classWithCategoryUsage . ClassName ) ;
79+ if ( classMapping is not null )
80+ {
81+ if ( classWithCategoryUsage . Categories . Any ( cat => classMapping . IsCategoryMapped ( classWithCategoryUsage . ClassName , cat ) ) )
82+ {
83+ targetDataClass = kxpClassFacade . GetClass ( classMapping . TargetClassName ) ;
84+ }
85+ }
86+ }
87+
88+ if ( targetDataClass is null )
89+ {
90+ logger . LogWarning ( $ "Class(ClassGuid {{Guid}}) has documents with categories, but no directly-mapped data class nor custom-mapped class that would receive the categories (declared via { nameof ( IClassMapping . IsCategoryMapped ) } ) was found", classWithCategoryUsage . ClassGuid ) ;
7191 continue ;
7292 }
7393
@@ -82,6 +102,8 @@ FROM View_CMS_Tree_Joined [TJ]
82102 DataClassInfoProvider . SetDataClassInfo ( targetDataClass ) ;
83103 }
84104 }
105+ #endregion
106+
85107
86108 var categories = modelFacade . Select < ICmsCategory > (
87109 "CategoryEnabled = 1 AND CategoryUserID IS NULL" ,
@@ -95,23 +117,24 @@ FROM View_CMS_Tree_Joined [TJ]
95117 categoryId2Guid . Add ( cmsCategory . CategoryID , cmsCategory . CategoryGUID ) ;
96118 // CategorySiteID - not migrated, Taxonomies are global!
97119
98- var mapped = tagModelMapper . Map ( new TagModelSource (
120+ var tagUMTModels = tagModelMapper . Map ( new TagModelSource (
99121 taxonomy . TaxonomyGUID ,
100122 cmsCategory ,
101123 categoryId2Guid
102124 ) ) ;
103125
104- foreach ( var umtModel in mapped )
126+ foreach ( var tagUMTModel in tagUMTModels )
105127 {
106128 if ( await importer
107- . ImportAsync ( umtModel )
129+ . ImportAsync ( tagUMTModel )
108130 . AssertSuccess < TagInfo > ( logger ) is { Success : true , Info : { } tag } )
109131 {
110132 query = """
111- SELECT TJ.DocumentGUID, TJ.NodeSiteID, TJ.NodeID, TJ.DocumentID, CDC.CategoryID, TJ.DocumentCheckedOutVersionHistoryID, TJ.NodeClassID
133+ SELECT TJ.DocumentGUID, TJ.NodeSiteID, TJ.NodeID, TJ.DocumentID, CDC.CategoryID, TJ.DocumentCheckedOutVersionHistoryID, TJ.NodeClassID, C.ClassName
112134 FROM View_CMS_Tree_Joined [TJ]
113135 JOIN dbo.CMS_DocumentCategory [CDC] on [TJ].DocumentID = [CDC].DocumentID
114136 JOIN dbo.CMS_Category CC on CDC.CategoryID = CC.CategoryID AND CC.CategoryUserID IS NULL
137+ JOIN CMS_Class [C] ON TJ.NodeClassID = [C].ClassID
115138 WHERE CDC.CategoryID = @categoryId
116139 """ ;
117140
@@ -120,6 +143,7 @@ FROM View_CMS_Tree_Joined [TJ]
120143 CategoryID = reader . Unbox < int ? > ( "CategoryID" ) ,
121144 DocumentCheckedOutVersionHistoryID = reader . Unbox < int ? > ( "DocumentCheckedOutVersionHistoryID" ) ,
122145 NodeClassID = reader . Unbox < int > ( "NodeClassID" ) ,
146+ NodeClassName = reader . Unbox < string > ( "ClassName" ) ,
123147 NodeSiteID = reader . Unbox < int > ( "NodeSiteID" ) ,
124148 DocumentGUID = spoiledGuidContext . EnsureDocumentGuid (
125149 reader . Unbox < Guid > ( "DocumentGUID" ) ,
@@ -137,13 +161,23 @@ FROM View_CMS_Tree_Joined [TJ]
137161 continue ;
138162 }
139163
164+ var classMapping = classMappingProvider . GetMapping ( dwc . NodeClassName ) ;
165+ if ( classMapping is not null )
166+ {
167+ Debug . Assert ( dwc . CategoryID . HasValue , "dwc.CategoryID should have value, otherwise the row would not be included in the query due to inner join" ) ;
168+ if ( ! classMapping . IsCategoryMapped ( dwc . NodeClassName , dwc . CategoryID . Value ) )
169+ {
170+ continue ;
171+ }
172+ }
173+
140174 var commonData = ContentItemCommonDataInfo . Provider . Get ( )
141175 . WhereEquals ( nameof ( ContentItemCommonDataInfo . ContentItemCommonDataGUID ) , dwc . DocumentGUID )
142176 . FirstOrDefault ( ) ;
143177
144178 if ( commonData is null )
145179 {
146- logger . LogWarning ( "ContentItemCommonDataInfo not found by guid {Guid}, taxonomy cannot be migrated" , dwc . DocumentGUID ) ;
180+ logger . LogWarning ( "ContentItemCommonDataInfo not found by Guid {Guid}. Taxonomy cannot be migrated" , dwc . DocumentGUID ) ;
147181 continue ;
148182 }
149183
0 commit comments