├── .artifactignore ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .globalconfig ├── CONTRIBUTING.md ├── Directory.Build.props ├── Directory.Packages.props ├── LICENSE.md ├── README.md ├── Umbraco.Deploy.Contrib.sln ├── azure-pipelines.yml ├── global.json ├── icon.png ├── nuget.config ├── src └── Umbraco.Deploy.Contrib │ ├── DataTypeConfigurationConnectors │ └── DocTypeGridEditorDataTypeConfigurationConnector.cs │ ├── Extensions │ ├── ArtifactMigratorCollectionBuilderExtensions.cs │ └── ArtifactTypeResolverCollectionBuilderExtensions.cs │ ├── GridCellValueConnectors │ └── DocTypeGridEditorCellValueConnector.cs │ ├── Migrators │ └── Legacy │ │ ├── Content │ │ ├── CheckBoxListPropertyValueArtifactMigrator.cs │ │ ├── DocumentArtifactJsonMigrator.cs │ │ ├── DropDownListFlexiblePropertyValueArtifactMigrator.cs │ │ ├── PrevaluePropertyValueArtifactMigratorBase.cs │ │ └── RadioButtonListPropertyValueArtifactMigrator.cs │ │ ├── ContentType │ │ ├── ContentTypeArtifactJsonMigrator.cs │ │ └── ElementTypeArtifactMigratorBase.cs │ │ └── DataType │ │ ├── CheckBoxListDataTypeArtifactMigrator.cs │ │ ├── ColorPickerAliasDataTypeArtifactMigrator.cs │ │ ├── ContentPicker2DataTypeArtifactMigrator.cs │ │ ├── ContentPickerAliasDataTypeArtifactMigrator.cs │ │ ├── ContentPickerReplaceDataTypeArtifactMigratorBase.cs │ │ ├── DateDataTypeArtifactMigrator.cs │ │ ├── DropDownDataTypeArtifactMigrator.cs │ │ ├── DropDownFlexibleDataTypeArtifactMigrator.cs │ │ ├── DropDownMultipleDataTypeArtifactMigrator.cs │ │ ├── DropDownReplaceDataTypeArtifactMigratorBase.cs │ │ ├── DropdownlistMultiplePublishKeysDataTypeArtifactMigrator.cs │ │ ├── DropdownlistPublishingKeysDataTypeArtifactMigrator.cs │ │ ├── MediaPicker2DataTypeArtifactMigrator.cs │ │ ├── MediaPickerDataTypeArtifactMigrator.cs │ │ ├── MediaPickerReplaceDataTypeArtifactMigratorBase.cs │ │ ├── MemberPicker2DataTypeArtifactMigrator.cs │ │ ├── MultiNodeTreePicker2DataTypeArtifactMigrator.cs │ │ ├── MultiNodeTreePickerDataTypeArtifactMigrator.cs │ │ ├── MultiUrlPickerReplaceDataTypeArtifactMigratorBase.cs │ │ ├── MultipleMediaPickerDataTypeArtifactMigrator.cs │ │ ├── NoEditDataTypeArtifactMigrator.cs │ │ ├── PreValuesDataTypeArtifactJsonMigrator.cs │ │ ├── RadioButtonListDataTypeArtifactMigrator.cs │ │ ├── RelatedLinks2DataTypeArtifactMigrator.cs │ │ ├── RelatedLinksDataTypeArtifactMigrator.cs │ │ ├── TextboxDataTypeArtifactMigrator.cs │ │ ├── TextboxMultipleDataTypeArtifactMigrator.cs │ │ └── TinyMCEv3DataTypeArtifactMigrator.cs │ ├── Serialization │ └── LegacyArtifactTypeResolver.cs │ ├── Umbraco.Deploy.Contrib.csproj │ └── ValueConnectors │ ├── BlockEditorValueConnector.cs │ ├── BlockListValueConnector.cs │ ├── MultiUrlPickerValueConnector.cs │ └── NestedContentValueConnector.cs ├── tests ├── .editorconfig ├── Directory.Build.props ├── Directory.Packages.props ├── Umbraco.Deploy.Contrib.Tests │ └── Umbraco.Deploy.Contrib.Tests.csproj └── codeanalysis.ruleset └── version.json /.artifactignore: -------------------------------------------------------------------------------- 1 | **/* 2 | !tests/*/bin/** 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Version: 1.6.2 (Using https://semver.org/) 2 | # Updated: 2020-11-02 3 | # See https://github.com/RehanSaeed/EditorConfig/releases for release notes. 4 | # See https://github.com/RehanSaeed/EditorConfig for updates to this file. 5 | # See http://EditorConfig.org for more information about .editorconfig files. 6 | 7 | ########################################## 8 | # Common Settings 9 | ########################################## 10 | 11 | # This file is the top-most EditorConfig file 12 | root = true 13 | 14 | # All Files 15 | [*] 16 | charset = utf-8 17 | indent_style = space 18 | indent_size = 4 19 | insert_final_newline = true 20 | trim_trailing_whitespace = true 21 | 22 | ########################################## 23 | # File Extension Settings 24 | ########################################## 25 | 26 | # Visual Studio Solution Files 27 | [*.sln] 28 | indent_style = tab 29 | 30 | # Visual Studio XML Project Files 31 | [*.{csproj,vbproj,vcxproj.filters,proj,projitems,shproj}] 32 | indent_size = 2 33 | 34 | # XML Configuration Files 35 | [*.{xml,config,props,targets,nuspec,resx,ruleset,vsixmanifest,vsct}] 36 | indent_size = 2 37 | 38 | # JSON Files 39 | [*.{json,json5,webmanifest}] 40 | indent_size = 2 41 | 42 | # YAML Files 43 | [*.{yml,yaml}] 44 | indent_size = 2 45 | 46 | # Markdown Files 47 | [*.md] 48 | trim_trailing_whitespace = false 49 | 50 | # Web Files 51 | [*.{htm,html,js,jsm,ts,tsx,css,sass,scss,less,svg,vue}] 52 | indent_size = 2 53 | 54 | # Batch Files 55 | [*.{cmd,bat}] 56 | end_of_line = crlf 57 | 58 | # Bash Files 59 | [*.sh] 60 | end_of_line = lf 61 | 62 | # Makefiles 63 | [Makefile] 64 | indent_style = tab 65 | 66 | 67 | [*.js] 68 | trim_trailing_whitespace = true 69 | 70 | [*.less] 71 | trim_trailing_whitespace = false 72 | 73 | ########################################## 74 | # File Header (Uncomment to support file headers) 75 | # https://docs.microsoft.com/visualstudio/ide/reference/add-file-header 76 | ########################################## 77 | 78 | # [*.{cs,csx,cake,vb,vbx}] 79 | file_header_template = Copyright (c) Umbraco.\nSee LICENSE for more details. 80 | 81 | # SA1636: File header copyright text should match 82 | # Justification: .editorconfig supports file headers. If this is changed to a value other than "none", a stylecop.json file will need to added to the project. 83 | # dotnet_diagnostic.SA1636.severity = none 84 | 85 | ########################################## 86 | # .NET Language Conventions 87 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions 88 | ########################################## 89 | 90 | # .NET Code Style Settings 91 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#net-code-style-settings 92 | [*.{cs,csx,cake,vb,vbx}] 93 | # "this." and "Me." qualifiers 94 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#this-and-me 95 | dotnet_style_qualification_for_field = false:suggestion 96 | dotnet_style_qualification_for_property = false:suggestion 97 | dotnet_style_qualification_for_method = false:suggestion 98 | dotnet_style_qualification_for_event = false:suggestion 99 | # Language keywords instead of framework type names for type references 100 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#language-keywords 101 | dotnet_style_predefined_type_for_locals_parameters_members = true:warning 102 | dotnet_style_predefined_type_for_member_access = true:warning 103 | # Modifier preferences 104 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#normalize-modifiers 105 | dotnet_style_require_accessibility_modifiers = always:warning 106 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:warning 107 | visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:warning 108 | dotnet_style_readonly_field = true:warning 109 | # Parentheses preferences 110 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#parentheses-preferences 111 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning 112 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning 113 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning 114 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion 115 | # Expression-level preferences 116 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#expression-level-preferences 117 | dotnet_style_object_initializer = true:warning 118 | dotnet_style_collection_initializer = true:warning 119 | dotnet_style_explicit_tuple_names = true:warning 120 | dotnet_style_prefer_inferred_tuple_names = true:warning 121 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning 122 | dotnet_style_prefer_auto_properties = true:warning 123 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning 124 | dotnet_style_prefer_conditional_expression_over_assignment = false:suggestion 125 | dotnet_style_prefer_conditional_expression_over_return = false:suggestion 126 | dotnet_style_prefer_compound_assignment = true:warning 127 | # Null-checking preferences 128 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#null-checking-preferences 129 | dotnet_style_coalesce_expression = true:warning 130 | dotnet_style_null_propagation = true:warning 131 | # Parameter preferences 132 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#parameter-preferences 133 | dotnet_code_quality_unused_parameters = all:warning 134 | # More style options (Undocumented) 135 | # https://github.com/MicrosoftDocs/visualstudio-docs/issues/3641 136 | dotnet_style_operator_placement_when_wrapping = end_of_line 137 | # https://github.com/dotnet/roslyn/pull/40070 138 | dotnet_style_prefer_simplified_interpolation = true:warning 139 | 140 | # C# Code Style Settings 141 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#c-code-style-settings 142 | [*.{cs,csx,cake}] 143 | # Implicit and explicit types 144 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#implicit-and-explicit-types 145 | csharp_style_var_for_built_in_types = never 146 | csharp_style_var_when_type_is_apparent = true:warning 147 | csharp_style_var_elsewhere = true:warning 148 | # Expression-bodied members 149 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#expression-bodied-members 150 | csharp_style_expression_bodied_methods = true:suggestion 151 | csharp_style_expression_bodied_constructors = true:suggestion 152 | csharp_style_expression_bodied_operators = true:suggestion 153 | csharp_style_expression_bodied_properties = true:suggestion 154 | csharp_style_expression_bodied_indexers = true:suggestion 155 | csharp_style_expression_bodied_accessors = true:suggestion 156 | csharp_style_expression_bodied_lambdas = true:suggestion 157 | csharp_style_expression_bodied_local_functions = true:suggestion 158 | # Pattern matching 159 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#pattern-matching 160 | csharp_style_pattern_matching_over_is_with_cast_check = true:warning 161 | csharp_style_pattern_matching_over_as_with_null_check = true:warning 162 | # Inlined variable declarations 163 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#inlined-variable-declarations 164 | csharp_style_inlined_variable_declaration = true:warning 165 | # Expression-level preferences 166 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#expression-level-preferences 167 | csharp_prefer_simple_default_expression = true:warning 168 | # "Null" checking preferences 169 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#c-null-checking-preferences 170 | csharp_style_throw_expression = true:warning 171 | csharp_style_conditional_delegate_call = true:warning 172 | # Code block preferences 173 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#code-block-preferences 174 | csharp_prefer_braces = true:warning 175 | # Unused value preferences 176 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#unused-value-preferences 177 | csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion 178 | csharp_style_unused_value_assignment_preference = discard_variable:suggestion 179 | # Index and range preferences 180 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#index-and-range-preferences 181 | csharp_style_prefer_index_operator = true:warning 182 | csharp_style_prefer_range_operator = true:warning 183 | # Miscellaneous preferences 184 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#miscellaneous-preferences 185 | csharp_style_deconstructed_variable_declaration = true:warning 186 | csharp_style_pattern_local_over_anonymous_function = true:warning 187 | csharp_using_directive_placement = outside_namespace:warning 188 | csharp_prefer_static_local_function = true:warning 189 | csharp_prefer_simple_using_statement = true:suggestion 190 | 191 | ########################################## 192 | # .NET Formatting Conventions 193 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-code-style-settings-reference#formatting-conventions 194 | ########################################## 195 | 196 | # Organize usings 197 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#organize-using-directives 198 | dotnet_sort_system_directives_first = true 199 | # Newline options 200 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#new-line-options 201 | csharp_new_line_before_open_brace = all 202 | csharp_new_line_before_else = true 203 | csharp_new_line_before_catch = true 204 | csharp_new_line_before_finally = true 205 | csharp_new_line_before_members_in_object_initializers = true 206 | csharp_new_line_before_members_in_anonymous_types = true 207 | csharp_new_line_between_query_expression_clauses = true 208 | # Indentation options 209 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#indentation-options 210 | csharp_indent_case_contents = true 211 | csharp_indent_switch_labels = true 212 | csharp_indent_labels = no_change 213 | csharp_indent_block_contents = true 214 | csharp_indent_braces = false 215 | csharp_indent_case_contents_when_block = false 216 | # Spacing options 217 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#spacing-options 218 | csharp_space_after_cast = false 219 | csharp_space_after_keywords_in_control_flow_statements = true 220 | csharp_space_between_parentheses = false 221 | csharp_space_before_colon_in_inheritance_clause = true 222 | csharp_space_after_colon_in_inheritance_clause = true 223 | csharp_space_around_binary_operators = before_and_after 224 | csharp_space_between_method_declaration_parameter_list_parentheses = false 225 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 226 | csharp_space_between_method_declaration_name_and_open_parenthesis = false 227 | csharp_space_between_method_call_parameter_list_parentheses = false 228 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 229 | csharp_space_between_method_call_name_and_opening_parenthesis = false 230 | csharp_space_after_comma = true 231 | csharp_space_before_comma = false 232 | csharp_space_after_dot = false 233 | csharp_space_before_dot = false 234 | csharp_space_after_semicolon_in_for_statement = true 235 | csharp_space_before_semicolon_in_for_statement = false 236 | csharp_space_around_declaration_statements = false 237 | csharp_space_before_open_square_brackets = false 238 | csharp_space_between_empty_square_brackets = false 239 | csharp_space_between_square_brackets = false 240 | # Wrapping options 241 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#wrap-options 242 | csharp_preserve_single_line_statements = false 243 | csharp_preserve_single_line_blocks = true 244 | 245 | ########################################## 246 | # .NET Naming Conventions 247 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-naming-conventions 248 | ########################################## 249 | 250 | [*.{cs,csx,cake,vb,vbx}] 251 | dotnet_diagnostic.CS1591.severity = suggestion 252 | 253 | ########################################## 254 | # Styles 255 | ########################################## 256 | 257 | # camel_case_style - Define the camelCase style 258 | dotnet_naming_style.camel_case_style.capitalization = camel_case 259 | # pascal_case_style - Define the PascalCase style 260 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case 261 | # first_upper_style - The first character must start with an upper-case character 262 | dotnet_naming_style.first_upper_style.capitalization = first_word_upper 263 | # prefix_interface_with_i_style - Interfaces must be PascalCase and the first character of an interface must be an 'I' 264 | dotnet_naming_style.prefix_interface_with_i_style.capitalization = pascal_case 265 | dotnet_naming_style.prefix_interface_with_i_style.required_prefix = I 266 | # prefix_type_parameters_with_t_style - Generic Type Parameters must be PascalCase and the first character must be a 'T' 267 | dotnet_naming_style.prefix_type_parameters_with_t_style.capitalization = pascal_case 268 | dotnet_naming_style.prefix_type_parameters_with_t_style.required_prefix = T 269 | # disallowed_style - Anything that has this style applied is marked as disallowed 270 | dotnet_naming_style.disallowed_style.capitalization = pascal_case 271 | dotnet_naming_style.disallowed_style.required_prefix = ____RULE_VIOLATION____ 272 | dotnet_naming_style.disallowed_style.required_suffix = ____RULE_VIOLATION____ 273 | # internal_error_style - This style should never occur... if it does, it indicates a bug in file or in the parser using the file 274 | dotnet_naming_style.internal_error_style.capitalization = pascal_case 275 | dotnet_naming_style.internal_error_style.required_prefix = ____INTERNAL_ERROR____ 276 | dotnet_naming_style.internal_error_style.required_suffix = ____INTERNAL_ERROR____ 277 | 278 | ########################################## 279 | # .NET Design Guideline Field Naming Rules 280 | # Naming rules for fields follow the .NET Framework design guidelines 281 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/index 282 | ########################################## 283 | 284 | # All public/protected/protected_internal constant fields must be PascalCase 285 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/field 286 | dotnet_naming_symbols.public_protected_constant_fields_group.applicable_accessibilities = public, protected, protected_internal, internal, private 287 | dotnet_naming_symbols.public_protected_constant_fields_group.required_modifiers = const 288 | dotnet_naming_symbols.public_protected_constant_fields_group.applicable_kinds = field 289 | dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.symbols = public_protected_constant_fields_group 290 | dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.style = pascal_case_style 291 | dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.severity = warning 292 | 293 | # All public/protected/protected_internal static readonly fields must be PascalCase 294 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/field 295 | dotnet_naming_symbols.public_protected_static_readonly_fields_group.applicable_accessibilities = public, protected, protected_internal 296 | dotnet_naming_symbols.public_protected_static_readonly_fields_group.required_modifiers = static, readonly 297 | dotnet_naming_symbols.public_protected_static_readonly_fields_group.applicable_kinds = field 298 | dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.symbols = public_protected_static_readonly_fields_group 299 | dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.style = pascal_case_style 300 | dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.severity = warning 301 | 302 | # No other public/protected/protected_internal fields are allowed 303 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/field 304 | dotnet_naming_symbols.other_public_protected_fields_group.applicable_accessibilities = public, protected, protected_internal 305 | dotnet_naming_symbols.other_public_protected_fields_group.applicable_kinds = field 306 | dotnet_naming_rule.other_public_protected_fields_disallowed_rule.symbols = other_public_protected_fields_group 307 | dotnet_naming_rule.other_public_protected_fields_disallowed_rule.style = disallowed_style 308 | dotnet_naming_rule.other_public_protected_fields_disallowed_rule.severity = error 309 | 310 | # This rule should never fire. However, it's included for at least two purposes: 311 | # First, it helps to understand, reason about, and root-case certain types of issues, such as bugs in .editorconfig parsers. 312 | # Second, it helps to raise immediate awareness if a new field type is added (as occurred recently in C#). 313 | dotnet_naming_symbols.sanity_check_uncovered_field_case_group.applicable_accessibilities = * 314 | dotnet_naming_symbols.sanity_check_uncovered_field_case_group.applicable_kinds = field 315 | dotnet_naming_rule.sanity_check_uncovered_field_case_rule.symbols = sanity_check_uncovered_field_case_group 316 | dotnet_naming_rule.sanity_check_uncovered_field_case_rule.style = internal_error_style 317 | dotnet_naming_rule.sanity_check_uncovered_field_case_rule.severity = error 318 | 319 | ########################################## 320 | # Other Naming Rules 321 | ########################################## 322 | 323 | # All of the following must be PascalCase: 324 | # - Namespaces 325 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-namespaces 326 | # https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md 327 | # - Classes and Enumerations 328 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces 329 | # https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md 330 | # - Delegates 331 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces#names-of-common-types 332 | # - Constructors, Properties, Events, Methods 333 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-type-members 334 | dotnet_naming_symbols.element_group.applicable_kinds = namespace, class, enum, struct, delegate, event, method, property 335 | dotnet_naming_rule.element_rule.symbols = element_group 336 | dotnet_naming_rule.element_rule.style = pascal_case_style 337 | dotnet_naming_rule.element_rule.severity = warning 338 | 339 | # Interfaces use PascalCase and are prefixed with uppercase 'I' 340 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces 341 | dotnet_naming_symbols.interface_group.applicable_kinds = interface 342 | dotnet_naming_rule.interface_rule.symbols = interface_group 343 | dotnet_naming_rule.interface_rule.style = prefix_interface_with_i_style 344 | dotnet_naming_rule.interface_rule.severity = warning 345 | 346 | # Generics Type Parameters use PascalCase and are prefixed with uppercase 'T' 347 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces 348 | dotnet_naming_symbols.type_parameter_group.applicable_kinds = type_parameter 349 | dotnet_naming_rule.type_parameter_rule.symbols = type_parameter_group 350 | dotnet_naming_rule.type_parameter_rule.style = prefix_type_parameters_with_t_style 351 | dotnet_naming_rule.type_parameter_rule.severity = warning 352 | 353 | # Function parameters use camelCase 354 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/naming-parameters 355 | dotnet_naming_symbols.parameters_group.applicable_kinds = parameter 356 | dotnet_naming_rule.parameters_rule.symbols = parameters_group 357 | dotnet_naming_rule.parameters_rule.style = camel_case_style 358 | dotnet_naming_rule.parameters_rule.severity = warning 359 | 360 | # Instance fields use camelCase and are prefixed with '_' 361 | dotnet_naming_rule.instance_fields_should_be_camel_case.severity = warning 362 | dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields 363 | dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style 364 | dotnet_naming_symbols.instance_fields.applicable_kinds = field 365 | dotnet_naming_style.instance_field_style.capitalization = camel_case 366 | dotnet_naming_style.instance_field_style.required_prefix = _ 367 | 368 | ########################################## 369 | # License 370 | ########################################## 371 | # The following applies as to the .editorconfig file ONLY, and is 372 | # included below for reference, per the requirements of the license 373 | # corresponding to this .editorconfig file. 374 | # See: https://github.com/RehanSaeed/EditorConfig 375 | # 376 | # MIT License 377 | # 378 | # Copyright (c) 2017-2019 Muhammad Rehan Saeed 379 | # Copyright (c) 2019 Henry Gabryjelski 380 | # 381 | # Permission is hereby granted, free of charge, to any 382 | # person obtaining a copy of this software and associated 383 | # documentation files (the "Software"), to deal in the 384 | # Software without restriction, including without limitation 385 | # the rights to use, copy, modify, merge, publish, distribute, 386 | # sublicense, and/or sell copies of the Software, and to permit 387 | # persons to whom the Software is furnished to do so, subject 388 | # to the following conditions: 389 | # 390 | # The above copyright notice and this permission notice shall be 391 | # included in all copies or substantial portions of the Software. 392 | # 393 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 394 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 395 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 396 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 397 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 398 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 399 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 400 | # OTHER DEALINGS IN THE SOFTWARE. 401 | ########################################## 402 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.doc diff=astextplain 3 | *.DOC diff=astextplain 4 | *.docx diff=astextplain 5 | *.DOCX diff=astextplain 6 | *.dot diff=astextplain 7 | *.DOT diff=astextplain 8 | *.pdf diff=astextplain 9 | *.PDF diff=astextplain 10 | *.rtf diff=astextplain 11 | *.RTF diff=astextplain 12 | 13 | *.jpg binary 14 | *.png binary 15 | *.gif binary 16 | 17 | *.cs text=auto diff=csharp 18 | *.vb text=auto 19 | *.c text=auto 20 | *.cpp text=auto 21 | *.cxx text=auto 22 | *.h text=auto 23 | *.hxx text=auto 24 | *.py text=auto 25 | *.rb text=auto 26 | *.java text=auto 27 | *.html text=auto 28 | *.htm text=auto 29 | *.css text=auto 30 | *.scss text=auto 31 | *.sass text=auto 32 | *.less text=auto 33 | *.js text=auto 34 | *.lisp text=auto 35 | *.clj text=auto 36 | *.sql text=auto 37 | *.php text=auto 38 | *.lua text=auto 39 | *.m text=auto 40 | *.asm text=auto 41 | *.erl text=auto 42 | *.fs text=auto 43 | *.fsx text=auto 44 | *.hs text=auto 45 | *.json text=auto 46 | *.xml text=auto 47 | *.resx text=auto 48 | *.yml text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2 49 | 50 | *.csproj text=auto merge=union 51 | *.vbproj text=auto merge=union 52 | *.fsproj text=auto merge=union 53 | *.dbproj text=auto merge=union 54 | *.sln text=auto eol=crlf merge=union 55 | 56 | *.gitattributes text=auto 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from `dotnet new gitignore` 5 | 6 | # dotenv files 7 | .env 8 | 9 | # User-specific files 10 | *.rsuser 11 | *.suo 12 | *.user 13 | *.userosscache 14 | *.sln.docstates 15 | 16 | # User-specific files (MonoDevelop/Xamarin Studio) 17 | *.userprefs 18 | 19 | # Mono auto generated files 20 | mono_crash.* 21 | 22 | # Build results 23 | [Dd]ebug/ 24 | [Dd]ebugPublic/ 25 | [Rr]elease/ 26 | [Rr]eleases/ 27 | x64/ 28 | x86/ 29 | [Ww][Ii][Nn]32/ 30 | [Aa][Rr][Mm]/ 31 | [Aa][Rr][Mm]64/ 32 | bld/ 33 | [Bb]in/ 34 | [Oo]bj/ 35 | [Ll]og/ 36 | [Ll]ogs/ 37 | 38 | # Visual Studio 2015/2017 cache/options directory 39 | .vs/ 40 | # Uncomment if you have tasks that create the project's static files in wwwroot 41 | #wwwroot/ 42 | 43 | # Visual Studio 2017 auto generated files 44 | Generated\ Files/ 45 | 46 | # MSTest test Results 47 | [Tt]est[Rr]esult*/ 48 | [Bb]uild[Ll]og.* 49 | 50 | # NUnit 51 | *.VisualState.xml 52 | TestResult.xml 53 | nunit-*.xml 54 | 55 | # Build Results of an ATL Project 56 | [Dd]ebugPS/ 57 | [Rr]eleasePS/ 58 | dlldata.c 59 | 60 | # Benchmark Results 61 | BenchmarkDotNet.Artifacts/ 62 | 63 | # .NET 64 | project.lock.json 65 | project.fragment.lock.json 66 | artifacts/ 67 | 68 | # Tye 69 | .tye/ 70 | 71 | # ASP.NET Scaffolding 72 | ScaffoldingReadMe.txt 73 | 74 | # StyleCop 75 | StyleCopReport.xml 76 | 77 | # Files built by Visual Studio 78 | *_i.c 79 | *_p.c 80 | *_h.h 81 | *.ilk 82 | *.meta 83 | *.obj 84 | *.iobj 85 | *.pch 86 | *.pdb 87 | *.ipdb 88 | *.pgc 89 | *.pgd 90 | *.rsp 91 | *.sbr 92 | *.tlb 93 | *.tli 94 | *.tlh 95 | *.tmp 96 | *.tmp_proj 97 | *_wpftmp.csproj 98 | *.log 99 | *.tlog 100 | *.vspscc 101 | *.vssscc 102 | .builds 103 | *.pidb 104 | *.svclog 105 | *.scc 106 | 107 | # Chutzpah Test files 108 | _Chutzpah* 109 | 110 | # Visual C++ cache files 111 | ipch/ 112 | *.aps 113 | *.ncb 114 | *.opendb 115 | *.opensdf 116 | *.sdf 117 | *.cachefile 118 | *.VC.db 119 | *.VC.VC.opendb 120 | 121 | # Visual Studio profiler 122 | *.psess 123 | *.vsp 124 | *.vspx 125 | *.sap 126 | 127 | # Visual Studio Trace Files 128 | *.e2e 129 | 130 | # TFS 2012 Local Workspace 131 | $tf/ 132 | 133 | # Guidance Automation Toolkit 134 | *.gpState 135 | 136 | # ReSharper is a .NET coding add-in 137 | _ReSharper*/ 138 | *.[Rr]e[Ss]harper 139 | *.DotSettings.user 140 | 141 | # TeamCity is a build add-in 142 | _TeamCity* 143 | 144 | # DotCover is a Code Coverage Tool 145 | *.dotCover 146 | 147 | # AxoCover is a Code Coverage Tool 148 | .axoCover/* 149 | !.axoCover/settings.json 150 | 151 | # Coverlet is a free, cross platform Code Coverage Tool 152 | coverage*.json 153 | coverage*.xml 154 | coverage*.info 155 | 156 | # Visual Studio code coverage results 157 | *.coverage 158 | *.coveragexml 159 | 160 | # NCrunch 161 | _NCrunch_* 162 | .*crunch*.local.xml 163 | nCrunchTemp_* 164 | 165 | # MightyMoose 166 | *.mm.* 167 | AutoTest.Net/ 168 | 169 | # Web workbench (sass) 170 | .sass-cache/ 171 | 172 | # Installshield output folder 173 | [Ee]xpress/ 174 | 175 | # DocProject is a documentation generator add-in 176 | DocProject/buildhelp/ 177 | DocProject/Help/*.HxT 178 | DocProject/Help/*.HxC 179 | DocProject/Help/*.hhc 180 | DocProject/Help/*.hhk 181 | DocProject/Help/*.hhp 182 | DocProject/Help/Html2 183 | DocProject/Help/html 184 | 185 | # Click-Once directory 186 | publish/ 187 | 188 | # Publish Web Output 189 | *.[Pp]ublish.xml 190 | *.azurePubxml 191 | # Note: Comment the next line if you want to checkin your web deploy settings, 192 | # but database connection strings (with potential passwords) will be unencrypted 193 | *.pubxml 194 | *.publishproj 195 | 196 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 197 | # checkin your Azure Web App publish settings, but sensitive information contained 198 | # in these scripts will be unencrypted 199 | PublishScripts/ 200 | 201 | # NuGet Packages 202 | *.nupkg 203 | # NuGet Symbol Packages 204 | *.snupkg 205 | # The packages folder can be ignored because of Package Restore 206 | **/[Pp]ackages/* 207 | # except build/, which is used as an MSBuild target. 208 | !**/[Pp]ackages/build/ 209 | # Uncomment if necessary however generally it will be regenerated when needed 210 | #!**/[Pp]ackages/repositories.config 211 | # NuGet v3's project.json files produces more ignorable files 212 | *.nuget.props 213 | *.nuget.targets 214 | 215 | # Microsoft Azure Build Output 216 | csx/ 217 | *.build.csdef 218 | 219 | # Microsoft Azure Emulator 220 | ecf/ 221 | rcf/ 222 | 223 | # Windows Store app package directories and files 224 | AppPackages/ 225 | BundleArtifacts/ 226 | Package.StoreAssociation.xml 227 | _pkginfo.txt 228 | *.appx 229 | *.appxbundle 230 | *.appxupload 231 | 232 | # Visual Studio cache files 233 | # files ending in .cache can be ignored 234 | *.[Cc]ache 235 | # but keep track of directories ending in .cache 236 | !?*.[Cc]ache/ 237 | 238 | # Others 239 | ClientBin/ 240 | ~$* 241 | *~ 242 | *.dbmdl 243 | *.dbproj.schemaview 244 | *.jfm 245 | *.pfx 246 | *.publishsettings 247 | orleans.codegen.cs 248 | 249 | # Including strong name files can present a security risk 250 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 251 | #*.snk 252 | 253 | # Since there are multiple workflows, uncomment next line to ignore bower_components 254 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 255 | #bower_components/ 256 | 257 | # RIA/Silverlight projects 258 | Generated_Code/ 259 | 260 | # Backup & report files from converting an old project file 261 | # to a newer Visual Studio version. Backup files are not needed, 262 | # because we have git ;-) 263 | _UpgradeReport_Files/ 264 | Backup*/ 265 | UpgradeLog*.XML 266 | UpgradeLog*.htm 267 | ServiceFabricBackup/ 268 | *.rptproj.bak 269 | 270 | # SQL Server files 271 | *.mdf 272 | *.ldf 273 | *.ndf 274 | 275 | # Business Intelligence projects 276 | *.rdl.data 277 | *.bim.layout 278 | *.bim_*.settings 279 | *.rptproj.rsuser 280 | *- [Bb]ackup.rdl 281 | *- [Bb]ackup ([0-9]).rdl 282 | *- [Bb]ackup ([0-9][0-9]).rdl 283 | 284 | # Microsoft Fakes 285 | FakesAssemblies/ 286 | 287 | # GhostDoc plugin setting file 288 | *.GhostDoc.xml 289 | 290 | # Node.js Tools for Visual Studio 291 | .ntvs_analysis.dat 292 | node_modules/ 293 | 294 | # Visual Studio 6 build log 295 | *.plg 296 | 297 | # Visual Studio 6 workspace options file 298 | *.opt 299 | 300 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 301 | *.vbw 302 | 303 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 304 | *.vbp 305 | 306 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 307 | *.dsw 308 | *.dsp 309 | 310 | # Visual Studio 6 technical files 311 | *.ncb 312 | *.aps 313 | 314 | # Visual Studio LightSwitch build output 315 | **/*.HTMLClient/GeneratedArtifacts 316 | **/*.DesktopClient/GeneratedArtifacts 317 | **/*.DesktopClient/ModelManifest.xml 318 | **/*.Server/GeneratedArtifacts 319 | **/*.Server/ModelManifest.xml 320 | _Pvt_Extensions 321 | 322 | # Paket dependency manager 323 | .paket/paket.exe 324 | paket-files/ 325 | 326 | # FAKE - F# Make 327 | .fake/ 328 | 329 | # CodeRush personal settings 330 | .cr/personal 331 | 332 | # Python Tools for Visual Studio (PTVS) 333 | __pycache__/ 334 | *.pyc 335 | 336 | # Cake - Uncomment if you are using it 337 | # tools/** 338 | # !tools/packages.config 339 | 340 | # Tabs Studio 341 | *.tss 342 | 343 | # Telerik's JustMock configuration file 344 | *.jmconfig 345 | 346 | # BizTalk build output 347 | *.btp.cs 348 | *.btm.cs 349 | *.odx.cs 350 | *.xsd.cs 351 | 352 | # OpenCover UI analysis results 353 | OpenCover/ 354 | 355 | # Azure Stream Analytics local run output 356 | ASALocalRun/ 357 | 358 | # MSBuild Binary and Structured Log 359 | *.binlog 360 | 361 | # NVidia Nsight GPU debugger configuration file 362 | *.nvuser 363 | 364 | # MFractors (Xamarin productivity tool) working folder 365 | .mfractor/ 366 | 367 | # Local History for Visual Studio 368 | .localhistory/ 369 | 370 | # Visual Studio History (VSHistory) files 371 | .vshistory/ 372 | 373 | # BeatPulse healthcheck temp database 374 | healthchecksdb 375 | 376 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 377 | MigrationBackup/ 378 | 379 | # Ionide (cross platform F# VS Code tools) working folder 380 | .ionide/ 381 | 382 | # Fody - auto-generated XML schema 383 | FodyWeavers.xsd 384 | 385 | # VS Code files for those working on multiple tools 386 | .vscode/* 387 | !.vscode/settings.json 388 | !.vscode/tasks.json 389 | !.vscode/launch.json 390 | !.vscode/extensions.json 391 | *.code-workspace 392 | 393 | # Local History for Visual Studio Code 394 | .history/ 395 | 396 | # Windows Installer files from build outputs 397 | *.cab 398 | *.msi 399 | *.msix 400 | *.msm 401 | *.msp 402 | 403 | # JetBrains Rider 404 | *.sln.iml 405 | .idea 406 | 407 | ## 408 | ## Visual studio for Mac 409 | ## 410 | 411 | 412 | # globs 413 | Makefile.in 414 | *.userprefs 415 | *.usertasks 416 | config.make 417 | config.status 418 | aclocal.m4 419 | install-sh 420 | autom4te.cache/ 421 | *.tar.gz 422 | tarballs/ 423 | test-results/ 424 | 425 | # Mac bundle stuff 426 | *.dmg 427 | *.app 428 | 429 | # content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore 430 | # General 431 | .DS_Store 432 | .AppleDouble 433 | .LSOverride 434 | 435 | # Icon must end with two \r 436 | Icon 437 | 438 | 439 | # Thumbnails 440 | ._* 441 | 442 | # Files that might appear in the root of a volume 443 | .DocumentRevisions-V100 444 | .fseventsd 445 | .Spotlight-V100 446 | .TemporaryItems 447 | .Trashes 448 | .VolumeIcon.icns 449 | .com.apple.timemachine.donotpresent 450 | 451 | # Directories potentially created on remote AFP share 452 | .AppleDB 453 | .AppleDesktop 454 | Network Trash Folder 455 | Temporary Items 456 | .apdisk 457 | 458 | # content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore 459 | # Windows thumbnail cache files 460 | Thumbs.db 461 | ehthumbs.db 462 | ehthumbs_vista.db 463 | 464 | # Dump file 465 | *.stackdump 466 | 467 | # Folder config file 468 | [Dd]esktop.ini 469 | 470 | # Recycle Bin used on file shares 471 | $RECYCLE.BIN/ 472 | 473 | # Windows Installer files 474 | *.cab 475 | *.msi 476 | *.msix 477 | *.msm 478 | *.msp 479 | 480 | # Windows shortcuts 481 | *.lnk 482 | 483 | # Vim temporary swap files 484 | *.swp 485 | -------------------------------------------------------------------------------- /.globalconfig: -------------------------------------------------------------------------------- 1 | is_global = true 2 | 3 | ########################################## 4 | # StyleCopAnalyzers Settings 5 | ########################################## 6 | 7 | # All constant fields must be PascalCase 8 | # https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1303.md 9 | dotnet_naming_symbols.stylecop_constant_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected, private 10 | dotnet_naming_symbols.stylecop_constant_fields_group.required_modifiers = const 11 | dotnet_naming_symbols.stylecop_constant_fields_group.applicable_kinds = field 12 | dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.symbols = stylecop_constant_fields_group 13 | dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.style = pascal_case_style 14 | 15 | # All static readonly fields must be PascalCase 16 | # Ajusted to ignore private fields. 17 | # https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1311.md 18 | dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected 19 | dotnet_naming_symbols.stylecop_static_readonly_fields_group.required_modifiers = static, readonly 20 | dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_kinds = field 21 | dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.symbols = stylecop_static_readonly_fields_group 22 | dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.style = pascal_case_style 23 | 24 | # No non-private instance fields are allowed 25 | # https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1401.md 26 | dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected 27 | dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_kinds = field 28 | dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.symbols = stylecop_fields_must_be_private_group 29 | dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.style = disallowed_style 30 | 31 | # Local variables must be camelCase 32 | # https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1312.md 33 | dotnet_naming_symbols.stylecop_local_fields_group.applicable_accessibilities = local 34 | dotnet_naming_symbols.stylecop_local_fields_group.applicable_kinds = local 35 | dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.symbols = stylecop_local_fields_group 36 | dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.style = camel_case_style 37 | 38 | ########################################## 39 | # StyleCopAnalyzers rule severity 40 | # https://github.com/DotNetAnalyzers/StyleCopAnalyzers 41 | ########################################## 42 | 43 | dotnet_analyzer_diagnostic.category-StyleCop.CSharp.DocumentationRules.severity = suggestion 44 | dotnet_analyzer_diagnostic.category-StyleCop.CSharp.ReadabilityRules.severity = suggestion 45 | dotnet_analyzer_diagnostic.category-StyleCop.CSharp.NamingRules.severity = suggestion 46 | dotnet_analyzer_diagnostic.category-StyleCop.CSharp.SpacingRules.severity = suggestion 47 | dotnet_analyzer_diagnostic.category-StyleCop.CSharp.OrderingRules.severity = suggestion 48 | dotnet_analyzer_diagnostic.category-StyleCop.CSharp.MaintainabilityRules.severity = suggestion 49 | dotnet_analyzer_diagnostic.category-StyleCop.CSharp.LayoutRules.severity = suggestion 50 | 51 | dotnet_diagnostic.SA1636.severity = none # SA1636: File header copyright text should match 52 | dotnet_diagnostic.SA1101.severity = none # PrefixLocalCallsWithThis - stylecop appears to be ignoring dotnet_style_qualification_for_* 53 | dotnet_diagnostic.SA1309.severity = none # FieldNamesMustNotBeginWithUnderscore 54 | 55 | dotnet_diagnostic.SA1503.severity = warning # BracesMustNotBeOmitted 56 | dotnet_diagnostic.SA1117.severity = warning # ParametersMustBeOnSameLineOrSeparateLines 57 | dotnet_diagnostic.SA1116.severity = warning # SplitParametersMustStartOnLineAfterDeclaration 58 | dotnet_diagnostic.SA1122.severity = warning # UseStringEmptyForEmptyStrings 59 | dotnet_diagnostic.SA1028.severity = warning # CodeMustNotContainTrailingWhitespace 60 | dotnet_diagnostic.SA1500.severity = warning # BracesForMultiLineStatementsMustNotShareLine 61 | dotnet_diagnostic.SA1401.severity = warning # FieldsMustBePrivate 62 | dotnet_diagnostic.SA1519.severity = warning # BracesMustNotBeOmittedFromMultiLineChildStatement 63 | dotnet_diagnostic.SA1111.severity = warning # ClosingParenthesisMustBeOnLineOfLastParameter 64 | dotnet_diagnostic.SA1520.severity = warning # UseBracesConsistently 65 | dotnet_diagnostic.SA1407.severity = warning # ArithmeticExpressionsMustDeclarePrecedence 66 | dotnet_diagnostic.SA1400.severity = warning # AccessModifierMustBeDeclared 67 | dotnet_diagnostic.SA1119.severity = warning # StatementMustNotUseUnnecessaryParenthesis 68 | dotnet_diagnostic.SA1649.severity = warning # FileNameMustMatchTypeName 69 | dotnet_diagnostic.SA1121.severity = warning # UseBuiltInTypeAlias 70 | dotnet_diagnostic.SA1132.severity = warning # DoNotCombineFields 71 | dotnet_diagnostic.SA1134.severity = warning # AttributesMustNotShareLine 72 | dotnet_diagnostic.SA1106.severity = warning # CodeMustNotContainEmptyStatements 73 | dotnet_diagnostic.SA1312.severity = warning # VariableNamesMustBeginWithLowerCaseLetter 74 | dotnet_diagnostic.SA1310.severity = warning # FieldNamesMustNotContainUnderscore 75 | dotnet_diagnostic.SA1303.severity = warning # ConstFieldNamesMustBeginWithUpperCaseLetter 76 | dotnet_diagnostic.SA1130.severity = warning # UseLambdaSyntax 77 | dotnet_diagnostic.SA1405.severity = warning # DebugAssertMustProvideMessageText 78 | dotnet_diagnostic.SA1205.severity = warning # PartialElementsMustDeclareAccess 79 | dotnet_diagnostic.SA1306.severity = warning # FieldNamesMustBeginWithLowerCaseLetter 80 | dotnet_diagnostic.SA1209.severity = warning # UsingAliasDirectivesMustBePlacedAfterOtherUsingDirectives 81 | dotnet_diagnostic.SA1216.severity = warning # UsingStaticDirectivesMustBePlacedAtTheCorrectLocation 82 | dotnet_diagnostic.SA1133.severity = warning # DoNotCombineAttributes 83 | dotnet_diagnostic.SA1135.severity = warning # UsingDirectivesMustBeQualified -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to this project 2 | 3 | Please take a moment to review this document in order to make the contribution 4 | process easy and effective for everyone involved. 5 | 6 | Following these guidelines helps to communicate that you respect the time of 7 | the developers managing and developing this open source project. In return, 8 | they should reciprocate that respect in addressing your issue or assessing 9 | patches and features. 10 | 11 | 12 | ## Using the issue tracker 13 | 14 | The issue tracker is the preferred channel for [bug reports](#bugs), 15 | [features requests](#features) and [submitting pull 16 | requests](#pull-requests), but please respect the following restrictions: 17 | 18 | * Please **do not** use the issue tracker for personal or commercial support 19 | requests (use the official Umbraco support channels). 20 | 21 | * Please **do not** derail or troll issues. Keep the discussion on topic and 22 | respect the opinions of others. 23 | 24 | 25 | 26 | ## Bug reports 27 | 28 | A bug is a _demonstrable problem_ that is caused by the code in the repository. 29 | Good bug reports are extremely helpful - thank you! 30 | 31 | Guidelines for bug reports: 32 | 33 | 1. **Use the search function** — check if the issue has already been 34 | reported. 35 | 36 | 2. **Check if the issue has been fixed** — try to reproduce it using the 37 | latest `master` or development branch in the repository. 38 | 39 | 3. **Isolate the problem** — create a reduced test 40 | case and a live example. 41 | 42 | A good bug report shouldn't leave others needing to chase you up for more 43 | information. Please try to be as detailed as possible in your report. What is 44 | your environment? What steps will reproduce the issue? What browser(s) and OS 45 | experience the problem? What would you expect to be the outcome? All these 46 | details will help people to fix any potential bugs. 47 | 48 | Example: 49 | 50 | > Short and descriptive example bug report title 51 | > 52 | > A summary of the issue and the browser/OS environment in which it occurs. If 53 | > suitable, include the steps required to reproduce the bug. 54 | > 55 | > 1. This is the first step 56 | > 2. This is the second step 57 | > 3. Further steps, etc. 58 | > 59 | > `` - a link to the reduced test case 60 | > 61 | > Any other information you want to share that is relevant to the issue being 62 | > reported. This might include the lines of code that you have identified as 63 | > causing the bug, and potential solutions (and your opinions on their 64 | > merits). 65 | 66 | 67 | 68 | ## Feature requests 69 | 70 | Feature requests are welcome. But take a moment to find out whether your idea 71 | fits with the scope and aims of the project. It's up to *you* to make a strong 72 | case to convince the project's developers of the merits of this feature. Please 73 | provide as much detail and context as possible. 74 | 75 | 76 | 77 | ## Pull requests 78 | 79 | Good pull requests - patches, improvements, new features - are a fantastic 80 | help. They should remain focused in scope and avoid containing unrelated 81 | commits. 82 | 83 | **Please ask first** before embarking on any significant pull request (e.g. 84 | implementing features, refactoring code, porting to a different language), 85 | otherwise you risk spending a lot of time working on something that the 86 | project's developers might not want to merge into the project. 87 | 88 | Please adhere to the coding conventions used throughout a project (indentation, 89 | accurate comments, etc.) and any other requirements (such as test coverage). 90 | 91 | Follow this process if you'd like your work considered for inclusion in the 92 | project: 93 | 94 | 1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork, 95 | and configure the remotes: 96 | 97 | ```bash 98 | # Clone your fork of the repo into the current directory 99 | git clone https://github.com// 100 | # Navigate to the newly cloned directory 101 | cd 102 | # Assign the original repo to a remote called "upstream" 103 | git remote add upstream https://github.com// 104 | ``` 105 | 106 | 2. If you cloned a while ago, get the latest changes from upstream: 107 | 108 | ```bash 109 | git checkout develop 110 | git pull upstream develop 111 | ``` 112 | 113 | 3. Create a new topic branch (off the main project `develop` branch) to 114 | contain your feature, change, or fix: 115 | 116 | ```bash 117 | git checkout -b 118 | ``` 119 | 120 | 4. Commit your changes in logical chunks. Please adhere to these [git commit 121 | message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) 122 | or your code is unlikely be merged into the main project. Use Git's 123 | [interactive rebase](https://help.github.com/articles/interactive-rebase) 124 | feature to tidy up your commits before making them public. 125 | 126 | 5. Locally merge (or rebase) the upstream development branch into your topic branch: 127 | 128 | ```bash 129 | git pull [--rebase] upstream develop 130 | ``` 131 | 132 | 6. Push your topic branch up to your fork: 133 | 134 | ```bash 135 | git push origin 136 | ``` 137 | 138 | 7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) 139 | with a clear title and description. 140 | 141 | **IMPORTANT**: By submitting a patch, you agree to allow the project owner to 142 | license your work under the same license as that used by the project. 143 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net472 5 | Umbraco HQ 6 | Umbraco 7 | Copyright © Umbraco $([System.DateTime]::Today.ToString('yyyy')) 8 | Umbraco Deploy Contrib 9 | https://github.com/umbraco/Umbraco.Deploy.Contrib 10 | https://umbraco.com/dist/nuget/logo-small.png 11 | icon.png 12 | MIT 13 | umbraco deploy contrib 14 | en-US 15 | true 16 | false 17 | 18 | 19 | 20 | 21 | true 22 | true 23 | snupkg 24 | 25 | 26 | 27 | 28 | false 29 | 30 | false 31 | 4.0.0 32 | true 33 | true 34 | 35 | 36 | 37 | 38 | $(MSBuildThisFileDirectory) 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | <_ProjectReferencesWithVersions Condition="'%(ProjectVersion)' != ''"> 50 | [%(ProjectVersion), $([MSBuild]::Add($([System.Text.RegularExpressions.Regex]::Match('%(ProjectVersion)', '^\d+').Value), 1))) 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /Directory.Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2016 Umbraco 4 | 5 | Copyright © 2016 Our Umbraco and other contributors 6 | 7 | Copyright © 2014 Lee Kelleher, Umbrella Inc 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of 10 | this software and associated documentation files (the "Software"), to deal in 11 | the Software without restriction, including without limitation the rights to 12 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 13 | of the Software, and to permit persons to whom the Software is furnished to do 14 | so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://dev.azure.com/umbraco/Umbraco%20Deploy%20Contrib/_apis/build/status/Umbraco%20Deploy%20Contrib%20v3%20-%20Release?branchName=master-v3)](https://dev.azure.com/umbraco/Umbraco%20Deploy%20Contrib/_build/latest?definitionId=185&branchName=master-v3) [![NuGet release](https://img.shields.io/nuget/v/UmbracoDeploy.Contrib.svg)](https://www.nuget.org/packages/UmbracoDeploy.Contrib) 2 | 3 | # Umbraco Deploy Contrib 4 | 5 | This project contains community contributions for Umbraco Deploy targetted for version 8 and above. 6 | 7 | Primarily this project offers connectors for the most popular Umbraco community packages - these are used by Deploy to aid with the deployment and transferring of content/property-data between environments on [Umbraco Cloud](https://umbraco.com/cloud). 8 | 9 | ## Branching 10 | 11 | The main branches corresponding to the Umbraco and Umbraco Deploy major releases are: 12 | 13 | - Umbraco 8/Deploy 4: `v4/dev` 14 | - Umbraco 9/Deploy 9: `v9/dev` 15 | - Umbraco 10/Deploy 10: `v10/dev` 16 | 17 | ## Connectors 18 | 19 | This project offers Umbraco Deploy connectors for the following community packages: 20 | 21 | - [Doc Type Grid Editor](https://our.umbraco.com/packages/backoffice-extensions/doc-type-grid-editor/) 22 | 23 | Value connectors for certain core property editors are also included: 24 | 25 | - Block List 26 | - Multi URL Picker 27 | - Nested Content 28 | 29 | --- 30 | 31 | ## Getting Started 32 | 33 | ### Installation 34 | 35 | When working with Umbraco 8, you can install the NuGet package using `Install-Package UmbracoDeploy.Contrib`. 36 | 37 | For Umbraco 9+ the package is available for install by: `Install-Package Umbraco.Deploy.Contrib` 38 | 39 | --- 40 | ## Contributing to this project 41 | 42 | Anyone who wishes to get involved with this project is more than welcome to contribute. Please take a moment to review the [guidelines for contributing](CONTRIBUTING.md), this applies for any bug reports, feature requests and pull requests. 43 | 44 | * [Bug reports](CONTRIBUTING.md#bugs) 45 | * [Feature requests](CONTRIBUTING.md#features) 46 | * [Pull requests](CONTRIBUTING.md#pull-requests) 47 | 48 | 49 | ### Issues 50 | 51 | We encourage you to report any issues you might find with these connectors, so we can have them fixed for everyone! 52 | 53 | When reporting issues with a connector it will help us a whole lot if you can reduce your report to being the absolute minimum required to encounter the error you are seeing. 54 | 55 | This means try removing anything unnecessary or unrelated to the actual issue, from your setup and also try reducing the steps to reproduce, to only cover exactly what we would need to do in order to see the error you are getting. 56 | 57 | Please use the [Umbraco Deploy Issue Tracker (for both Deploy and Deploy.Contrib)](https://github.com/umbraco/Umbraco.Deploy.Issues/issues). 58 | 59 | ## Credits 60 | 61 | Special thanks to the following community members for assisting on this project. 62 | 63 | * [Umbrella](https://github.com/UmbrellaInc) and [Lee Kelleher](https://github.com/leekelleher) 64 | * [Matt Brailsford](https://github.com/mattbrailsford) 65 | 66 | ## License 67 | 68 | Copyright © 2016 Umbraco 69 | 70 | Copyright © 2016 Our Umbraco and [other contributors](https://github.com/umbraco/Umbraco.Deploy.Contrib/graphs/contributors) 71 | 72 | Copyright © 2014 Lee Kelleher, Umbrella Inc 73 | 74 | Licensed under the [MIT License](LICENSE.md) 75 | -------------------------------------------------------------------------------- /Umbraco.Deploy.Contrib.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.8.34330.188 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Deploy.Contrib", "src\Umbraco.Deploy.Contrib\Umbraco.Deploy.Contrib.csproj", "{42FB2213-0815-41CD-94F8-DFB0EC1D8061}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Deploy.Contrib.Tests", "tests\Umbraco.Deploy.Contrib.Tests\Umbraco.Deploy.Contrib.Tests.csproj", "{48B09241-9C44-47EF-B4DF-397B39A631DC}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7AF3C28D-DBD0-4771-9A78-D6E982094DCC}" 11 | ProjectSection(SolutionItems) = preProject 12 | .artifactignore = .artifactignore 13 | .editorconfig = .editorconfig 14 | .gitattributes = .gitattributes 15 | .gitignore = .gitignore 16 | .globalconfig = .globalconfig 17 | azure-pipelines.yml = azure-pipelines.yml 18 | CONTRIBUTING.md = CONTRIBUTING.md 19 | Directory.Build.props = Directory.Build.props 20 | Directory.Packages.props = Directory.Packages.props 21 | global.json = global.json 22 | icon.png = icon.png 23 | LICENSE.md = LICENSE.md 24 | nuget.config = nuget.config 25 | README.md = README.md 26 | version.json = version.json 27 | EndProjectSection 28 | EndProject 29 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{71D68BC9-0940-4644-8342-4806401B4ACD}" 30 | ProjectSection(SolutionItems) = preProject 31 | tests\.editorconfig = tests\.editorconfig 32 | tests\codeanalysis.ruleset = tests\codeanalysis.ruleset 33 | tests\Directory.Build.props = tests\Directory.Build.props 34 | tests\Directory.Packages.props = tests\Directory.Packages.props 35 | EndProjectSection 36 | EndProject 37 | Global 38 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 39 | Debug|Any CPU = Debug|Any CPU 40 | Release|Any CPU = Release|Any CPU 41 | EndGlobalSection 42 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 43 | {42FB2213-0815-41CD-94F8-DFB0EC1D8061}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {42FB2213-0815-41CD-94F8-DFB0EC1D8061}.Debug|Any CPU.Build.0 = Debug|Any CPU 45 | {42FB2213-0815-41CD-94F8-DFB0EC1D8061}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {42FB2213-0815-41CD-94F8-DFB0EC1D8061}.Release|Any CPU.Build.0 = Release|Any CPU 47 | {48B09241-9C44-47EF-B4DF-397B39A631DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 48 | {48B09241-9C44-47EF-B4DF-397B39A631DC}.Debug|Any CPU.Build.0 = Debug|Any CPU 49 | {48B09241-9C44-47EF-B4DF-397B39A631DC}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {48B09241-9C44-47EF-B4DF-397B39A631DC}.Release|Any CPU.Build.0 = Release|Any CPU 51 | EndGlobalSection 52 | GlobalSection(SolutionProperties) = preSolution 53 | HideSolutionNode = FALSE 54 | EndGlobalSection 55 | GlobalSection(NestedProjects) = preSolution 56 | {48B09241-9C44-47EF-B4DF-397B39A631DC} = {71D68BC9-0940-4644-8342-4806401B4ACD} 57 | EndGlobalSection 58 | GlobalSection(ExtensibilityGlobals) = postSolution 59 | SolutionGuid = {20357E31-FA18-4D77-B38B-6C878A179849} 60 | EndGlobalSection 61 | EndGlobal 62 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | branches: 3 | include: 4 | - 'v2/*' 5 | - 'v3/*' 6 | - 'v4/*' 7 | tags: 8 | include: 9 | - release-2.* 10 | - release-3.* 11 | - release-4.* 12 | 13 | pr: 14 | branches: 15 | include: 16 | - 'v2/*' 17 | - 'v3/*' 18 | - 'v4/*' 19 | 20 | parameters: 21 | - name: cache_nuget 22 | displayName: Cache NuGet packages 23 | type: boolean 24 | default: false # As long as we're overwriting versions on MyGet, we can't cache NuGet packages by default 25 | - name: release_myget 26 | displayName: Release to pre-release/nightly MyGet feed 27 | type: boolean 28 | default: false 29 | - name: release_myget_nightly 30 | displayName: Release to nightly MyGet feed (instead of pre-release) 31 | type: boolean 32 | default: false 33 | - name: release_nuget 34 | displayName: Release to public NuGet feed 35 | type: boolean 36 | default: false 37 | 38 | variables: 39 | solution: Umbraco.Deploy.Contrib.sln 40 | buildConfiguration: Release 41 | DOTNET_NOLOGO: true 42 | DOTNET_GENERATE_ASPNET_CERTIFICATE: false 43 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 44 | DOTNET_CLI_TELEMETRY_OPTOUT: true 45 | 46 | stages: 47 | - stage: Build 48 | variables: 49 | NUGET_PACKAGES: $(Pipeline.Workspace)/.nuget/packages 50 | jobs: 51 | - job: Build 52 | pool: 53 | vmImage: 'windows-latest' # Required for .NET Framework projects 54 | steps: 55 | # Checkout source (avoid shallow clone to calculate version height) 56 | - checkout: self 57 | fetchDepth: 0 58 | 59 | # Setup build environment 60 | - task: UseDotNet@2 61 | displayName: Use .NET SDK from global.json 62 | inputs: 63 | useGlobalJson: true 64 | 65 | # Cache and restore NuGet packages 66 | - task: Cache@2 67 | condition: ${{ parameters.cache_nuget }} 68 | displayName: Cache NuGet packages 69 | inputs: 70 | key: 'nuget | "$(Agent.OS)" | **/packages.lock.json, !**/bin/**, !**/obj/**' 71 | restoreKeys: | 72 | nuget | "$(Agent.OS)" 73 | nuget 74 | path: $(NUGET_PACKAGES) 75 | 76 | - script: dotnet restore $(solution) --locked-mode 77 | displayName: Restore NuGet packages 78 | 79 | # Build 80 | - script: dotnet build $(solution) --configuration $(buildConfiguration) --no-restore -p:ContinuousIntegrationBuild=true 81 | displayName: Run dotnet build 82 | name: build 83 | 84 | # Pack 85 | - script: dotnet pack $(solution) --configuration $(buildConfiguration) --no-build --output $(Build.ArtifactStagingDirectory)/nupkg 86 | displayName: Run dotnet pack 87 | 88 | # Publish 89 | - task: PublishPipelineArtifact@1 90 | displayName: Publish NuGet packages 91 | inputs: 92 | targetPath: $(Build.ArtifactStagingDirectory)/nupkg 93 | artifactName: nupkg 94 | 95 | - task: PublishPipelineArtifact@1 96 | displayName: Publish build output 97 | inputs: 98 | targetPath: $(Build.SourcesDirectory) 99 | artifactName: build_output 100 | 101 | - stage: Test 102 | dependsOn: Build 103 | jobs: 104 | - job: UnitTests 105 | displayName: Unit Tests 106 | pool: 107 | vmImage: 'windows-latest' 108 | steps: 109 | # Setup test environment 110 | - task: DownloadPipelineArtifact@2 111 | displayName: Download build artifacts 112 | inputs: 113 | artifact: build_output 114 | path: $(Build.SourcesDirectory) 115 | 116 | - task: UseDotNet@2 117 | displayName: Use .NET SDK from global.json 118 | inputs: 119 | useGlobalJson: true 120 | 121 | # Test 122 | - script: dotnet test $(solution) --configuration $(buildConfiguration) --no-build --logger trx --results-directory $(Build.ArtifactStagingDirectory)/tests 123 | displayName: Run dotnet test 124 | 125 | # Publish 126 | - task: PublishTestResults@2 127 | displayName: Publish test results 128 | condition: succeededOrFailed() 129 | inputs: 130 | testResultsFormat: VSTest 131 | testResultsFiles: '*.trx' 132 | searchFolder: $(Build.ArtifactStagingDirectory)/tests 133 | testRunTitle: Unit Tests 134 | configuration: $(buildConfiguration) 135 | 136 | - stage: ReleaseMyGet 137 | displayName: MyGet release 138 | dependsOn: Test 139 | condition: and(succeeded(), or(eq(dependencies.Build.outputs['Build.build.NBGV_PublicRelease'], 'True'), ${{ parameters.release_myget }})) 140 | jobs: 141 | - job: 142 | displayName: Release to pre-release/nightly MyGet feed 143 | steps: 144 | - checkout: none 145 | - task: DownloadPipelineArtifact@2 146 | displayName: Download nupkg 147 | inputs: 148 | artifact: nupkg 149 | path: $(Build.ArtifactStagingDirectory)/nupkg 150 | - task: NuGetCommand@2 151 | displayName: NuGet push 152 | inputs: 153 | command: 'push' 154 | packagesToPush: $(Build.ArtifactStagingDirectory)/nupkg/*.nupkg 155 | nuGetFeedType: 'external' 156 | ${{ if eq(parameters.release_myget_nightly, true) }}: 157 | publishFeedCredentials: 'MyGet - Nightly' 158 | ${{ else }}: 159 | publishFeedCredentials: 'MyGet - Pre-releases' 160 | 161 | - stage: ReleaseNuGet 162 | displayName: NuGet release 163 | dependsOn: ReleaseMyGet 164 | condition: and(succeeded(), or(eq(dependencies.Build.outputs['Build.build.NBGV_PublicRelease'], 'True'), ${{ parameters.release_nuget }})) 165 | jobs: 166 | - job: 167 | displayName: Release to public NuGet feed 168 | steps: 169 | - checkout: none 170 | - task: DownloadPipelineArtifact@2 171 | displayName: Download nupkg 172 | inputs: 173 | artifact: nupkg 174 | path: $(Build.ArtifactStagingDirectory)/nupkg 175 | - task: NuGetCommand@2 176 | displayName: NuGet push 177 | inputs: 178 | command: 'push' 179 | packagesToPush: $(Build.ArtifactStagingDirectory)/nupkg/*.nupkg 180 | nuGetFeedType: 'external' 181 | publishFeedCredentials: 'NuGet' 182 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.100", 4 | "rollForward": "latestFeature" 5 | } 6 | } -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umbraco/Umbraco.Deploy.Contrib/6c27f514aca2300704315178bfe5e9ca772c0c3c/icon.png -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/DataTypeConfigurationConnectors/DocTypeGridEditorDataTypeConfigurationConnector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.RegularExpressions; 5 | using Newtonsoft.Json; 6 | using Newtonsoft.Json.Linq; 7 | using Umbraco.Core; 8 | using Umbraco.Core.Configuration.Grid; 9 | using Umbraco.Core.Deploy; 10 | using Umbraco.Core.Models; 11 | using Umbraco.Core.Services; 12 | using Umbraco.Deploy.Connectors.DataTypeConfigurationConnectors; 13 | using Umbraco.Deploy.Core; 14 | using Umbraco.Web.PropertyEditors; 15 | 16 | namespace Umbraco.Deploy.Contrib.DataTypeConfigurationConnectors 17 | { 18 | /// 19 | /// Implements a Grid layout data type configuration connector supporting DocTypeGridEditor. 20 | /// 21 | public class DocTypeGridEditorDataTypeConfigurationConnector : DataTypeConfigurationConnectorBase2 22 | { 23 | private readonly IGridConfig _gridConfig; 24 | private readonly IContentTypeService _contentTypeService; 25 | 26 | /// 27 | public override IEnumerable PropertyEditorAliases { get; } = new[] 28 | { 29 | Constants.PropertyEditors.Aliases.Grid 30 | }; 31 | 32 | /// 33 | /// Initializes a new instance of the class. 34 | /// 35 | /// The grid configuration. 36 | /// The content type service. 37 | public DocTypeGridEditorDataTypeConfigurationConnector(IGridConfig gridConfig, IContentTypeService contentTypeService) 38 | { 39 | _gridConfig = gridConfig; 40 | _contentTypeService = contentTypeService; 41 | } 42 | 43 | /// 44 | public override string ToArtifact(IDataType dataType, ICollection dependencies, IContextCache contextCache) 45 | { 46 | if (dataType.ConfigurationAs() is GridConfiguration gridConfiguration && 47 | gridConfiguration.Items?.ToObject() is GridConfigurationItems gridConfigurationItems) 48 | { 49 | // Get all element types (when needed) 50 | var allElementTypes = new Lazy>(() => _contentTypeService.GetAll().Where(x => x.IsElement).ToList()); 51 | 52 | // Process DTGE editors 53 | foreach (var gridEditor in GetGridEditors(gridConfigurationItems).Where(IsDocTypeGridEditor)) 54 | { 55 | if (gridEditor.Config.TryGetValue("allowedDocTypes", out var allowedDocTypesConfig) && 56 | allowedDocTypesConfig is JArray allowedDocTypes && 57 | allowedDocTypes.Count > 0) 58 | { 59 | string[] docTypes = allowedDocTypes.Values().WhereNotNull().ToArray(); 60 | 61 | // Use regex matching 62 | AddDependencies(dependencies, allElementTypes.Value.Where(x => docTypes.Any(y => Regex.IsMatch(x.Alias, y)))); 63 | } 64 | else 65 | { 66 | // Add all element types as dependencies and stop processing 67 | AddDependencies(dependencies, allElementTypes.Value); 68 | break; 69 | } 70 | } 71 | } 72 | 73 | return base.ToArtifact(dataType, dependencies, contextCache); 74 | } 75 | 76 | private static void AddDependencies(ICollection dependencies, IEnumerable contentTypes) 77 | { 78 | foreach (var contentType in contentTypes) 79 | { 80 | dependencies.Add(new ArtifactDependency(contentType.GetUdi(), true, ArtifactDependencyMode.Exist)); 81 | } 82 | } 83 | 84 | /// 85 | /// Gets the grid editors used within the grid configuration. 86 | /// 87 | /// The grid configuration items. 88 | /// 89 | /// The used grid editors (returns all editors if any of the areas allows all editors). 90 | /// 91 | protected virtual IEnumerable GetGridEditors(GridConfigurationItems gridConfigurationItems) 92 | { 93 | foreach (var gridEditor in _gridConfig.EditorsConfig.Editors) 94 | { 95 | if (IsAllowedGridEditor(gridConfigurationItems, gridEditor.Alias)) 96 | { 97 | yield return gridEditor; 98 | } 99 | } 100 | } 101 | 102 | /// 103 | /// Determines whether the grid editor alias is allowed in the specified grid configuration. 104 | /// 105 | /// The grid configuration items. 106 | /// The alias. 107 | /// 108 | /// true if the grid editor alias is allowed in the specified grid configuration; otherwise, false. 109 | /// 110 | protected static bool IsAllowedGridEditor(GridConfigurationItems gridConfigurationItems, string alias) 111 | => gridConfigurationItems.Layouts.Any(x => x.Areas.Any(y => y.AllowAll || y.Allowed.Contains(alias))); 112 | 113 | /// 114 | /// Determines whether the grid editor is the DTGE. 115 | /// 116 | /// The grid editor. 117 | /// 118 | /// true if the grid editor is the DTGE; otherwise, false. 119 | /// 120 | protected static bool IsDocTypeGridEditor(IGridEditorConfig gridEditor) 121 | => !string.IsNullOrEmpty(gridEditor.View) && gridEditor.View.Contains("doctypegrideditor"); 122 | 123 | /// 124 | /// The grid configuration items. 125 | /// 126 | protected sealed class GridConfigurationItems 127 | { 128 | /// 129 | /// Gets or sets the row configurations. 130 | /// 131 | /// 132 | /// The row configurations. 133 | /// 134 | [JsonProperty("layouts")] 135 | public GridConfigurationLayout[] Layouts { get; set; } = Array.Empty(); 136 | } 137 | 138 | /// 139 | /// The grid row configuration. 140 | /// 141 | protected sealed class GridConfigurationLayout 142 | { 143 | /// 144 | /// Gets or sets the areas. 145 | /// 146 | /// 147 | /// The areas. 148 | /// 149 | [JsonProperty("areas")] 150 | public GridConfigurationLayoutArea[] Areas { get; set; } = Array.Empty(); 151 | } 152 | 153 | /// 154 | /// The grid row configuration area. 155 | /// 156 | protected sealed class GridConfigurationLayoutArea 157 | { 158 | /// 159 | /// Gets or sets a value indicating whether all grid editors are allowed. 160 | /// 161 | /// 162 | /// true if all grid editors are allowed; otherwise, false. 163 | /// 164 | /// 165 | /// Defaults to true. 166 | /// 167 | [JsonProperty("allowAll")] 168 | public bool AllowAll { get; set; } = true; 169 | 170 | /// 171 | /// Gets or sets the allowed grid editor aliases. 172 | /// 173 | /// 174 | /// The allowed grid editor aliases. 175 | /// 176 | [JsonProperty("allowed")] 177 | public string[] Allowed { get; set; } = Array.Empty(); 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Extensions/ArtifactMigratorCollectionBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Umbraco.Deploy.Contrib.Migrators.Legacy; 2 | using Umbraco.Deploy.Migrators; 3 | 4 | namespace Umbraco.Extensions 5 | { 6 | public static class ArtifactMigratorCollectionBuilderExtensions 7 | { 8 | /// 9 | /// Adds the legacy artifact migrators to allow importing from Umbraco 7. 10 | /// 11 | /// The artifact migrator collection builder. 12 | /// 13 | /// The artifact migrator collection builder. 14 | /// 15 | public static ArtifactMigratorCollectionBuilder AddLegacyMigrators(this ArtifactMigratorCollectionBuilder artifactMigratorCollectionBuilder) 16 | => artifactMigratorCollectionBuilder 17 | // Pre-values to configuration 18 | .Append() 19 | // Release/expire dates to schedule 20 | .Append() 21 | // Allowed at root and child content types to permissions 22 | .Append() 23 | // Data types 24 | .Append() 25 | .Append() 26 | .Append() 27 | .Append() 28 | .Append() 29 | .Append() 30 | .Append() 31 | .Append() 32 | .Append() 33 | .Append() 34 | .Append() 35 | .Append() 36 | .Append() 37 | .Append() 38 | .Append() 39 | .Append() 40 | .Append() 41 | .Append() 42 | .Append() 43 | .Append() 44 | .Append() 45 | .Append() 46 | .Append() 47 | // Property values 48 | .Append() 49 | .Append() 50 | .Append(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Extensions/ArtifactTypeResolverCollectionBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Umbraco.Deploy.Contrib.Connectors.Serialization; 2 | using Umbraco.Deploy.Serialization; 3 | 4 | namespace Umbraco.Extensions 5 | { 6 | public static class ArtifactTypeResolverCollectionBuilderExtensions 7 | { 8 | /// 9 | /// Adds the legacy artifact type resolver to allow importing from Umbraco 7. 10 | /// 11 | /// The artifact type resolver collection builder. 12 | /// 13 | /// The artifact type resolver collection builder. 14 | /// 15 | public static ArtifactTypeResolverCollectionBuilder AddLegacyTypeResolver(this ArtifactTypeResolverCollectionBuilder artifactTypeResolverCollectionBuilder) 16 | => artifactTypeResolverCollectionBuilder.Append(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/GridCellValueConnectors/DocTypeGridEditorCellValueConnector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Linq; 5 | using Umbraco.Core; 6 | using Umbraco.Core.Composing; 7 | using Umbraco.Core.Deploy; 8 | using Umbraco.Core.Logging; 9 | using Umbraco.Core.Models; 10 | using Umbraco.Core.Services; 11 | using Umbraco.Deploy.Connectors; 12 | using Umbraco.Deploy.Connectors.GridCellValueConnectors; 13 | using Umbraco.Deploy.Connectors.ValueConnectors.Services; 14 | using Umbraco.Deploy.Core; 15 | using Umbraco.Deploy.Extensions; 16 | 17 | namespace Umbraco.Deploy.Contrib.Connectors.GridCellValueConnectors 18 | { 19 | public class DocTypeGridEditorCellValueConnector : GridCellValueConnectorBase2 20 | { 21 | private readonly ILogger _logger; 22 | private readonly IContentTypeService _contentTypeService; 23 | private readonly Lazy _valueConnectorsLazy; 24 | 25 | private ValueConnectorCollection ValueConnectors => _valueConnectorsLazy.Value; 26 | 27 | [Obsolete("Please use the constructor taking all parameters. This constructor will be removed in a future version.")] 28 | public DocTypeGridEditorCellValueConnector(ILogger logger, IContentTypeService contentTypeService, Lazy valueConnectors) 29 | : this( 30 | Current.Factory.GetInstance(), 31 | Current.Factory.GetInstance(), 32 | logger, 33 | contentTypeService, 34 | valueConnectors) 35 | { } 36 | 37 | public DocTypeGridEditorCellValueConnector(IEntityService entityService, ILocalLinkParser localLinkParser, ILogger logger, IContentTypeService contentTypeService, Lazy valueConnectors) 38 | : base(entityService, localLinkParser) 39 | { 40 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 41 | _contentTypeService = contentTypeService ?? throw new ArgumentNullException(nameof(contentTypeService)); 42 | _valueConnectorsLazy = valueConnectors ?? throw new ArgumentNullException(nameof(valueConnectors)); 43 | } 44 | 45 | public sealed override bool IsConnector(string view) 46 | => !string.IsNullOrWhiteSpace(view) && view.Contains("doctypegrideditor"); 47 | 48 | public sealed override string GetValue(GridValue.GridControl gridControl, ICollection dependencies, IContextCache contextCache) 49 | { 50 | // cancel if there's no values 51 | if (gridControl.Value == null || gridControl.Value.HasValues == false) 52 | { 53 | return null; 54 | } 55 | 56 | _logger.Debug($"GetValue - Grid Values: {gridControl.Value}"); 57 | 58 | var docTypeGridEditorContent = JsonConvert.DeserializeObject(gridControl.Value.ToString()); 59 | 60 | // if an 'empty' dtge item has been added - it has no ContentTypeAlias set .. just return and don't throw. 61 | if (docTypeGridEditorContent == null || string.IsNullOrWhiteSpace(docTypeGridEditorContent.ContentTypeAlias)) 62 | { 63 | _logger.Debug("GetValue - DTGE Empty without ContentTypeAlias"); 64 | return null; 65 | } 66 | 67 | _logger.Debug($"GetValue - ContentTypeAlias - {docTypeGridEditorContent.ContentTypeAlias}"); 68 | 69 | // check if the doc type exist - else abort packaging 70 | var contentType = contextCache.GetContentTypeByAlias(_contentTypeService, docTypeGridEditorContent.ContentTypeAlias); 71 | if (contentType == null) 72 | { 73 | _logger.Debug("GetValue - Missing ContentType"); 74 | throw new InvalidOperationException($"Could not resolve the Content Type for the Doc Type Grid Editor property: {docTypeGridEditorContent.ContentTypeAlias}"); 75 | } 76 | 77 | // add content type as a dependency 78 | dependencies.Add(new ArtifactDependency(contentType.GetUdi(), false, ArtifactDependencyMode.Match)); 79 | 80 | // find all properties 81 | var propertyTypes = contentType.CompositionPropertyTypes; 82 | 83 | foreach (var propertyType in propertyTypes) 84 | { 85 | _logger.Debug($"GetValue - PropertyTypeAlias - {propertyType.Alias}"); 86 | 87 | // test if there's a value for the given property 88 | if (!TryGetValue(docTypeGridEditorContent, propertyType, out var value)) 89 | { 90 | continue; 91 | } 92 | 93 | // if the value is an Udi then add it as a dependency 94 | if (AddUdiDependency(dependencies, value)) 95 | { 96 | continue; 97 | } 98 | 99 | // throws if not found - no need for a null check 100 | var propValueConnector = ValueConnectors.Get(propertyType); 101 | 102 | _logger.Debug($"GetValue - PropertyValueConnectorAlias - {string.Join(", ", propValueConnector.PropertyEditorAliases)}"); 103 | _logger.Debug($"GetValue - propertyTypeValue - {value}"); 104 | 105 | //properties like MUP / Nested Content are JSON, we need to convert to string for the conversion to artifact 106 | string parsedValue = propValueConnector.ToArtifact(value != null && value.ToString().TryParseJson(out JToken _) ? value.ToString() : value, propertyType, dependencies, contextCache); 107 | 108 | _logger.Debug($"GetValue - ParsedValue - {parsedValue}"); 109 | 110 | docTypeGridEditorContent.Value[propertyType.Alias] = parsedValue; 111 | } 112 | 113 | var resolvedValue = JsonConvert.SerializeObject(docTypeGridEditorContent, Formatting.None); 114 | 115 | _logger.Debug($"GetValue - ResolvedValue - {resolvedValue}"); 116 | 117 | return resolvedValue; 118 | } 119 | 120 | public sealed override void SetValue(GridValue.GridControl gridControl, IContextCache contextCache) 121 | { 122 | // cancel if there's no values 123 | if (string.IsNullOrWhiteSpace(gridControl.Value.ToString())) 124 | { 125 | return; 126 | } 127 | 128 | // For some reason the control value isn't properly parsed so we need this extra step to parse it into a JToken 129 | gridControl.Value = JToken.Parse(gridControl.Value.ToString()); 130 | 131 | _logger.Debug($"SetValue - GridControlValue - {gridControl.Value}"); 132 | 133 | var docTypeGridEditorContent = JsonConvert.DeserializeObject(gridControl.Value.ToString()); 134 | if (docTypeGridEditorContent == null) 135 | { 136 | return; 137 | } 138 | 139 | _logger.Debug($"SetValue - ContentTypeAlias - {docTypeGridEditorContent.ContentTypeAlias}"); 140 | 141 | // check if the doc type exist - else abort packaging 142 | var contentType = contextCache.GetContentTypeByAlias(_contentTypeService, docTypeGridEditorContent.ContentTypeAlias) 143 | ?? throw new InvalidOperationException($"Could not resolve the Content Type for the Doc Type Grid Editor property: {docTypeGridEditorContent.ContentTypeAlias}"); 144 | 145 | _logger.Debug($"SetValue - ContentType - {contentType}"); 146 | 147 | // find all properties 148 | var propertyTypes = contentType.CompositionPropertyTypes; 149 | 150 | foreach (var propertyType in propertyTypes) 151 | { 152 | _logger.Debug($"SetValue - PropertyEditorAlias -- {propertyType.PropertyEditorAlias}"); 153 | 154 | if (!docTypeGridEditorContent.Value.TryGetValue(propertyType.Alias, out object value) || value == null) 155 | { 156 | _logger.Debug("SetValue - Value Is Null"); 157 | continue; 158 | } 159 | 160 | // throws if not found - no need for a null check 161 | var propValueConnector = ValueConnectors.Get(propertyType); 162 | var convertedValue = propValueConnector.FromArtifact(value.ToString(), propertyType, string.Empty, contextCache); 163 | 164 | // test if the value is a json object (thus could be a nested complex editor) 165 | // if that's the case we'll need to add it as a json object instead of string to avoid it being escaped 166 | if (convertedValue != null && convertedValue.ToString().TryParseJson(out JToken jtokenValue)) 167 | { 168 | _logger.Debug($"SetValue - jtokenValue - {jtokenValue}"); 169 | docTypeGridEditorContent.Value[propertyType.Alias] = jtokenValue; 170 | } 171 | else 172 | { 173 | _logger.Debug($"SetValue - convertedValue - {convertedValue}"); 174 | docTypeGridEditorContent.Value[propertyType.Alias] = convertedValue; 175 | } 176 | } 177 | 178 | var jtokenObj = JToken.FromObject(docTypeGridEditorContent); 179 | _logger.Debug($"SetValue - jtokenObject - {jtokenObj}"); 180 | gridControl.Value = jtokenObj; 181 | } 182 | 183 | private bool AddUdiDependency(ICollection dependencies, object value) 184 | { 185 | if (Udi.TryParse(value.ToString(), out var udi)) 186 | { 187 | _logger.Debug($"GetValue - Udi Dependency - {udi}"); 188 | dependencies.Add(new ArtifactDependency(udi, false, ArtifactDependencyMode.Match)); 189 | return true; 190 | } 191 | 192 | return false; 193 | } 194 | 195 | private bool TryGetValue(DocTypeGridEditorValue docTypeGridEditorContent, PropertyType propertyType, out object objVal) 196 | { 197 | if (!docTypeGridEditorContent.Value.TryGetValue(propertyType.Alias, out objVal) || objVal == null) 198 | { 199 | _logger.Debug("GetValue - Value is null"); 200 | return false; 201 | } 202 | 203 | return true; 204 | } 205 | 206 | private class DocTypeGridEditorValue 207 | { 208 | [JsonProperty("id")] 209 | public Guid Id { get; set; } 210 | 211 | [JsonProperty("dtgeContentTypeAlias")] 212 | public string ContentTypeAlias { get; set; } 213 | 214 | [JsonProperty("value")] 215 | public Dictionary Value { get; set; } 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/Content/CheckBoxListPropertyValueArtifactMigrator.cs: -------------------------------------------------------------------------------- 1 | using Umbraco.Core; 2 | using Umbraco.Core.Services; 3 | using Umbraco.Deploy.Artifacts.Content; 4 | using Umbraco.Deploy.Connectors.ServiceConnectors.Wrappers; 5 | 6 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 7 | { 8 | /// 9 | /// Migrates the using the editor from the containing prevalues (seperated by ) from Umbraco 7 to a JSON array. 10 | /// 11 | public class CheckBoxListPropertyValueArtifactMigrator : PrevaluePropertyValueArtifactMigratorBase 12 | { 13 | /// 14 | protected override bool Multiple => true; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The content type service. 20 | /// The media type service. 21 | /// The member type service. 22 | public CheckBoxListPropertyValueArtifactMigrator(IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService) 23 | : base(Constants.PropertyEditors.Aliases.CheckBoxList, contentTypeService, mediaTypeService, memberTypeService) 24 | { } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/Content/DocumentArtifactJsonMigrator.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json.Linq; 2 | using Semver; 3 | using Umbraco.Core.Models; 4 | using Umbraco.Deploy.Artifacts.Content; 5 | using Umbraco.Deploy.Migrators; 6 | 7 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 8 | { 9 | /// 10 | /// Migrates the JSON from Umbraco 7 release/expire date to schedule. 11 | /// 12 | public class DocumentArtifactJsonMigrator : ArtifactJsonMigratorBase 13 | { 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | public DocumentArtifactJsonMigrator() 18 | => MaxVersion = new SemVersion(3, 0, 0); 19 | 20 | /// 21 | public override JToken Migrate(JToken artifactJson) 22 | { 23 | var schedule = new JArray(); 24 | 25 | if (artifactJson["ReleaseDate"] is JValue releaseDate && 26 | releaseDate.Value != null) 27 | { 28 | schedule.Add(new JObject() 29 | { 30 | ["Date"] = releaseDate.Value(), 31 | ["Culture"] = string.Empty, 32 | ["Action"] = nameof(ContentScheduleAction.Release) 33 | }); 34 | } 35 | 36 | if (artifactJson["ExpireDate"] is JValue expireDate && 37 | expireDate.Value != null) 38 | { 39 | schedule.Add(new JObject() 40 | { 41 | ["Date"] = expireDate, 42 | ["Culture"] = string.Empty, 43 | ["Action"] = nameof(ContentScheduleAction.Expire) 44 | }); 45 | } 46 | 47 | artifactJson["Schedule"] = schedule; 48 | 49 | return artifactJson; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/Content/DropDownListFlexiblePropertyValueArtifactMigrator.cs: -------------------------------------------------------------------------------- 1 | using Umbraco.Core; 2 | using Umbraco.Core.Services; 3 | using Umbraco.Deploy.Artifacts.Content; 4 | using Umbraco.Deploy.Connectors.ServiceConnectors.Wrappers; 5 | 6 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 7 | { 8 | /// 9 | /// Migrates the using the editor from the containing prevalues (seperated by ) from Umbraco 7 to a JSON array. 10 | /// 11 | public class DropDownListFlexiblePropertyValueArtifactMigrator : PrevaluePropertyValueArtifactMigratorBase 12 | { 13 | /// 14 | protected override bool Multiple => true; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The content type service. 20 | /// The media type service. 21 | /// The member type service. 22 | public DropDownListFlexiblePropertyValueArtifactMigrator(IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService) 23 | : base(Constants.PropertyEditors.Aliases.DropDownListFlexible, contentTypeService, mediaTypeService, memberTypeService) 24 | { } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/Content/PrevaluePropertyValueArtifactMigratorBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | using Semver; 4 | using Umbraco.Core.Services; 5 | using Umbraco.Deploy.Artifacts.Content; 6 | using Umbraco.Deploy.Connectors.ServiceConnectors.Wrappers; 7 | using Umbraco.Deploy.Migrators; 8 | 9 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 10 | { 11 | /// 12 | /// Migrates the using the specified editor alias from the containing prevalues (seperated by ) from Umbraco 7 to a single value or JSON array. 13 | /// 14 | public abstract class PrevaluePropertyValueArtifactMigratorBase : PropertyValueArtifactMigratorBase 15 | { 16 | private const string Delimiter = ";;"; 17 | 18 | /// 19 | /// Gets a value indicating whether the property stored multiple prevalues as a JSON array or single value. 20 | /// 21 | /// 22 | /// true if multiple prevalues are stored as a JSON array; otherwise, false. 23 | /// 24 | protected abstract bool Multiple { get; } 25 | 26 | /// 27 | /// Initializes a new instance of the class. 28 | /// 29 | /// The editor alias. 30 | /// The content type service. 31 | /// The media type service. 32 | /// The member type service. 33 | public PrevaluePropertyValueArtifactMigratorBase(string editorAlias, IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService) 34 | : base(editorAlias, contentTypeService, mediaTypeService, memberTypeService) 35 | => MaxVersion = new SemVersion(3, 0, 0); 36 | 37 | /// 38 | protected override string Migrate(string value) 39 | { 40 | var values = value?.Split(new[] { Delimiter }, StringSplitOptions.RemoveEmptyEntries); 41 | if (values == null || values.Length == 0) 42 | { 43 | return null; 44 | } 45 | 46 | return Multiple 47 | ? JsonConvert.SerializeObject(values) 48 | : values[0]; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/Content/RadioButtonListPropertyValueArtifactMigrator.cs: -------------------------------------------------------------------------------- 1 | using Umbraco.Core; 2 | using Umbraco.Core.Services; 3 | using Umbraco.Deploy.Artifacts.Content; 4 | using Umbraco.Deploy.Connectors.ServiceConnectors.Wrappers; 5 | 6 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 7 | { 8 | /// 9 | /// Migrates the using the editor from the containing prevalues (seperated by ) from Umbraco 7 to a JSON array. 10 | /// 11 | public class RadioButtonListPropertyValueArtifactMigrator : PrevaluePropertyValueArtifactMigratorBase 12 | { 13 | /// 14 | protected override bool Multiple => false; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The content type service. 20 | /// The media type service. 21 | /// The member type service. 22 | public RadioButtonListPropertyValueArtifactMigrator(IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService) 23 | : base(Constants.PropertyEditors.Aliases.RadioButtonList, contentTypeService, mediaTypeService, memberTypeService) 24 | { } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/ContentType/ContentTypeArtifactJsonMigrator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json.Linq; 3 | using Semver; 4 | using Umbraco.Core; 5 | using Umbraco.Deploy.Artifacts.ContentType; 6 | using Umbraco.Deploy.Migrators; 7 | 8 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 9 | { 10 | /// 11 | /// Migrates the JSON from Umbraco 7 allowed at root and child content types to permissions. 12 | /// 13 | public class ContentTypeArtifactJsonMigrator : ArtifactJsonMigratorBase 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | public ContentTypeArtifactJsonMigrator() 19 | => MaxVersion = new SemVersion(3, 0, 0); 20 | 21 | protected override bool CanMigrateType(Type type) 22 | => type.Inherits(); 23 | 24 | /// 25 | public override JToken Migrate(JToken artifactJson) 26 | { 27 | var permissions = new JObject(); 28 | 29 | if (artifactJson["AllowedAtRoot"] is JValue allowedAtRootValue && 30 | allowedAtRootValue.Value != null) 31 | { 32 | permissions["AllowedAtRoot"] = allowedAtRootValue; 33 | } 34 | 35 | if (artifactJson["AllowedChildContentTypes"] is JArray allowedChildContentTypesToken) 36 | { 37 | permissions["AllowedChildContentTypes"] = allowedChildContentTypesToken; 38 | } 39 | 40 | artifactJson["Permissions"] = permissions; 41 | 42 | return artifactJson; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/ContentType/ElementTypeArtifactMigratorBase.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Semver; 3 | using Umbraco.Deploy.Artifacts.ContentType; 4 | using Umbraco.Deploy.Migrators; 5 | 6 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 7 | { 8 | /// 9 | /// Migrates the from Umbraco 7 to an element type. 10 | /// 11 | public abstract class ElementTypeArtifactMigratorBase : ArtifactMigratorBase 12 | { 13 | private readonly string[] _elementTypeAliases; 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The element type aliases. 19 | protected ElementTypeArtifactMigratorBase(params string[] elementTypeAliases) 20 | { 21 | _elementTypeAliases = elementTypeAliases; 22 | 23 | MaxVersion = new SemVersion(3, 0, 0); 24 | } 25 | 26 | /// 27 | protected override DocumentTypeArtifact Migrate(DocumentTypeArtifact artifact) 28 | { 29 | if (_elementTypeAliases.Contains(artifact.Alias)) 30 | { 31 | artifact.Permissions.IsElementType = true; 32 | } 33 | 34 | return artifact; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/DataType/CheckBoxListDataTypeArtifactMigrator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Semver; 3 | using Umbraco.Core; 4 | using Umbraco.Core.PropertyEditors; 5 | using Umbraco.Deploy.Artifacts; 6 | using Umbraco.Deploy.Migrators; 7 | 8 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 9 | { 10 | /// 11 | /// Migrates the to replace the editor configuration from Umbraco 7 to . 12 | /// 13 | public class CheckBoxListDataTypeArtifactMigrator : DataTypeConfigurationArtifactMigratorBase 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | public CheckBoxListDataTypeArtifactMigrator() 19 | : base(Constants.PropertyEditors.Aliases.CheckBoxList) 20 | => MaxVersion = new SemVersion(3, 0, 0); 21 | 22 | /// 23 | protected override ValueListConfiguration MigrateConfiguration(IDictionary fromConfiguration) 24 | { 25 | var toConfiguration = new ValueListConfiguration(); 26 | 27 | foreach (var (key, value) in fromConfiguration) 28 | { 29 | if (int.TryParse(key, out var id) && value != null) 30 | { 31 | toConfiguration.Items.Add(new ValueListConfiguration.ValueListItem() 32 | { 33 | Id = id, 34 | Value = value.ToString() 35 | }); 36 | } 37 | } 38 | 39 | return toConfiguration; 40 | } 41 | 42 | /// 43 | protected override ValueListConfiguration GetDefaultConfiguration() 44 | => new ValueListConfiguration(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/DataType/ColorPickerAliasDataTypeArtifactMigrator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Semver; 3 | using Umbraco.Core; 4 | using Umbraco.Core.PropertyEditors; 5 | using Umbraco.Deploy.Artifacts; 6 | using Umbraco.Deploy.Migrators; 7 | 8 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 9 | { 10 | /// 11 | /// Migrates the to replace the editor with and the configuration from Umbraco 7 to . 12 | /// 13 | public class ColorPickerAliasDataTypeArtifactMigrator : ReplaceDataTypeArtifactMigratorBase 14 | { 15 | private const string FromEditorAlias = "Umbraco.ColorPickerAlias"; 16 | private const string TrueValue = "1"; 17 | 18 | /// 19 | /// Initializes a new instance of the class. 20 | /// 21 | /// The property editors. 22 | public ColorPickerAliasDataTypeArtifactMigrator(PropertyEditorCollection propertyEditors) 23 | : base(FromEditorAlias, Constants.PropertyEditors.Aliases.ColorPicker, propertyEditors) 24 | => MaxVersion = new SemVersion(3, 0, 0); 25 | 26 | /// 27 | protected override ColorPickerConfiguration MigrateConfiguration(IDictionary fromConfiguration) 28 | { 29 | var toConfiguration = new ColorPickerConfiguration(); 30 | 31 | foreach (var (key, value) in fromConfiguration) 32 | { 33 | if (key == "useLabel") 34 | { 35 | toConfiguration.UseLabel = TrueValue.Equals(value); 36 | } 37 | else if (int.TryParse(key, out var id) && value != null) 38 | { 39 | toConfiguration.Items.Add(new ValueListConfiguration.ValueListItem() 40 | { 41 | Id = id, 42 | Value = value.ToString() 43 | }); 44 | } 45 | } 46 | 47 | return toConfiguration; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/DataType/ContentPicker2DataTypeArtifactMigrator.cs: -------------------------------------------------------------------------------- 1 | using Umbraco.Core; 2 | using Umbraco.Core.PropertyEditors; 3 | using Umbraco.Deploy.Artifacts; 4 | using Umbraco.Web.PropertyEditors; 5 | 6 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 7 | { 8 | /// 9 | /// Migrates the to replace the editor with and the configuration from Umbraco 7 to . 10 | /// 11 | public class ContentPicker2DataTypeArtifactMigrator : ContentPickerReplaceDataTypeArtifactMigratorBase 12 | { 13 | private const string FromEditorAlias = "Umbraco.ContentPicker2"; 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The property editors. 19 | public ContentPicker2DataTypeArtifactMigrator(PropertyEditorCollection propertyEditors) 20 | : base(FromEditorAlias, propertyEditors) 21 | { } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/DataType/ContentPickerAliasDataTypeArtifactMigrator.cs: -------------------------------------------------------------------------------- 1 | using Umbraco.Core; 2 | using Umbraco.Core.PropertyEditors; 3 | using Umbraco.Deploy.Artifacts; 4 | using Umbraco.Web.PropertyEditors; 5 | 6 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 7 | { 8 | /// 9 | /// Migrates the to replace the editor with and the configuration from Umbraco 7 to . 10 | /// 11 | public class ContentPickerAliasDataTypeArtifactMigrator : ContentPickerReplaceDataTypeArtifactMigratorBase 12 | { 13 | private const string FromEditorAlias = "Umbraco.ContentPickerAlias"; 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The property editors. 19 | public ContentPickerAliasDataTypeArtifactMigrator(PropertyEditorCollection propertyEditors) 20 | : base(FromEditorAlias, propertyEditors) 21 | { } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/DataType/ContentPickerReplaceDataTypeArtifactMigratorBase.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Semver; 3 | using Umbraco.Core; 4 | using Umbraco.Core.PropertyEditors; 5 | using Umbraco.Deploy.Artifacts; 6 | using Umbraco.Deploy.Migrators; 7 | 8 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 9 | { 10 | /// 11 | /// Migrates the to replace the editor alias with and update the configuration. 12 | /// 13 | public abstract class ContentPickerReplaceDataTypeArtifactMigratorBase : ReplaceDataTypeArtifactMigratorBase 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The editor alias to migrate from. 19 | /// The property editors. 20 | protected ContentPickerReplaceDataTypeArtifactMigratorBase(string fromEditorAlias, PropertyEditorCollection propertyEditors) 21 | : base(fromEditorAlias, Constants.PropertyEditors.Aliases.ContentPicker, propertyEditors) 22 | => MaxVersion = new SemVersion(3, 0, 0); 23 | 24 | /// 25 | protected override IDictionary MigrateConfiguration(IDictionary configuration) 26 | { 27 | if (configuration.TryGetValue("startNodeId", out var startNodeIdValue) && 28 | (!(startNodeIdValue?.ToString() is string startNodeIdString) || !Udi.TryParse(startNodeIdString, out _))) 29 | { 30 | // Remove invalid start node id 31 | configuration.Remove("startNodeId"); 32 | } 33 | 34 | return base.MigrateConfiguration(configuration); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/DataType/DateDataTypeArtifactMigrator.cs: -------------------------------------------------------------------------------- 1 | using Semver; 2 | using Umbraco.Core; 3 | using Umbraco.Core.PropertyEditors; 4 | using Umbraco.Deploy.Artifacts; 5 | using Umbraco.Deploy.Migrators; 6 | 7 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 8 | { 9 | /// 10 | /// Migrates the to replace the editor with . 11 | /// 12 | public class DateDataTypeArtifactMigrator : ReplaceDataTypeArtifactMigratorBase 13 | { 14 | private const string FromEditorAlias = "Umbraco.Date"; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The property editors. 20 | public DateDataTypeArtifactMigrator(PropertyEditorCollection propertyEditors) 21 | : base(FromEditorAlias, Constants.PropertyEditors.Aliases.DateTime, propertyEditors) 22 | => MaxVersion = new SemVersion(3, 0, 0); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/DataType/DropDownDataTypeArtifactMigrator.cs: -------------------------------------------------------------------------------- 1 | using Umbraco.Core; 2 | using Umbraco.Core.PropertyEditors; 3 | using Umbraco.Deploy.Artifacts; 4 | 5 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 6 | { 7 | /// 8 | /// Migrates the to replace the editor with and the configuration from Umbraco 7 to . 9 | /// 10 | public class DropDownDataTypeArtifactMigrator : DropDownReplaceDataTypeArtifactMigratorBase 11 | { 12 | private const string FromEditorAlias = "Umbraco.DropDown"; 13 | 14 | /// 15 | protected override bool Multiple => false; 16 | 17 | /// 18 | /// Initializes a new instance of the class. 19 | /// 20 | /// The property editors. 21 | public DropDownDataTypeArtifactMigrator(PropertyEditorCollection propertyEditors) 22 | : base(FromEditorAlias, propertyEditors) 23 | { } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/DataType/DropDownFlexibleDataTypeArtifactMigrator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Semver; 3 | using Umbraco.Core; 4 | using Umbraco.Core.PropertyEditors; 5 | using Umbraco.Deploy.Artifacts; 6 | using Umbraco.Deploy.Migrators; 7 | 8 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 9 | { 10 | /// 11 | /// Migrates the to replace the configuration of the editor from Umbraco 7 to . 12 | /// 13 | public class DropDownFlexibleDataTypeArtifactMigrator : DataTypeConfigurationArtifactMigratorBase 14 | { 15 | private const string TrueValue = "1"; 16 | 17 | /// 18 | /// Initializes a new instance of the class. 19 | /// 20 | public DropDownFlexibleDataTypeArtifactMigrator() 21 | : base(Constants.PropertyEditors.Aliases.DropDownListFlexible) 22 | => MaxVersion = new SemVersion(3, 0, 0); 23 | 24 | /// 25 | protected override DropDownFlexibleConfiguration MigrateConfiguration(IDictionary fromConfiguration) 26 | { 27 | var toConfiguration = new DropDownFlexibleConfiguration() 28 | { 29 | Multiple = true 30 | }; 31 | 32 | foreach (var (key, value) in fromConfiguration) 33 | { 34 | if (key == "multiple") 35 | { 36 | toConfiguration.Multiple = TrueValue.Equals(value); 37 | } 38 | else if (int.TryParse(key, out var id) && value != null) 39 | { 40 | toConfiguration.Items.Add(new ValueListConfiguration.ValueListItem() 41 | { 42 | Id = id, 43 | Value = value.ToString() 44 | }); 45 | } 46 | } 47 | 48 | return toConfiguration; 49 | } 50 | 51 | /// 52 | protected override DropDownFlexibleConfiguration GetDefaultConfiguration() 53 | => new DropDownFlexibleConfiguration() 54 | { 55 | Multiple = true 56 | }; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/DataType/DropDownMultipleDataTypeArtifactMigrator.cs: -------------------------------------------------------------------------------- 1 | using Umbraco.Core; 2 | using Umbraco.Core.PropertyEditors; 3 | using Umbraco.Deploy.Artifacts; 4 | 5 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 6 | { 7 | /// 8 | /// Migrates the to replace the editor with and the configuration from Umbraco 7 to . 9 | /// 10 | public class DropDownMultipleDataTypeArtifactMigrator : DropDownReplaceDataTypeArtifactMigratorBase 11 | { 12 | private const string FromEditorAlias = "Umbraco.DropDownMultiple"; 13 | 14 | /// 15 | protected override bool Multiple => true; 16 | 17 | /// 18 | /// Initializes a new instance of the class. 19 | /// 20 | /// The property editors. 21 | public DropDownMultipleDataTypeArtifactMigrator(PropertyEditorCollection propertyEditors) 22 | : base(FromEditorAlias, propertyEditors) 23 | { } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/DataType/DropDownReplaceDataTypeArtifactMigratorBase.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Semver; 3 | using Umbraco.Core; 4 | using Umbraco.Core.PropertyEditors; 5 | using Umbraco.Deploy.Artifacts; 6 | using Umbraco.Deploy.Migrators; 7 | 8 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 9 | { 10 | /// 11 | /// Migrates the to replace the editor alias with and the configuration from Umbraco 7 to . 12 | /// 13 | public abstract class DropDownReplaceDataTypeArtifactMigratorBase : ReplaceDataTypeArtifactMigratorBase 14 | { 15 | private const string TrueValue = "1"; 16 | 17 | /// 18 | /// Gets a value indicating whether the configuration allows multiple items to be selected. 19 | /// 20 | /// 21 | /// true if multiple items can be selected; otherwise, false. 22 | /// 23 | protected abstract bool Multiple { get; } 24 | 25 | /// 26 | /// Initializes a new instance of the class. 27 | /// 28 | /// The editor alias to migrate from. 29 | /// The property editors. 30 | protected DropDownReplaceDataTypeArtifactMigratorBase(string fromEditorAlias, PropertyEditorCollection propertyEditors) 31 | : base(fromEditorAlias, Constants.PropertyEditors.Aliases.DropDownListFlexible, propertyEditors) 32 | => MaxVersion = new SemVersion(3, 0, 0); 33 | 34 | /// 35 | protected override DropDownFlexibleConfiguration MigrateConfiguration(IDictionary fromConfiguration) 36 | { 37 | var toConfiguration = new DropDownFlexibleConfiguration() 38 | { 39 | Multiple = Multiple 40 | }; 41 | 42 | foreach (var (key, value) in fromConfiguration) 43 | { 44 | if (key == "multiple") 45 | { 46 | toConfiguration.Multiple = TrueValue.Equals(value); 47 | } 48 | else if (int.TryParse(key, out var id) && value != null) 49 | { 50 | toConfiguration.Items.Add(new ValueListConfiguration.ValueListItem() 51 | { 52 | Id = id, 53 | Value = value.ToString() 54 | }); 55 | } 56 | } 57 | 58 | return toConfiguration; 59 | } 60 | 61 | /// 62 | protected override DropDownFlexibleConfiguration GetDefaultConfiguration(IConfigurationEditor toConfigurationEditor) 63 | { 64 | var configuration = base.GetDefaultConfiguration(toConfigurationEditor); 65 | if (configuration != null) 66 | { 67 | configuration.Multiple = Multiple; 68 | } 69 | 70 | return configuration; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/DataType/DropdownlistMultiplePublishKeysDataTypeArtifactMigrator.cs: -------------------------------------------------------------------------------- 1 | using Umbraco.Core; 2 | using Umbraco.Core.PropertyEditors; 3 | using Umbraco.Deploy.Artifacts; 4 | 5 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 6 | { 7 | /// 8 | /// Migrates the to replace the editor with and the configuration from Umbraco 7 to . 9 | /// 10 | public class DropdownlistMultiplePublishKeysDataTypeArtifactMigrator : DropDownReplaceDataTypeArtifactMigratorBase 11 | { 12 | private const string FromEditorAlias = "Umbraco.DropdownlistMultiplePublishKeys"; 13 | 14 | /// 15 | protected override bool Multiple => true; 16 | 17 | /// 18 | /// Initializes a new instance of the class. 19 | /// 20 | /// The property editors. 21 | public DropdownlistMultiplePublishKeysDataTypeArtifactMigrator(PropertyEditorCollection propertyEditors) 22 | : base(FromEditorAlias, propertyEditors) 23 | { } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/DataType/DropdownlistPublishingKeysDataTypeArtifactMigrator.cs: -------------------------------------------------------------------------------- 1 | using Umbraco.Core; 2 | using Umbraco.Core.PropertyEditors; 3 | using Umbraco.Deploy.Artifacts; 4 | 5 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 6 | { 7 | /// 8 | /// Migrates the to replace the editor with and the configuration from Umbraco 7 to . 9 | /// 10 | public class DropdownlistPublishingKeysDataTypeArtifactMigrator : DropDownReplaceDataTypeArtifactMigratorBase 11 | { 12 | private const string FromEditorAlias = "Umbraco.DropdownlistPublishingKeys"; 13 | 14 | /// 15 | protected override bool Multiple => false; 16 | 17 | /// 18 | /// Initializes a new instance of the class. 19 | /// 20 | /// The property editors. 21 | public DropdownlistPublishingKeysDataTypeArtifactMigrator(PropertyEditorCollection propertyEditors) 22 | : base(FromEditorAlias, propertyEditors) 23 | { } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/DataType/MediaPicker2DataTypeArtifactMigrator.cs: -------------------------------------------------------------------------------- 1 | using Umbraco.Core; 2 | using Umbraco.Core.PropertyEditors; 3 | using Umbraco.Deploy.Artifacts; 4 | using Umbraco.Web.PropertyEditors; 5 | 6 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 7 | { 8 | /// 9 | /// Migrates the to replace the editor with and the configuration from Umbraco 7 to . 10 | /// 11 | public class MediaPicker2DataTypeArtifactMigrator : MediaPickerReplaceDataTypeArtifactMigratorBase 12 | { 13 | private const string FromEditorAlias = "Umbraco.MediaPicker2"; 14 | 15 | /// 16 | protected override bool Multiple => false; 17 | 18 | /// 19 | /// Initializes a new instance of the class. 20 | /// 21 | /// The property editors. 22 | public MediaPicker2DataTypeArtifactMigrator(PropertyEditorCollection propertyEditors) 23 | : base(FromEditorAlias, propertyEditors) 24 | { } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/DataType/MediaPickerDataTypeArtifactMigrator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Semver; 3 | using Umbraco.Core; 4 | using Umbraco.Deploy.Artifacts; 5 | using Umbraco.Deploy.Migrators; 6 | 7 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 8 | { 9 | /// 10 | /// Migrates the to update the editor configuration. 11 | /// 12 | public class MediaPickerDataTypeArtifactMigrator : DataTypeConfigurationArtifactMigratorBase 13 | { 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | public MediaPickerDataTypeArtifactMigrator() 18 | : base(Constants.PropertyEditors.Aliases.MediaPicker) 19 | => MaxVersion = new SemVersion(3, 0, 0); 20 | 21 | /// 22 | protected override IDictionary MigrateConfiguration(IDictionary fromConfiguration) 23 | { 24 | if (fromConfiguration.TryGetValue("startNodeId", out var startNodeIdValue) && 25 | (!(startNodeIdValue?.ToString() is string startNodeId) || !Udi.TryParse(startNodeId, out _))) 26 | { 27 | // Remove invalid start node ID 28 | fromConfiguration.Remove("startNodeId"); 29 | } 30 | 31 | return fromConfiguration; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/DataType/MediaPickerReplaceDataTypeArtifactMigratorBase.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Semver; 3 | using Umbraco.Core; 4 | using Umbraco.Core.PropertyEditors; 5 | using Umbraco.Deploy.Artifacts; 6 | using Umbraco.Deploy.Migrators; 7 | 8 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 9 | { 10 | /// 11 | /// Migrates the to replace the editor alias with and update the configuration. 12 | /// 13 | public abstract class MediaPickerReplaceDataTypeArtifactMigratorBase : ReplaceDataTypeArtifactMigratorBase 14 | { 15 | /// 16 | /// Gets a value indicating whether the configuration allows multiple items to be picked. 17 | /// 18 | /// 19 | /// true if multiple items can be picked; otherwise, false. 20 | /// 21 | protected abstract bool Multiple { get; } 22 | 23 | /// 24 | /// Initializes a new instance of the class. 25 | /// 26 | /// From editor alias. 27 | /// The property editors. 28 | protected MediaPickerReplaceDataTypeArtifactMigratorBase(string fromEditorAlias, PropertyEditorCollection propertyEditors) 29 | : base(fromEditorAlias, Constants.PropertyEditors.Aliases.MediaPicker, propertyEditors) 30 | => MaxVersion = new SemVersion(3, 0, 0); 31 | 32 | /// 33 | protected override IDictionary MigrateConfiguration(IDictionary configuration) 34 | { 35 | if (!configuration.ContainsKey("multiPicker")) 36 | { 37 | configuration["multiPicker"] = Multiple; 38 | } 39 | 40 | if (configuration.TryGetValue("startNodeId", out var startNodeIdValue) && 41 | (!(startNodeIdValue?.ToString() is string startNodeId) || !Udi.TryParse(startNodeId, out _))) 42 | { 43 | // Remove invalid start node ID 44 | configuration.Remove("startNodeId"); 45 | } 46 | 47 | return configuration; 48 | } 49 | 50 | /// 51 | protected override IDictionary GetDefaultConfiguration(IConfigurationEditor toConfigurationEditor) 52 | { 53 | var configuration = base.GetDefaultConfiguration(toConfigurationEditor); 54 | if (configuration != null) 55 | { 56 | configuration["multiPicker"] = Multiple; 57 | } 58 | 59 | return configuration; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/DataType/MemberPicker2DataTypeArtifactMigrator.cs: -------------------------------------------------------------------------------- 1 | using Semver; 2 | using Umbraco.Core; 3 | using Umbraco.Core.PropertyEditors; 4 | using Umbraco.Deploy.Artifacts; 5 | using Umbraco.Deploy.Migrators; 6 | 7 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 8 | { 9 | /// 10 | /// Migrates the to replace the editor with . 11 | /// 12 | public class MemberPicker2DataTypeArtifactMigrator : ReplaceDataTypeArtifactMigratorBase 13 | { 14 | private const string FromEditorAlias = "Umbraco.MemberPicker2"; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The property editors. 20 | public MemberPicker2DataTypeArtifactMigrator(PropertyEditorCollection propertyEditors) 21 | : base(FromEditorAlias, Constants.PropertyEditors.Aliases.MemberPicker, propertyEditors) 22 | => MaxVersion = new SemVersion(3, 0, 0); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/DataType/MultiNodeTreePicker2DataTypeArtifactMigrator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Newtonsoft.Json.Linq; 3 | using Semver; 4 | using Umbraco.Core; 5 | using Umbraco.Core.PropertyEditors; 6 | using Umbraco.Deploy.Artifacts; 7 | using Umbraco.Deploy.Migrators; 8 | 9 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 10 | { 11 | /// 12 | /// Migrates the to replace the editor with and update the configuration. 13 | /// 14 | public class MultiNodeTreePicker2DataTypeArtifactMigrator : ReplaceDataTypeArtifactMigratorBase 15 | { 16 | private const string FromEditorAlias = "Umbraco.MultiNodeTreePicker2"; 17 | 18 | /// 19 | /// Initializes a new instance of the class. 20 | /// 21 | /// The property editors. 22 | public MultiNodeTreePicker2DataTypeArtifactMigrator(PropertyEditorCollection propertyEditors) 23 | : base(FromEditorAlias, Constants.PropertyEditors.Aliases.MultiNodeTreePicker, propertyEditors) 24 | => MaxVersion = new SemVersion(3, 0, 0); 25 | 26 | /// 27 | protected override IDictionary MigrateConfiguration(IDictionary configuration) 28 | { 29 | if (configuration.TryGetValue("startNode", out var startNodeValue) && 30 | startNodeValue is JObject startNode && 31 | startNode["id"] is JValue idValue && 32 | (!(idValue.Value?.ToString() is string id) || !Udi.TryParse(id, out _))) 33 | { 34 | // Remove invalid start node ID 35 | startNode.Remove("id"); 36 | } 37 | 38 | return configuration; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/DataType/MultiNodeTreePickerDataTypeArtifactMigrator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Newtonsoft.Json.Linq; 3 | using Semver; 4 | using Umbraco.Core; 5 | using Umbraco.Deploy.Artifacts; 6 | using Umbraco.Deploy.Migrators; 7 | 8 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 9 | { 10 | /// 11 | /// Migrates the to update the editor configuration. 12 | /// 13 | public class MultiNodeTreePickerDataTypeArtifactMigrator : DataTypeConfigurationArtifactMigratorBase 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | public MultiNodeTreePickerDataTypeArtifactMigrator() 19 | : base(Constants.PropertyEditors.Aliases.MultiNodeTreePicker) 20 | => MaxVersion = new SemVersion(3, 0, 0); 21 | 22 | /// 23 | protected override IDictionary MigrateConfiguration(IDictionary fromConfiguration) 24 | { 25 | if (fromConfiguration.TryGetValue("startNode", out var startNodeValue) && 26 | startNodeValue is JObject startNode && 27 | startNode["id"] is JValue idValue && 28 | (!(idValue.Value?.ToString() is string id) || !Udi.TryParse(id, out _))) 29 | { 30 | // Remove invalid start node ID 31 | startNode.Remove("id"); 32 | } 33 | 34 | return fromConfiguration; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/DataType/MultiUrlPickerReplaceDataTypeArtifactMigratorBase.cs: -------------------------------------------------------------------------------- 1 | using Semver; 2 | using Umbraco.Core; 3 | using Umbraco.Core.PropertyEditors; 4 | using Umbraco.Deploy.Artifacts; 5 | using Umbraco.Deploy.Migrators; 6 | 7 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 8 | { 9 | /// 10 | /// Migrates the to replace the editor alias with . 11 | /// 12 | public abstract class MultiUrlPickerReplaceDataTypeArtifactMigratorBase : ReplaceDataTypeArtifactMigratorBase 13 | { 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// The editor alias to migrate from. 18 | /// The property editors. 19 | protected MultiUrlPickerReplaceDataTypeArtifactMigratorBase(string fromEditorAlias, PropertyEditorCollection propertyEditors) 20 | : base(fromEditorAlias, Constants.PropertyEditors.Aliases.MultiUrlPicker, propertyEditors) 21 | => MaxVersion = new SemVersion(3, 0, 0); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/DataType/MultipleMediaPickerDataTypeArtifactMigrator.cs: -------------------------------------------------------------------------------- 1 | using Umbraco.Core; 2 | using Umbraco.Core.PropertyEditors; 3 | using Umbraco.Deploy.Artifacts; 4 | 5 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 6 | { 7 | /// 8 | /// Migrates the to replace the editor with and update the configuration. 9 | /// 10 | public class MultipleMediaPickerDataTypeArtifactMigrator : MediaPickerReplaceDataTypeArtifactMigratorBase 11 | { 12 | private const string FromEditorAlias = "Umbraco.MultipleMediaPicker"; 13 | 14 | /// 15 | protected override bool Multiple => true; 16 | 17 | /// 18 | /// Initializes a new instance of the class. 19 | /// 20 | /// The property editors. 21 | public MultipleMediaPickerDataTypeArtifactMigrator(PropertyEditorCollection propertyEditors) 22 | : base(FromEditorAlias, propertyEditors) 23 | { } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/DataType/NoEditDataTypeArtifactMigrator.cs: -------------------------------------------------------------------------------- 1 | using Semver; 2 | using Umbraco.Core; 3 | using Umbraco.Core.PropertyEditors; 4 | using Umbraco.Deploy.Artifacts; 5 | using Umbraco.Deploy.Migrators; 6 | 7 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 8 | { 9 | /// 10 | /// Migrates the to replace the editor with . 11 | /// 12 | public class NoEditDataTypeArtifactMigrator : ReplaceDataTypeArtifactMigratorBase 13 | { 14 | private const string FromEditorAlias = "Umbraco.NoEdit"; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The property editors. 20 | public NoEditDataTypeArtifactMigrator(PropertyEditorCollection propertyEditors) 21 | : base(FromEditorAlias, Constants.PropertyEditors.Aliases.Label, propertyEditors) 22 | => MaxVersion = new SemVersion(3, 0, 0); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/DataType/PreValuesDataTypeArtifactJsonMigrator.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Newtonsoft.Json.Linq; 3 | using Semver; 4 | using Umbraco.Core; 5 | using Umbraco.Deploy.Artifacts; 6 | using Umbraco.Deploy.Migrators; 7 | 8 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 9 | { 10 | /// 11 | /// Migrates the JSON from Umbraco 7 pre-values to configuration objects/arrays. 12 | /// 13 | public class PreValuesDataTypeArtifactJsonMigrator : ArtifactJsonMigratorBase 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | public PreValuesDataTypeArtifactJsonMigrator() 19 | => MaxVersion = new SemVersion(3, 0, 0); 20 | 21 | /// 22 | public override JToken Migrate(JToken artifactJson) 23 | { 24 | if (artifactJson["PreValues"] is JObject preValues) 25 | { 26 | var configuration = new JObject(); 27 | 28 | foreach (var property in preValues.Properties()) 29 | { 30 | var propertyValue = property.Value; 31 | 32 | // Convert pre-value serialized JSON to actual JSON objects/arrays 33 | if (propertyValue.Type == JTokenType.String && 34 | propertyValue.Value() is string value) 35 | { 36 | if (string.IsNullOrEmpty(value)) 37 | { 38 | // Skip empty value 39 | continue; 40 | } 41 | else if (value.DetectIsJson()) 42 | { 43 | propertyValue = JToken.Parse(value); 44 | } 45 | } 46 | 47 | configuration.Add(property.Name, propertyValue); 48 | } 49 | 50 | artifactJson["Configuration"] = configuration; 51 | } 52 | 53 | return artifactJson; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/DataType/RadioButtonListDataTypeArtifactMigrator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Semver; 3 | using Umbraco.Core; 4 | using Umbraco.Core.Models; 5 | using Umbraco.Core.PropertyEditors; 6 | using Umbraco.Deploy.Artifacts; 7 | using Umbraco.Deploy.Migrators; 8 | 9 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 10 | { 11 | /// 12 | /// Migrates the to replace the configuration of the editor from Umbraco 7 to . 13 | /// 14 | public class RadioButtonListDataTypeArtifactMigrator : DataTypeConfigurationArtifactMigratorBase 15 | { 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | public RadioButtonListDataTypeArtifactMigrator() 20 | : base(Constants.PropertyEditors.Aliases.RadioButtonList) 21 | => MaxVersion = new SemVersion(3, 0, 0); 22 | 23 | /// 24 | protected override DataTypeArtifact Migrate(DataTypeArtifact artifact) 25 | { 26 | artifact.DatabaseType = ValueStorageType.Nvarchar; 27 | 28 | return base.Migrate(artifact); 29 | } 30 | 31 | /// 32 | protected override ValueListConfiguration MigrateConfiguration(IDictionary fromConfiguration) 33 | { 34 | var toConfiguration = new ValueListConfiguration(); 35 | 36 | foreach (var (key, value) in fromConfiguration) 37 | { 38 | if (int.TryParse(key, out var id) && value != null) 39 | { 40 | toConfiguration.Items.Add(new ValueListConfiguration.ValueListItem() 41 | { 42 | Id = id, 43 | Value = value.ToString() 44 | }); 45 | } 46 | } 47 | 48 | return toConfiguration; 49 | } 50 | 51 | /// 52 | protected override ValueListConfiguration GetDefaultConfiguration() 53 | => new ValueListConfiguration(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/DataType/RelatedLinks2DataTypeArtifactMigrator.cs: -------------------------------------------------------------------------------- 1 | using Umbraco.Core; 2 | using Umbraco.Core.PropertyEditors; 3 | using Umbraco.Deploy.Artifacts; 4 | using Umbraco.Web.PropertyEditors; 5 | 6 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 7 | { 8 | /// 9 | /// Migrates the to replace the editor with and the configuration from Umbraco 7 to . 10 | /// 11 | public class RelatedLinks2DataTypeArtifactMigrator : MultiUrlPickerReplaceDataTypeArtifactMigratorBase 12 | { 13 | private const string FromEditorAlias = "Umbraco.RelatedLinks2"; 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The property editors. 19 | public RelatedLinks2DataTypeArtifactMigrator(PropertyEditorCollection propertyEditors) 20 | : base(FromEditorAlias, propertyEditors) 21 | { } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/DataType/RelatedLinksDataTypeArtifactMigrator.cs: -------------------------------------------------------------------------------- 1 | using Umbraco.Core; 2 | using Umbraco.Core.PropertyEditors; 3 | using Umbraco.Deploy.Artifacts; 4 | using Umbraco.Web.PropertyEditors; 5 | 6 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 7 | { 8 | /// 9 | /// Migrates the to replace the editor with and the configuration from Umbraco 7 to . 10 | /// 11 | public class RelatedLinksDataTypeArtifactMigrator : MultiUrlPickerReplaceDataTypeArtifactMigratorBase 12 | { 13 | private const string FromEditorAlias = "Umbraco.RelatedLinks"; 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The property editors. 19 | public RelatedLinksDataTypeArtifactMigrator(PropertyEditorCollection propertyEditors) 20 | : base(FromEditorAlias, propertyEditors) 21 | { } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/DataType/TextboxDataTypeArtifactMigrator.cs: -------------------------------------------------------------------------------- 1 | using Semver; 2 | using Umbraco.Core; 3 | using Umbraco.Core.PropertyEditors; 4 | using Umbraco.Deploy.Artifacts; 5 | using Umbraco.Deploy.Migrators; 6 | 7 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 8 | { 9 | /// 10 | /// Migrates the to replace the editor with . 11 | /// 12 | public class TextboxDataTypeArtifactMigrator : ReplaceDataTypeArtifactMigratorBase 13 | { 14 | private const string FromEditorAlias = "Umbraco.Textbox"; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The property editors. 20 | public TextboxDataTypeArtifactMigrator(PropertyEditorCollection propertyEditors) 21 | : base(FromEditorAlias, Constants.PropertyEditors.Aliases.TextBox, propertyEditors) 22 | => MaxVersion = new SemVersion(3, 0, 0); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/DataType/TextboxMultipleDataTypeArtifactMigrator.cs: -------------------------------------------------------------------------------- 1 | using Semver; 2 | using Umbraco.Core; 3 | using Umbraco.Core.PropertyEditors; 4 | using Umbraco.Deploy.Artifacts; 5 | using Umbraco.Deploy.Migrators; 6 | 7 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 8 | { 9 | /// 10 | /// Migrates the to replace the editor with . 11 | /// 12 | public class TextboxMultipleDataTypeArtifactMigrator : ReplaceDataTypeArtifactMigratorBase 13 | { 14 | private const string FromEditorAlias = "Umbraco.TextboxMultiple"; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The property editors. 20 | public TextboxMultipleDataTypeArtifactMigrator(PropertyEditorCollection propertyEditors) 21 | : base(FromEditorAlias, Constants.PropertyEditors.Aliases.TextArea, propertyEditors) 22 | => MaxVersion = new SemVersion(3, 0, 0); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Migrators/Legacy/DataType/TinyMCEv3DataTypeArtifactMigrator.cs: -------------------------------------------------------------------------------- 1 | using Semver; 2 | using Umbraco.Core; 3 | using Umbraco.Core.PropertyEditors; 4 | using Umbraco.Deploy.Artifacts; 5 | using Umbraco.Deploy.Migrators; 6 | 7 | namespace Umbraco.Deploy.Contrib.Migrators.Legacy 8 | { 9 | /// 10 | /// Migrates the to replace the editor with . 11 | /// 12 | public class TinyMCEv3DataTypeArtifactMigrator : ReplaceDataTypeArtifactMigratorBase 13 | { 14 | private const string FromEditorAlias = "Umbraco.TinyMCEv3"; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The property editors. 20 | public TinyMCEv3DataTypeArtifactMigrator(PropertyEditorCollection propertyEditors) 21 | : base(FromEditorAlias, Constants.PropertyEditors.Aliases.TinyMce, propertyEditors) 22 | => MaxVersion = new SemVersion(3, 0, 0); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Serialization/LegacyArtifactTypeResolver.cs: -------------------------------------------------------------------------------- 1 | using Semver; 2 | using Umbraco.Deploy.Serialization; 3 | 4 | namespace Umbraco.Deploy.Contrib.Connectors.Serialization 5 | { 6 | /// 7 | /// Resolves legacy artifact type names from Umbraco 7. 8 | /// 9 | public sealed class LegacyArtifactTypeResolver : ArtifactTypeResolverBase 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | public LegacyArtifactTypeResolver() 15 | => MaxVersion = new SemVersion(3, 0, 0); 16 | 17 | /// 18 | protected override string ResolveTypeName(string typeName) 19 | { 20 | // v2 to v4 21 | switch (typeName) 22 | { 23 | // Content 24 | case "Umbraco.Deploy.Artifacts.DocumentArtifact": 25 | typeName = "Umbraco.Deploy.Artifacts.Content.DocumentArtifact"; 26 | break; 27 | case "Umbraco.Deploy.Artifacts.MediaArtifact": 28 | typeName = "Umbraco.Deploy.Artifacts.Content.MediaArtifact"; 29 | break; 30 | case "Umbraco.Deploy.Artifacts.MemberArtifact": 31 | typeName = "Umbraco.Deploy.Artifacts.Content.MemberArtifact"; 32 | break; 33 | // Content types 34 | case "Umbraco.Deploy.Artifacts.DocumentTypeArtifact": 35 | typeName = "Umbraco.Deploy.Artifacts.ContentType.DocumentTypeArtifact"; 36 | break; 37 | case "Umbraco.Deploy.Artifacts.MediaTypeArtifact": 38 | typeName = "Umbraco.Deploy.Artifacts.ContentType.MediaTypeArtifact"; 39 | break; 40 | case "Umbraco.Deploy.Artifacts.MemberTypeArtifact": 41 | typeName = "Umbraco.Deploy.Artifacts.ContentType.MemberTypeArtifact"; 42 | break; 43 | case "Umbraco.Deploy.Artifacts.RelationTypeArtifact": 44 | typeName = "Umbraco.Deploy.Artifacts.ContentType.RelationTypeArtifact"; 45 | break; 46 | } 47 | 48 | return typeName; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/Umbraco.Deploy.Contrib.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | UmbracoDeploy.Contrib 4 | Umbraco Deploy - Contrib 5 | This project contains community contributions for the Umbraco deployment engine, Umbraco Deploy. Primarily this project offers ValueConnectors for the most popular Umbraco community packages - these are used by Umbraco Deploy (and Umbraco Cloud) to aid with the deployment and transferring of content/property-data to a target environment. 6 | Umbraco.Deploy.Contrib.Connectors 7 | Umbraco.Deploy.Contrib 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/ValueConnectors/BlockEditorValueConnector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Newtonsoft.Json; 5 | using Newtonsoft.Json.Linq; 6 | using Umbraco.Core; 7 | using Umbraco.Core.Deploy; 8 | using Umbraco.Core.Logging; 9 | using Umbraco.Core.Models; 10 | using Umbraco.Core.Services; 11 | using Umbraco.Deploy.Connectors; 12 | using Umbraco.Deploy.Connectors.ValueConnectors; 13 | using Umbraco.Deploy.Connectors.ValueConnectors.Services; 14 | using Umbraco.Deploy.Core; 15 | using Umbraco.Deploy.Extensions; 16 | 17 | namespace Umbraco.Deploy.Contrib.Connectors.ValueConnectors 18 | { 19 | /// 20 | /// A Deploy connector for BlockEditor based property editors (ie. BlockList) 21 | /// 22 | [Obsolete("Use Umbraco.Deploy.Connectors.ValueConnectors.BlockValueConnectorBase instead to support recursive migrators. This class will be removed in a future version.")] 23 | public abstract class BlockEditorValueConnector : ValueConnectorBase 24 | { 25 | private readonly IContentTypeService _contentTypeService; 26 | private readonly Lazy _valueConnectorsLazy; 27 | private readonly ILogger _logger; 28 | 29 | /// 30 | public override IEnumerable PropertyEditorAliases { get; } = Enumerable.Empty(); 31 | 32 | // cannot inject ValueConnectorCollection directly as it would cause a circular (recursive) dependency, 33 | // so we have to inject it lazily and use the lazy value when actually needing it 34 | private ValueConnectorCollection ValueConnectors => _valueConnectorsLazy.Value; 35 | 36 | public BlockEditorValueConnector(IContentTypeService contentTypeService, Lazy valueConnectors, ILogger logger) 37 | { 38 | _contentTypeService = contentTypeService ?? throw new ArgumentNullException(nameof(contentTypeService)); 39 | _valueConnectorsLazy = valueConnectors ?? throw new ArgumentNullException(nameof(valueConnectors)); 40 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 41 | } 42 | 43 | public override string ToArtifact(object value, PropertyType propertyType, ICollection dependencies, IContextCache contextCache) 44 | { 45 | _logger.Debug("Converting {PropertyType} to artifact.", propertyType.Alias); 46 | var valueAsString = value as string; 47 | 48 | // nested values will arrive here as JObject - convert to string to enable reuse of same code as when non-nested. 49 | if (value is JObject) 50 | { 51 | _logger.Debug("Value is a JObject - converting to string."); 52 | valueAsString = value.ToString(); 53 | } 54 | 55 | if (string.IsNullOrWhiteSpace(valueAsString)) 56 | { 57 | _logger.Debug($"Value is null or whitespace. Skipping conversion to artifact."); 58 | return null; 59 | } 60 | 61 | if (!valueAsString.TryParseJson(out BlockEditorValue blockEditorValue)) 62 | { 63 | _logger.Warn("Value '{Value}' is not a JSON string. Skipping conversion to artifact.", valueAsString); 64 | return null; 65 | } 66 | 67 | if (blockEditorValue == null) 68 | { 69 | _logger.Warn("Deserialized value is null. Skipping conversion to artifact."); 70 | return null; 71 | } 72 | 73 | var allBlocks = blockEditorValue.Content.Concat(blockEditorValue.Settings ?? Enumerable.Empty()).ToList(); 74 | 75 | // get all the content types used in block editor items 76 | var allContentTypes = allBlocks.Select(x => x.ContentTypeKey) 77 | .Distinct() 78 | .ToDictionary(a => a, a => 79 | { 80 | if (!Guid.TryParse(a, out var keyAsGuid)) 81 | { 82 | throw new InvalidOperationException($"Could not parse ContentTypeKey as GUID {keyAsGuid}."); 83 | } 84 | 85 | return contextCache.GetContentTypeByKey(_contentTypeService, keyAsGuid); 86 | }); 87 | 88 | //Ensure all of these content types are found 89 | if (allContentTypes.Values.Any(contentType => contentType == null)) 90 | { 91 | throw new InvalidOperationException($"Could not resolve these content types for the Block Editor property: {string.Join(",", allContentTypes.Where(x => x.Value == null).Select(x => x.Key))}"); 92 | } 93 | 94 | //Ensure that these content types have dependencies added 95 | foreach (var contentType in allContentTypes.Values) 96 | { 97 | _logger.Debug("Adding dependency for content type {ContentType}.", contentType.Alias); 98 | dependencies.Add(new ArtifactDependency(contentType.GetUdi(), false, ArtifactDependencyMode.Match)); 99 | } 100 | 101 | foreach (var block in allBlocks) 102 | { 103 | var contentType = allContentTypes[block.ContentTypeKey]; 104 | 105 | if (block.PropertyValues != null) 106 | { 107 | foreach (var key in block.PropertyValues.Keys.ToArray()) 108 | { 109 | var innerPropertyType = contentType.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == key); 110 | 111 | if (innerPropertyType == null) 112 | { 113 | _logger.Warn("No property type found with alias {PropertyType} on content type {ContentType}.", key, contentType.Alias); 114 | continue; 115 | } 116 | 117 | // fetch the right value connector from the collection of connectors, intended for use with this property type. 118 | // throws if not found - no need for a null check 119 | var propertyValueConnector = ValueConnectors.Get(innerPropertyType); 120 | 121 | // pass the value, property type and the dependencies collection to the connector to get a "artifact" value 122 | var innerValue = block.PropertyValues[key]; 123 | object parsedValue = propertyValueConnector.ToArtifact(innerValue, innerPropertyType, dependencies, contextCache); 124 | 125 | _logger.Debug("Mapped {Key} value '{PropertyValue}' to '{ParsedValue}' using {PropertyValueConnectorType} for {PropertyType}.", key, block.PropertyValues[key], parsedValue, propertyValueConnector.GetType(), innerPropertyType.Alias); 126 | 127 | parsedValue = parsedValue?.ToString(); 128 | 129 | block.PropertyValues[key] = parsedValue; 130 | } 131 | } 132 | } 133 | 134 | value = JsonConvert.SerializeObject(blockEditorValue); 135 | _logger.Debug("Finished converting {PropertyType} to artifact.", propertyType.Alias); 136 | return (string)value; 137 | } 138 | 139 | public override object FromArtifact(string value, PropertyType propertyType, object currentValue, IContextCache contextCache) 140 | { 141 | _logger.Debug("Converting {PropertyType} from artifact.", propertyType.Alias); 142 | if (string.IsNullOrWhiteSpace(value)) 143 | { 144 | return value; 145 | } 146 | 147 | if (!value.TryParseJson(out BlockEditorValue blockEditorValue)) 148 | { 149 | return value; 150 | } 151 | 152 | if (blockEditorValue == null) 153 | { 154 | return value; 155 | } 156 | 157 | var allBlocks = blockEditorValue.Content.Concat(blockEditorValue.Settings ?? Enumerable.Empty()).ToList(); 158 | 159 | var allContentTypes = allBlocks.Select(x => x.ContentTypeKey) 160 | .Distinct() 161 | .ToDictionary(a => a, a => 162 | { 163 | if (!Guid.TryParse(a, out var keyAsGuid)) 164 | { 165 | throw new InvalidOperationException($"Could not parse ContentTypeKey as GUID {keyAsGuid}."); 166 | } 167 | 168 | return contextCache.GetContentTypeByKey(_contentTypeService, keyAsGuid); 169 | }); 170 | 171 | //Ensure all of these content types are found 172 | if (allContentTypes.Values.Any(contentType => contentType == null)) 173 | { 174 | throw new InvalidOperationException($"Could not resolve these content types for the Block Editor property: {string.Join(",", allContentTypes.Where(x => x.Value == null).Select(x => x.Key))}"); 175 | } 176 | 177 | foreach (var block in allBlocks) 178 | { 179 | var contentType = allContentTypes[block.ContentTypeKey]; 180 | 181 | if (block.PropertyValues != null) 182 | { 183 | foreach (var key in block.PropertyValues.Keys.ToArray()) 184 | { 185 | var innerPropertyType = contentType.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == key); 186 | 187 | if (innerPropertyType == null) 188 | { 189 | _logger.Warn("No property type found with alias {Key} on content type {ContentType}.", key, contentType.Alias); 190 | continue; 191 | } 192 | 193 | // fetch the right value connector from the collection of connectors, intended for use with this property type. 194 | // throws if not found - no need for a null check 195 | var propertyValueConnector = ValueConnectors.Get(innerPropertyType); 196 | 197 | var innerValue = block.PropertyValues[key]; 198 | 199 | if (innerValue != null) 200 | { 201 | // pass the artifact value and property type to the connector to get a real value from the artifact 202 | var convertedValue = propertyValueConnector.FromArtifact(innerValue.ToString(), innerPropertyType, null, contextCache); 203 | 204 | if (convertedValue == null) 205 | { 206 | block.PropertyValues[key] = null; 207 | } 208 | else 209 | { 210 | block.PropertyValues[key] = convertedValue; 211 | } 212 | _logger.Debug("Mapped {Key} value '{PropertyValue}' to '{ConvertedValue}' using {PropertyValueConnectorType} for {PropertyType}.", key, innerValue, convertedValue, propertyValueConnector.GetType(), innerPropertyType.Alias); 213 | } 214 | else 215 | { 216 | block.PropertyValues[key] = innerValue; 217 | _logger.Debug("{Key} value was null. Setting value as null without conversion.", key); 218 | } 219 | } 220 | } 221 | } 222 | 223 | _logger.Debug("Finished converting {PropertyType} from artifact.", propertyType.Alias); 224 | 225 | return JObject.FromObject(blockEditorValue); 226 | } 227 | 228 | /// 229 | /// Strongly typed representation of the stored value for a block editor value 230 | /// 231 | /// 232 | /// Example JSON: 233 | /// 267 | /// 268 | public class BlockEditorValue 269 | { 270 | /// 271 | /// We do not have to actually handle anything in the layout since it should only contain references to items existing as data. 272 | /// JObject is fine for transferring this over. 273 | /// 274 | [JsonProperty("layout")] 275 | public JObject Layout { get; set; } 276 | 277 | /// 278 | /// This contains all the blocks created in the block editor. 279 | /// 280 | [JsonProperty("contentData")] 281 | public IEnumerable Content { get; set; } 282 | 283 | /// 284 | /// This contains the settings associated with the block editor. 285 | /// 286 | [JsonProperty("settingsData")] 287 | public IEnumerable Settings { get; set; } 288 | } 289 | 290 | public class Block 291 | { 292 | [JsonProperty("contentTypeKey")] 293 | public string ContentTypeKey { get; set; } 294 | 295 | [JsonProperty("udi")] 296 | public string Udi { get; set; } 297 | 298 | /// 299 | /// This is the property values defined on the block. 300 | /// These can be anything so we have to use a dictionary to represent them and JsonExtensionData attribute ensures all otherwise unmapped properties are stored here. 301 | /// 302 | [JsonExtensionData] 303 | public IDictionary PropertyValues { get; set; } 304 | } 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/ValueConnectors/BlockListValueConnector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Umbraco.Core.Logging; 4 | using Umbraco.Core.Services; 5 | using Umbraco.Deploy.Connectors.ValueConnectors.Services; 6 | 7 | namespace Umbraco.Deploy.Contrib.Connectors.ValueConnectors 8 | { 9 | /// 10 | /// A Deploy connector for the BlockList property editor 11 | /// 12 | [Obsolete("Deploy 4.9.0 adds an explicit binding to use Umbraco.Deploy.Connectors.ValueConnectors.BlockEditorValueConnector instead to support recursive migrators. This class will be removed in a future version.")] 13 | public class BlockListValueConnector : BlockEditorValueConnector 14 | { 15 | public override IEnumerable PropertyEditorAliases { get; } = new[] 16 | { 17 | "Umbraco.BlockList" 18 | }; 19 | 20 | public BlockListValueConnector(IContentTypeService contentTypeService, Lazy valueConnectors, ILogger logger) 21 | : base(contentTypeService, valueConnectors, logger) 22 | { } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/ValueConnectors/MultiUrlPickerValueConnector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text.RegularExpressions; 4 | using Newtonsoft.Json; 5 | using Newtonsoft.Json.Linq; 6 | using Umbraco.Core; 7 | using Umbraco.Core.Deploy; 8 | using Umbraco.Core.Logging; 9 | using Umbraco.Core.Models; 10 | using Umbraco.Core.Services; 11 | using Umbraco.Deploy.Connectors.ValueConnectors; 12 | using Umbraco.Deploy.Core; 13 | 14 | namespace Umbraco.Deploy.Contrib.Connectors.ValueConnectors 15 | { 16 | [Obsolete("Deploy 4.9.0 adds an explicit binding to use Umbraco.Deploy.Connectors.ValueConnectors.MultiUrlPickerValueConnector instead to support recursive migrators. This class will be removed in a future version.")] 17 | public class MultiUrlPickerValueConnector : ValueConnectorBase 18 | { 19 | private readonly IEntityService _entityService; 20 | private readonly IMediaService _mediaService; 21 | private readonly ILogger _logger; 22 | 23 | // Used to fetch the udi from a umb://-based url 24 | private static readonly Regex MediaUdiSrcRegex = new Regex(@"(?umb://media/[A-z0-9]+)", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); 25 | 26 | /// 27 | public override IEnumerable PropertyEditorAliases { get; } = new[] 28 | { 29 | "Umbraco.MultiUrlPicker" 30 | }; 31 | 32 | /// 33 | /// Initializes a new instance of the class. 34 | /// Source found here: https://github.com/rasmusjp/umbraco-multi-url-picker 35 | /// MultiUrlPicker can have a list of links, these can be document links, links to files in the 36 | /// media library, or just a text string with a link to whatever. We need to resolve if its a link 37 | /// to a document or a file in the media library, and transfer it as an artifact to the target environment. 38 | /// The object value is stored as json, and is an array of Links, defined with the following properties 39 | /// Link = { 40 | /// id, 41 | /// name, 42 | /// url, 43 | /// target, 44 | /// isMedia, 45 | /// icon 46 | /// } 47 | /// https://github.com/rasmusjp/umbraco-multi-url-picker/blob/master/src/RJP.MultiUrlPicker/App_Plugins/RJP.MultiUrlPicker/MultiUrlPicker.js#L120 48 | /// 49 | /// An implementation. 50 | /// 51 | /// 52 | public MultiUrlPickerValueConnector(IEntityService entityService, IMediaService mediaService, ILogger logger) 53 | { 54 | _entityService = entityService ?? throw new ArgumentNullException(nameof(entityService)); 55 | _mediaService = mediaService ?? throw new ArgumentNullException(nameof(mediaService)); 56 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 57 | } 58 | 59 | public sealed override string ToArtifact(object value, PropertyType propertyType, ICollection dependencies, IContextCache contextCache) 60 | { 61 | var svalue = value as string; 62 | if (string.IsNullOrWhiteSpace(svalue)) 63 | { 64 | return null; 65 | } 66 | 67 | var valueAsJToken = JToken.Parse(svalue); 68 | if (valueAsJToken is JArray) 69 | { 70 | // Multiple links, parse as JArray 71 | var links = JsonConvert.DeserializeObject(svalue); 72 | if (links == null) 73 | { 74 | return null; 75 | } 76 | 77 | foreach (var link in links) 78 | { 79 | var isMedia = link["isMedia"] != null; 80 | 81 | // Only do processing if the Id is set on the element. OR if the url is set and its a media item 82 | if (TryParseJTokenAttr(link, "id", out int intId)) 83 | { 84 | // Checks weather we are resolving a media item or a document 85 | var objectTypeId = isMedia 86 | ? UmbracoObjectTypes.Media 87 | : UmbracoObjectTypes.Document; 88 | var entityType = isMedia ? Constants.UdiEntityType.Media : Constants.UdiEntityType.Document; 89 | 90 | var guidAttempt = contextCache.GetEntityKeyById(_entityService, intId, objectTypeId); 91 | if (guidAttempt.Success == false) 92 | { 93 | continue; 94 | } 95 | 96 | var udi = new GuidUdi(entityType, guidAttempt.Result); 97 | 98 | // Add the artifact dependency 99 | dependencies.Add(new ArtifactDependency(udi, false, ArtifactDependencyMode.Exist)); 100 | 101 | // Set the Id attribute to the udi 102 | link["id"] = udi.ToString(); 103 | } 104 | else if (TryParseJTokenAttr(link, "udi", out GuidUdi guidUdi)) 105 | { 106 | var entityExists = contextCache.EntityExists(_entityService, guidUdi.Guid); 107 | if (!entityExists) 108 | { 109 | continue; 110 | } 111 | 112 | // Add the artifact dependency 113 | dependencies.Add(new ArtifactDependency(guidUdi, false, ArtifactDependencyMode.Exist)); 114 | } 115 | else if (isMedia && TryParseJTokenAttr(link, "url", out string url)) 116 | { 117 | // This state can happen due to an issue in RJP.MultiUrlPicker(or our linkPicker in RTE which it relies on), 118 | // where you edit a media link, and just hit "Select". 119 | // That will set the id to null, but the url will still be filled. We try to get the media item, and if so add it as 120 | // a dependency to the package. If we can't find it, we abort(aka continue) 121 | var entry = _mediaService.GetMediaByPath(url); 122 | if (entry == null) 123 | { 124 | continue; 125 | } 126 | 127 | // Add the artifact dependency 128 | var udi = entry.GetUdi(); 129 | dependencies.Add(new ArtifactDependency(udi, false, ArtifactDependencyMode.Exist)); 130 | 131 | // Update the url on the item to the udi aka umb://media/fileguid 132 | link["url"] = udi.ToString(); 133 | } 134 | } 135 | 136 | return JsonConvert.SerializeObject(links); 137 | } 138 | 139 | if (valueAsJToken is JObject) 140 | { 141 | // Single link, parse as JToken 142 | var link = JsonConvert.DeserializeObject(svalue); 143 | if (link == null) 144 | { 145 | return string.Empty; 146 | } 147 | 148 | var isMedia = link["isMedia"] != null; 149 | 150 | // Only do processing if the Id is set on the element. OR if the url is set and its a media item 151 | if (TryParseJTokenAttr(link, "id", out int intId)) 152 | { 153 | // Checks weather we are resolving a media item or a document 154 | var objectTypeId = isMedia 155 | ? UmbracoObjectTypes.Media 156 | : UmbracoObjectTypes.Document; 157 | var entityType = isMedia ? Constants.UdiEntityType.Media : Constants.UdiEntityType.Document; 158 | 159 | var guidAttempt = contextCache.GetEntityKeyById(_entityService, intId, objectTypeId); 160 | if (guidAttempt.Success) 161 | { 162 | var udi = new GuidUdi(entityType, guidAttempt.Result); 163 | 164 | // Add the artifact dependency 165 | dependencies.Add(new ArtifactDependency(udi, false, ArtifactDependencyMode.Exist)); 166 | 167 | // Set the Id attribute to the udi 168 | link["id"] = udi.ToString(); 169 | } 170 | } 171 | else if (TryParseJTokenAttr(link, "udi", out GuidUdi guidUdi)) 172 | { 173 | if (contextCache.EntityExists(_entityService, guidUdi.Guid)) 174 | { 175 | // Add the artifact dependency 176 | dependencies.Add(new ArtifactDependency(guidUdi, false, ArtifactDependencyMode.Exist)); 177 | } 178 | } 179 | else if (isMedia && TryParseJTokenAttr(link, "url", out string url)) 180 | { 181 | // This state can happen due to an issue in RJP.MultiUrlPicker(or our linkPicker in RTE which it relies on), 182 | // where you edit a media link, and just hits "Select". 183 | // That will set the id to null, but the url will still be filled. We try to get the media item, and if so add it as 184 | // a dependency to the package. If we can't find it, we abort(aka continue) 185 | var entry = _mediaService.GetMediaByPath(url); 186 | if (entry != null) 187 | { 188 | // Add the artifact dependency 189 | var udi = entry.GetUdi(); 190 | dependencies.Add(new ArtifactDependency(udi, false, ArtifactDependencyMode.Exist)); 191 | 192 | // Update the url on the item to the udi aka umb://media/fileguid 193 | link["url"] = udi.ToString(); 194 | } 195 | } 196 | 197 | return JsonConvert.SerializeObject(link); 198 | } 199 | 200 | // If none of the above... 201 | return string.Empty; 202 | } 203 | 204 | public sealed override object FromArtifact(string value, PropertyType propertyType, object currentValue, IContextCache contextCache) 205 | { 206 | if (string.IsNullOrWhiteSpace(value)) 207 | { 208 | return value; 209 | } 210 | 211 | var valueAsJToken = JToken.Parse(value); 212 | if (valueAsJToken is JArray) 213 | { 214 | // Multiple links, parse as JArray 215 | var links = JsonConvert.DeserializeObject(value); 216 | if (links != null) 217 | { 218 | foreach (var link in links) 219 | { 220 | // Only do processing on an item if the Id or the url is set 221 | if (TryParseJTokenAttr(link, "id", out GuidUdi udi)) 222 | { 223 | // Check the type of the link 224 | var nodeObjectType = link["isMedia"] != null 225 | ? UmbracoObjectTypes.Media 226 | : UmbracoObjectTypes.Document; 227 | 228 | // Get the Id corresponding to the Guid 229 | // it *should* succeed when deploying, due to dependencies management 230 | // nevertheless, assume it can fail, and then create an invalid localLink 231 | var idAttempt = contextCache.GetEntityIdByKey(_entityService, udi.Guid, nodeObjectType); 232 | if (idAttempt) 233 | { 234 | link["id"] = idAttempt.Success ? idAttempt.Result : 0; 235 | } 236 | } 237 | else if (TryParseJTokenAttr(link, "url", out string url)) 238 | { 239 | // Check whether the url attribute of the link contains a udi, if so, replace it with the 240 | // path to the file, i.e. the regex replaces with /path/to/file 241 | var newUrl = MediaUdiSrcRegex.Replace(url, match => 242 | { 243 | var udiString = match.Groups["udi"].ToString(); 244 | if (GuidUdi.TryParse(udiString, out var foundUdi) && 245 | foundUdi.EntityType == Constants.UdiEntityType.Media) 246 | { 247 | // (take care of nulls) 248 | var media = _mediaService.GetById(foundUdi.Guid); 249 | if (media != null) 250 | { 251 | return media.GetUrl("umbracoFile", _logger); 252 | } 253 | } 254 | 255 | return string.Empty; 256 | }); 257 | 258 | link["url"] = newUrl; 259 | } 260 | } 261 | 262 | value = JsonConvert.SerializeObject(links); 263 | } 264 | } 265 | else if (valueAsJToken is JObject) 266 | { 267 | // Single link, parse as JToken 268 | var link = JsonConvert.DeserializeObject(value); 269 | 270 | // Only do processing on an item if the Id or the url is set 271 | if (TryParseJTokenAttr(link, "id", out GuidUdi udi)) 272 | { 273 | // Check the type of the link 274 | var nodeObjectType = link["isMedia"] != null 275 | ? UmbracoObjectTypes.Media 276 | : UmbracoObjectTypes.Document; 277 | 278 | // Get the Id corresponding to the Guid 279 | // it *should* succeed when deploying, due to dependencies management 280 | // nevertheless, assume it can fail, and then create an invalid localLink 281 | var idAttempt = contextCache.GetEntityIdByKey(_entityService, udi.Guid, nodeObjectType); 282 | if (idAttempt) 283 | { 284 | link["id"] = idAttempt.Success ? idAttempt.Result : 0; 285 | } 286 | } 287 | else if (TryParseJTokenAttr(link, "url", out string url)) 288 | { 289 | // Check whether the url attribute of the link contains a udi, if so, replace it with the 290 | // path to the file, i.e. the regex replaces with /path/to/file 291 | var newUrl = MediaUdiSrcRegex.Replace(url, match => 292 | { 293 | var udiString = match.Groups["udi"].ToString(); 294 | if (GuidUdi.TryParse(udiString, out var foundUdi) && 295 | foundUdi.EntityType == Constants.UdiEntityType.Media) 296 | { 297 | // (take care of nulls) 298 | var media = _mediaService.GetById(foundUdi.Guid); 299 | if (media != null) 300 | { 301 | return media.GetUrl("umbracoFile", _logger); 302 | } 303 | } 304 | 305 | return string.Empty; 306 | }); 307 | 308 | link["url"] = newUrl; 309 | } 310 | 311 | value = JsonConvert.SerializeObject(link); 312 | } 313 | 314 | return value; 315 | } 316 | 317 | private bool TryParseJTokenAttr(JToken link, string attrName, out int attrValue) 318 | { 319 | if (link[attrName] != null) 320 | { 321 | var val = link[attrName].ToString(); 322 | return int.TryParse(val, out attrValue); 323 | } 324 | 325 | attrValue = 0; 326 | return false; 327 | } 328 | 329 | private bool TryParseJTokenAttr(JToken link, string attrName, out GuidUdi attrValue) 330 | { 331 | if (link[attrName] != null) 332 | { 333 | var val = link[attrName].ToString(); 334 | return GuidUdi.TryParse(val, out attrValue); 335 | } 336 | 337 | attrValue = null; 338 | return false; 339 | } 340 | 341 | private bool TryParseJTokenAttr(JToken link, string attrName, out string strAttr) 342 | { 343 | if (link[attrName] != null) 344 | { 345 | var val = link[attrName].ToString(); 346 | if (string.IsNullOrEmpty(val) == false) 347 | { 348 | strAttr = val; 349 | return true; 350 | } 351 | } 352 | 353 | strAttr = string.Empty; 354 | return false; 355 | } 356 | } 357 | } 358 | -------------------------------------------------------------------------------- /src/Umbraco.Deploy.Contrib/ValueConnectors/NestedContentValueConnector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Newtonsoft.Json; 5 | using Newtonsoft.Json.Linq; 6 | using Umbraco.Core; 7 | using Umbraco.Core.Deploy; 8 | using Umbraco.Core.Logging; 9 | using Umbraco.Core.Models; 10 | using Umbraco.Core.Services; 11 | using Umbraco.Deploy.Connectors; 12 | using Umbraco.Deploy.Connectors.ValueConnectors; 13 | using Umbraco.Deploy.Connectors.ValueConnectors.Services; 14 | using Umbraco.Deploy.Core; 15 | using Umbraco.Deploy.Extensions; 16 | 17 | namespace Umbraco.Deploy.Contrib.Connectors.ValueConnectors 18 | { 19 | /// 20 | /// A Deploy connector for the NestedContent property editor 21 | /// 22 | [Obsolete("Deploy 4.9.0 adds an explicit binding to use Umbraco.Deploy.Connectors.ValueConnectors.NestedContentValueConnector instead to support recursive migrators. This class will be removed in a future version.")] 23 | public class NestedContentValueConnector : ValueConnectorBase 24 | { 25 | private readonly IContentTypeService _contentTypeService; 26 | private readonly Lazy _valueConnectorsLazy; 27 | private readonly ILogger _logger; 28 | 29 | /// 30 | /// 31 | /// Our.Umbraco.NestedContent is the original NestedContent package 32 | /// Umbraco.NestedContent is Core NestedContent (introduced in v7.7) 33 | /// 34 | public override IEnumerable PropertyEditorAliases { get; } = new[] 35 | { 36 | "Umbraco.NestedContent" 37 | }; 38 | 39 | // cannot inject ValueConnectorCollection as it creates a circular (recursive) dependency, 40 | // so we have to inject it lazily and use the lazy value when actually needing it 41 | private ValueConnectorCollection ValueConnectors => _valueConnectorsLazy.Value; 42 | 43 | public NestedContentValueConnector(IContentTypeService contentTypeService, Lazy valueConnectors, ILogger logger) 44 | { 45 | _contentTypeService = contentTypeService ?? throw new ArgumentNullException(nameof(contentTypeService)); 46 | _valueConnectorsLazy = valueConnectors ?? throw new ArgumentNullException(nameof(valueConnectors)); 47 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 48 | } 49 | 50 | public sealed override string ToArtifact(object value, PropertyType propertyType, ICollection dependencies, IContextCache contextCache) 51 | { 52 | _logger.Debug("Converting {PropertyType} to artifact.", propertyType.Alias); 53 | var valueAsString = value as string; 54 | 55 | if (string.IsNullOrWhiteSpace(valueAsString)) 56 | { 57 | _logger.Debug($"Value is null or whitespace. Skipping conversion to artifact."); 58 | return null; 59 | } 60 | 61 | var nestedContent = new List(); 62 | if (valueAsString.Trim().StartsWith("{")) 63 | { 64 | if (valueAsString.TryParseJson(out NestedContentValue nestedContentObjectValue)) 65 | { 66 | nestedContent.Add(nestedContentObjectValue); 67 | } 68 | else 69 | { 70 | _logger.Warn("Value '{Value}' is not a JSON string. Skipping conversion to artifact.", valueAsString); 71 | return null; 72 | } 73 | } 74 | else 75 | { 76 | if (valueAsString.TryParseJson(out NestedContentValue[] nestedContentCollectionValue)) 77 | { 78 | nestedContent.AddRange(nestedContentCollectionValue); 79 | } 80 | else 81 | { 82 | _logger.Warn("Value '{Value}' is not a JSON string. Skipping conversion to artifact.", valueAsString); 83 | return null; 84 | } 85 | } 86 | 87 | if (nestedContent.All(x => x == null)) 88 | { 89 | _logger.Warn("Value contained no elements. Skipping conversion to artifact."); 90 | return null; 91 | } 92 | 93 | var allContentTypes = nestedContent.Select(x => x.ContentTypeAlias) 94 | .Distinct() 95 | .ToDictionary(a => a, a => contextCache.GetContentTypeByAlias(_contentTypeService, a)); 96 | 97 | //Ensure all of these content types are found 98 | if (allContentTypes.Values.Any(contentType => contentType == null)) 99 | { 100 | throw new InvalidOperationException($"Could not resolve these content types for the Nested Content property: {string.Join(",", allContentTypes.Where(x => x.Value == null).Select(x => x.Key))}."); 101 | } 102 | 103 | //Ensure that these content types have dependencies added 104 | foreach (var contentType in allContentTypes.Values) 105 | { 106 | _logger.Debug("Adding dependency for content type {ContentType}.", contentType.Alias); 107 | dependencies.Add(new ArtifactDependency(contentType.GetUdi(), false, ArtifactDependencyMode.Match)); 108 | } 109 | 110 | foreach (var row in nestedContent) 111 | { 112 | var contentType = allContentTypes[row.ContentTypeAlias]; 113 | 114 | foreach (var key in row.PropertyValues.Keys.ToArray()) 115 | { 116 | // key is a system property that is added by NestedContent in Core v7.7 117 | // see note in NestedContentValue - leave it unchanged 118 | if (key == "key") 119 | { 120 | continue; 121 | } 122 | 123 | var innerPropertyType = contentType.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == key); 124 | 125 | if (innerPropertyType == null) 126 | { 127 | _logger.Warn("No property type found with alias {PropertyType} on content type {ContentType}.", key, propertyType.Alias); 128 | continue; 129 | } 130 | 131 | // fetch the right value connector from the collection of connectors, intended for use with this property type. 132 | // throws if not found - no need for a null check 133 | var propertyValueConnector = ValueConnectors.Get(innerPropertyType); 134 | 135 | // pass the value, property type and the dependencies collection to the connector to get a "artifact" value 136 | var innerValue = row.PropertyValues[key]; 137 | 138 | // connectors are expecting strings, not JTokens 139 | object preparedValue = innerValue is JToken 140 | ? innerValue?.ToString() 141 | : innerValue; 142 | object parsedValue = propertyValueConnector.ToArtifact(preparedValue, innerPropertyType, dependencies, contextCache); 143 | 144 | // getting Map image value umb://media/43e7401fb3cd48ceaa421df511ec703c to (nothing) - why?! 145 | _logger.Debug("Mapped {Key} value '{PropertyValue}' to '{ParsedValue}' using {PropertyValueConnectorType} for {PropertyType}.", key, row.PropertyValues[key], parsedValue, propertyValueConnector.GetType(), innerPropertyType.Alias); 146 | 147 | parsedValue = parsedValue?.ToString(); 148 | 149 | row.PropertyValues[key] = parsedValue; 150 | } 151 | } 152 | 153 | value = JsonConvert.SerializeObject(nestedContent); 154 | _logger.Debug("Finished converting {PropertyType} to artifact.", propertyType.Alias); 155 | return (string)value; 156 | } 157 | 158 | public sealed override object FromArtifact(string value, PropertyType propertyType, object currentValue, IContextCache contextCache) 159 | { 160 | _logger.Debug("Converting {PropertyType} from artifact.", propertyType.Alias); 161 | if (string.IsNullOrWhiteSpace(value)) 162 | { 163 | _logger.Debug($"Value is null or whitespace. Skipping conversion from artifact."); 164 | return value; 165 | } 166 | 167 | if (!value.TryParseJson(out NestedContentValue[] nestedContent)) 168 | { 169 | _logger.Warn("Value '{Value}' is not a json string. Skipping conversion from artifact.", value); 170 | return value; 171 | } 172 | 173 | if (nestedContent == null || nestedContent.All(x => x == null)) 174 | { 175 | _logger.Warn("Value contained no elements. Skipping conversion from artifact."); 176 | return value; 177 | } 178 | 179 | var allContentTypes = nestedContent.Select(x => x.ContentTypeAlias) 180 | .Distinct() 181 | .ToDictionary(a => a, a => contextCache.GetContentTypeByAlias(_contentTypeService, a)); 182 | 183 | //Ensure all of these content types are found 184 | if (allContentTypes.Values.Any(contentType => contentType == null)) 185 | { 186 | throw new InvalidOperationException($"Could not resolve these content types for the Nested Content property: {string.Join(",", allContentTypes.Where(x => x.Value == null).Select(x => x.Key))}."); 187 | } 188 | 189 | foreach (var row in nestedContent) 190 | { 191 | var contentType = allContentTypes[row.ContentTypeAlias]; 192 | 193 | foreach (var key in row.PropertyValues.Keys.ToArray()) 194 | { 195 | // key is a system property that is added by NestedContent in Core v7.7 196 | // see note in NestedContentValue - leave it unchanged 197 | if (key == "key") 198 | { 199 | continue; 200 | } 201 | 202 | var innerPropertyType = contentType.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == key); 203 | 204 | if (innerPropertyType == null) 205 | { 206 | _logger.Warn("No property type found with alias {PropertyType} on content type {ContentType}.", key, contentType.Alias); 207 | continue; 208 | } 209 | 210 | // fetch the right value connector from the collection of connectors, intended for use with this property type. 211 | // throws if not found - no need for a null check 212 | var propertyValueConnector = ValueConnectors.Get(innerPropertyType); 213 | 214 | var innerValue = row.PropertyValues[key]; 215 | 216 | if (innerValue != null) 217 | { 218 | // pass the artifact value and property type to the connector to get a real value from the artifact 219 | var convertedValue = propertyValueConnector.FromArtifact(innerValue.ToString(), innerPropertyType, null, contextCache); 220 | 221 | if (convertedValue == null) 222 | { 223 | row.PropertyValues[key] = null; 224 | } 225 | // integers needs to be converted into strings 226 | else if (convertedValue is int) 227 | { 228 | row.PropertyValues[key] = convertedValue.ToString(); 229 | } 230 | // json strings need to be converted into JTokens 231 | else if (convertedValue is string convertedStringValue && convertedStringValue.TryParseJson(out JToken valueAsJToken)) 232 | { 233 | row.PropertyValues[key] = valueAsJToken; 234 | } 235 | else 236 | { 237 | row.PropertyValues[key] = convertedValue; 238 | } 239 | _logger.Debug("Mapped {Key} value '{PropertyValue}' to '{ConvertedValue}' using {PropertyValueConnectorType} for {PropertyType}.", key, innerValue, convertedValue, propertyValueConnector.GetType(), innerPropertyType.Alias); 240 | } 241 | else 242 | { 243 | row.PropertyValues[key] = innerValue; 244 | _logger.Debug("{Key} value was null. Setting value as null without conversion.", key); 245 | } 246 | } 247 | } 248 | 249 | // Note: NestedContent does not use formatting when serializing JSON values. 250 | value = JArray.FromObject(nestedContent).ToString(Formatting.None); 251 | 252 | _logger.Debug("Finished converting {PropertyType} from artifact.", propertyType.Alias); 253 | 254 | return value; 255 | } 256 | 257 | /// 258 | /// The typed value stored for Nested Content 259 | /// 260 | /// 261 | /// An example of the JSON stored for NestedContent is: 262 | /// asdfasdfasdfasdf

\n

asdf

\n

\"\"

\n

asdf

"}, 265 | /// {"name":"Content","ncContentTypeAlias":"nC1","text":"This is ","multiText":"pretty cool","rTE":""} 266 | /// ] 267 | /// ]]> 268 | ///
269 | public class NestedContentValue 270 | { 271 | [JsonProperty("name")] 272 | public string Name { get; set; } 273 | 274 | [JsonProperty("ncContentTypeAlias")] 275 | public string ContentTypeAlias { get; set; } 276 | 277 | /// 278 | /// The remaining properties will be serialized to a dictionary 279 | /// 280 | /// 281 | /// The JsonExtensionDataAttribute is used to put the non-typed properties into a bucket 282 | /// http://www.newtonsoft.com/json/help/html/DeserializeExtensionData.htm 283 | /// NestedContent serializes to string, int, whatever eg 284 | /// "stringValue":"Some String","numericValue":125,"otherNumeric":null 285 | /// 286 | [JsonExtensionData] 287 | public IDictionary PropertyValues { get; set; } 288 | } 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /tests/.editorconfig: -------------------------------------------------------------------------------- 1 | root = false 2 | 3 | [*.cs] 4 | csharp_style_var_when_type_is_apparent = true:none 5 | csharp_style_var_elsewhere = true:none -------------------------------------------------------------------------------- /tests/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | false 9 | $(EnablePackageValidation) 10 | false 11 | 12 | 13 | 14 | 15 | $(MSBuildThisFileDirectory)codeanalysis.ruleset 16 | 17 | 18 | -------------------------------------------------------------------------------- /tests/Directory.Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/Umbraco.Deploy.Contrib.Tests/Umbraco.Deploy.Contrib.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/codeanalysis.ruleset: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /version.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", 3 | "version": "4.5.0-alpha", 4 | "assemblyVersion": { 5 | "precision": "build" 6 | }, 7 | "gitCommitIdShortFixedLength": 7, 8 | "nuGetPackageVersion": { 9 | "semVer": 2.0 10 | }, 11 | "publicReleaseRefSpec": [ 12 | "^refs/heads/main$", 13 | "^refs/tags/release-" 14 | ], 15 | "cloudBuild": { 16 | "setAllVariables": true, 17 | "buildNumber": { 18 | "enabled": true 19 | } 20 | }, 21 | "release": { 22 | "tagName": "release-{version}", 23 | "branchName": "release/{version}" 24 | } 25 | } --------------------------------------------------------------------------------